Projet

Général

Profil

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

root / drupal7 / sites / all / modules / views / modules / field / views_handler_field_field.inc @ 7547bb19

1
<?php
2

    
3
/**
4
 * @file
5
 * Definition of views_handler_field_field.
6
 */
7

    
8
/**
9
 * Helper function: Return an array of formatter options for a field type.
10
 *
11
 * Borrowed from field_ui.
12
 */
13
function _field_view_formatter_options($field_type = NULL) {
14
  $options = &drupal_static(__FUNCTION__);
15

    
16
  if (!isset($options)) {
17
    $field_types = field_info_field_types();
18
    $options = array();
19
    foreach (field_info_formatter_types() as $name => $formatter) {
20
      foreach ($formatter['field types'] as $formatter_field_type) {
21
        // Check that the field type exists.
22
        if (isset($field_types[$formatter_field_type])) {
23
          $options[$formatter_field_type][$name] = $formatter['label'];
24
        }
25
      }
26
    }
27
  }
28

    
29
  if ($field_type) {
30
    return !empty($options[$field_type]) ? $options[$field_type] : array();
31
  }
32
  return $options;
33
}
34

    
35
/**
36
 * A field that displays fieldapi fields.
37
 *
38
 * @ingroup views_field_handlers
39
 */
40
class views_handler_field_field extends views_handler_field {
41
  /**
42
   * An array to store field renderable arrays for use by render_items.
43
   *
44
   * @var array
45
   */
46
  public $items = array();
47

    
48
  /**
49
   * Store the field information.
50
   *
51
   * @var array
52
   */
53
  public $field_info = array();
54

    
55

    
56
  /**
57
   * Does the field supports multiple field values.
58
   *
59
   * @var bool
60
   */
61
  public $multiple;
62

    
63
  /**
64
   * Does the rendered fields get limited.
65
   *
66
   * @var bool
67
   */
68
  public $limit_values;
69

    
70
  /**
71
   * A shortcut for $view->base_table.
72
   *
73
   * @var string
74
   */
75
  public $base_table;
76

    
77
  /**
78
   * Store the field instance.
79
   *
80
   * @var array
81
   */
82
  public $instance;
83

    
84
  function init(&$view, &$options) {
85
    parent::init($view, $options);
86

    
87
    $this->field_info = $field = field_info_field($this->definition['field_name']);
88
    $this->multiple = FALSE;
89
    $this->limit_values = FALSE;
90

    
91
    if ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
92
      $this->multiple = TRUE;
93

    
94
      // If "Display all values in the same row" is FALSE, then we always limit
95
      // in order to show a single unique value per row.
96
      if (!$this->options['group_rows']) {
97
        $this->limit_values = TRUE;
98
      }
99

    
100
      // If "First and last only" is chosen, limit the values
101
      if (!empty($this->options['delta_first_last'])) {
102
        $this->limit_values = TRUE;
103
      }
104

    
105
      // Otherwise, we only limit values if the user hasn't selected "all", 0, or
106
      // the value matching field cardinality.
107
      if ((intval($this->options['delta_limit']) && ($this->options['delta_limit'] != $field['cardinality'])) || intval($this->options['delta_offset'])) {
108
        $this->limit_values = TRUE;
109
      }
110
    }
111

    
112
    // Convert old style entity id group column to new format.
113
    // @todo Remove for next major version.
114
    if ($this->options['group_column'] == 'entity id') {
115
      $this->options['group_column'] = 'entity_id';
116
    }
117
  }
118

    
119
  /**
120
   * Check whether current user has access to this handler.
121
   *
122
   * @return bool
123
   *   Return TRUE if the user has access to view this field.
124
   */
125
  function access() {
126
    $base_table = $this->get_base_table();
127
    return field_access('view', $this->field_info, $this->definition['entity_tables'][$base_table]);
128
  }
129

    
130
  /**
131
   * Set the base_table and base_table_alias.
132
   *
133
   * @return string
134
   *   The base table which is used in the current view "context".
135
   */
136
  function get_base_table() {
137
    if (!isset($this->base_table)) {
138
      // This base_table is coming from the entity not the field.
139
      $this->base_table = $this->view->base_table;
140

    
141
      // If the current field is under a relationship you can't be sure that the
142
      // base table of the view is the base table of the current field.
143
      // For example a field from a node author on a node view does have users as base table.
144
      if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
145
        $relationships = $this->view->display_handler->get_option('relationships');
146
        if (!empty($relationships[$this->options['relationship']])) {
147
          $options = $relationships[$this->options['relationship']];
148
          $data = views_fetch_data($options['table']);
149
          $this->base_table = $data[$options['field']]['relationship']['base'];
150
        }
151
      }
152
    }
