Projet

Général

Profil

Paste
Télécharger (20,9 ko) Statistiques
| Branche: | Révision:

root / htmltest / modules / field / field.info.class.inc @ 85ad3d82

1
<?php
2

    
3
/*
4
 * @file
5
 * Definition of the FieldInfo class.
6
 */
7

    
8
/**
9
 * Provides field and instance definitions for the current runtime environment.
10
 *
11
 * A FieldInfo object is created and statically persisted through the request
12
 * by the _field_info_field_cache() function. The object properties act as a
13
 * "static cache" of fields and instances definitions.
14
 *
15
 * The preferred way to access definitions is through the getBundleInstances()
16
 * method, which keeps cache entries per bundle, storing both fields and
17
 * instances for a given bundle. Fields used in multiple bundles are duplicated
18
 * in several cache entries, and are merged into a single list in the memory
19
 * cache. Cache entries are loaded for bundles as a whole, optimizing memory
20
 * and CPU usage for the most common pattern of iterating over all instances of
21
 * a bundle rather than accessing a single instance.
22
 *
23
 * The getFields() and getInstances() methods, which return all existing field
24
 * and instance definitions, are kept mainly for backwards compatibility, and
25
 * should be avoided when possible, since they load and persist in memory a
26
 * potentially large array of information. In many cases, the lightweight
27
 * getFieldMap() method should be preferred.
28
 */
29
class FieldInfo {
30

    
31
  /**
32
   * Lightweight map of fields across entity types and bundles.
33
   *
34
   * @var array
35
   */
36
  protected $fieldMap;
37

    
38
  /**
39
   * List of $field structures keyed by ID. Includes deleted fields.
40
   *
41
   * @var array
42
   */
43
  protected $fieldsById = array();
44

    
45
  /**
46
   * Mapping of field names to the ID of the corresponding non-deleted field.
47
   *
48
   * @var array
49
   */
50
  protected $fieldIdsByName = array();
51

    
52
  /**
53
   * Whether $fieldsById contains all field definitions or a subset.
54
   *
55
   * @var bool
56
   */
57
  protected $loadedAllFields = FALSE;
58

    
59
  /**
60
   * Separately tracks requested field names or IDs that do not exist.
61
   *
62
   * @var array
63
   */
64
  protected $unknownFields = array();
65

    
66
  /**
67
   * Instance definitions by bundle.
68
   *
69
   * @var array
70
   */
71
  protected $bundleInstances = array();
72

    
73
  /**
74
   * Whether $bundleInstances contains all instances definitions or a subset.
75
   *
76
   * @var bool
77
   */
78
  protected $loadedAllInstances = FALSE;
79

    
80
  /**
81
   * Separately tracks requested bundles that are empty (or do not exist).
82
   *
83
   * @var array
84
   */
85
  protected $emptyBundles = array();
86

    
87
  /**
88
   * Extra fields by bundle.
89
   *
90
   * @var array
91
   */
92
  protected $bundleExtraFields = array();
93

    
94
  /**
95
   * Clears the "static" and persistent caches.
96
   */
97
  public function flush() {
98
    $this->fieldMap = NULL;
99

    
100
    $this->fieldsById = array();
101
    $this->fieldIdsByName = array();
102
    $this->loadedAllFields = FALSE;
103
    $this->unknownFields = array();
104

    
105
    $this->bundleInstances = array();
106
    $this->loadedAllInstances = FALSE;
107
    $this->emptyBundles = array();
108

    
109
    $this->bundleExtraFields = array();
110

    
111
    cache_clear_all('field_info:', 'cache_field', TRUE);
112
  }
113

    
114
  /**
115
   * Collects a lightweight map of fields across bundles.
116
   *
117
   * @return
118
   *   An array keyed by field name. Each value is an array with two entries:
119
   *   - type: The field type.
120
   *   - bundles: The bundles in which the field appears, as an array with
121
   *     entity types as keys and the array of bundle names as values.
122
   */
123
  public function getFieldMap() {
124
    // Read from the "static" cache.
125
    if ($this->fieldMap !== NULL) {
126
      return $this->fieldMap;
127
    }
128

    
129
    // Read from persistent cache.
130
    if ($cached = cache_get('field_info:field_map', 'cache_field')) {
131
      $map = $cached->data;
132

    
133
      // Save in "static" cache.
134
      $this->fieldMap = $map;
135

    
136
      return $map;
137
    }
138

    
139
    $map = array();
140

    
141
    $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0');
142
    foreach ($query as $row) {
143
      $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle;
144
      $map[$row->field_name]['type'] = $row->type;
145
    }
146

    
147
    // Save in "static" and persistent caches.
148
    $this->fieldMap = $map;
149
    cache_set('field_info:field_map', $map, 'cache_field');
150

    
151
    return $map;
152
  }
153

    
154
  /**
155
   * Returns all active fields, including deleted ones.
156
   *
157
   * @return
158
   *   An array of field definitions, keyed by field ID.
159
   */
160
  public function getFields() {
161
    // Read from the "static" cache.
162
    if ($this->loadedAllFields) {
163
      return $this->fieldsById;
164
    }
165

    
166
    // Read from persistent cache.
167
    if ($cached = cache_get('field_info:fields', 'cache_field')) {
168
      $this->fieldsById = $cached->data;
169
    }
170
    else {
171
      // Collect and prepare fields.
172
      foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) {
173
        $this->fieldsById[$field['id']] = $this->prepareField($field);
174
      }
175

    
176
      // Store in persistent cache.
177
      cache_set('field_info:fields', $this->fieldsById, 'cache_field');
178
    }
179

    
180
    // Fill the name/ID map.
181
    foreach ($this->fieldsById as $field) {
182
      if (!$field['deleted']) {
183
        $this->fieldIdsByName[$field['field_name']] = $field['id'];
184
      }
185
    }
186

    
187
    $this->loadedAllFields = TRUE;
188

    
189
    return $this->fieldsById;
190
  }
