Projet

Général

Profil

Paste
Télécharger (34,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / views / modules / field / views_handler_field_field.inc @ 5d12d676

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
  /**
43
   * An array to store field renderable arrays for use by render_items.
44
   *
45
   * @var array
46
   */
47
  public $items = array();
48

    
49
  /**
50
   * Store the field information.
51
   *
52
   * @var array
53
   */
54
  public $field_info = array();
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
  /**
85
   * {@inheritdoc}
86
   */
87
  public function init(&$view, &$options) {
88
    parent::init($view, $options);
89

    
90
    $this->field_info = $field = field_info_field($this->definition['field_name']);
91
    $this->multiple = FALSE;
92
    $this->limit_values = FALSE;
93

    
94
    if ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
95
      $this->multiple = TRUE;
96

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

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

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

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

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

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

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

    
158
    return $this->base_table;
159
  }
160

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

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

    
184
      $this->ensure_my_table();
185
    }
186

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

    
193
    if (isset($this->relationship)) {
194
      $this->base_table_alias = $this->relationship;
195
    }
196
    else {
197
      $this->base_table_alias = $this->base_table;
198
    }
199

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

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

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

    
221
    if ($use_groupby) {
222
      // Add the fields that we're actually grouping on.
223
      $options = array();
224

    
225
      if ($this->options['group_column'] != 'entity_id') {
226
        $options = array($this->options['group_column'] => $this->options['group_column']);
227
      }
228

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

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

    
241
        $fields[$column] = $name;
242
      }
243

    
244
      $this->group_fields = $fields;
245
    }
246

    
247
    // Add additional fields (and the table join itself) if needed.
248
    if ($this->add_field_table($use_groupby)) {
249
      $this->ensure_my_table();
250
      $this->add_additional_fields($fields);
251

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

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

    
284
    $this->add_additional_fields(array('revision_id'));
285
  }
286

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

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

    
319
  /**
320
   * Called to determine what to tell the clicksorter.
321
   */
322
  public function click_sort($order) {
323
    // No column selected, can't continue.
324
    if (empty($this->options['click_sort_column'])) {
325
      return;
326
    }
327

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

    
337
  /**
338
   * {@inheritdoc}
339
   */
340
  public function option_definition() {
341
    $options = parent::option_definition();
342

    
343
    // option_definition runs before init/construct, so no $this->field_info
344
    $field = field_info_field($this->definition['field_name']);
345
    $field_type = field_info_field_types($field['type']);
346
    $column_names = array_keys($field['columns']);
347
    $default_column = '';
348
    // Try to determine a sensible default.
349
    if (count($column_names) == 1) {
350
      $default_column = $column_names[0];
351
    }
352
    elseif (in_array('value', $column_names)) {
353
      $default_column = 'value';
354
    }
355

    
356
    // If the field has a "value" column, we probably need that one.
357
    $options['click_sort_column'] = array(
358
      'default' => $default_column,
359
    );
360
    $options['type'] = array(
361
      'default' => $field_type['default_formatter'],
362
    );
363
    $options['settings'] = array(
364
      'default' => array(),
365
    );
366
    $options['group_column'] = array(
367
      'default' => $default_column,
368
    );
369
    $options['group_columns'] = array(
370
      'default' => array(),
371
    );
372

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

    
399
    $options['multi_type'] = array(
400
      'default' => 'separator',
401
    );
402
    $options['separator'] = array(
403
      'default' => ', ',
404
    );
405

    
406
    $options['field_api_classes'] = array(
407
      'default' => FALSE,
408
      'bool' => TRUE,
409
    );
410

    
411
    return $options;
412
  }
413

    
414
  /**
415
   * {@inheritdoc}
416
   */
417
  public function options_form(&$form, &$form_state) {
418
    parent::options_form($form, $form_state);
419

    
420
    $field = $this->field_info;
421
    $formatters = _field_view_formatter_options($field['type']);
422
    $column_names = array_keys($field['columns']);
423

    
424
    // If this is a multiple value field, add its options.
425
    if ($this->multiple) {
426
      $this->multiple_options_form($form, $form_state);
427
    }
428

    
429
    // No need to ask the user anything if the field has only one column.
430
    if (count($field['columns']) == 1) {
431
      $form['click_sort_column'] = array(
432
        '#type' => 'value',
433
        '#value' => isset($column_names[0]) ? $column_names[0] : '',
434
      );
435
    }
436
    else {
437
      $form['click_sort_column'] = array(
438
        '#type' => 'select',
439
        '#title' => t('Column used for click sorting'),
440
        '#options' => drupal_map_assoc($column_names),
441
        '#default_value' => $this->options['click_sort_column'],
442
        '#description' => t('Used by Style: Table to determine the actual column to click sort the field on. The default is usually fine.'),
443
        '#fieldset' => 'more',
444
      );
445
    }
446

    
447
    $form['type'] = array(
448
      '#type' => 'select',
449
      '#title' => t('Formatter'),
450
      '#options' => $formatters,
451
      '#default_value' => $this->options['type'],
452
      '#ajax' => array(
453
        'path' => views_ui_build_form_url($form_state),
454
      ),
455
      '#submit' => array('views_ui_config_item_form_submit_temporary'),
456
      '#executes_submit_callback' => TRUE,
457
    );
458

    
459
    $form['field_api_classes'] = array(
460
      '#title' => t('Use field template'),
461
      '#type' => 'checkbox',
462
      '#default_value' => $this->options['field_api_classes'],
463
      '#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.'),
464
      '#fieldset' => 'style_settings',
465
      '#weight' => 20,
466
    );
467

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

    
472
    // Get the currently selected formatter.
473
    $format = $this->options['type'];
474

    
475
    $formatter = field_info_formatter_types($format);
476
    $settings = $this->options['settings'] + field_info_formatter_settings($format);
477

    
478
    // Provide an instance array for hook_field_formatter_settings_form().
479
    ctools_include('fields');
480
    $this->instance = ctools_fields_fake_field_instance($this->definition['field_name'], '_custom', $formatter, $settings);
481

    
482
    // Store the settings in a '_custom' view mode.
483
    $this->instance['display']['_custom'] = array(
484
      'type' => $format,
485
      'settings' => $settings,
486
    );
487

    
488
    // Get the settings form.
489
    $settings_form = array('#value' => array());
490
    $function = $formatter['module'] . '_field_formatter_settings_form';
491
    if (function_exists($function)) {
492
      $settings_form = $function($field, $this->instance, '_custom', $form, $form_state);
493
    }
494
    $form['settings'] = $settings_form;
495
  }
