Projet

Général

Profil

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

root / drupal7 / sites / all / modules / views / modules / field / views_handler_field_field.inc @ 6eb8d15f

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

    
387
    $options['multi_type'] = array(
388
      'default' => 'separator'
389
    );
390
    $options['separator'] = array(
391
      'default' => ', '
392
    );
393

    
394
    $options['field_api_classes'] = array(
395
      'default' => FALSE,
396
      'bool' => TRUE,
397
    );
398

    
399
    return $options;
400
  }
401

    
402
  function options_form(&$form, &$form_state) {
403
    parent::options_form($form, $form_state);
404

    
405
    $field = $this->field_info;
406
    $formatters = _field_view_formatter_options($field['type']);
407
    $column_names = array_keys($field['columns']);
408

    
409
    // If this is a multiple value field, add its options.
410
    if ($this->multiple) {
411
      $this->multiple_options_form($form, $form_state);
412
    }
413

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

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

    
444
    $form['field_api_classes'] = array(
445
      '#title' => t('Use field template'),
446
      '#type' => 'checkbox',
447
      '#default_value' => $this->options['field_api_classes'],
448
      '#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.'),
449
      '#fieldset' => 'style_settings',
450
      '#weight' => 20,
451
    );
452

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

    
457
    // Get the currently selected formatter.
458
    $format = $this->options['type'];
459

    
460
    $formatter = field_info_formatter_types($format);
461
    $settings = $this->options['settings'] + field_info_formatter_settings($format);
462

    
463
    // Provide an instance array for hook_field_formatter_settings_form().
464
    ctools_include('fields');
465
    $this->instance = ctools_fields_fake_field_instance($this->definition['field_name'], '_custom', $formatter, $settings);
466

    
467
    // Store the settings in a '_custom' view mode.
468
    $this->instance['display']['_custom'] = array(
469
      'type' => $format,
470
      'settings' => $settings,
471
    );
472

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

    
482
  /**
483
   * Provide options for multiple value fields.
484
   */
485
  function multiple_options_form(&$form, &$form_state) {
486
    $field = $this->field_info;
487

    
488
    $form['multiple_field_settings'] = array(
489
      '#type' => 'fieldset',
490
      '#title' => t('Multiple field settings'),
491
      '#collapsible' => TRUE,
492
      '#collapsed' => TRUE,
493
      '#weight' => 5,
494
    );
495

    
496
    $form['group_rows'] = array(
497
      '#title' => t('Display all values in the same row'),
498
      '#type' => 'checkbox',
499
      '#default_value' => $this->options['group_rows'],
500
      '#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.'),
501
      '#fieldset' => 'multiple_field_settings',
502
    );
503

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

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

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

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

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

    
585
  /**
586
   * Extend the groupby form with group columns.
587
   */
588
  function groupby_form(&$form, &$form_state) {
589
    parent::groupby_form($form, $form_state);
590
    // With "field API" fields, the column target of the grouping function
591
    // and any additional grouping columns must be specified.
592
    $group_columns = array(
593
      'entity_id' => t('Entity ID'),
594
    ) + drupal_map_assoc(array_keys($this->field_info['columns']), 'ucfirst');
595

    
596
    $form['group_column'] = array(
597
      '#type' => 'select',
598
      '#title' => t('Group column'),
599
      '#default_value' => $this->options['group_column'],
600
      '#description' => t('Select the column of this field to apply the grouping function selected above.'),
601
      '#options' => $group_columns,
602
    );
603

    
604
    $options = drupal_map_assoc(array('bundle', 'language', 'entity_type'), 'ucfirst');
605

    
606
    // Add on defined fields, noting that they're prefixed with the field name.
607
    $form['group_columns'] = array(
608
      '#type' => 'checkboxes',
609
      '#title' => t('Group columns (additional)'),
610
      '#default_value' => $this->options['group_columns'],
611
      '#description' => t('Select any additional columns of this field to include in the query and to group on.'),
612
      '#options' => $options + $group_columns,
613
    );
614
  }