153

    
154
    return $this->base_table;
155
  }
156

    
157
  /**
158
   * Called to add the field to a query.
159
   *
160
   * By default, the only columns added to the query are entity_id and
161
   * entity_type. This is because other needed data is fetched by entity_load().
162
   * Other columns are added only if they are used in groupings, or if
163
   * 'add fields to query' is specifically set to TRUE in the field definition.
164
   *
165
   * The 'add fields to query' switch is used by modules which need all data
166
   * present in the query itself (such as "sphinx").
167
   */
168
  function query($use_groupby = FALSE) {
169
    $this->get_base_table();
170

    
171
    $params = array();
172
    if ($use_groupby) {
173
      // When grouping on a "field API" field (whose "real_field" is set to
174
      // entity_id), retrieve the minimum entity_id to have a valid entity_id to
175
      // pass to field_view_field().
176
      $params = array(
177
        'function' => 'min',
178
      );
179

    
180
      $this->ensure_my_table();
181
    }
182

    
183
    // Get the entity type according to the base table of the field.
184
    // Then add it to the query as a formula. That way we can avoid joining
185
    // the field table if all we need is entity_id and entity_type.
186
    $entity_type = $this->definition['entity_tables'][$this->base_table];
187
    $entity_info = entity_get_info($entity_type);
188

    
189
    if (isset($this->relationship)) {
190
      $this->base_table_alias = $this->relationship;
191
    }
192
    else {
193
      $this->base_table_alias = $this->base_table;
194
    }
195

    
196
    // We always need the base field (entity_id / revision_id).
197
    if (empty($this->definition['is revision'])) {
198
      $this->field_alias = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['id'], '', $params);
199
    }
200
    else {
201
      $this->field_alias = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['revision'], '', $params);
202
      $this->aliases['entity_id'] = $this->query->add_field($this->base_table_alias, $entity_info['entity keys']['id'], '', $params);
203
    }
204

    
205

    
206
    // The alias needs to be unique, so we use both the field table and the entity type.
207
    $entity_type_alias = $this->definition['table'] . '_' . $entity_type . '_entity_type';
208
    $this->aliases['entity_type'] = $this->query->add_field(NULL, "'$entity_type'", $entity_type_alias);
209

    
210
    $fields = $this->additional_fields;
211
    // We've already added entity_type, so we can remove it from the list.
212
    $entity_type_key = array_search('entity_type', $fields);
213
    if ($entity_type_key !== FALSE) {
214
      unset($fields[$entity_type_key]);
215
    }
216

    
217
    if ($use_groupby) {
218
      // Add the fields that we're actually grouping on.
219
      $options = array();
220

    
221
      if ($this->options['group_column'] != 'entity_id') {
222
        $options = array($this->options['group_column'] => $this->options['group_column']);
223
      }
224

    
225
      $options += is_array($this->options['group_columns']) ? $this->options['group_columns'] : array();
226

    
227

    
228
      $fields = array();
229
      $rkey = $this->definition['is revision'] ? 'FIELD_LOAD_REVISION' : 'FIELD_LOAD_CURRENT';
230
      // Go through the list and determine the actual column name from field api.
231
      foreach ($options as $column) {
232
        $name = $column;
233
        if (isset($this->field_info['storage']['details']['sql'][$rkey][$this->table][$column])) {
234
          $name = $this->field_info['storage']['details']['sql'][$rkey][$this->table][$column];
235
        }
236

    
237
        $fields[$column] = $name;
238
      }
239

    
240
      $this->group_fields = $fields;
241
    }
242

    
243
    // Add additional fields (and the table join itself) if needed.