191

    
192
  /**
193
   * Retrieves all active, non-deleted instances definitions.
194
   *
195
   * @param $entity_type
196
   *   (optional) The entity type.
197
   *
198
   * @return
199
   *   If $entity_type is not set, all instances keyed by entity type and bundle
200
   *   name. If $entity_type is set, all instances for that entity type, keyed
201
   *   by bundle name.
202
   */
203
  public function getInstances($entity_type = NULL) {
204
    // If the full list is not present in "static" cache yet.
205
    if (!$this->loadedAllInstances) {
206

    
207
      // Read from persistent cache.
208
      if ($cached = cache_get('field_info:instances', 'cache_field')) {
209
        $this->bundleInstances = $cached->data;
210
      }
211
      else {
212
        // Collect and prepare instances.
213

    
214
        // We also need to populate the static field cache, since it will not
215
        // be set by subsequent getBundleInstances() calls.
216
        $this->getFields();
217

    
218
        // Initialize empty arrays for all existing entity types and bundles.
219
        // This is not strictly needed, but is done to preserve the behavior of
220
        // field_info_instances() before http://drupal.org/node/1915646.
221
        foreach (field_info_bundles() as $existing_entity_type => $bundles) {
222
          foreach ($bundles as $bundle => $bundle_info) {
223
            $this->bundleInstances[$existing_entity_type][$bundle] = array();
224
          }
225
        }
226

    
227
        foreach (field_read_instances() as $instance) {
228
          $field = $this->getField($instance['field_name']);
229
          $instance = $this->prepareInstance($instance, $field['type']);
230
          $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance;
231
        }
232

    
233
        // Store in persistent cache.
234
        cache_set('field_info:instances', $this->bundleInstances, 'cache_field');
235
      }
236

    
237
      $this->loadedAllInstances = TRUE;
238
    }
239

    
240
    if (isset($entity_type)) {
241
      return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array();
242
    }
243
    else {
244
      return $this->bundleInstances;
245
    }
246
  }
247

    
248
  /**
249
   * Returns a field definition from a field name.
250
   *
251
   * This method only retrieves active, non-deleted fields.
252
   *
253
   * @param $field_name
254
   *   The field name.
255
   *
256
   * @return
257
   *   The field definition, or NULL if no field was found.
258
   */
259
  public function getField($field_name) {
260
    // Read from the "static" cache.
261
    if (isset($this->fieldIdsByName[$field_name])) {
262
      $field_id = $this->fieldIdsByName[$field_name];
263
      return $this->fieldsById[$field_id];
264
    }
265
    if (isset($this->unknownFields[$field_name])) {
266
      return;
267
    }
268

    
269
    // Do not check the (large) persistent cache, but read the definition.
270

    
271
    // Cache miss: read from definition.
272
    if ($field = field_read_field($field_name)) {
273
      $field = $this->prepareField($field);
274

    
275
      // Save in the "static" cache.
276
      $this->fieldsById[$field['id']] = $field;
277
      $this->fieldIdsByName[$field['field_name']] = $field['id'];
278

    
279
      return $field;
280
    }
281
    else {
282
      $this->unknownFields[$field_name] = TRUE;
283
    }
284
  }