615

    
616
  function groupby_form_submit(&$form, &$form_state) {
617
    parent::groupby_form_submit($form, $form_state);
618
    $item =& $form_state['handler']->options;
619

    
620
    // Add settings for "field API" fields.
621
    $item['group_column'] = $form_state['values']['options']['group_column'];
622
    $item['group_columns'] = array_filter($form_state['values']['options']['group_columns']);
623
  }
624

    
625
  /**
626
   * Load the entities for all fields that are about to be displayed.
627
   */
628
  function post_execute(&$values) {
629
    if (!empty($values)) {
630
      // Divide the entity ids by entity type, so they can be loaded in bulk.
631
      $entities_by_type = array();
632
      $revisions_by_type = array();
633
      foreach ($values as $key => $object) {
634
        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])) {
635
          $entity_type = $object->{$this->aliases['entity_type']};
636
          if (empty($this->definition['is revision'])) {
637
            $entity_id = $object->{$this->field_alias};
638
            $entities_by_type[$entity_type][$key] = $entity_id;
639
          }
640
          else {
641
            $revision_id = $object->{$this->field_alias};
642
            $entity_id = $object->{$this->aliases['entity_id']};
643
            $entities_by_type[$entity_type][$key] = array($entity_id, $revision_id);
644
          }
645
        }
646
      }
647

    
648
      // Load the entities.
649
      foreach ($entities_by_type as $entity_type => $entity_ids) {
650
        $entity_info = entity_get_info($entity_type);
651
        if (empty($this->definition['is revision'])) {
652
          $entities = entity_load($entity_type, $entity_ids);
653
          $keys = $entity_ids;
654
        }
655
        else {
656
          // Revisions can't be loaded multiple, so we have to load them
657
          // one by one.
658
          $entities = array();
659
          $keys = array();
660
          foreach ($entity_ids as $key => $combined) {
661
            list($entity_id, $revision_id) = $combined;
662
            $entity = entity_load($entity_type, array($entity_id), array($entity_info['entity keys']['revision'] => $revision_id));
663
            if ($entity) {
664
              $entities[$revision_id] = array_shift($entity);
665
              $keys[$key] = $revision_id;
666
            }
667
          }
668
        }
669

    
670
        foreach ($keys as $key => $entity_id) {
671
          // If this is a revision, load the revision instead.
672
          if (isset($entities[$entity_id])) {
673
            $values[$key]->_field_data[$this->field_alias] = array(
674
              'entity_type' => $entity_type,
675
              'entity' => $entities[$entity_id],
676
            );
677
          }
678
        }
679
      }
680

    
681
      // Now, transfer the data back into the resultset so it can be easily used.
682
      foreach ($values as $row_id => &$value) {
683
        $value->{'field_' . $this->options['id']} = $this->set_items($value, $row_id);
684
      }
685
    }
686
  }
687

    
688
  /**
689
   * Render all items in this field together.
690
   *
691
   * When using advanced render, each possible item in the list is rendered
692
   * individually. Then the items are all pasted together.
693
   */
694
  function render_items($items) {
695
    if (!empty($items)) {
696
      if (!$this->options['group_rows']) {
697
        return implode('', $items);
698
      }
699

    
700
      if ($this->options['multi_type'] == 'separator') {
701
        return implode(filter_xss_admin($this->options['separator']), $items);
702
      }
703
      else {
704
        return theme('item_list',
705
          array(
706
            'items' => $items,
707
            'title' => NULL,
708
            'type' => $this->options['multi_type']
709
          ));
710
      }
711
    }
712
  }
713

    
714
  function get_items($values) {
715
    return $values->{'field_' . $this->options['id']};
716
  }