244
    if ($this->add_field_table($use_groupby)) {
245
      $this->ensure_my_table();
246
      $this->add_additional_fields($fields);
247

    
248
      // Filter by language, if field translation is enabled.
249
      $field = $this->field_info;
250
      if (field_is_translatable($entity_type, $field) && !empty($this->view->display_handler->options['field_language_add_to_query'])) {
251
        $column = $this->table_alias . '.language';
252
        // By the same reason as field_language the field might be LANGUAGE_NONE in reality so allow it as well.
253
        // @see this::field_language()
254
        global $language_content;
255
        $default_language = language_default('language');
256
        $language = str_replace(array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
257
                                array($language_content->language, $default_language),
258
                                $this->view->display_handler->options['field_language']);
259
        $placeholder = $this->placeholder();
260
        $language_fallback_candidates = array($language);
261
        if (variable_get('locale_field_language_fallback', TRUE)) {
262
          require_once DRUPAL_ROOT . '/includes/language.inc';
263
          $language_fallback_candidates = array_merge($language_fallback_candidates, language_fallback_get_candidates());
264
        }
265
        else {
266
          $language_fallback_candidates[] = LANGUAGE_NONE;
267
        }
268
        $this->query->add_where_expression(0, "$column IN($placeholder) OR $column IS NULL", array($placeholder => $language_fallback_candidates));
269
      }
270
    }
271

    
272
    // The revision id inhibits grouping.
273
    // So, stop here if we're using grouping, or if aren't adding all columns to
274
    // the query.
275
    if ($use_groupby || empty($this->definition['add fields to query'])) {
276
      return;
277
    }
278

    
279
    $this->add_additional_fields(array('revision_id'));
280
  }
281

    
282
  /**
283
   * Determine if the field table should be added to the query.
284
   */
285
  function add_field_table($use_groupby) {
286
    // Grouping is enabled, or we are explicitly required to do this.
287
    if ($use_groupby || !empty($this->definition['add fields to query'])) {
288
      return TRUE;
289
    }
290
    // This a multiple value field, but "group multiple values" is not checked.
291
    if ($this->multiple && !$this->options['group_rows']) {
292
      return TRUE;
293
    }
294
    return FALSE;
295
  }
296

    
297
  /**
298
   * Determine if this field is click sortable.
299
   */
300
  function click_sortable() {
301
    // Not click sortable in any case.
302
    if (empty($this->definition['click sortable'])) {
303
      return FALSE;
304
    }
305
    // A field is not click sortable if it's a multiple field with
306
    // "group multiple values" checked, since a click sort in that case would
307
    // add a join to the field table, which would produce unwanted duplicates.
308
    if ($this->multiple && $this->options['group_rows']) {
309
      return FALSE;
310
    }
311
    return TRUE;
312
  }
313

    
314
  /**
315
   * Called to determine what to tell the clicksorter.
316
   */
317
  function click_sort($order) {
318
    // No column selected, can't continue.
319
    if (empty($this->options['click_sort_column'])) {
320
      return;
321
    }
322

    
323
    $this->ensure_my_table();
324
    $column = _field_sql_storage_columnname($this->definition['field_name'], $this->options['click_sort_column']);
325
    if (!isset($this->aliases[$column])) {
326
      // Column is not in query; add a sort on it (without adding the column).
327
      $this->aliases[$column] = $this->table_alias . '.' . $column;
328
    }
329
    $this->query->add_orderby(NULL, NULL, $order, $this->aliases[$column]);
330
  }
331

    
332
  function option_definition() {
333
    $options = parent::option_definition();
334

    
335
    // option_definition runs before init/construct, so no $this->field_info
336
    $field = field_info_field($this->definition['field_name']);
337
    $field_type = field_info_field_types($field['type']);
338
    $column_names = array_keys($field['columns']);
339
    $default_column = '';
340
    // Try to determine a sensible default.
341
    if (count($column_names) == 1) {
342
      $default_column = $column_names[0];
343
    }
344
    elseif (in_array('value', $column_names)) {
345
      $default_column = 'value';
346
    }
347

    
348
    // If the field has a "value" column, we probably need that one.
349
    $options['click_sort_column'] = array(
350
      'default' => $default_column,
351
    );
352
    $options['type'] = array(
353
      'default' => $field_type['default_formatter'],
354
    );
355
    $options['settings'] = array(
356
      'default' => array(),
357
    );
358
    $options['group_column'] = array(
359
      'default' => $default_column,
360
    );
361
    $options['group_columns'] = array(
362
      'default' => array(),
363
    );
364

    
365
    // Options used for multiple value fields.
366
    $options['group_rows'] = array(
367
      'default' => TRUE,
368
      'bool' => TRUE,
369
    );
370
    // If we know the exact number of allowed values, then that can be
371
    // the default. Otherwise, default to 'all'.
372
    $options['delta_limit'] = array(
373
      'default' => ($field['cardinality'] > 1) ? $field['cardinality'] : 'all',
374
    );
375
    $options['delta_offset'] = array(
376
      'default' => 0,
377
    );
378
    $options['delta_reversed'] = array(
379
      'default' => FALSE,
380
      'bool' => TRUE,
381
    );
382
    $options['delta_first_last'] = array(
383
      'default' => FALSE,
384
      'bool' => TRUE,
385
    );
386
    $options['delta_random'] = array(
387
      'default' => FALSE,
388
      'bool' => TRUE,
389
    );
390

    
391
    $options['multi_type'] = array(
392
      'default' => 'separator'
393
    );
394
    $options['separator'] = array(
395
      'default' => ', '
396
    );
397

    
398
    $options['field_api_classes'] = array(
399
      'default' => FALSE,
400
      'bool' => TRUE,
401
    );
402

    
403
    return $options;
404
  }