285

    
286
  /**
287
   * Returns a field definition from a field ID.
288
   *
289
   * This method only retrieves active fields, deleted or not.
290
   *
291
   * @param $field_id
292
   *   The field ID.
293
   *
294
   * @return
295
   *   The field definition, or NULL if no field was found.
296
   */
297
  public function getFieldById($field_id) {
298
    // Read from the "static" cache.
299
    if (isset($this->fieldsById[$field_id])) {
300
      return $this->fieldsById[$field_id];
301
    }
302
    if (isset($this->unknownFields[$field_id])) {
303
      return;
304
    }
305

    
306
    // No persistent cache, fields are only persistently cached as part of a
307
    // bundle.
308

    
309
    // Cache miss: read from definition.
310
    if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) {
311
      $field = current($fields);
312
      $field = $this->prepareField($field);
313

    
314
      // Store in the static cache.
315
      $this->fieldsById[$field['id']] = $field;
316
      if (!$field['deleted']) {
317
        $this->fieldIdsByName[$field['field_name']] = $field['id'];
318
      }
319

    
320
      return $field;
321
    }
322
    else {
323
      $this->unknownFields[$field_id] = TRUE;
324
    }
325
  }
326

    
327
  /**
328
   * Retrieves the instances for a bundle.
329
   *
330
   * The function also populates the corresponding field definitions in the
331
   * "static" cache.
332
   *
333
   * @param $entity_type
334
   *   The entity type.
335
   * @param $bundle
336
   *   The bundle name.
337
   *
338
   * @return
339
   *   The array of instance definitions, keyed by field name.
340
   */
341
  public function getBundleInstances($entity_type, $bundle) {
342
    // Read from the "static" cache.
343
    if (isset($this->bundleInstances[$entity_type][$bundle])) {
344
      return $this->bundleInstances[$entity_type][$bundle];
345
    }
346
    if (isset($this->emptyBundles[$entity_type][$bundle])) {
347
      return array();
348
    }
349

    
350
    // Read from the persistent cache.
351
    if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) {
352
      $info = $cached->data;
353

    
354
      // Extract the field definitions and save them in the "static" cache.
355
      foreach ($info['fields'] as $field) {
356
        if (!isset($this->fieldsById[$field['id']])) {
357
          $this->fieldsById[$field['id']] = $field;
358
          if (!$field['deleted']) {
359
            $this->fieldIdsByName[$field['field_name']] = $field['id'];
360
          }
361
        }
362
      }
363
      unset($info['fields']);
364

    
365
      // Store the instance definitions in the "static" cache'. Empty (or
366
      // non-existent) bundles are stored separately, so that they do not
367
      // pollute the global list returned by getInstances().
368
      if ($info['instances']) {
369
        $this->bundleInstances[$entity_type][$bundle] = $info['instances'];
370
      }
371
      else {
372
        $this->emptyBundles[$entity_type][$bundle] = TRUE;
373
      }
374

    
375
      return $info['instances'];
376
    }
377

    
378
    // Cache miss: collect from the definitions.
379

    
380
    $instances = array();
381

    
382
    // Collect the fields in the bundle.
383
    $params = array('entity_type' => $entity_type, 'bundle' => $bundle);
384
    $fields = field_read_fields($params);
385

    
386
    // This iterates on non-deleted instances, so deleted fields are kept out of
387
    // the persistent caches.
388
    foreach (field_read_instances($params) as $instance) {
389
      $field = $fields[$instance['field_name']];
390

    
391
      $instance = $this->prepareInstance($instance, $field['type']);
392
      $instances[$field['field_name']] = $instance;
393

    
394
      // If the field is not in our global "static" list yet, add it.
395
      if (!isset($this->fieldsById[$field['id']])) {
396
        $field = $this->prepareField($field);
397

    
398
        $this->fieldsById[$field['id']] = $field;
399
        $this->fieldIdsByName[$field['field_name']] = $field['id'];
400
      }
401
    }
402

    
403
    // Store in the 'static' cache'. Empty (or non-existent) bundles are stored
404
    // separately, so that they do not pollute the global list returned by