717

    
718
  function get_value($values, $field = NULL) {
719
    // Go ahead and render and store in $this->items.
720
    $entity = clone $values->_field_data[$this->field_alias]['entity'];
721

    
722
    $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
723
    $langcode = $this->field_language($entity_type, $entity);
724
    // If we are grouping, copy our group fields into the cloned entity.
725
    // It's possible this will cause some weirdness, but there's only
726
    // so much we can hope to do.
727
    if (!empty($this->group_fields)) {
728
      // first, test to see if we have a base value.
729
      $base_value = array();
730
      // Note: We would copy original values here, but it can cause problems.
731
      // For example, text fields store cached filtered values as
732
      // 'safe_value' which doesn't appear anywhere in the field definition
733
      // so we can't affect it. Other side effects could happen similarly.
734
      $data = FALSE;
735
      foreach ($this->group_fields as $field_name => $column) {
736
        if (property_exists($values, $this->aliases[$column])) {
737
          $base_value[$field_name] = $values->{$this->aliases[$column]};
738
          if (isset($base_value[$field_name])) {
739
            $data = TRUE;
740
          }
741
        }
742
      }
743

    
744
      // If any of our aggregated fields have data, fake it:
745
      if ($data) {
746
        // Now, overwrite the original value with our aggregated value.
747
        // This overwrites it so there is always just one entry.
748
        $entity->{$this->definition['field_name']}[$langcode] = array($base_value);
749
      }
750
      else {
751
        $entity->{$this->definition['field_name']}[$langcode] = array();
752
      }
753
    }
754

    
755
    // The field we are trying to display doesn't exist on this entity.
756
    if (!isset($entity->{$this->definition['field_name']})) {
757
      return array();
758
    }
759

    
760
    // We are supposed to show only certain deltas.
761
    if ($this->limit_values && !empty($entity->{$this->definition['field_name']})) {
762
      $all_values = !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
763
      if ($this->options['delta_reversed']) {
764
        $all_values = array_reverse($all_values);
765
      }
766

    
767
      // Offset is calculated differently when row grouping for a field is
768
      // not enabled. Since there are multiple rows, the delta needs to be
769
      // taken into account, so that different values are shown per row.
770
      if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($values->{$this->aliases['delta']})) {
771
        $delta_limit = 1;
772
        $offset = $values->{$this->aliases['delta']};
773
      }
774
      // Single fields don't have a delta available so choose 0.
775
      elseif (!$this->options['group_rows'] && !$this->multiple) {
776
        $delta_limit = 1;
777
        $offset = 0;
778
      }
779
      else {
780
        $delta_limit = $this->options['delta_limit'];
781
        $offset = intval($this->options['delta_offset']);
782

    
783
        // We should only get here in this case if there's an offset, and
784
        // in that case we're limiting to all values after the offset.
785
        if ($delta_limit == 'all') {
786
          $delta_limit = count($all_values) - $offset;
787
        }
788
      }
789

    
790
      // Determine if only the first and last values should be shown
791
      $delta_first_last = $this->options['delta_first_last'];
792

    
793
      $new_values = array();
794
      for ($i = 0; $i < $delta_limit; $i++) {
795
        $new_delta = $offset + $i;
796

    
797
        if (isset($all_values[$new_delta])) {
798
          // If first-last option was selected, only use the first and last values
799
          if (!$delta_first_last
800
            // Use the first value.
801
            || $new_delta == $offset
802
            // Use the last value.
803
            || $new_delta == ($delta_limit + $offset - 1)) {
804
            $new_values[] = $all_values[$new_delta];
805
          }
806
        }
807
      }
808
      $entity->{$this->definition['field_name']}[$langcode] = $new_values;
809
    }
810

    
811
    if ($field == 'entity') {
812
      return $entity;
813
    }
814
    else {
815
      return !empty($entity->{$this->definition['field_name']}[$langcode]) ? $entity->{$this->definition['field_name']}[$langcode] : array();
816
    }
817
  }