405

    
406
  function options_form(&$form, &$form_state) {
407
    parent::options_form($form, $form_state);
408

    
409
    $field = $this->field_info;
410
    $formatters = _field_view_formatter_options($field['type']);
411
    $column_names = array_keys($field['columns']);
412

    
413
    // If this is a multiple value field, add its options.
414
    if ($this->multiple) {
415
      $this->multiple_options_form($form, $form_state);
416
    }
417

    
418
    // No need to ask the user anything if the field has only one column.
419
    if (count($field['columns']) == 1) {
420
      $form['click_sort_column'] = array(
421
        '#type' => 'value',
422
        '#value' => isset($column_names[0]) ? $column_names[0] : '',
423
      );
424
    }
425
    else {
426
      $form['click_sort_column'] = array(
427
        '#type' => 'select',
428
        '#title' => t('Column used for click sorting'),
429
        '#options' => drupal_map_assoc($column_names),
430
        '#default_value' => $this->options['click_sort_column'],
431
        '#description' => t('Used by Style: Table to determine the actual column to click sort the field on. The default is usually fine.'),
432
        '#fieldset' => 'more',
433
      );
434
    }
435

    
436
    $form['type'] = array(
437
      '#type' => 'select',
438
      '#title' => t('Formatter'),
439
      '#options' => $formatters,
440
      '#default_value' => $this->options['type'],
441
      '#ajax' => array(
442
        'path' => views_ui_build_form_url($form_state),
443
      ),
444
      '#submit' => array('views_ui_config_item_form_submit_temporary'),
445
      '#executes_submit_callback' => TRUE,
446
    );
447

    
448
    $form['field_api_classes'] = array(
449
      '#title' => t('Use field template'),
450
      '#type' => 'checkbox',
451
      '#default_value' => $this->options['field_api_classes'],
452
      '#description' => t('If checked, field api classes will be added using field.tpl.php (or equivalent). This is not recommended unless your CSS depends upon these classes. If not checked, template will not be used.'),
453
      '#fieldset' => 'style_settings',
454
      '#weight' => 20,
455
    );
456

    
457
    if ($this->multiple) {
458
      $form['field_api_classes']['#description'] .= ' ' . t('Checking this option will cause the group Display Type and Separator values to be ignored.');
459
    }
460

    
461
    // Get the currently selected formatter.
462
    $format = $this->options['type'];
463

    
464
    $formatter = field_info_formatter_types($format);
465
    $settings = $this->options['settings'] + field_info_formatter_settings($format);
466

    
467
    // Provide an instance array for hook_field_formatter_settings_form().
468
    ctools_include('fields');
469
    $this->instance = ctools_fields_fake_field_instance($this->definition['field_name'], '_custom', $formatter, $settings);
470

    
471
    // Store the settings in a '_custom' view mode.
472
    $this->instance['display']['_custom'] = array(
473
      'type' => $format,
474
      'settings' => $settings,
475
    );
476

    
477
    // Get the settings form.
478
    $settings_form = array('#value' => array());
479
    $function = $formatter['module'] . '_field_formatter_settings_form';
480
    if (function_exists($function)) {
481
      $settings_form = $function($field, $this->instance, '_custom', $form, $form_state);
482
    }
483
    $form['settings'] = $settings_form;
484
  }
485

    
486
  /**
487
   * Provide options for multiple value fields.
488
   */