405
    // getInstances().
406
    if ($instances) {
407
      $this->bundleInstances[$entity_type][$bundle] = $instances;
408
    }
409
    else {
410
      $this->emptyBundles[$entity_type][$bundle] = TRUE;
411
    }
412

    
413
    // The persistent cache additionally contains the definitions of the fields
414
    // involved in the bundle.
415
    $cache = array(
416
      'instances' => $instances,
417
      'fields' => array()
418
    );
419
    foreach ($instances as $instance) {
420
      $cache['fields'][] = $this->fieldsById[$instance['field_id']];
421
    }
422
    cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field');
423

    
424
    return $instances;
425
  }
426

    
427
  /**
428
   * Retrieves the "extra fields" for a bundle.
429
   *
430
   * @param $entity_type
431
   *   The entity type.
432
   * @param $bundle
433
   *   The bundle name.
434
   *
435
   * @return
436
   *   The array of extra fields.
437
   */
438
  public function getBundleExtraFields($entity_type, $bundle) {
439
    // Read from the "static" cache.
440
    if (isset($this->bundleExtraFields[$entity_type][$bundle])) {
441
      return $this->bundleExtraFields[$entity_type][$bundle];
442
    }
443

    
444
    // Read from the persistent cache.
445
    if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) {
446
      $this->bundleExtraFields[$entity_type][$bundle] = $cached->data;
447
      return $this->bundleExtraFields[$entity_type][$bundle];
448
    }
449

    
450
    // Cache miss: read from hook_field_extra_fields(). Note: given the current
451
    // shape of the hook, we have no other way than collecting extra fields on
452
    // all bundles.
453
    $info = array();
454
    $extra = module_invoke_all('field_extra_fields');
455
    drupal_alter('field_extra_fields', $extra);
456
    // Merge in saved settings.
457
    if (isset($extra[$entity_type][$bundle])) {
458
      $info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle);
459
    }
460

    
461
    // Store in the 'static' and persistent caches.
462
    $this->bundleExtraFields[$entity_type][$bundle] = $info;
463
    cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field');
464

    
465
    return $this->bundleExtraFields[$entity_type][$bundle];
466
  }
467

    
468
  /**
469
   * Prepares a field definition for the current run-time context.
470
   *
471
   * @param $field
472
   *   The raw field structure as read from the database.
473
   *
474
   * @return
475
   *   The field definition completed for the current runtime context.
476
   */
477
  public function prepareField($field) {
478
    // Make sure all expected field settings are present.
479
    $field['settings'] += field_info_field_settings($field['type']);
480
    $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
481

    
482
    // Add storage details.
483
    $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field);
484
    drupal_alter('field_storage_details', $details, $field);
485
    $field['storage']['details'] = $details;
486

    
487
    // Populate the list of bundles using the field.
488
    $field['bundles'] = array();
489
    if (!$field['deleted']) {
490
      $map = $this->getFieldMap();
491
      if (isset($map[$field['field_name']])) {
492
        $field['bundles'] = $map[$field['field_name']]['bundles'];
493
      }
494
    }
495

    
496
    return $field;
497
  }
498

    
499
  /**
500
   * Prepares an instance definition for the current run-time context.
501
   *
502
   * @param $instance
503
   *   The raw instance structure as read from the database.
504
   * @param $field_type
505
   *   The field type.
506
   *
507
   * @return
508
   *   The field instance array completed for the current runtime context.
509
   */
510
  public function prepareInstance($instance, $field_type) {
511
    // Make sure all expected instance settings are present.
512
    $instance['settings'] += field_info_instance_settings($field_type);
513

    
514
    // Set a default value for the instance.
515
    if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) {
516
      $instance['default_value'] = NULL;
517
    }
518

    
519
    // Prepare widget settings.
520
    $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type);
521

    
522
    // Prepare display settings.
523
    foreach ($instance['display'] as $view_mode => $display) {
524
      $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type);
525
    }
526

    
527
    // Fall back to 'hidden' for view modes configured to use custom display
528
    // settings, and for which the instance has no explicit settings.
529
    $entity_info = entity_get_info($instance['entity_type']);
530
    $view_modes = array_merge(array('default'), array_keys($entity_info['view modes']));
531
    $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']);
532
    foreach ($view_modes as $view_mode) {
533
      if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) {
534
        if (!isset($instance['display'][$view_mode])) {
535
          $instance['display'][$view_mode] = array(
536
            'type' => 'hidden',
537
            'label' => 'above',
538
            'settings' => array(),
539
            'weight' => 0,
540
          );
541
        }
542
      }
