5ee05a24d57bd9c85889fb1c81375fe03db73ed1
[yaffs-website] / web / core / modules / views / src / Plugin / views / field / EntityField.php
1 <?php
2
3 namespace Drupal\views\Plugin\views\field;
4
5 use Drupal\Component\Plugin\DependentPluginInterface;
6 use Drupal\Component\Utility\Xss;
7 use Drupal\Core\Cache\Cache;
8 use Drupal\Core\Cache\CacheableDependencyInterface;
9 use Drupal\Core\Entity\EntityInterface;
10 use Drupal\Core\Entity\EntityManagerInterface;
11 use Drupal\Core\Field\FieldStorageDefinitionInterface;
12 use Drupal\Core\Field\FieldTypePluginManagerInterface;
13 use Drupal\Core\Field\FormatterPluginManager;
14 use Drupal\Core\Form\FormHelper;
15 use Drupal\Core\Form\FormStateInterface;
16 use Drupal\Core\Language\LanguageManagerInterface;
17 use Drupal\Core\Plugin\PluginDependencyTrait;
18 use Drupal\Core\Render\BubbleableMetadata;
19 use Drupal\Core\Render\Element;
20 use Drupal\Core\Render\RendererInterface;
21 use Drupal\Core\Session\AccountInterface;
22 use Drupal\Core\TypedData\TypedDataInterface;
23 use Drupal\views\FieldAPIHandlerTrait;
24 use Drupal\views\Entity\Render\EntityFieldRenderer;
25 use Drupal\views\Plugin\views\display\DisplayPluginBase;
26 use Drupal\views\ResultRow;
27 use Drupal\views\ViewExecutable;
28 use Symfony\Component\DependencyInjection\ContainerInterface;
29
30 /**
31  * A field that displays entity field data.
32  *
33  * @ingroup views_field_handlers
34  *
35  * @ViewsField("field")
36  */
37 class EntityField extends FieldPluginBase implements CacheableDependencyInterface, MultiItemsFieldHandlerInterface {
38
39   use FieldAPIHandlerTrait;
40   use PluginDependencyTrait;
41
42   /**
43    * An array to store field renderable arrays for use by renderItems().
44    *
45    * @var array
46    */
47   public $items = [];
48
49   /**
50    * Does the field supports multiple field values.
51    *
52    * @var bool
53    */
54   public $multiple;
55
56   /**
57    * Does the rendered fields get limited.
58    *
59    * @var bool
60    */
61   public $limit_values;
62
63   /**
64    * A shortcut for $view->base_table.
65    *
66    * @var string
67    */
68   public $base_table;
69
70   /**
71    * An array of formatter options.
72    *
73    * @var array
74    */
75   protected $formatterOptions;
76
77   /**
78    * The entity manager.
79    *
80    * @var \Drupal\Core\Entity\EntityManagerInterface
81    */
82   protected $entityManager;
83
84   /**
85    * The field formatter plugin manager.
86    *
87    * @var \Drupal\Core\Field\FormatterPluginManager
88    */
89   protected $formatterPluginManager;
90
91   /**
92    * The language manager.
93    *
94    * @var \Drupal\Core\Language\LanguageManagerInterface
95    */
96   protected $languageManager;
97
98   /**
99    * The renderer.
100    *
101    * @var \Drupal\Core\Render\RendererInterface
102    */
103   protected $renderer;
104
105   /**
106    * The field type plugin manager.
107    *
108    * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
109    */
110   protected $fieldTypePluginManager;
111
112   /**
113    * Static cache for ::getEntityFieldRenderer().
114    *
115    * @var \Drupal\views\Entity\Render\EntityFieldRenderer
116    */
117   protected $entityFieldRenderer;
118
119   /**
120    * Constructs a \Drupal\field\Plugin\views\field\Field object.
121    *
122    * @param array $configuration
123    *   A configuration array containing information about the plugin instance.
124    * @param string $plugin_id
125    *   The plugin_id for the plugin instance.
126    * @param mixed $plugin_definition
127    *   The plugin implementation definition.
128    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
129    *   The field formatter plugin manager.
130    * @param \Drupal\Core\Field\FormatterPluginManager $formatter_plugin_manager
131    *   The field formatter plugin manager.
132    * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager
133    *   The field plugin type manager.
134    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
135    *   The language manager.
136    * @param \Drupal\Core\Render\RendererInterface $renderer
137    *   The renderer.
138    */
139   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, FormatterPluginManager $formatter_plugin_manager, FieldTypePluginManagerInterface $field_type_plugin_manager, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
140     parent::__construct($configuration, $plugin_id, $plugin_definition);
141
142     $this->entityManager = $entity_manager;
143     $this->formatterPluginManager = $formatter_plugin_manager;
144     $this->fieldTypePluginManager = $field_type_plugin_manager;
145     $this->languageManager = $language_manager;
146     $this->renderer = $renderer;
147
148     // @todo Unify 'entity field'/'field_name' instead of converting back and
149     //   forth. https://www.drupal.org/node/2410779
150     if (isset($this->definition['entity field'])) {
151       $this->definition['field_name'] = $this->definition['entity field'];
152     }
153   }
154
155   /**
156    * {@inheritdoc}
157    */
158   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
159     return new static(
160       $configuration,
161       $plugin_id,
162       $plugin_definition,
163       $container->get('entity.manager'),
164       $container->get('plugin.manager.field.formatter'),
165       $container->get('plugin.manager.field.field_type'),
166       $container->get('language_manager'),
167       $container->get('renderer')
168     );
169   }
170
171   /**
172    * {@inheritdoc}
173    */
174   public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
175     parent::init($view, $display, $options);
176
177     $this->multiple = FALSE;
178     $this->limit_values = FALSE;
179
180     $field_definition = $this->getFieldDefinition();
181     $cardinality = $field_definition->getFieldStorageDefinition()->getCardinality();
182     if ($field_definition->getFieldStorageDefinition()->isMultiple()) {
183       $this->multiple = TRUE;
184
185       // If "Display all values in the same row" is FALSE, then we always limit
186       // in order to show a single unique value per row.
187       if (!$this->options['group_rows']) {
188         $this->limit_values = TRUE;
189       }
190
191       // If "First and last only" is chosen, limit the values
192       if (!empty($this->options['delta_first_last'])) {
193         $this->limit_values = TRUE;
194       }
195
196       // Otherwise, we only limit values if the user hasn't selected "all", 0, or
197       // the value matching field cardinality.
198       if ((($this->options['delta_limit'] > 0) && ($this->options['delta_limit'] != $cardinality)) || intval($this->options['delta_offset'])) {
199         $this->limit_values = TRUE;
200       }
201     }
202   }
203
204   /**
205    * {@inheritdoc}
206    */
207   protected function getEntityManager() {
208     return $this->entityManager;
209   }
210
211   /**
212    * {@inheritdoc}
213    */
214   public function access(AccountInterface $account) {
215     $access_control_handler = $this->entityManager->getAccessControlHandler($this->getEntityType());
216     return $access_control_handler->fieldAccess('view', $this->getFieldDefinition(), $account);
217   }
218
219   /**
220    * Called to add the field to a query.
221    *
222    * By default, all needed data is taken from entities loaded by the query
223    * plugin. Columns are added only if they are used in groupings.
224    */
225   public function query($use_groupby = FALSE) {
226     $fields = $this->additional_fields;
227     // No need to add the entity type.
228     $entity_type_key = array_search('entity_type', $fields);
229     if ($entity_type_key !== FALSE) {
230       unset($fields[$entity_type_key]);
231     }
232
233     if ($use_groupby) {
234       // Add the fields that we're actually grouping on.
235       $options = [];
236       if ($this->options['group_column'] != 'entity_id') {
237         $options = [$this->options['group_column'] => $this->options['group_column']];
238       }
239       $options += is_array($this->options['group_columns']) ? $this->options['group_columns'] : [];
240
241       // Go through the list and determine the actual column name from field api.
242       $fields = [];
243       $table_mapping = $this->getTableMapping();
244       $field_definition = $this->getFieldStorageDefinition();
245
246       foreach ($options as $column) {
247         $fields[$column] = $table_mapping->getFieldColumnName($field_definition, $column);
248       }
249
250       $this->group_fields = $fields;
251     }
252
253     // Add additional fields (and the table join itself) if needed.
254     if ($this->add_field_table($use_groupby)) {
255       $this->ensureMyTable();
256       $this->addAdditionalFields($fields);
257     }
258
259     // Let the entity field renderer alter the query if needed.
260     $this->getEntityFieldRenderer()->query($this->query, $this->relationship);
261   }
262
263   /**
264    * Determine if the field table should be added to the query.
265    */
266   public function add_field_table($use_groupby) {
267     // Grouping is enabled.
268     if ($use_groupby) {
269       return TRUE;
270     }
271     // This a multiple value field, but "group multiple values" is not checked.
272     if ($this->multiple && !$this->options['group_rows']) {
273       return TRUE;
274     }
275     return FALSE;
276   }
277
278   /**
279    * {@inheritdoc}
280    */
281   public function clickSortable() {
282     // A field is not click sortable if it's a multiple field with
283     // "group multiple values" checked, since a click sort in that case would
284     // add a join to the field table, which would produce unwanted duplicates.
285     if ($this->multiple && $this->options['group_rows']) {
286       return FALSE;
287     }
288
289     // If field definition is set, use that.
290     if (isset($this->definition['click sortable'])) {
291       return (bool) $this->definition['click sortable'];
292     }
293
294     // Default to true.
295     return TRUE;
296   }
297
298   /**
299    * Called to determine what to tell the clicksorter.
300    */
301   public function clickSort($order) {
302     // No column selected, can't continue.
303     if (empty($this->options['click_sort_column'])) {
304       return;
305     }
306
307     $this->ensureMyTable();
308     $field_storage_definition = $this->getFieldStorageDefinition();
309     $column = $this->getTableMapping()->getFieldColumnName($field_storage_definition, $this->options['click_sort_column']);
310     if (!isset($this->aliases[$column])) {
311       // Column is not in query; add a sort on it (without adding the column).
312       $this->aliases[$column] = $this->tableAlias . '.' . $column;
313     }
314     $this->query->addOrderBy(NULL, NULL, $order, $this->aliases[$column]);
315   }
316
317   /**
318    * Gets the field storage of the used field.
319    *
320    * @return \Drupal\Core\Field\FieldStorageDefinitionInterface
321    */
322   protected function getFieldStorageDefinition() {
323     $entity_type_id = $this->definition['entity_type'];
324     $field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
325
326     $field_storage = NULL;
327     // @todo Unify 'entity field'/'field_name' instead of converting back and
328     //   forth. https://www.drupal.org/node/2410779
329     if (isset($this->definition['field_name'])) {
330       $field_storage = $field_storage_definitions[$this->definition['field_name']];
331     }
332     elseif (isset($this->definition['entity field'])) {
333       $field_storage = $field_storage_definitions[$this->definition['entity field']];
334     }
335     return $field_storage;
336   }
337
338   /**
339    * {@inheritdoc}
340    */
341   protected function defineOptions() {
342     $options = parent::defineOptions();
343
344     $field_storage_definition = $this->getFieldStorageDefinition();
345     $field_type = $this->fieldTypePluginManager->getDefinition($field_storage_definition->getType());
346     $column_names = array_keys($field_storage_definition->getColumns());
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'] = [
358       'default' => $default_column,
359     ];
360
361     if (isset($this->definition['default_formatter'])) {
362       $options['type'] = ['default' => $this->definition['default_formatter']];
363     }
364     elseif (isset($field_type['default_formatter'])) {
365       $options['type'] = ['default' => $field_type['default_formatter']];
366     }
367     else {
368       $options['type'] = ['default' => ''];
369     }
370
371     $options['settings'] = [
372       'default' => isset($this->definition['default_formatter_settings']) ? $this->definition['default_formatter_settings'] : [],
373     ];
374     $options['group_column'] = [
375       'default' => $default_column,
376     ];
377     $options['group_columns'] = [
378       'default' => [],
379     ];
380
381     // Options used for multiple value fields.
382     $options['group_rows'] = [
383       'default' => TRUE,
384     ];
385     // If we know the exact number of allowed values, then that can be
386     // the default. Otherwise, default to 'all'.
387     $options['delta_limit'] = [
388       'default' => ($field_storage_definition->getCardinality() > 1) ? $field_storage_definition->getCardinality() : 0,
389     ];
390     $options['delta_offset'] = [
391       'default' => 0,
392     ];
393     $options['delta_reversed'] = [
394       'default' => FALSE,
395     ];
396     $options['delta_first_last'] = [
397       'default' => FALSE,
398     ];
399
400     $options['multi_type'] = [
401       'default' => 'separator'
402     ];
403     $options['separator'] = [
404       'default' => ', '
405     ];
406
407     $options['field_api_classes'] = [
408       'default' => FALSE,
409     ];
410
411     return $options;
412   }
413
414   /**
415    * {@inheritdoc}
416    */
417   public function buildOptionsForm(&$form, FormStateInterface $form_state) {
418     parent::buildOptionsForm($form, $form_state);
419
420     $field = $this->getFieldDefinition();
421     $formatters = $this->formatterPluginManager->getOptions($field->getType());
422     $column_names = array_keys($field->getColumns());
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->getColumns()) == 1) {
431       $form['click_sort_column'] = [
432         '#type' => 'value',
433         '#value' => isset($column_names[0]) ? $column_names[0] : '',
434       ];
435     }
436     else {
437       $form['click_sort_column'] = [
438         '#type' => 'select',
439         '#title' => $this->t('Column used for click sorting'),
440         '#options' => array_combine($column_names, $column_names),
441         '#default_value' => $this->options['click_sort_column'],
442         '#description' => $this->t('Used by Style: Table to determine the actual column to click sort the field on. The default is usually fine.'),
443       ];
444     }
445
446     $form['type'] = [
447       '#type' => 'select',
448       '#title' => $this->t('Formatter'),
449       '#options' => $formatters,
450       '#default_value' => $this->options['type'],
451       '#ajax' => [
452         'url' => views_ui_build_form_url($form_state),
453       ],
454       '#submit' => [[$this, 'submitTemporaryForm']],
455       '#executes_submit_callback' => TRUE,
456     ];
457
458     $form['field_api_classes'] = [
459       '#title' => $this->t('Use field template'),
460       '#type' => 'checkbox',
461       '#default_value' => $this->options['field_api_classes'],
462       '#description' => $this->t('If checked, field api classes will be added by field templates. This is not recommended unless your CSS depends upon these classes. If not checked, template will not be used.'),
463       '#fieldset' => 'style_settings',
464       '#weight' => 20,
465     ];
466
467     if ($this->multiple) {
468       $form['field_api_classes']['#description'] .= ' ' . $this->t('Checking this option will cause the group Display Type and Separator values to be ignored.');
469     }
470
471     // Get the settings form.
472     $settings_form = ['#value' => []];
473     $format = isset($form_state->getUserInput()['options']['type']) ? $form_state->getUserInput()['options']['type'] : $this->options['type'];
474     if ($formatter = $this->getFormatterInstance($format)) {
475       $settings_form = $formatter->settingsForm($form, $form_state);
476       // Convert field UI selector states to work in the Views field form.
477       FormHelper::rewriteStatesSelector($settings_form, "fields[{$field->getName()}][settings_edit_form]", 'options');
478     }
479     $form['settings'] = $settings_form;
480   }
481
482   /**
483    * {@inheritdoc}
484    */
485   public function submitFormCalculateOptions(array $options, array $form_state_options) {
486     // When we change the formatter type we don't want to keep any of the
487     // previous configured formatter settings, as there might be schema
488     // conflict.
489     unset($options['settings']);
490     $options = $form_state_options + $options;
491     if (!isset($options['settings'])) {
492       $options['settings'] = [];
493     }
494     return $options;
495   }
496
497   /**
498    * Provide options for multiple value fields.
499    */
500   public function multiple_options_form(&$form, FormStateInterface $form_state) {
501     $field = $this->getFieldDefinition();
502
503     $form['multiple_field_settings'] = [
504       '#type' => 'details',
505       '#title' => $this->t('Multiple field settings'),
506       '#weight' => 5,
507     ];
508
509     $form['group_rows'] = [
510       '#title' => $this->t('Display all values in the same row'),
511       '#type' => 'checkbox',
512       '#default_value' => $this->options['group_rows'],
513       '#description' => $this->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.'),
514       '#fieldset' => 'multiple_field_settings',
515     ];
516
517     // Make the string translatable by keeping it as a whole rather than
518     // translating prefix and suffix separately.
519     list($prefix, $suffix) = explode('@count', $this->t('Display @count value(s)'));
520
521     if ($field->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
522       $type = 'textfield';
523       $options = NULL;
524       $size = 5;
525     }
526     else {
527       $type = 'select';
528       $range = range(1, $field->getCardinality());
529       $options = array_combine($range, $range);
530       $size = 1;
531     }
532     $form['multi_type'] = [
533       '#type' => 'radios',
534       '#title' => $this->t('Display type'),
535       '#options' => [
536         'ul' => $this->t('Unordered list'),
537         'ol' => $this->t('Ordered list'),
538         'separator' => $this->t('Simple separator'),
539       ],
540       '#states' => [
541         'visible' => [
542           ':input[name="options[group_rows]"]' => ['checked' => TRUE],
543         ],
544       ],
545       '#default_value' => $this->options['multi_type'],
546       '#fieldset' => 'multiple_field_settings',
547     ];
548
549     $form['separator'] = [
550       '#type' => 'textfield',
551       '#title' => $this->t('Separator'),
552       '#default_value' => $this->options['separator'],
553       '#states' => [
554         'visible' => [
555           ':input[name="options[group_rows]"]' => ['checked' => TRUE],
556           ':input[name="options[multi_type]"]' => ['value' => 'separator'],
557         ],
558       ],
559       '#fieldset' => 'multiple_field_settings',
560     ];
561
562     $form['delta_limit'] = [
563       '#type' => $type,
564       '#size' => $size,
565       '#field_prefix' => $prefix,
566       '#field_suffix' => $suffix,
567       '#options' => $options,
568       '#default_value' => $this->options['delta_limit'],
569       '#prefix' => '<div class="container-inline">',
570       '#states' => [
571         'visible' => [
572           ':input[name="options[group_rows]"]' => ['checked' => TRUE],
573         ],
574       ],
575       '#fieldset' => 'multiple_field_settings',
576     ];
577
578     list($prefix, $suffix) = explode('@count', $this->t('starting from @count'));
579     $form['delta_offset'] = [
580       '#type' => 'textfield',
581       '#size' => 5,
582       '#field_prefix' => $prefix,
583       '#field_suffix' => $suffix,
584       '#default_value' => $this->options['delta_offset'],
585       '#states' => [
586         'visible' => [
587           ':input[name="options[group_rows]"]' => ['checked' => TRUE],
588         ],
589       ],
590       '#description' => $this->t('(first item is 0)'),
591       '#fieldset' => 'multiple_field_settings',
592     ];
593     $form['delta_reversed'] = [
594       '#title' => $this->t('Reversed'),
595       '#type' => 'checkbox',
596       '#default_value' => $this->options['delta_reversed'],
597       '#suffix' => $suffix,
598       '#states' => [
599         'visible' => [
600           ':input[name="options[group_rows]"]' => ['checked' => TRUE],
601         ],
602       ],
603       '#description' => $this->t('(start from last values)'),
604       '#fieldset' => 'multiple_field_settings',
605     ];
606     $form['delta_first_last'] = [
607       '#title' => $this->t('First and last only'),
608       '#type' => 'checkbox',
609       '#default_value' => $this->options['delta_first_last'],
610       '#suffix' => '</div>',
611       '#states' => [
612         'visible' => [
613           ':input[name="options[group_rows]"]' => ['checked' => TRUE],
614         ],
615       ],
616       '#fieldset' => 'multiple_field_settings',
617     ];
618   }
619
620   /**
621    * Extend the groupby form with group columns.
622    */
623   public function buildGroupByForm(&$form, FormStateInterface $form_state) {
624     parent::buildGroupByForm($form, $form_state);
625     // With "field API" fields, the column target of the grouping function
626     // and any additional grouping columns must be specified.
627
628     $field_columns = array_keys($this->getFieldDefinition()->getColumns());
629     $group_columns = [
630       'entity_id' => $this->t('Entity ID'),
631     ] + array_map('ucfirst', array_combine($field_columns, $field_columns));
632
633     $form['group_column'] = [
634       '#type' => 'select',
635       '#title' => $this->t('Group column'),
636       '#default_value' => $this->options['group_column'],
637       '#description' => $this->t('Select the column of this field to apply the grouping function selected above.'),
638       '#options' => $group_columns,
639     ];
640
641     $options = [
642       'bundle' => 'Bundle',
643       'language' => 'Language',
644       'entity_type' => 'Entity_type',
645     ];
646     // Add on defined fields, noting that they're prefixed with the field name.
647     $form['group_columns'] = [
648       '#type' => 'checkboxes',
649       '#title' => $this->t('Group columns (additional)'),
650       '#default_value' => $this->options['group_columns'],
651       '#description' => $this->t('Select any additional columns of this field to include in the query and to group on.'),
652       '#options' => $options + $group_columns,
653     ];
654   }
655
656   public function submitGroupByForm(&$form, FormStateInterface $form_state) {
657     parent::submitGroupByForm($form, $form_state);
658     $item = &$form_state->get('handler')->options;
659
660     // Add settings for "field API" fields.
661     $item['group_column'] = $form_state->getValue(['options', 'group_column']);
662     $item['group_columns'] = array_filter($form_state->getValue(['options', 'group_columns']));
663   }
664
665   /**
666    * Render all items in this field together.
667    *
668    * When using advanced render, each possible item in the list is rendered
669    * individually. Then the items are all pasted together.
670    */
671   public function renderItems($items) {
672     if (!empty($items)) {
673       $items = $this->prepareItemsByDelta($items);
674       if ($this->options['multi_type'] == 'separator' || !$this->options['group_rows']) {
675         $separator = $this->options['multi_type'] == 'separator' ? Xss::filterAdmin($this->options['separator']) : '';
676         $build = [
677           '#type' => 'inline_template',
678           '#template' => '{{ items | safe_join(separator) }}',
679           '#context' => ['separator' => $separator, 'items' => $items],
680         ];
681       }
682       else {
683         $build = [
684           '#theme' => 'item_list',
685           '#items' => $items,
686           '#title' => NULL,
687           '#list_type' => $this->options['multi_type'],
688         ];
689       }
690       return $this->renderer->render($build);
691     }
692   }
693
694   /**
695    * Adapts the $items according to the delta configuration.
696    *
697    * This selects displayed deltas, reorders items, and takes offsets into
698    * account.
699    *
700    * @param array $all_values
701    *   The items for individual rendering.
702    *
703    * @return array
704    *   The manipulated items.
705    */
706   protected function prepareItemsByDelta(array $all_values) {
707     if ($this->options['delta_reversed']) {
708       $all_values = array_reverse($all_values);
709     }
710
711     // We are supposed to show only certain deltas.
712     if ($this->limit_values) {
713       $row = $this->view->result[$this->view->row_index];
714
715       // Offset is calculated differently when row grouping for a field is not
716       // enabled. Since there are multiple rows, delta needs to be taken into
717       // account, so that different values are shown per row.
718       if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($row->{$this->aliases['delta']})) {
719         $delta_limit = 1;
720         $offset = $row->{$this->aliases['delta']};
721       }
722       // Single fields don't have a delta available so choose 0.
723       elseif (!$this->options['group_rows'] && !$this->multiple) {
724         $delta_limit = 1;
725         $offset = 0;
726       }
727       else {
728         $delta_limit = $this->options['delta_limit'];
729         $offset = intval($this->options['delta_offset']);
730
731         // We should only get here in this case if there is an offset, and in
732         // that case we are limiting to all values after the offset.
733         if ($delta_limit === 0) {
734           $delta_limit = count($all_values) - $offset;
735         }
736       }
737
738       // Determine if only the first and last values should be shown.
739       $delta_first_last = $this->options['delta_first_last'];
740
741       $new_values = [];
742       for ($i = 0; $i < $delta_limit; $i++) {
743         $new_delta = $offset + $i;
744
745         if (isset($all_values[$new_delta])) {
746           // If first-last option was selected, only use the first and last
747           // values.
748           if (!$delta_first_last
749             // Use the first value.
750             || $new_delta == $offset
751             // Use the last value.
752             || $new_delta == ($delta_limit + $offset - 1)) {
753             $new_values[] = $all_values[$new_delta];
754           }
755         }
756       }
757       $all_values = $new_values;
758     }
759
760     return $all_values;
761   }
762
763   /**
764    * {@inheritdoc}
765    */
766   public function preRender(&$values) {
767     parent::preRender($values);
768     $this->getEntityFieldRenderer()->preRender($values);
769   }
770
771   /**
772    * Returns the entity field renderer.
773    *
774    * @return \Drupal\views\Entity\Render\EntityFieldRenderer
775    *   The entity field renderer.
776    */
777   protected function getEntityFieldRenderer() {
778     if (!isset($this->entityFieldRenderer)) {
779       // This can be invoked during field handler initialization in which case
780       // view fields are not set yet.
781       if (!empty($this->view->field)) {
782         foreach ($this->view->field as $field) {
783           // An entity field renderer can handle only a single relationship.
784           if ($field->relationship == $this->relationship && isset($field->entityFieldRenderer)) {
785             $this->entityFieldRenderer = $field->entityFieldRenderer;
786             break;
787           }
788         }
789       }
790       if (!isset($this->entityFieldRenderer)) {
791         $entity_type = $this->entityManager->getDefinition($this->getEntityType());
792         $this->entityFieldRenderer = new EntityFieldRenderer($this->view, $this->relationship, $this->languageManager, $entity_type, $this->entityManager);
793       }
794     }
795     return $this->entityFieldRenderer;
796   }
797
798   /**
799    * Gets an array of items for the field.
800    *
801    * @param \Drupal\views\ResultRow $values
802    *   The result row object containing the values.
803    *
804    * @return array
805    *   An array of items for the field.
806    */
807   public function getItems(ResultRow $values) {
808     if (!$this->displayHandler->useGroupBy()) {
809       $build_list = $this->getEntityFieldRenderer()->render($values, $this);
810     }
811     else {
812       // For grouped results we need to retrieve a massaged entity having
813       // grouped field values to ensure that "grouped by" values, especially
814       // those with multiple cardinality work properly. See
815       // \Drupal\Tests\views\Kernel\QueryGroupByTest::testGroupByFieldWithCardinality.
816       $display = [
817         'type' => $this->options['type'],
818         'settings' => $this->options['settings'],
819         'label' => 'hidden',
820       ];
821       // Optional relationships may not provide an entity at all. So we can't
822       // use createEntityForGroupBy() for those rows.
823       if ($entity = $this->getEntity($values)) {
824         $entity = $this->createEntityForGroupBy($entity, $values);
825         // Some bundles might not have a specific field, in which case the faked
826         // entity doesn't have it either.
827         $build_list = isset($entity->{$this->definition['field_name']}) ? $entity->{$this->definition['field_name']}->view($display) : NULL;
828       }
829       else {
830         $build_list = NULL;
831       }
832     }
833
834     if (!$build_list) {
835       return [];
836     }
837
838     if ($this->options['field_api_classes']) {
839       return [['rendered' => $this->renderer->render($build_list)]];
840     }
841
842     // Render using the formatted data itself.
843     $items = [];
844     // Each item is extracted and rendered separately, the top-level formatter
845     // render array itself is never rendered, so we extract its bubbleable
846     // metadata and add it to each child individually.
847     $bubbleable = BubbleableMetadata::createFromRenderArray($build_list);
848     foreach (Element::children($build_list) as $delta) {
849       BubbleableMetadata::createFromRenderArray($build_list[$delta])
850         ->merge($bubbleable)
851         ->applyTo($build_list[$delta]);
852       $items[$delta] = [
853         'rendered' => $build_list[$delta],
854         // Add the raw field items (for use in tokens).
855         'raw' => $build_list['#items'][$delta],
856       ];
857     }
858     return $items;
859   }
860
861   /**
862    * Creates a fake entity with grouped field values.
863    *
864    * @param \Drupal\Core\Entity\EntityInterface $entity
865    *   The entity to be processed.
866    * @param \Drupal\views\ResultRow $row
867    *   The result row object containing the values.
868    *
869    * @return bool|\Drupal\Core\Entity\FieldableEntityInterface
870    *   Returns a new entity object containing the grouped field values.
871    */
872   protected function createEntityForGroupBy(EntityInterface $entity, ResultRow $row) {
873     // Retrieve the correct translation object.
874     $processed_entity = clone $this->getEntityFieldRenderer()->getEntityTranslation($entity, $row);
875
876     // Copy our group fields into the cloned entity. It is possible this will
877     // cause some weirdness, but there is only so much we can hope to do.
878     if (!empty($this->group_fields) && isset($entity->{$this->definition['field_name']})) {
879       // first, test to see if we have a base value.
880       $base_value = [];
881       // Note: We would copy original values here, but it can cause problems.
882       // For example, text fields store cached filtered values as 'safe_value'
883       // which does not appear anywhere in the field definition so we cannot
884       // affect it. Other side effects could happen similarly.
885       $data = FALSE;
886       foreach ($this->group_fields as $field_name => $column) {
887         if (property_exists($row, $this->aliases[$column])) {
888           $base_value[$field_name] = $row->{$this->aliases[$column]};
889           if (isset($base_value[$field_name])) {
890             $data = TRUE;
891           }
892         }
893       }
894
895       // If any of our aggregated fields have data, fake it:
896       if ($data) {
897         // Now, overwrite the original value with our aggregated value.
898         // This overwrites it so there is always just one entry.
899         $processed_entity->{$this->definition['field_name']} = [$base_value];
900       }
901       else {
902         $processed_entity->{$this->definition['field_name']} = [];
903       }
904     }
905
906     return $processed_entity;
907   }
908
909   public function render_item($count, $item) {
910     return render($item['rendered']);
911   }
912
913   protected function documentSelfTokens(&$tokens) {
914     $field = $this->getFieldDefinition();
915     foreach ($field->getColumns() as $id => $column) {
916       $tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = $this->t('Raw @column', ['@column' => $id]);
917     }
918   }
919
920   protected function addSelfTokens(&$tokens, $item) {
921     $field = $this->getFieldDefinition();
922     foreach ($field->getColumns() as $id => $column) {
923       // Use \Drupal\Component\Utility\Xss::filterAdmin() because it's user data
924       // and we can't be sure it is safe. We know nothing about the data,
925       // though, so we can't really do much else.
926       if (isset($item['raw'])) {
927         $raw = $item['raw'];
928
929         if (is_array($raw)) {
930           if (isset($raw[$id]) && is_scalar($raw[$id])) {
931             $tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = Xss::filterAdmin($raw[$id]);
932           }
933           else {
934             // Make sure that empty values are replaced as well.
935             $tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = '';
936           }
937         }
938
939         if (is_object($raw)) {
940           $property = $raw->get($id);
941           // Check if TypedDataInterface is implemented so we know how to render
942           // the item as a string.
943           if (!empty($property) && $property instanceof TypedDataInterface) {
944             $tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = Xss::filterAdmin($property->getString());
945           }
946           else {
947             // Make sure that empty values are replaced as well.
948             $tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = '';
949           }
950         }
951       }
952     }
953   }
954
955   /**
956    * Returns the field formatter instance.
957    *
958    * @return \Drupal\Core\Field\FormatterInterface|null
959    *   The field formatter instance.
960    */
961   protected function getFormatterInstance($format = NULL) {
962     if (!isset($format)) {
963       $format = $this->options['type'];
964     }
965     $settings = $this->options['settings'] + $this->formatterPluginManager->getDefaultSettings($format);
966
967     $options = [
968       'field_definition' => $this->getFieldDefinition(),
969       'configuration' => [
970         'type' => $format,
971         'settings' => $settings,
972         'label' => '',
973         'weight' => 0,
974       ],
975       'view_mode' => '_custom',
976     ];
977
978     return $this->formatterPluginManager->getInstance($options);
979   }
980
981   /**
982    * {@inheritdoc}
983    */
984   public function calculateDependencies() {
985     $this->dependencies = parent::calculateDependencies();
986
987     // Add the module providing the configured field storage as a dependency.
988     if (($field_storage_definition = $this->getFieldStorageDefinition()) && $field_storage_definition instanceof EntityInterface) {
989       $this->dependencies['config'][] = $field_storage_definition->getConfigDependencyName();
990     }
991     if (!empty($this->options['type'])) {
992       // Add the module providing the formatter.
993       $this->dependencies['module'][] = $this->formatterPluginManager->getDefinition($this->options['type'])['provider'];
994
995       // Add the formatter's dependencies.
996       if (($formatter = $this->getFormatterInstance()) && $formatter instanceof DependentPluginInterface) {
997         $this->calculatePluginDependencies($formatter);
998       }
999     }
1000
1001     return $this->dependencies;
1002   }
1003
1004   /**
1005    * {@inheritdoc}
1006    */
1007   public function getCacheMaxAge() {
1008     return Cache::PERMANENT;
1009   }
1010
1011   /**
1012    * {@inheritdoc}
1013    */
1014   public function getCacheContexts() {
1015     return $this->getEntityFieldRenderer()->getCacheContexts();
1016   }
1017
1018   /**
1019    * {@inheritdoc}
1020    */
1021   public function getCacheTags() {
1022     $field_definition = $this->getFieldDefinition();
1023     $field_storage_definition = $this->getFieldStorageDefinition();
1024     return Cache::mergeTags(
1025       $field_definition instanceof CacheableDependencyInterface ? $field_definition->getCacheTags() : [],
1026       $field_storage_definition instanceof CacheableDependencyInterface ? $field_storage_definition->getCacheTags() : []
1027     );
1028   }
1029
1030   /**
1031    * Gets the table mapping for the entity type of the field.
1032    *
1033    * @return \Drupal\Core\Entity\Sql\DefaultTableMapping
1034    *   The table mapping.
1035    */
1036   protected function getTableMapping() {
1037     return $this->entityManager->getStorage($this->definition['entity_type'])->getTableMapping();
1038   }
1039
1040   /**
1041    * {@inheritdoc}
1042    */
1043   public function getValue(ResultRow $values, $field = NULL) {
1044     $entity = $this->getEntity($values);
1045     // Some bundles might not have a specific field, in which case the entity
1046     // (potentially a fake one) doesn't have it either.
1047     /** @var \Drupal\Core\Field\FieldItemListInterface $field_item_list */
1048     $field_item_list = isset($entity->{$this->definition['field_name']}) ? $entity->{$this->definition['field_name']} : NULL;
1049
1050     if (!isset($field_item_list)) {
1051       // There isn't anything we can do without a valid field.
1052       return NULL;
1053     }
1054
1055     $field_item_definition = $field_item_list->getFieldDefinition();
1056
1057     $values = [];
1058     foreach ($field_item_list as $field_item) {
1059       /** @var \Drupal\Core\Field\FieldItemInterface $field_item */
1060       if ($field) {
1061         $values[] = $field_item->$field;
1062       }
1063       // Find the value using the main property of the field. If no main
1064       // property is provided fall back to 'value'.
1065       elseif ($main_property_name = $field_item->mainPropertyName()) {
1066         $values[] = $field_item->{$main_property_name};
1067       }
1068       else {
1069         $values[] = $field_item->value;
1070       }
1071     }
1072     if ($field_item_definition->getFieldStorageDefinition()->getCardinality() == 1) {
1073       return reset($values);
1074     }
1075     else {
1076       return $values;
1077     }
1078   }
1079
1080 }