489
  function multiple_options_form(&$form, &$form_state) {
490
    $field = $this->field_info;
491

    
492
    $form['multiple_field_settings'] = array(
493
      '#type' => 'fieldset',
494
      '#title' => t('Multiple field settings'),
495
      '#collapsible' => TRUE,
496
      '#collapsed' => TRUE,
497
      '#weight' => 5,
498
    );
499

    
500
    $form['group_rows'] = array(
501
      '#title' => t('Display all values in the same row'),
502
      '#type' => 'checkbox',
503
      '#default_value' => $this->options['group_rows'],
504
      '#description' => t('If checked, multiple values for this field will be shown in the same row. If not checked, each value in this field will create a new row. If using group by, please make sure to group by "Entity ID" for this setting to have any effect.'),
505
      '#fieldset' => 'multiple_field_settings',
506
    );
507

    
508
    // Make the string translatable by keeping it as a whole rather than
509
    // translating prefix and suffix separately.
510
    list($prefix, $suffix) = explode('@count', t('Display @count value(s)'));
511

    
512
    if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
513
      $type = 'textfield';
514
      $options = NULL;
515
      $size = 5;
516
    }
517
    else {
518
      $type = 'select';
519
      $options = drupal_map_assoc(range(1, $field['cardinality']));
520
      $size = 1;
521
    }
522
    $form['multi_type'] = array(
523
      '#type' => 'radios',
524
      '#title' => t('Display type'),
525
      '#options' => array(
526
        'ul' => t('Unordered list'),
527
        'ol' => t('Ordered list'),
528
        'separator' => t('Simple separator'),
529
      ),
530
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
531
      '#default_value' => $this->options['multi_type'],
532
      '#fieldset' => 'multiple_field_settings',
533
    );
534

    
535
    $form['separator'] = array(
536
      '#type' => 'textfield',
537
      '#title' => t('Separator'),
538
      '#default_value' => $this->options['separator'],
539
      '#dependency' => array(
540
        'radio:options[multi_type]' => array('separator'),
541
        'edit-options-group-rows' => array(TRUE),
542
      ),
543
      '#dependency_count' => 2,
544
      '#fieldset' => 'multiple_field_settings',
545
    );
546

    
547
    $form['delta_limit'] = array(
548
      '#type' => $type,
549
      '#size' => $size,
550
      '#field_prefix' => $prefix,
551
      '#field_suffix' => $suffix,
552
      '#options' => $options,
553
      '#default_value' => $this->options['delta_limit'],
554
      '#prefix' => '<div class="container-inline">',
555
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
556
      '#fieldset' => 'multiple_field_settings',
557
    );
558

    
559
    list($prefix, $suffix) = explode('@count', t('starting from @count'));
560
    $form['delta_offset'] = array(
561
      '#type' => 'textfield',
562
      '#size' => 5,
563
      '#field_prefix' => $prefix,
564
      '#field_suffix' => $suffix,
565
      '#default_value' => $this->options['delta_offset'],
566
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
567
      '#description' => t('(first item is 0)'),
568
      '#fieldset' => 'multiple_field_settings',
569
    );
570
    $form['delta_reversed'] = array(
571
      '#title' => t('Reversed'),
572
      '#type' => 'checkbox',
573
      '#default_value' => $this->options['delta_reversed'],
574
      '#suffix' => $suffix,
575
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
576
      '#description' => t('(start from last values)'),
577
      '#fieldset' => 'multiple_field_settings',
578
    );
579
    $form['delta_first_last'] = array(
580
      '#title' => t('First and last only'),
581
      '#type' => 'checkbox',
582
      '#default_value' => $this->options['delta_first_last'],
583
      '#suffix' => $suffix,
584
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
585
      '#fieldset' => 'multiple_field_settings',
586
    );
587
    $form['delta_random'] = array(
588
      '#title' => t('Random order'),
589
      '#type' => 'checkbox',
590
      '#default_value' => $this->options['delta_random'],
591
      '#suffix' => '</div>',
592
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
593
      '#fieldset' => 'multiple_field_settings',
594
    );
595
  }
596

    
597
  /**
598
   * Extend the groupby form with group columns.
599
   */
600
  function groupby_form(&$form, &$form_state) {
601
    parent::groupby_form($form, $form_state);
602
    // With "field API" fields, the column target of the grouping function
603
    // and any additional grouping columns must be specified.
604
    $group_columns = array(
605
      'entity_id' => t('Entity ID'),
606
    ) + drupal_map_assoc(array_keys($this->field_info['columns']), 'ucfirst');
607

    
608
    $form['group_column'] = array(
609
      '#type' => 'select',
610
      '#title' => t('Group column'),
611
      '#default_value' => $this->options['group_column'],
612
      '#description' => t('Select the column of this field to apply the grouping function selected above.'),
613
      '#options' => $group_columns,
614
    );
615

    
616
    $options = drupal_map_assoc(array('bundle', 'language', 'entity_type'), 'ucfirst');
617

    
618
    // Add on defined fields, noting that they're prefixed with the field name.
619
    $form['group_columns'] = array(
620
      '#type' => 'checkboxes',
621
      '#title' => t('Group columns (additional)'),
622
      '#default_value' => $this->options['group_columns'],
623
      '#description' => t('Select any additional columns of this field to include in the query and to group on.'),
624
      '#options' => $options + $group_columns,
625
    );
626
  }