543
    }
544

    
545
    return $instance;
546
  }
547

    
548
  /**
549
   * Prepares widget properties for the current run-time context.
550
   *
551
   * @param $widget
552
   *   Widget specifications as found in $instance['widget'].
553
   * @param $field_type
554
   *   The field type.
555
   *
556
   * @return
557
   *   The widget properties completed for the current runtime context.
558
   */
559
  public function prepareInstanceWidget($widget, $field_type) {
560
    $field_type_info = field_info_field_types($field_type);
561

    
562
    // Fill in default values.
563
    $widget += array(
564
      'type' => $field_type_info['default_widget'],
565
      'settings' => array(),
566
      'weight' => 0,
567
    );
568

    
569
    $widget_type_info = field_info_widget_types($widget['type']);
570
    // Fall back to default formatter if formatter type is not available.
571
    if (!$widget_type_info) {
572
      $widget['type'] = $field_type_info['default_widget'];
573
      $widget_type_info = field_info_widget_types($widget['type']);
574
    }
575
    $widget['module'] = $widget_type_info['module'];
576
    // Fill in default settings for the widget.
577
    $widget['settings'] += field_info_widget_settings($widget['type']);
578

    
579
    return $widget;
580
  }
581

    
582
  /**
583
   * Adapts display specifications to the current run-time context.
584
   *
585
   * @param $display
586
   *   Display specifications as found in $instance['display']['a_view_mode'].
587
   * @param $field_type
588
   *   The field type.
589
   *
590
   * @return
591
   *   The display properties completed for the current runtime context.
592
   */
593
  public function prepareInstanceDisplay($display, $field_type) {
594
    $field_type_info = field_info_field_types($field_type);
595

    
596
    // Fill in default values.
597
    $display += array(
598
      'label' => 'above',
599
      'type' => $field_type_info['default_formatter'],
600
      'settings' => array(),
601
      'weight' => 0,
602
    );
603
    if ($display['type'] != 'hidden') {
604
      $formatter_type_info = field_info_formatter_types($display['type']);
605
      // Fall back to default formatter if formatter type is not available.
606
      if (!$formatter_type_info) {
607
        $display['type'] = $field_type_info['default_formatter'];
608
        $formatter_type_info = field_info_formatter_types($display['type']);
609
      }
610
      $display['module'] = $formatter_type_info['module'];
611
      // Fill in default settings for the formatter.
612
      $display['settings'] += field_info_formatter_settings($display['type']);
613
    }
614

    
615
    return $display;
616
  }
617

    
618
  /**
619
   * Prepares 'extra fields' for the current run-time context.
620
   *
621
   * @param $extra_fields
622
   *   The array of extra fields, as collected in hook_field_extra_fields().
623
   * @param $entity_type
624
   *   The entity type.
625
   * @param $bundle
626
   *   The bundle name.
627
   *
628
   * @return
629
   *   The list of extra fields completed for the current runtime context.
630
   */
631
  public function prepareExtraFields($extra_fields, $entity_type, $bundle) {
632
    $entity_type_info = entity_get_info($entity_type);
633
    $bundle_settings = field_bundle_settings($entity_type, $bundle);
634
    $extra_fields += array('form' => array(), 'display' => array());
635

    
636
    $result = array();
637
    // Extra fields in forms.
638
    foreach ($extra_fields['form'] as $name => $field_data) {
639
      $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array();
640
      if (isset($settings['weight'])) {
641
        $field_data['weight'] = $settings['weight'];
642
      }
643
      $result['form'][$name] = $field_data;
644
    }
645

    
646
    // Extra fields in displayed entities.
647
    $data = $extra_fields['display'];
648
    foreach ($extra_fields['display'] as $name => $field_data) {
649
      $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array();
650
      $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes']));
651
      foreach ($view_modes as $view_mode) {
652
        if (isset($settings[$view_mode])) {
653
          $field_data['display'][$view_mode] = $settings[$view_mode];
654
        }
655
        else {
656
          $field_data['display'][$view_mode] = array(
657
            'weight' => $field_data['weight'],
658
            'visible' => TRUE,
659
          );
660
        }
661
      }
662
      unset($field_data['weight']);
663
      $result['display'][$name] = $field_data;
664
    }
665

    
666
    return $result;
667
  }
668
}