496

    
497
  /**
498
   * Provide options for multiple value fields.
499
   */
500
  public function multiple_options_form(&$form, &$form_state) {
501
    $field = $this->field_info;
502

    
503
    $form['multiple_field_settings'] = array(
504
      '#type' => 'fieldset',
505
      '#title' => t('Multiple field settings'),
506
      '#collapsible' => TRUE,
507
      '#collapsed' => TRUE,
508
      '#weight' => 5,
509
    );
510

    
511
    $form['group_rows'] = array(
512
      '#title' => t('Display all values in the same row'),
513
      '#type' => 'checkbox',
514
      '#default_value' => $this->options['group_rows'],
515
      '#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.'),
516
      '#fieldset' => 'multiple_field_settings',
517
    );
518

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

    
523
    if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
524
      $type = 'textfield';
525
      $options = NULL;
526
      $size = 5;
527
    }
528
    else {
529
      $type = 'select';
530
      $options = drupal_map_assoc(range(1, $field['cardinality']));
531
      $size = 1;
532
    }
533
    $form['multi_type'] = array(
534
      '#type' => 'radios',
535
      '#title' => t('Display type'),
536
      '#options' => array(
537
        'ul' => t('Unordered list'),
538
        'ol' => t('Ordered list'),
539
        'separator' => t('Simple separator'),
540
      ),
541
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
542
      '#default_value' => $this->options['multi_type'],
543
      '#fieldset' => 'multiple_field_settings',
544
    );
545

    
546
    $form['separator'] = array(
547
      '#type' => 'textfield',
548
      '#title' => t('Separator'),
549
      '#default_value' => $this->options['separator'],
550
      '#dependency' => array(
551
        'radio:options[multi_type]' => array('separator'),
552
        'edit-options-group-rows' => array(TRUE),
553
      ),
554
      '#dependency_count' => 2,
555
      '#fieldset' => 'multiple_field_settings',
556
    );
557

    
558
    $form['delta_limit'] = array(
559
      '#type' => $type,
560
      '#size' => $size,
561
      '#field_prefix' => $prefix,
562
      '#field_suffix' => $suffix,
563
      '#options' => $options,
564
      '#default_value' => $this->options['delta_limit'],
565
      '#prefix' => '<div class="container-inline">',
566
      '#dependency' => array('edit-options-group-rows' => array(TRUE)),
567
      '#fieldset' => 'multiple_field_settings',
568
    );
569

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

    
608
  /**
609
   * Extend the groupby form with group columns.
610
   */