627

    
628
  function groupby_form_submit(&$form, &$form_state) {
629
    parent::groupby_form_submit($form, $form_state);
630
    $item =& $form_state['handler']->options;
631

    
632
    // Add settings for "field API" fields.
633
    $item['group_column'] = $form_state['values']['options']['group_column'];
634
    $item['group_columns'] = array_filter($form_state['values']['options']['group_columns']);
635
  }
636

    
637
  /**
638
   * Load the entities for all fields that are about to be displayed.
639
   */
640
  function post_execute(&$values) {
641
    if (!empty($values)) {
642
      // Divide the entity ids by entity type, so they can be loaded in bulk.
643
      $entities_by_type = array();
644
      $revisions_by_type = array();
645
      foreach ($values as $key => $object) {
646
        if (isset($this->aliases['entity_type']) && isset($object->{$this->aliases['entity_type']}) && isset($object->{$this->field_alias}) && !isset($values[$key]->_field_data[$this->field_alias])) {
647
          $entity_type = $object->{$this->aliases['entity_type']};
648
          if (empty($this->definition['is revision'])) {
649
            $entity_id = $object->{$this->field_alias};
650
            $entities_by_type[$entity_type][$key] = $entity_id;
651
          }
652
          else {
653
            $revision_id = $object->{$this->field_alias};
654
            $entity_id = $object->{$this->aliases['entity_id']};
655
            $entities_by_type[$entity_type][$key] = array($entity_id, $revision_id);
656
          }
657
        }
658
      }
659

    
660
      // Load the entities.
661
      foreach ($entities_by_type as $entity_type => $entity_ids) {
662
        $entity_info = entity_get_info($entity_type);
663
        if (empty($this->definition['is revision'])) {
664
          $entities = entity_load($entity_type, $entity_ids);
665
          $keys = $entity_ids;
666
        }
667
        else {
668
          // Revisions can't be loaded multiple, so we have to load them
669
          // one by one.
670
          $entities = array();
671
          $keys = array();
672
          foreach ($entity_ids as $key => $combined) {
673
            list($entity_id, $revision_id) = $combined;
674
            $entity = entity_load($entity_type, array($entity_id), array($entity_info['entity keys']['revision'] => $revision_id));
675
            if ($entity) {
676
              $entities[$revision_id] = array_shift($entity);
677
              $keys[$key] = $revision_id;
678
            }
679
          }
680
        }
681

    
682
        foreach ($keys as $key => $entity_id) {
683
          // If this is a revision, load the revision instead.
684
          if (isset($entities[$entity_id])) {
685
            $values[$key]->_field_data[$this->field_alias] = array(
686
              'entity_type' => $entity_type,
687
              'entity' => $entities[$entity_id],
688
            );
689
          }
690
        }
691
      }
692

    
693
      // Now, transfer the data back into the resultset so it can be easily used.
694
      foreach ($values as $row_id => &$value) {
695
        $value->{'field_' . $this->options['id']} = $this->set_items($value, $row_id);
696
      }
697
    }
698
  }
699

    
700
  /**
701
   * Render all items in this field together.
702
   *
703
   * When using advanced render, each possible item in the list is rendered
704
   * individually. Then the items are all pasted together.
705
   */
706
  function render_items($items) {
707
    if (!empty($items)) {
708
      if (!$this->options['group_rows']) {
709
        return implode('', $items);
710
      }
711

    
712
      if ($this->options['multi_type'] == 'separator') {
713
        return implode(filter_xss_admin($this->options['separator']), $items);
714
      }
715
      else {
716
        return theme('item_list',
717
          array(
718
            'items' => $items,
719
            'title' => NULL,
720
            'type' => $this->options['multi_type']
721
          ));
722
      }
723
    }
724
  }
725

    
726
  function get_items($values) {
727
    return $values->{'field_' . $this->options['id']};
728
  }