818

    
819
  /**
820
   * Return an array of items for the field.
821
   */
822
  function set_items($values, $row_id) {
823
    // In some cases the instance on the entity might be easy, see
824
    // https://drupal.org/node/1161708 and https://drupal.org/node/1461536 for
825
    // more information.
826
    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']})) {
827
      return array();
828
    }
829

    
830
    $display = array(
831
      'type' => $this->options['type'],
832
      'settings' => $this->options['settings'],
833
      'label' => 'hidden',
834
      // Pass the View object in the display so that fields can act on it.
835
      'views_view' => $this->view,
836
      'views_field' => $this,
837
      'views_row_id' => $row_id,
838
    );
839

    
840

    
841
    $entity_type = $values->_field_data[$this->field_alias]['entity_type'];
842
    $entity = $this->get_value($values, 'entity');
843
    if (!$entity) {
844
      return array();
845
    }
846

    
847
    $langcode = $this->field_language($entity_type, $entity);
848
    $render_array = field_view_field($entity_type, $entity, $this->definition['field_name'], $display, $langcode);
849

    
850
    $items = array();
851
    if ($this->options['field_api_classes']) {
852
      // Make a copy.
853
      $array = $render_array;
854
      return array(array('rendered' => drupal_render($render_array)));
855
    }
856

    
857
    foreach (element_children($render_array) as $count) {
858
      $items[$count]['rendered'] = $render_array[$count];
859
      // field_view_field() adds an #access property to the render array that
860
      // determines whether or not the current user is allowed to view the
861
      // field in the context of the current entity. We need to respect this
862
      // parameter when we pull out the children of the field array for
863
      // rendering.
864
      if (isset($render_array['#access'])) {
865
        $items[$count]['rendered']['#access'] = $render_array['#access'];
866
      }
867
      // Only add the raw field items (for use in tokens) if the current user
868
      // has access to view the field content.
869
      if ((!isset($items[$count]['rendered']['#access']) || $items[$count]['rendered']['#access']) && !empty($render_array['#items'][$count])) {
870
        $items[$count]['raw'] = $render_array['#items'][$count];
871
      }
872
    }
873
    return $items;
874
  }
875

    
876
  function render_item($count, $item) {
877
    return render($item['rendered']);
878
  }
879

    
880
  function document_self_tokens(&$tokens) {
881
    $field = $this->field_info;
882
    foreach ($field['columns'] as $id => $column) {
883
      $tokens['[' . $this->options['id'] . '-' . $id . ']'] = t('Raw @column', array('@column' => $id));
884
    }
885
  }
886

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

    
893
      if (isset($item['raw'])) {
894
        // If $item['raw'] is an array then we can use as is, if it's an object
895
        // we cast it to an array, if it's neither, we can't use it.
896
        $raw = is_array($item['raw']) ? $item['raw'] :
897
               (is_object($item['raw']) ? (array)$item['raw'] : NULL);
898
      }
899
      if (isset($raw) && isset($raw[$id]) && is_scalar($raw[$id])) {
900
        $tokens['[' . $this->options['id'] . '-' . $id . ']'] = filter_xss_admin($raw[$id]);
901
      }
902
      else {
903
        // Take sure that empty values are replaced as well.
904
        $tokens['[' . $this->options['id'] . '-' . $id . ']'] = '';
905
      }
906
    }
907
  }
908

    
909
  /**
910
   * Return the language code of the language the field should be displayed in,
911
   * according to the settings.
912
   */
913
  function field_language($entity_type, $entity) {
914
    global $language_content;
915

    
916
    if (field_is_translatable($entity_type, $this->field_info)) {
917
      $default_language = language_default('language');
918
      $language = str_replace(array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
919
                              array($language_content->language, $default_language),
920
                              $this->view->display_handler->options['field_language']);
921

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

    
928
      return $language;
929
    }
930
    else {
931
      return LANGUAGE_NONE;
932
    }
933
  }
934
}