611
  public function groupby_form(&$form, &$form_state) {
612
    parent::groupby_form($form, $form_state);
613
    // With "field API" fields, the column target of the grouping function
614
    // and any additional grouping columns must be specified.
615
    $group_columns = array(
616
      'entity_id' => t('Entity ID'),
617
    ) + drupal_map_assoc(array_keys($this->field_info['columns']), 'ucfirst');
618

    
619
    $form['group_column'] = array(
620
      '#type' => 'select',
621
      '#title' => t('Group column'),
622
      '#default_value' => $this->options['group_column'],
623
      '#description' => t('Select the column of this field to apply the grouping function selected above.'),
624
      '#options' => $group_columns,
625
    );
626

    
627
    $options = drupal_map_assoc(array('bundle', 'language', 'entity_type'), 'ucfirst');
628

    
629
    // Add on defined fields, noting that they're prefixed with the field name.
630
    $form['group_columns'] = array(
631
      '#type' => 'checkboxes',
632
      '#title' => t('Group columns (additional)'),
633
      '#default_value' => $this->options['group_columns'],
634
      '#description' => t('Select any additional columns of this field to include in the query and to group on.'),
635
      '#options' => $options + $group_columns,
636
    );
637
  }
638

    
639
  /**
640
   * {@inheritdoc}
641
   */
642
  public function groupby_form_submit(&$form, &$form_state) {
643
    parent::groupby_form_submit($form, $form_state);
644
    $item =& $form_state['handler']->options;
645

    
646
    // Add settings for "field API" fields.
647
    $item['group_column'] = $form_state['values']['options']['group_column'];
648
    $item['group_columns'] = array_filter($form_state['values']['options']['group_columns']);
649
  }
650

    
651
  /**
652
   * Load the entities for all fields that are about to be displayed.
653
   */
654
  public function post_execute(&$values) {
655
    if (!empty($values)) {
656
      // Divide the entity ids by entity type, so they can be loaded in bulk.
657
      $entities_by_type = array();
658
      $revisions_by_type = array();
659
      foreach ($values as $key => $object) {
660
        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])) {
661
          $entity_type = $object->{$this->aliases['entity_type']};
662
          if (empty($this->definition['is revision'])) {
663
            $entity_id = $object->{$this->field_alias};
664
            $entities_by_type[$entity_type][$key] = $entity_id;
665
          }
666
          else {
667
            $revision_id = $object->{$this->field_alias};
668
            $entity_id = $object->{$this->aliases['entity_id']};
669
            $entities_by_type[$entity_type][$key] = array($entity_id, $revision_id);
670
          }
671
        }
672
      }
673

    
674
      // Load the entities.
675
      foreach ($entities_by_type as $entity_type => $entity_ids) {
676
        $entity_info = entity_get_info($entity_type);
677
        if (empty($this->definition['is revision'])) {
678
          $entities = entity_load($entity_type, $entity_ids);
679
          $keys = $entity_ids;
680
        }
681
        else {
682
          // Revisions can't be loaded multiple, so we have to load them
683
          // one by one.
684
          $entities = array();
685
          $keys = array();
686
          foreach ($entity_ids as $key => $combined) {
687
            list($entity_id, $revision_id) = $combined;
688
            $entity = entity_load($entity_type, array($entity_id), array($entity_info['entity keys']['revision'] => $revision_id));
689
            if ($entity) {
690
              $entities[$revision_id] = array_shift($entity);
691
              $keys[$key] = $revision_id;
692
            }
693
          }
694
        }
695

    
696
        foreach ($keys as $key => $entity_id) {
697
          // If this is a revision, load the revision instead.
698
          if (isset($entities[$entity_id])) {
699
            $values[$key]->_field_data[$this->field_alias] = array(
700
              'entity_type' => $entity_type,
701
              'entity' => $entities[$entity_id],
702
            );
703
          }
704
        }
705
      }
706

    
707
      // Now, transfer the data back into the resultset so it can be easily
708
      // used.
709
      foreach ($values as $row_id => &$value) {
710
        $value->{'field_' . $this->options['id']} = $this->set_items($value, $row_id);
711
      }
712
    }
713
  }
714

    
715
  /**
716
   * Render all items in this field together.
717
   *
718
   * When using advanced render, each possible item in the list is rendered
719
   * individually. Then the items are all pasted together.
720
   */