729

    
730
  function get_value($values, $field = NULL) {
731
    if (!isset($values->_field_data[$this->field_alias]['entity']) || !is_object($values->_field_data[$this->field_alias]['entity'])) {
732
      return array();
733
    }
734

    
735
    // Go ahead and render and store in $this->items.
736
    $entity = clone $values->_field_data[$this->field_alias]['entity'];
737

    
738
    $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
739
    $langcode = $this->field_language($entity_type, $entity);
740
    // If we are grouping, copy our group fields into the cloned entity.
741
    // It's possible this will cause some weirdness, but there's only
742
    // so much we can hope to do.
743
    if (!empty($this->group_fields)) {
744
      // first, test to see if we have a base value.
745
      $base_value = array();
746
      // Note: We would copy original values here, but it can cause problems.
747
      // For example, text fields store cached filtered values as
748
      // 'safe_value' which doesn't appear anywhere in the field definition
749
      // so we can't affect it. Other side effects could happen similarly.
750
      $data = FALSE;
751
      foreach ($this->group_fields as $field_name => $column) {
752
        if (property_exists($values, $this->aliases[$column])) {
753
          $base_value[$field_name] = $values->{$this->aliases[$column]};
754
          if (isset($base_value[$field_name])) {
755
            $data = TRUE;
756
          }
757
        }
758
      }
759

    
760
      // If any of our aggregated fields have data, fake it:
761
      if ($data) {
762
        // Now, overwrite the original value with our aggregated value.
763
        // This overwrites it so there is always just one entry.
764
        $entity->{$this->definition['field_name']}[$langcode] = array($base_value);
765
      }
766
      else {
767
        $entity->{$this->definition['field_name']}[$langcode] = array();
768
      }
769
    }
770

    
771
    // The field we are trying to display doesn't exist on this entity.
772
    if (!isset($entity->{$this->definition['field_name']})) {
773
      return array();
774
    }
775

    
776
    // If requested, randomize the order of the deltas.
777
    if ($this->options['delta_random'] && !empty($entity->{$this->definition['field_name']})) {
778
      shuffle($entity->{$this->definition['field_name']}[$langcode]);
779
    }
780

    
781
    // We are supposed to show only certain deltas.
782
    if ($this->limit_values && !empty($entity->{$this->definition['field_name']})) {
783
      $all_values = !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
784
      if ($this->options['delta_reversed']) {
785
        $all_values = array_reverse($all_values);
786
      }
787

    
788
      // Offset is calculated differently when row grouping for a field is
789
      // not enabled. Since there are multiple rows, the delta needs to be
790
      // taken into account, so that different values are shown per row.
791
      if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($values->{$this->aliases['delta']})) {
792
        $delta_limit = 1;
793
        $offset = $values->{$this->aliases['delta']};
794
      }
795
      // Single fields don't have a delta available so choose 0.
796
      elseif (!$this->options['group_rows'] && !$this->multiple) {
797
        $delta_limit = 1;
798
        $offset = 0;
799
      }
800
      else {
801
        $delta_limit = $this->options['delta_limit'];
802
        $offset = intval($this->options['delta_offset']);
803

    
804
        // We should only get here in this case if there's an offset, and
805
        // in that case we're limiting to all values after the offset.
806
        if ($delta_limit == 'all') {
807
          $delta_limit = count($all_values) - $offset;
808
        }
809
      }
810

    
811
      // Determine if only the first and last values should be shown
812
      $delta_first_last = $this->options['delta_first_last'];
813

    
814
      $new_values = array();
815
      for ($i = 0; $i < $delta_limit; $i++) {
816
        $new_delta = $offset + $i;
817

    
818
        if (isset($all_values[$new_delta])) {
819
          // If first-last option was selected, only use the first and last values
820
          if (!$delta_first_last
821
            // Use the first value.
822
            || $new_delta == $offset
823
            // Use the last value.
824
            || $new_delta == ($delta_limit + $offset - 1)) {
825
            $new_values[] = $all_values[$new_delta];
826
          }
827
        }
828
      }
829
      $entity->{$this->definition['field_name']}[$langcode] = $new_values;
830
    }
831

    
832
    if ($field == 'entity') {
833
      return $entity;
834
    }
835
    else {
836
      return !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
837
    }
838
  }
839

    
840
  /**
841
   * Return an array of items for the field.
842
   */
