custom_field-1.0.x-dev/src/Plugin/views/field/CustomField.php
src/Plugin/views/field/CustomField.php
<?php
declare(strict_types=1);
namespace Drupal\custom_field\Plugin\views\field;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Entity\Sql\TableMappingInterface;
use Drupal\Core\Entity\TranslatableInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\custom_field\Entity\Render\EntityFieldRenderer;
use Drupal\custom_field\Plugin\CustomFieldFormatterManagerInterface;
use Drupal\custom_field\Plugin\CustomFieldTypeManagerInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\views\Attribute\ViewsField;
use Drupal\views\FieldAPIHandlerTrait;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\field\FieldPluginBase;
use Drupal\views\Plugin\views\field\MultiItemsFieldHandlerInterface;
use Drupal\views\Plugin\views\query\Sql;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Field handler to properly render custom fields with formatters.
*
* @ingroup views_field_handlers
*/
#[ViewsField('custom_field')]
final class CustomField extends FieldPluginBase implements MultiItemsFieldHandlerInterface {
use FieldAPIHandlerTrait;
/**
* Does the rendered fields get limited.
*
* @var bool
*/
private bool $limitValues;
/**
* Does the field supports multiple field values.
*
* @var bool
*/
private bool $multiple;
/**
* Static cache for ::getEntityFieldRenderer().
*
* @var \Drupal\custom_field\Entity\Render\EntityFieldRenderer|null
*/
protected ?EntityFieldRenderer $entityFieldRenderer = NULL;
/**
* Constructs a CustomField object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* The entity field manager.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
* The entity type manager.
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository
* The entity repository.
* @param \Drupal\custom_field\Plugin\CustomFieldTypeManagerInterface $customFieldTypeManager
* The custom field type manager.
* @param \Drupal\custom_field\Plugin\CustomFieldFormatterManagerInterface $customFieldFormatterManager
* The custom field formatter manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
* The language manager.
*/
public function __construct(
array $configuration,
string $plugin_id,
$plugin_definition,
RendererInterface $renderer,
EntityFieldManagerInterface $entity_field_manager,
private readonly EntityTypeManagerInterface $entityTypeManager,
private readonly EntityRepositoryInterface $entityRepository,
private readonly CustomFieldTypeManagerInterface $customFieldTypeManager,
private readonly CustomFieldFormatterManagerInterface $customFieldFormatterManager,
private readonly LanguageManagerInterface $languageManager,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityFieldManager = $entity_field_manager;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new CustomField(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('renderer'),
$container->get('entity_field.manager'),
$container->get('entity_type.manager'),
$container->get('entity.repository'),
$container->get('plugin.manager.custom_field_type'),
$container->get('plugin.manager.custom_field_formatter'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, ?array &$options = NULL): void {
parent::init($view, $display, $options);
$this->limitValues = FALSE;
$this->multiple = FALSE;
$field_definition = $this->getFieldDefinition();
$cardinality = $field_definition->getFieldStorageDefinition()->getCardinality();
if ($field_definition->getFieldStorageDefinition()->isMultiple()) {
$this->multiple = TRUE;
}
// If "Display all values in the same row" is FALSE, then we always limit
// in order to show a single unique value per row.
if (!$this->options['group_rows']) {
$this->limitValues = TRUE;
}
// If "First and last only" is chosen, limit the values.
if (!empty($this->options['delta_first_last'])) {
$this->limitValues = TRUE;
}
// Otherwise, we only limit values if the user hasn't selected "all", 0, or
// the value matching field cardinality.
if ((($this->options['delta_limit'] > 0) && ($this->options['delta_limit'] != $cardinality)) || intval($this->options['delta_offset'])) {
$this->limitValues = TRUE;
}
}
/**
* Called to add the field to a query.
*
* By default, all needed data is taken from entities loaded by the query
* plugin. Columns are added only if they are used in groupings.
*
* @param bool $use_groupby
* The columns are grouped.
*/
public function query(bool $use_groupby = FALSE): void {
$fields = $this->additional_fields;
// No need to add the entity type.
$entity_type_key = array_search('entity_type', $fields);
if ($entity_type_key !== FALSE) {
unset($fields[$entity_type_key]);
}
if ($use_groupby) {
// Add the fields that we're actually grouping on.
$options = [];
if ($this->options['group_column'] != 'entity_id') {
$options = [$this->options['group_column'] => $this->options['group_column']];
}
$options += is_array($this->options['group_columns']) ? $this->options['group_columns'] : [];
// Go through the list and determine the actual column name from field
// api.
$fields = [];
$table_mapping = $this->getTableMapping();
$field_definition = $this->getFieldStorageDefinition();
foreach ($options as $column) {
$fields[$column] = $table_mapping->getFieldColumnName($field_definition, $column);
}
}
// Add additional fields (and the table join itself) if needed.
if ($this->addFieldTable($use_groupby)) {
$this->ensureMyTable();
$this->addAdditionalFields($fields);
}
// Let the entity field renderer alter the query if needed.
$this->getEntityFieldRenderer()->query($this->query, $this->relationship);
}
/**
* Determine if the field table should be added to the query.
*
* @param bool $use_groupby
* If grouping is enabled on the field.
*
* @return bool
* Whether to add join for field table.
*/
public function addFieldTable(bool $use_groupby = FALSE): bool {
// Grouping is enabled.
if ($use_groupby) {
return TRUE;
}
// This a multiple value field, but "group multiple values" is not checked.
if ($this->multiple && !$this->options['group_rows']) {
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function clickSortable(): bool {
// A field is not click sortable if it's a multiple field with
// "group multiple values" checked, since a click sort in that case would
// add a join to the field table, which would produce unwanted duplicates.
if ($this->multiple && $this->options['group_rows']) {
return FALSE;
}
// If the field definition is set, use that.
if (isset($this->definition['click sortable'])) {
return (bool) $this->definition['click sortable'];
}
// Default to true.
return TRUE;
}
/**
* Called to determine what to tell the click sorter.
*/
public function clickSort($order): void {
// No column selected, can't continue.
if (empty($this->options['click_sort_column'])) {
return;
}
// Currently, only the Sql plugin has the functions used below.
if (!$this->query instanceof Sql) {
return;
}
$this->ensureMyTable();
$field_storage_definition = $this->getFieldStorageDefinition();
$column = $this->getTableMapping()->getFieldColumnName($field_storage_definition, $this->options['click_sort_column']);
if (!isset($this->aliases[$column])) {
// Column is not in query; add a sort on it.
$this->aliases[$column] = $this->tableAlias . '.' . $column;
// If the query uses DISTINCT we need to add the column too.
if (!empty($this->view->getQuery()->options['distinct'])) {
$this->query->addField($this->tableAlias, $column);
}
}
$this->query->addOrderBy('', NULL, $order, $this->aliases[$column]);
}
/**
* {@inheritdoc}
*/
protected function defineOptions(): array {
$options = parent::defineOptions();
$field_storage_definition = $this->getFieldStorageDefinition();
// Add formatter options.
$options['click_sort_column'] = [
'default' => $this->definition['property'],
];
$options['type'] = ['default' => $this->definition['default_formatter']];
$options['settings'] = ['default' => $this->definition['default_formatter_settings']];
$options['group_column'] = [
'default' => $this->definition['property'],
];
$options['group_columns'] = [
'default' => [],
];
// Options used for multiple value fields.
$options['group_rows'] = [
'default' => TRUE,
];
// If we know the exact number of allowed values, then that can be the
// default. Otherwise, default to 'all'.
$options['delta_limit'] = [
'default' => ($field_storage_definition->getCardinality() > 1) ? $field_storage_definition->getCardinality() : 0,
];
$options['delta_offset'] = ['default' => 0];
$options['delta_reversed'] = ['default' => FALSE];
$options['delta_first_last'] = [
'default' => FALSE,
];
$options['multi_type'] = [
'default' => 'separator',
];
$options['separator'] = [
'default' => ', ',
];
return $options;
}
/**
* {@inheritdoc}
*
* @param array<string, mixed> $form
* The form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state): void {
parent::buildOptionsForm($form, $form_state);
// Extract field name and subfield from the field handler definition.
[$field_name, $subfield] = $this->extractFieldInfo();
$visibility_path = $this->customFieldFormatterManager->getInputPathForStatesApi($form_state, $field_name, $subfield, TRUE);
$form['#field_parents'] = ['options', 'settings'];
$form['#visibility_path'] = $visibility_path;
// Get field storage configuration.
$field_storage = $this->getFieldStorageDefinition();
// Get custom field items for this field.
$custom_fields = $this->customFieldTypeManager->getCustomFieldItems($field_storage->getSettings());
$custom_field = $custom_fields[$subfield];
$formatter_options = $this->customFieldFormatterManager->getOptions($custom_field);
// If this is a multiple value field, add its options.
if ($this->multiple) {
$this->multipleOptionsForm($form, $form_state);
}
$extra_columns = $this->definition['extra columns'];
// No need to ask the user anything if the field has only one column.
if (empty($extra_columns)) {
$form['click_sort_column'] = [
'#type' => 'value',
'#value' => $subfield,
];
}
else {
// Add the main property.
$sort_columns[$subfield] = $subfield;
// Add the extra properties keyed by the table column name pattern.
foreach ($extra_columns as $key => $column) {
$sort_columns[$subfield . '__' . $key] = $column;
}
$form['click_sort_column'] = [
'#type' => 'select',
'#title' => $this->t('Column used for click sorting'),
'#options' => array_combine(array_keys($sort_columns), $sort_columns),
'#default_value' => $this->options['click_sort_column'],
'#description' => $this->t('Used by Style: Table to determine the actual column to click sort the field on. The default is usually fine.'),
];
}
$form['type'] = [
'#type' => 'select',
'#title' => $this->t('Formatter'),
'#options' => $formatter_options,
'#default_value' => $this->options['type'],
'#ajax' => [
// @todo How to make PHPStan find this function?
// @phpstan-ignore-next-line
'url' => views_ui_build_form_url($form_state),
],
'#submit' => [[$this, 'submitTemporaryForm']],
'#executes_submit_callback' => TRUE,
];
// Get the settings form.
$settings_form = ['#value' => []];
$format = $form_state->getUserInput()['options']['type'] ?? $this->options['type'];
// Get current formatter.
$settings = $this->options['settings'] + $this->customFieldFormatterManager->getDefaultSettings($format);
$options = $this->customFieldFormatterManager->createOptionsForInstance($custom_field, $format, $settings, '_custom');
if ($format = $this->customFieldFormatterManager->getInstance($options)) {
$settings_form = $format->settingsForm($form, $form_state);
}
$form['settings'] = $settings_form;
}
/**
* Provide options for multiple value fields.
*
* @param array<string, mixed> $form
* The form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
private function multipleOptionsForm(array &$form, FormStateInterface $form_state): void {
$form['multiple_field_settings'] = [
'#type' => 'details',
'#title' => $this->t('Multiple field settings'),
'#weight' => 5,
];
$form['group_rows'] = [
'#title' => $this->t('Display all values in the same row'),
'#type' => 'checkbox',
'#default_value' => $this->options['group_rows'],
'#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, make sure to group by "Entity ID" for this setting to have any effect.'),
'#fieldset' => 'multiple_field_settings',
];
// Make the string translatable by keeping it as a whole rather than
// translating prefix and suffix separately.
[$prefix, $suffix] = explode('@count', $this->t('Display @count value(s)')->render());
$form['multi_type'] = [
'#type' => 'radios',
'#title' => $this->t('Display type'),
'#options' => [
'ul' => $this->t('Unordered list'),
'ol' => $this->t('Ordered list'),
'separator' => $this->t('Simple separator'),
],
'#states' => [
'visible' => [
':input[name="options[group_rows]"]' => ['checked' => TRUE],
],
],
'#default_value' => $this->options['multi_type'],
'#fieldset' => 'multiple_field_settings',
];
$form['separator'] = [
'#type' => 'textfield',
'#title' => $this->t('Separator'),
'#default_value' => $this->options['separator'],
'#states' => [
'visible' => [
':input[name="options[group_rows]"]' => ['checked' => TRUE],
':input[name="options[multi_type]"]' => ['value' => 'separator'],
],
],
'#fieldset' => 'multiple_field_settings',
];
$field = $this->getFieldDefinition();
// Not the best solution but safest as long as BaseFieldDefinition
// does not has its own interface that extends both FieldDefinitionInterface
// and FieldStorageDefinitionInterface.
if ($field instanceof BaseFieldDefinition) {
if ($field->getCardinality() === FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) {
$type = 'textfield';
$options = NULL;
$size = 5;
}
else {
$type = 'select';
$range = range(1, $field->getCardinality());
$options = array_combine($range, $range);
$size = 1;
}
$form['delta_limit'] = [
'#type' => $type,
'#size' => $size,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#options' => $options,
'#default_value' => $this->options['delta_limit'],
'#prefix' => '<div class="container-inline">',
'#states' => [
'visible' => [
':input[name="options[group_rows]"]' => ['checked' => TRUE],
],
],
'#fieldset' => 'multiple_field_settings',
];
}
[$prefix, $suffix] = explode('@count', $this->t('starting from @count')->render());
$form['delta_offset'] = [
'#type' => 'textfield',
'#size' => 5,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $this->options['delta_offset'],
'#states' => [
'visible' => [
':input[name="options[group_rows]"]' => ['checked' => TRUE],
],
],
'#description' => $this->t('(first item is 0)'),
'#fieldset' => 'multiple_field_settings',
];
$form['delta_reversed'] = [
'#title' => $this->t('Reversed'),
'#type' => 'checkbox',
'#default_value' => $this->options['delta_reversed'],
'#suffix' => $suffix,
'#states' => [
'visible' => [
':input[name="options[group_rows]"]' => ['checked' => TRUE],
],
],
'#description' => $this->t('(start from last values)'),
'#fieldset' => 'multiple_field_settings',
];
$form['delta_first_last'] = [
'#title' => $this->t('First and last only'),
'#type' => 'checkbox',
'#default_value' => $this->options['delta_first_last'],
'#suffix' => '</div>',
'#states' => [
'visible' => [
':input[name="options[group_rows]"]' => ['checked' => TRUE],
],
],
'#fieldset' => 'multiple_field_settings',
];
}
/**
* Extend the groupby form with group columns.
*/
public function buildGroupByForm(&$form, FormStateInterface $form_state): void {
parent::buildGroupByForm($form, $form_state);
// Not the best solution but safest as long as BaseFieldDefinition
// does not has its own interface that extends both FieldDefinitionInterface
// and FieldStorageDefinitionInterface.
$field = $this->getFieldDefinition();
$columns = $field instanceof BaseFieldDefinition ? $field->getColumns() : [];
// With "field API" fields, the column target of the grouping function
// and any additional grouping columns must be specified.
$field_columns = array_keys($columns);
// @todo What is happening here? What is the point of the array_combine()
// on the same array?
$group_columns = [
'entity_id' => $this->t('Entity ID'),
] + array_map('ucfirst', array_combine($field_columns, $field_columns));
$form['group_column'] = [
'#type' => 'select',
'#title' => $this->t('Group column'),
'#default_value' => $this->options['group_column'],
'#description' => $this->t('Select the column of this field to apply the grouping function selected above.'),
'#options' => $group_columns,
];
$options = [
'bundle' => 'Bundle',
'language' => 'Language',
'entity_type' => 'Entity_type',
];
// Add on defined fields, noting that they're prefixed with the field name.
$form['group_columns'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Group columns (additional)'),
'#default_value' => $this->options['group_columns'],
'#description' => $this->t('Select any additional columns of this field to include in the query and to group on.'),
'#options' => $options + $group_columns,
];
}
/**
* {@inheritdoc}
*/
public function submitGroupByForm(&$form, FormStateInterface $form_state): void {
parent::submitGroupByForm($form, $form_state);
$item = &$form_state->get('handler')->options;
// Add settings for "field API" fields.
$item['group_column'] = $form_state->getValue(['options', 'group_column']);
$item['group_columns'] = array_filter($form_state->getValue(['options', 'group_columns']));
}
/**
* {@inheritdoc}
*/
public function submitFormCalculateOptions(array $options, array $form_state_options): array {
// When we change the formatter type we don't want to keep any of the
// previous configured formatter settings, as there might be schema
// conflict.
unset($options['settings']);
$options = $form_state_options + $options;
if (!isset($options['settings'])) {
$options['settings'] = [];
}
return $options;
}
/**
* Adapts the $items according to the delta configuration.
*
* This selects displayed deltas, reorders items, and takes offsets into
* account.
*
* @param array<int, mixed> $all_values
* The items for individual rendering.
*
* @return array<int, mixed>
* The manipulated items.
*/
private function prepareItemsByDelta(array $all_values): array {
if ($this->options['delta_reversed']) {
$all_values = array_reverse($all_values);
}
// We are supposed to show only certain deltas.
if ($this->limitValues) {
$row = $this->view->result[$this->view->row_index];
// Offset is calculated differently when row grouping for a field is not
// enabled. Since there are multiple rows, delta needs to be taken into
// account, so that different values are shown per row.
if (!$this->options['group_rows'] && isset($this->aliases['delta']) && isset($row->{$this->aliases['delta']})) {
$delta_limit = 1;
$offset = $row->{$this->aliases['delta']};
}
// Single fields don't have a delta available so choose 0.
elseif (!$this->options['group_rows'] && !$this->multiple) {
$delta_limit = 1;
$offset = 0;
}
else {
$delta_limit = (int) $this->options['delta_limit'];
$offset = intval($this->options['delta_offset']);
// We should only get here in this case if there is an offset, and in
// that case we are limiting to all values after the offset.
if ($delta_limit === 0) {
$delta_limit = count($all_values) - $offset;
}
}
// Determine if only the first and last values should be shown.
$delta_first_last = $this->options['delta_first_last'];
$new_values = [];
for ($i = 0; $i < $delta_limit; $i++) {
$new_delta = $offset + $i;
if (isset($all_values[$new_delta])) {
// If first-last option was selected, only use the first and last
// values.
if (!$delta_first_last
// Use the first value.
|| $new_delta == $offset
// Use the last value.
|| $new_delta == ($delta_limit + $offset - 1)) {
$new_values[] = $all_values[$new_delta];
}
}
}
$all_values = $new_values;
}
return $all_values;
}
/**
* Extracts field name and subfield from the field ID.
*
* @return array<int, mixed>
* An array with field name and subfield name.
*/
private function extractFieldInfo(): array {
return [
$this->definition['field_name'],
$this->definition['property'],
];
}
/**
* Gets the table mapping for the entity type of the field.
*
* @return \Drupal\Core\Entity\Sql\TableMappingInterface|null
* The table mapping.
*/
private function getTableMapping(): ?TableMappingInterface {
try {
$entity_type_storage = $this->entityTypeManager->getStorage($this->definition['entity_type']);
assert($entity_type_storage instanceof SqlEntityStorageInterface);
return $entity_type_storage->getTableMapping();
}
catch (\Exception $exception) {
// Silent fail, for now.
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function preRender(&$values): void {
parent::preRender($values);
$this->getEntityFieldRenderer()->preRender($values);
}
/**
* Returns the entity field renderer.
*
* @return \Drupal\custom_field\Entity\Render\EntityFieldRenderer
* The entity field renderer.
*/
private function getEntityFieldRenderer(): EntityFieldRenderer {
if (is_null($this->entityFieldRenderer)) {
// This can be invoked during field handler initialization in which case
// view fields are not set yet.
if (!empty($this->view->field)) {
foreach ($this->view->field as $field) {
// An entity field renderer can handle only a single relationship.
if ($field->relationship == $this->relationship && isset($field->entityFieldRenderer)) {
$this->entityFieldRenderer = $field->entityFieldRenderer;
break;
}
}
}
if (is_null($this->entityFieldRenderer)) {
try {
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
$this->entityFieldRenderer = new EntityFieldRenderer($this->view, $this->relationship, $this->languageManager, $entity_type, $this->entityTypeManager, $this->entityRepository);
}
catch (\Exception $exception) {
// Silent fail, for now.
}
}
}
return $this->entityFieldRenderer;
}
/**
* {@inheritdoc}
*/
public function getItems(ResultRow $values): array {
$entity = $this->entityFieldRenderer->getEntityTranslationByRelationship($values->_entity, $values);
$langcode = $entity->language()->getId();
/** @var string $field_name */
/** @var string $subfield */
[$field_name, $subfield] = $this->extractFieldInfo();
/** @var \Drupal\custom_field\Plugin\Field\FieldType\CustomItemList $field_items */
$field_items = $entity->{$field_name};
// Return early if the field is empty.
if ($field_items->isEmpty()) {
return [];
}
$field_storage = $this->getFieldStorageDefinition();
$field_config = FieldConfig::loadByName($entity->getEntityTypeId(), $entity->bundle(), $field_storage->getName());
$settings = array_merge($field_storage->getSettings(), $field_config->getSettings());
$custom_fields = $this->customFieldTypeManager->getCustomFieldItems($settings);
$custom_field = $custom_fields[$subfield];
$formatter_id = $this->options['type'];
$formatter_settings = $this->options['settings'] ?? [];
$instance_options = $this->customFieldFormatterManager->createOptionsForInstance($custom_field, $formatter_id, $formatter_settings, '_custom');
$plugin = $this->customFieldFormatterManager->getInstance($instance_options);
$extra_columns = $this->definition['extra columns'];
// Prepare render array.
$items = [];
foreach ($field_items as $delta => $field_item) {
$raw_value = $field_item->{$subfield};
$raw_extra = [];
if (!empty($extra_columns)) {
foreach ($extra_columns as $key => $extra_column) {
$raw_extra[$key] = $field_item->{$subfield . '__' . $key};
}
}
$value = $raw_value;
if ($value == '' || $value == NULL) {
continue;
}
$data_type = $custom_field->getDataType();
$reference_types = [
'entity_reference',
'image',
'file',
];
if (in_array($data_type, $reference_types)) {
$value = $field_item->{$subfield . '__entity'};
if ($value instanceof TranslatableInterface) {
$value = $this->entityRepository->getTranslationFromContext($value, $langcode);
}
}
elseif ($data_type === 'viewfield') {
$value = [
'target_id' => $value,
'display_id' => $field_item->{$subfield . '__display'},
'arguments' => $field_item->{$subfield . '__arguments'},
'items_to_display' => $field_item->{$subfield . '__items'},
];
}
elseif ($data_type === 'uri') {
$value = [
'uri' => $value,
];
}
elseif ($data_type === 'link') {
$value = [
'uri' => $value,
'title' => $field_item->{$subfield . '__title'},
'options' => $field_item->{$subfield . '__options'},
];
}
elseif ($data_type === 'datetime') {
$value = [
'date' => $field_item->{$subfield . '__date'},
'timezone' => $field_item->{$subfield . '__timezone'},
];
}
elseif ($data_type === 'daterange') {
$value = [
'start_date' => $field_item->{$subfield . '__start_date'},
'end_date' => $field_item->{$subfield . '__end_date'},
'timezone' => $field_item->{$subfield . '__timezone'},
'duration' => $field_item->{$subfield . '__duration'},
];
}
elseif ($data_type === 'time_range') {
$value = [
'start' => $value,
'end' => $field_item->{$subfield . '__end'},
'duration' => $field_item->{$subfield . '__duration'},
];
}
$formatted_value = $plugin->formatValue($field_item, $value);
$items[(int) $delta] = [
'raw' => $raw_value,
'raw_extra' => $raw_extra,
'rendered' => $formatted_value,
];
}
if (empty($items)) {
return [];
}
return $this->prepareItemsByDelta($items);
}
/**
* {@inheritdoc}
*/
// phpcs:ignore Drupal.NamingConventions.ValidFunctionName.ScopeNotCamelCaps,Drupal.Commenting.FunctionComment.Missing
public function render_item($count, $item): MarkupInterface|string {
if (is_array($item['rendered'])) {
return $this->renderer->render($item['rendered']);
}
return (string) $item['rendered'];
}
/**
* {@inheritdoc}
*/
public function renderItems($items): MarkupInterface|string {
if ($items !== []) {
if ($this->options['multi_type'] === 'separator' || !$this->options['group_rows']) {
$separator = $this->options['multi_type'] === 'separator' ? Xss::filterAdmin($this->options['separator']) : '';
$build = [
'#type' => 'inline_template',
'#template' => '{{ items | safe_join(separator) }}',
'#context' => ['separator' => $separator, 'items' => $items],
];
}
else {
$build = [
'#theme' => 'item_list',
'#items' => $items,
'#title' => NULL,
'#list_type' => $this->options['multi_type'],
];
}
return $this->renderer->render($build);
}
return '';
}
/**
* {@inheritdoc}
*
* @param string[] $tokens
* The tokens array.
*/
protected function documentSelfTokens(&$tokens): void {
$property = $this->definition['property'];
$extra_columns = $this->definition['extra columns'];
$tokens['{{ ' . $this->options['id'] . ' }}'] = (string) $this->t('Raw @column', ['@column' => $property]);
if (!empty($extra_columns)) {
foreach ($extra_columns as $column) {
$tokens['{{ ' . $this->options['id'] . '__' . $column . ' }}'] = (string) $this->t('Raw @column', ['@column' => $column]);
}
}
}
/**
* {@inheritdoc}
*
* @param string[] $tokens
* The tokens array.
* @param array<string, mixed> $item
* The item.
*/
protected function addSelfTokens(&$tokens, $item): void {
if (isset($item['raw'])) {
$raw = $item['raw'];
if (!empty($raw)) {
$tokens['{{ ' . $this->options['id'] . ' }}'] = (string) $raw;
}
else {
// Make sure that empty values are replaced as well.
$tokens['{{ ' . $this->options['id'] . ' }}'] = '';
}
}
if (isset($item['raw_extra'])) {
$extra_columns = $this->definition['extra columns'];
$raw_extra = $item['raw_extra'];
foreach ($extra_columns as $id => $extra_column) {
if (isset($raw_extra[$id])) {
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = (string) $raw_extra[$id];
}
else {
// Make sure that empty values are replaced as well.
$tokens['{{ ' . $this->options['id'] . '__' . $id . ' }}'] = '';
}
}
}
}
}