721
  public function render_items($items) {
722
    if (!empty($items)) {
723
      if (!$this->options['group_rows']) {
724
        return implode('', $items);
725
      }
726

    
727
      if ($this->options['multi_type'] == 'separator') {
728
        return implode(filter_xss_admin($this->options['separator']), $items);
729
      }
730
      else {
731
        return theme('item_list',
732
          array(
733
            'items' => $items,
734
            'title' => NULL,
735
            'type' => $this->options['multi_type'],
736
          ));
737
      }
738
    }
739
  }
740

    
741
  /**
742
   * {@inheritdoc}
743
   */
744
  public function get_items($values) {
745
    return $values->{'field_' . $this->options['id']};
746
  }
747

    
748
  /**
749
   * {@inheritdoc}
750
   */
751
  public function get_value($values, $field = NULL) {
752
    if (!isset($values->_field_data[$this->field_alias]['entity']) || !is_object($values->_field_data[$this->field_alias]['entity'])) {
753
      return array();
754
    }
755

    
756
    // Go ahead and render and store in $this->items.
757
    $entity = clone $values->_field_data[$this->field_alias]['entity'];
758

    
759
    $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
760
    $langcode = $this->field_language($entity_type, $entity);
761
    // If we are grouping, copy our group fields into the cloned entity.
762
    // It's possible this will cause some weirdness, but there's only
763
    // so much we can hope to do.
764
    if (!empty($this->group_fields)) {
765
      // first, test to see if we have a base value.
766
      $base_value = array();
767
      // Note: We would copy original values here, but it can cause problems.
768
      // For example, text fields store cached filtered values as
769
      // 'safe_value' which doesn't appear anywhere in the field definition
770
      // so we can't affect it. Other side effects could happen similarly.
771
      $data = FALSE;
772
      foreach ($this->group_fields as $field_name => $column) {
773
        if (property_exists($values, $this->aliases[$column])) {
774
          $base_value[$field_name] = $values->{$this->aliases[$column]};
775
          if (isset($base_value[$field_name])) {
776
            $data = TRUE;
777
          }
778
        }
779
      }
780

    
781
      // If any of our aggregated fields have data, fake it.
782
      if ($data) {
783
        // Now, overwrite the original value with our aggregated value.
784
        // This overwrites it so there is always just one entry.
785
        $entity->{$this->definition['field_name']}[$langcode] = array($base_value);
786
      }
787
      else {
788
        $entity->{$this->definition['field_name']}[$langcode] = array();
789
      }
790
    }
791

    
792
    // The field we are trying to display doesn't exist on this entity.
793
    if (!isset($entity->{$this->definition['field_name']})) {
794
      return array();
795
    }
796

    
797
    // If requested, randomize the order of the deltas.
798
    if ($this->options['delta_random'] && !empty($entity->{$this->definition['field_name']})) {
799
      shuffle($entity->{$this->definition['field_name']}[$langcode]);
800
    }
801

    
802
    // We are supposed to show only certain deltas.
803
    if ($this->limit_values && !empty($entity->{$this->definition['field_name']})) {
804
      $all_values = !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
805
      if ($this->options['delta_reversed']) {
806
        $all_values = array_reverse($all_values);
807
      }
808

    
809
      // Offset is calculated differently when row grouping for a field is
810
      // not enabled. Since there are multiple rows, the delta needs to be
811
      // taken into account, so that different values are shown per row.
812
      if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($values->{$this->aliases['delta']})) {
813
        $delta_limit = 1;
814
        $offset = $values->{$this->aliases['delta']};
815
      }
816
      // Single fields don't have a delta available so choose 0.
817
      elseif (!$this->options['group_rows'] && !$this->multiple) {
818
        $delta_limit = 1;
819
        $offset = 0;
820
      }
821
      else {
822
        $delta_limit = $this->options['delta_limit'];
823
        $offset = intval($this->options['delta_offset']);
824

    
825
        // We should only get here in this case if there's an offset, and
826
        // in that case we're limiting to all values after the offset.
827
        if ($delta_limit == 'all') {
828
          $delta_limit = count($all_values) - $offset;
829
        }
830
      }
831

    
832
      // Determine if only the first and last values should be shown.
833
      $delta_first_last = $this->options['delta_first_last'];
834

    
835
      $new_values = array();
836
      for ($i = 0; $i < $delta_limit; $i++) {
837
        $new_delta = $offset + $i;
838

    
839
        if (isset($all_values[$new_delta])) {
840
          // If first-last option was selected, only use the first and last
841
          // values.
842
          if (!$delta_first_last
843
            // Use the first value.
844
            || $new_delta == $offset
845
            // Use the last value.
846
            || $new_delta == ($delta_limit + $offset - 1)) {
847
            $new_values[] = $all_values[$new_delta];
848
          }
849
        }
850
      }
851
      $entity->{$this->definition['field_name']}[$langcode] = $new_values;
852
    }
