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