843
  function set_items($values, $row_id) {
844
    // In some cases the instance on the entity might be easy, see
845
    // https://drupal.org/node/1161708 and https://drupal.org/node/1461536 for
846
    // more information.
847
    if (empty($values->_field_data[$this->field_alias]) || empty($values->_field_data[$this->field_alias]['entity']) || !isset($values->_field_data[$this->field_alias]['entity']->{$this->definition['field_name']})) {
848
      return array();
849
    }
850

    
851
    $display = array(
852
      'type' => $this->options['type'],
853
      'settings' => $this->options['settings'],
854
      'label' => 'hidden',
855
      // Pass the View object in the display so that fields can act on it.
856
      'views_view' => $this->view,
857
      'views_field' => $this,
858
      'views_row_id' => $row_id,
859
    );
860

    
861

    
862
    $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
863
    $entity = $this->get_value($values, 'entity');
864
    if (!$entity) {
865
      return array();
866
    }
867

    
868
    $langcode = $this->field_language($entity_type, $entity);
869
    $render_array = field_view_field($entity_type, $entity, $this->definition['field_name'], $display, $langcode);
870

    
871
    $items = array();
872
    if ($this->options['field_api_classes']) {
873
      // Make a copy.
874
      $array = $render_array;
875
      return array(array('rendered' => drupal_render($render_array)));
876
    }
877

    
878
    foreach (element_children($render_array) as $count) {
879
      $items[$count]['rendered'] = $render_array[$count];
880
      // field_view_field() adds an #access property to the render array that
881
      // determines whether or not the current user is allowed to view the
882
      // field in the context of the current entity. We need to respect this
883
      // parameter when we pull out the children of the field array for
884
      // rendering.
885
      if (isset($render_array['#access'])) {
886
        $items[$count]['rendered']['#access'] = $render_array['#access'];
887
      }
888
      // Only add the raw field items (for use in tokens) if the current user
889
      // has access to view the field content.
890
      if ((!isset($items[$count]['rendered']['#access']) || $items[$count]['rendered']['#access']) && !empty($render_array['#items'][$count])) {
891
        $items[$count]['raw'] = $render_array['#items'][$count];
892
      }
893
    }
894
    return $items;
895
  }
896

    
897
  function render_item($count, $item) {
898
    return render($item['rendered']);
899
  }
900

    
901
  function document_self_tokens(&$tokens) {
902
    $field = $this->field_info;
903
    foreach ($field['columns'] as $id => $column) {
904
      $tokens['[' . $this->options['id'] . '-' . $id . ']'] = t('Raw @column', array('@column' => $id));
905
    }
906
  }
907

    
908
  function add_self_tokens(&$tokens, $item) {
909
    $field = $this->field_info;
910
    foreach ($field['columns'] as $id => $column) {
911
      // Use filter_xss_admin because it's user data and we can't be sure it is safe.
912
      // We know nothing about the data, though, so we can't really do much else.
913

    
914
      if (isset($item['raw'])) {
915
        // If $item['raw'] is an array then we can use as is, if it's an object
916
        // we cast it to an array, if it's neither, we can't use it.
917
        $raw = is_array($item['raw']) ? $item['raw'] :
918
               (is_object($item['raw']) ? (array)$item['raw'] : NULL);
919
      }
920
      if (isset($raw) && isset($raw[$id]) && is_scalar($raw[$id])) {
921
        $tokens['[' . $this->options['id'] . '-' . $id . ']'] = filter_xss_admin($raw[$id]);
922
      }
923
      else {
924
        // Take sure that empty values are replaced as well.
925
        $tokens['[' . $this->options['id'] . '-' . $id . ']'] = '';
926
      }
927
    }
928
  }
929

    
930
  /**
931
   * Return the language code of the language the field should be displayed in,
932
   * according to the settings.
933
   */
934
  function field_language($entity_type, $entity) {
935
    global $language_content;
936

    
937
    if (field_is_translatable($entity_type, $this->field_info)) {
938
      $default_language = language_default('language');
939
      $language = str_replace(array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
940
                              array($language_content->language, $default_language),
941
                              $this->view->display_handler->options['field_language']);
942

    
943
      // Give the Field Language API a chance to fallback to a different language
944
      // (or LANGUAGE_NONE), in case the field has no data for the selected language.
945
      // field_view_field() does this as well, but since the returned language code
946
      // is used before calling it, the fallback needs to happen explicitly.
947
      $language = field_language($entity_type, $entity, $this->field_info['field_name'], $language);
948

    
949
      return $language;
950
    }
951
    else {
952
      return LANGUAGE_NONE;
953
    }
954
  }
955
}