853

    
854
    if ($field == 'entity') {
855
      return $entity;
856
    }
857
    else {
858
      return !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
859
    }
860
  }
861

    
862
  /**
863
   * Return an array of items for the field.
864
   */
865
  public function set_items($values, $row_id) {
866
    // In some cases the instance on the entity might be easy, see
867
    // https://drupal.org/node/1161708 and https://drupal.org/node/1461536 for
868
    // more information.
869
    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']})) {
870
      return array();
871
    }
872

    
873
    $display = array(
874
      'type' => $this->options['type'],
875
      'settings' => $this->options['settings'],
876
      'label' => 'hidden',
877
      // Pass the View object in the display so that fields can act on it.
878
      'views_view' => $this->view,
879
      'views_field' => $this,
880
      'views_row_id' => $row_id,
881
    );
882

    
883
    $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
884
    $entity = $this->get_value($values, 'entity');
885
    if (!$entity) {
886
      return array();
887
    }
888

    
889
    $langcode = $this->field_language($entity_type, $entity);
890
    $render_array = field_view_field($entity_type, $entity, $this->definition['field_name'], $display, $langcode);
891

    
892
    $items = array();
893
    if ($this->options['field_api_classes']) {
894
      return array(array('rendered' => drupal_render($render_array)));
895
    }
896

    
897
    foreach (element_children($render_array) as $count) {
898
      $items[$count]['rendered'] = $render_array[$count];
899
      // field_view_field() adds an #access property to the render array that
900
      // determines whether or not the current user is allowed to view the
901
      // field in the context of the current entity. We need to respect this
902
      // parameter when we pull out the children of the field array for
903
      // rendering.
904
      if (isset($render_array['#access'])) {
905
        $items[$count]['rendered']['#access'] = $render_array['#access'];
906
      }
907
      // Only add the raw field items (for use in tokens) if the current user
908
      // has access to view the field content.
909
      if ((!isset($items[$count]['rendered']['#access']) || $items[$count]['rendered']['#access']) && !empty($render_array['#items'][$count])) {
910
        $items[$count]['raw'] = $render_array['#items'][$count];
911
      }
912
    }
913
    return $items;
914
  }
915

    
916
  /**
917
   * {@inheritdoc}
918
   */
919
  public function render_item($count, $item) {
920
    return render($item['rendered']);
921
  }
922

    
923
  /**
924
   * {@inheritdoc}
925
   */
926
  public function document_self_tokens(&$tokens) {
927
    $field = $this->field_info;
928
    foreach ($field['columns'] as $id => $column) {
929
      $tokens['[' . $this->options['id'] . '-' . $id . ']'] = t('Raw @column', array('@column' => $id));
930
    }
931
  }
932

    
933
  /**
934
   * {@inheritdoc}
935
   */
936
  public function add_self_tokens(&$tokens, $item) {
937
    $field = $this->field_info;
938
    foreach ($field['columns'] as $id => $column) {
939
      // Use filter_xss_admin because it's user data and we can't be sure it is
940
      // safe. We know nothing about the data, though, so we can't really do
941
      // much else.
942
      if (isset($item['raw'])) {
943
        // If $item['raw'] is an array then we can use as is, if it's an object
944
        // we cast it to an array, if it's neither, we can't use it.
945
        $raw = is_array($item['raw']) ? $item['raw'] :
946
               (is_object($item['raw']) ? (array) $item['raw'] : NULL);
947
      }
948
      if (isset($raw) && isset($raw[$id]) && is_scalar($raw[$id])) {
949
        $tokens['[' . $this->options['id'] . '-' . $id . ']'] = filter_xss_admin($raw[$id]);
950
      }
951
      else {
952
        // Take sure that empty values are replaced as well.
953
        $tokens['[' . $this->options['id'] . '-' . $id . ']'] = '';
954
      }
955
    }
956
  }
957

    
958
  /**
959
   * Return the language code of the language the field should be displayed in,
960
   * according to the settings.
961
   */
962
  public function field_language($entity_type, $entity) {
963
    global $language_content;
964

    
965
    if (field_is_translatable($entity_type, $this->field_info)) {
966
      $default_language = language_default('language');
967
      $language = str_replace(array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
968
                              array($language_content->language, $default_language),
969
                              $this->view->display_handler->options['field_language']);
970

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

    
978
      return $language;
979
    }
980
    else {
981
      return LANGUAGE_NONE;
982
    }
983
  }
984

    
985
}