5 * Provide views data that isn't tied to any other module.
8 use Drupal\Component\Utility\NestedArray;
9 use Drupal\Core\Entity\EntityStorageInterface;
10 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
11 use Drupal\Core\Render\Markup;
12 use Drupal\field\Entity\FieldConfig;
13 use Drupal\field\FieldConfigInterface;
14 use Drupal\field\FieldStorageConfigInterface;
15 use Drupal\system\ActionConfigEntityInterface;
18 * Implements hook_views_data().
20 function views_views_data() {
21 $data['views']['table']['group'] = t('Global');
22 $data['views']['table']['join'] = [
23 // #global is a special flag which allows a table to appear all the time.
27 $data['views']['random'] = [
28 'title' => t('Random'),
29 'help' => t('Randomize the display order.'),
35 $data['views']['null'] = [
37 'help' => t('Allow a contextual filter value to be ignored. The query will not be altered by this contextual filter value. Can be used when contextual filter values come from the URL, and a part of the URL needs to be ignored.'),
43 $data['views']['nothing'] = [
44 'title' => t('Custom text'),
45 'help' => t('Provide custom text or link.'),
51 $data['views']['counter'] = [
52 'title' => t('View result counter'),
53 'help' => t('Displays the actual position of the view result'),
59 $data['views']['area'] = [
60 'title' => t('Text area'),
61 'help' => t('Provide markup text for the area.'),
67 $data['views']['area_text_custom'] = [
68 'title' => t('Unfiltered text'),
69 'help' => t('Add unrestricted, custom text or markup. This is similar to the custom text field.'),
71 'id' => 'text_custom',
75 $data['views']['title'] = [
76 'title' => t('Title override'),
77 'help' => t('Override the default view title for this view. This is useful to display an alternative title when a view is empty.'),
80 'sub_type' => 'empty',
84 $data['views']['view'] = [
85 'title' => t('View area'),
86 'help' => t('Insert a view inside an area.'),
92 $data['views']['result'] = [
93 'title' => t('Result summary'),
94 'help' => t('Shows result summary, for example the items per page.'),
100 $data['views']['messages'] = [
101 'title' => t('Messages'),
102 'help' => t('Displays messages in an area.'),
108 $data['views']['http_status_code'] = [
109 'title' => t('Response status code'),
110 'help' => t('Alter the HTTP response status code used by this view, mostly helpful for empty results.'),
112 'id' => 'http_status_code',
116 $data['views']['combine'] = [
117 'title' => t('Combine fields filter'),
118 'help' => t('Combine multiple fields together and search by them.'),
124 $data['views']['dropbutton'] = [
125 'title' => t('Dropbutton'),
126 'help' => t('Display fields in a dropbutton.'),
128 'id' => 'dropbutton',
132 // Registers an entity area handler per entity type.
133 foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) {
134 // Excludes entity types, which cannot be rendered.
135 if ($entity_type->hasViewBuilderClass()) {
136 $label = $entity_type->getLabel();
137 $data['views']['entity_' . $entity_type_id] = [
138 'title' => t('Rendered entity - @label', ['@label' => $label]),
139 'help' => t('Displays a rendered @label entity in an area.', ['@label' => $label]),
141 'entity_type' => $entity_type_id,
148 // Registers an action bulk form per entity.
149 foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) {
150 $actions = array_filter(\Drupal::entityManager()->getStorage('action')->loadMultiple(), function (ActionConfigEntityInterface $action) use ($entity_type) {
151 return $action->getType() == $entity_type;
153 if (empty($actions)) {
156 $data[$entity_info->getBaseTable()][$entity_type . '_bulk_form'] = [
157 'title' => t('Bulk update'),
158 'help' => t('Allows users to apply an action to one or more items.'),
165 // Registers views data for the entity itself.
166 foreach (\Drupal::entityManager()->getDefinitions() as $entity_type_id => $entity_type) {
167 if ($entity_type->hasHandlerClass('views_data')) {
168 /** @var \Drupal\views\EntityViewsDataInterface $views_data */
169 $views_data = \Drupal::entityManager()->getHandler($entity_type_id, 'views_data');
170 $data = NestedArray::mergeDeep($data, $views_data->getViewsData());
174 // Field modules can implement hook_field_views_data() to override the default
175 // behavior for adding fields.
176 $module_handler = \Drupal::moduleHandler();
178 $entity_manager = \Drupal::entityManager();
179 if ($entity_manager->hasDefinition('field_storage_config')) {
180 /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
181 foreach ($entity_manager->getStorage('field_storage_config')->loadMultiple() as $field_storage) {
182 if (_views_field_get_entity_type_storage($field_storage)) {
183 $result = (array) $module_handler->invoke($field_storage->getTypeProvider(), 'field_views_data', [$field_storage]);
184 if (empty($result)) {
185 $result = views_field_default_views_data($field_storage);
187 $module_handler->alter('field_views_data', $result, $field_storage);
189 if (is_array($result)) {
190 $data = NestedArray::mergeDeep($result, $data);
200 * Implements hook_views_data_alter().
202 * Field modules can implement hook_field_views_data_views_data_alter() to
203 * alter the views data on a per field basis. This is weirdly named so as
204 * not to conflict with the \Drupal::moduleHandler()->alter('field_views_data')
205 * in views_views_data().
207 function views_views_data_alter(&$data) {
208 $entity_manager = \Drupal::entityManager();
209 if (!$entity_manager->hasDefinition('field_storage_config')) {
212 /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
213 foreach ($entity_manager->getStorage('field_storage_config')->loadMultiple() as $field_storage) {
214 if (_views_field_get_entity_type_storage($field_storage)) {
215 $function = $field_storage->getTypeProvider() . '_field_views_data_views_data_alter';
216 if (function_exists($function)) {
217 $function($data, $field_storage);
224 * Determines whether the entity type the field appears in is SQL based.
226 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
227 * The field storage definition.
229 * @return \Drupal\Core\Entity\Sql\SqlContentEntityStorage
230 * Returns the entity type storage if supported.
232 function _views_field_get_entity_type_storage(FieldStorageConfigInterface $field_storage) {
234 $entity_manager = \Drupal::entityManager();
235 if ($entity_manager->hasDefinition($field_storage->getTargetEntityTypeId())) {
236 $storage = $entity_manager->getStorage($field_storage->getTargetEntityTypeId());
237 $result = $storage instanceof SqlContentEntityStorage ? $storage : FALSE;
243 * Returns the label of a certain field.
245 * Therefore it looks up in all bundles to find the most used field.
247 function views_entity_field_label($entity_type, $field_name) {
250 // Count the amount of fields per label per field storage.
251 foreach (array_keys(\Drupal::entityManager()->getBundleInfo($entity_type)) as $bundle) {
252 $bundle_fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle), function ($field_definition) {
253 return $field_definition instanceof FieldConfigInterface;
255 if (isset($bundle_fields[$field_name])) {
256 $field = $bundle_fields[$field_name];
257 $label = $field->getLabel();
258 $label_counter[$label] = isset($label_counter[$label]) ? ++$label_counter[$label] : 1;
259 $all_labels[$label] = TRUE;
262 if (empty($label_counter)) {
263 return [$field_name, $all_labels];
265 // Sort the field labels by it most used label and return the most used one.
266 // If the counts are equal, sort by the label to ensure the result is
268 uksort($label_counter, function ($a, $b) use ($label_counter) {
269 if ($label_counter[$a] === $label_counter[$b]) {
270 return strcmp($a, $b);
272 return $label_counter[$a] > $label_counter[$b] ? -1 : 1;
274 $label_counter = array_keys($label_counter);
275 return [$label_counter[0], $all_labels];
279 * Default views data implementation for a field.
281 * @param \Drupal\field\FieldStorageConfigInterface $field_storage
282 * The field definition.
285 * The default views data for the field.
287 function views_field_default_views_data(FieldStorageConfigInterface $field_storage) {
290 // Check the field type is available.
291 if (!\Drupal::service('plugin.manager.field.field_type')->hasDefinition($field_storage->getType())) {
294 // Check the field storage has fields.
295 if (!$field_storage->getBundles()) {
299 // Ignore custom storage too.
300 if ($field_storage->hasCustomStorage()) {
304 // Check whether the entity type storage is supported.
305 $storage = _views_field_get_entity_type_storage($field_storage);
310 $field_name = $field_storage->getName();
311 $field_columns = $field_storage->getColumns();
313 // Grab information about the entity type tables.
314 // We need to join to both the base table and the data table, if available.
315 $entity_manager = \Drupal::entityManager();
316 $entity_type_id = $field_storage->getTargetEntityTypeId();
317 $entity_type = $entity_manager->getDefinition($entity_type_id);
318 if (!$base_table = $entity_type->getBaseTable()) {
319 // We cannot do anything if for some reason there is no base table.
322 $entity_tables = [$base_table => $entity_type_id];
323 // Some entities may not have a data table.
324 $data_table = $entity_type->getDataTable();
326 $entity_tables[$data_table] = $entity_type_id;
328 $entity_revision_table = $entity_type->getRevisionTable();
329 $supports_revisions = $entity_type->hasKey('revision') && $entity_revision_table;
330 if ($supports_revisions) {
331 $entity_tables[$entity_revision_table] = $entity_type_id;
332 $entity_revision_data_table = $entity_type->getRevisionDataTable();
333 if ($entity_revision_data_table) {
334 $entity_tables[$entity_revision_data_table] = $entity_type_id;
338 // Description of the field tables.
339 // @todo Generalize this code to make it work with any table layout. See
340 // https://www.drupal.org/node/2079019.
341 $table_mapping = $storage->getTableMapping();
343 EntityStorageInterface::FIELD_LOAD_CURRENT => [
344 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
345 'alias' => "{$entity_type_id}__{$field_name}",
348 if ($supports_revisions) {
349 $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION] = [
350 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
351 'alias' => "{$entity_type_id}_revision__{$field_name}",
355 // Determine if the fields are translatable.
356 $bundles_names = $field_storage->getBundles();
357 $translation_join_type = FALSE;
359 $translatable_configs = [];
360 $untranslatable_configs = [];
361 $untranslatable_config_bundles = [];
363 foreach ($bundles_names as $bundle) {
364 $fields[$bundle] = FieldConfig::loadByName($entity_type->id(), $bundle, $field_name);
366 foreach ($fields as $bundle => $config_entity) {
367 if (!empty($config_entity)) {
368 if ($config_entity->isTranslatable()) {
369 $translatable_configs[$bundle] = $config_entity;
372 $untranslatable_configs[$bundle] = $config_entity;
376 // https://www.drupal.org/node/2451657#comment-11462881
377 \Drupal::logger('views')->error(
378 t('A non-existent config entity name returned by FieldStorageConfigInterface::getBundles(): field name: %field, bundle: %bundle',
379 ['%field' => $field_name, '%bundle' => $bundle]
384 // If the field is translatable on all the bundles, there will be a join on
386 if (!empty($translatable_configs) && empty($untranslatable_configs)) {
387 $translation_join_type = 'language';
389 // If the field is translatable only on certain bundles, there will be a join
390 // on langcode OR bundle name.
391 elseif (!empty($translatable_configs) && !empty($untranslatable_configs)) {
392 foreach ($untranslatable_configs as $config) {
393 $untranslatable_config_bundles[] = $config->getTargetBundle();
395 $translation_join_type = 'language_bundle';
398 // Build the relationships between the field table and the entity tables.
399 $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias'];
401 // Tell Views how to join to the base table, via the data table.
402 $data[$table_alias]['table']['join'][$data_table] = [
403 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
404 'left_field' => $entity_type->getKey('id'),
405 'field' => 'entity_id',
407 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
412 // If there is no data table, just join directly.
413 $data[$table_alias]['table']['join'][$base_table] = [
414 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
415 'left_field' => $entity_type->getKey('id'),
416 'field' => 'entity_id',
418 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
423 if ($translation_join_type === 'language_bundle') {
424 $data[$table_alias]['table']['join'][$data_table]['join_id'] = 'field_or_language_join';
425 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
426 'left_field' => 'langcode',
427 'field' => 'langcode',
429 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
431 'value' => $untranslatable_config_bundles,
434 elseif ($translation_join_type === 'language') {
435 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
436 'left_field' => 'langcode',
437 'field' => 'langcode',
441 if ($supports_revisions) {
442 $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias'];
443 if ($entity_revision_data_table) {
444 // Tell Views how to join to the revision table, via the data table.
445 $data[$table_alias]['table']['join'][$entity_revision_data_table] = [
446 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
447 'left_field' => $entity_type->getKey('revision'),
448 'field' => 'revision_id',
450 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
455 // If there is no data table, just join directly.
456 $data[$table_alias]['table']['join'][$entity_revision_table] = [
457 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
458 'left_field' => $entity_type->getKey('revision'),
459 'field' => 'revision_id',
461 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
465 if ($translation_join_type === 'language_bundle') {
466 $data[$table_alias]['table']['join'][$entity_revision_data_table]['join_id'] = 'field_or_language_join';
467 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
468 'left_field' => 'langcode',
469 'field' => 'langcode',
471 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
472 'value' => $untranslatable_config_bundles,
476 elseif ($translation_join_type === 'language') {
477 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
478 'left_field' => 'langcode',
479 'field' => 'langcode',
484 $group_name = $entity_type->getLabel();
485 // Get the list of bundles the field appears in.
486 $bundles_names = $field_storage->getBundles();
487 // Build the list of additional fields to add to queries.
488 $add_fields = ['delta', 'langcode', 'bundle'];
489 foreach (array_keys($field_columns) as $column) {
490 $add_fields[] = $table_mapping->getFieldColumnName($field_storage, $column);
492 // Determine the label to use for the field. We don't have a label available
493 // at the field level, so we just go through all fields and take the one
494 // which is used the most frequently.
495 list($label, $all_labels) = views_entity_field_label($entity_type_id, $field_name);
497 // Expose data for the field as a whole.
498 foreach ($field_tables as $type => $table_info) {
499 $table = $table_info['table'];
500 $table_alias = $table_info['alias'];
502 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
503 $group = $group_name;
504 $field_alias = $field_name;
507 $group = t('@group (historical data)', ['@group' => $group_name]);
508 $field_alias = $field_name . '-revision_id';
511 $data[$table_alias][$field_alias] = [
514 'title short' => $label,
515 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
518 // Go through and create a list of aliases for all possible combinations of
519 // entity type + name.
522 foreach ($all_labels as $label_name => $true) {
523 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
524 if ($label != $label_name) {
526 'base' => $base_table,
527 'group' => $group_name,
528 'title' => $label_name,
529 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
531 $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $label_name]);
534 elseif ($supports_revisions && $label != $label_name) {
537 'group' => t('@group (historical data)', ['@group' => $group_name]),
538 'title' => $label_name,
539 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
541 $also_known[] = t('@group (historical data): @field', ['@group' => $group_name, '@field' => $label_name]);
545 $data[$table_alias][$field_alias]['aliases'] = $aliases;
546 // The $also_known variable contains markup that is HTML escaped and that
547 // loses safeness when imploded. The help text is used in #description
548 // and therefore XSS admin filtered by default. Escaped HTML is not
549 // altered by XSS filtering, therefore it is safe to just concatenate the
550 // strings. Afterwards we mark the entire string as safe, so it won't be
551 // escaped, no matter where it is used.
552 // Considering the dual use of this help data (both as metadata and as
553 // help text), other patterns such as use of #markup would not be correct
555 $data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
558 $keys = array_keys($field_columns);
559 $real_field = reset($keys);
560 $data[$table_alias][$field_alias]['field'] = [
563 'field_name' => $field_name,
564 'entity_type' => $entity_type_id,
565 // Provide a real field for group by.
566 'real field' => $field_alias . '_' . $real_field,
567 'additional fields' => $add_fields,
568 // Default the element type to div, let the UI change it if necessary.
569 'element type' => 'div',
570 'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION,
574 // Expose data for each field property individually.
575 foreach ($field_columns as $column => $attributes) {
578 // Identify likely filters and arguments for each column based on field type.
579 switch ($attributes['type']) {
588 $argument = 'numeric';
590 if ($field_storage->getType() == 'boolean') {
596 // It does not make sense to sort by blob or text.
600 $argument = 'string';
605 if (count($field_columns) == 1 || $column == 'value') {
606 $title = t('@label (@name)', ['@label' => $label, '@name' => $field_name]);
607 $title_short = $label;
610 $title = t('@label (@name:@column)', ['@label' => $label, '@name' => $field_name, '@column' => $column]);
611 $title_short = t('@label:@column', ['@label' => $label, '@column' => $column]);
614 // Expose data for the property.
615 foreach ($field_tables as $type => $table_info) {
616 $table = $table_info['table'];
617 $table_alias = $table_info['alias'];
619 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
620 $group = $group_name;
623 $group = t('@group (historical data)', ['@group' => $group_name]);
625 $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column);
627 // Load all the fields from the table by default.
628 $additional_fields = $table_mapping->getAllColumns($table);
630 $data[$table_alias][$column_real_name] = [
633 'title short' => $title_short,
634 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
637 // Go through and create a list of aliases for all possible combinations of
638 // entity type + name.
641 foreach ($all_labels as $label_name => $true) {
642 if ($label != $label_name) {
643 if (count($field_columns) == 1 || $column == 'value') {
644 $alias_title = t('@label (@name)', ['@label' => $label_name, '@name' => $field_name]);
647 $alias_title = t('@label (@name:@column)', ['@label' => $label_name, '@name' => $field_name, '@column' => $column]);
650 'group' => $group_name,
651 'title' => $alias_title,
652 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $title]),
654 $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $title]);
658 $data[$table_alias][$column_real_name]['aliases'] = $aliases;
659 // The $also_known variable contains markup that is HTML escaped and
660 // that loses safeness when imploded. The help text is used in
661 // #description and therefore XSS admin filtered by default. Escaped
662 // HTML is not altered by XSS filtering, therefore it is safe to just
663 // concatenate the strings. Afterwards we mark the entire string as
664 // safe, so it won't be escaped, no matter where it is used.
665 // Considering the dual use of this help data (both as metadata and as
666 // help text), other patterns such as use of #markup would not be
668 $data[$table_alias][$column_real_name]['help'] = Markup::create($data[$table_alias][$column_real_name]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
671 $data[$table_alias][$column_real_name]['argument'] = [
672 'field' => $column_real_name,
675 'additional fields' => $additional_fields,
676 'field_name' => $field_name,
677 'entity_type' => $entity_type_id,
678 'empty field name' => t('- No value -'),
680 $data[$table_alias][$column_real_name]['filter'] = [
681 'field' => $column_real_name,
684 'additional fields' => $additional_fields,
685 'field_name' => $field_name,
686 'entity_type' => $entity_type_id,
687 'allow empty' => TRUE,
689 if (!empty($allow_sort)) {
690 $data[$table_alias][$column_real_name]['sort'] = [
691 'field' => $column_real_name,
694 'additional fields' => $additional_fields,
695 'field_name' => $field_name,
696 'entity_type' => $entity_type_id,
700 // Set click sortable if there is a field definition.
701 if (isset($data[$table_alias][$field_name]['field'])) {
702 $data[$table_alias][$field_name]['field']['click sortable'] = $allow_sort;
705 // Expose additional delta column for multiple value fields.
706 if ($field_storage->isMultiple()) {
707 $title_delta = t('@label (@name:delta)', ['@label' => $label, '@name' => $field_name]);
708 $title_short_delta = t('@label:delta', ['@label' => $label]);
710 $data[$table_alias]['delta'] = [
712 'title' => $title_delta,
713 'title short' => $title_short_delta,
714 'help' => t('Delta - Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
716 $data[$table_alias]['delta']['field'] = [
719 $data[$table_alias]['delta']['argument'] = [
723 'additional fields' => $additional_fields,
724 'empty field name' => t('- No value -'),
725 'field_name' => $field_name,
726 'entity_type' => $entity_type_id,
728 $data[$table_alias]['delta']['filter'] = [
732 'additional fields' => $additional_fields,
733 'field_name' => $field_name,
734 'entity_type' => $entity_type_id,
735 'allow empty' => TRUE,
737 $data[$table_alias]['delta']['sort'] = [
741 'additional fields' => $additional_fields,
742 'field_name' => $field_name,
743 'entity_type' => $entity_type_id,
753 * Implements hook_field_views_data().
755 * The function implements the hook in behalf of 'core' because it adds a
756 * relationship and a reverse relationship to entity_reference field type, which
757 * is provided by core.
759 function core_field_views_data(FieldStorageConfigInterface $field_storage) {
760 $data = views_field_default_views_data($field_storage);
762 // The code below only deals with the Entity reference field type.
763 if ($field_storage->getType() != 'entity_reference') {
767 $entity_manager = \Drupal::entityManager();
768 $entity_type_id = $field_storage->getTargetEntityTypeId();
769 /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
770 $table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping();
772 foreach ($data as $table_name => $table_data) {
773 // Add a relationship to the target entity type.
774 $target_entity_type_id = $field_storage->getSetting('target_type');
775 $target_entity_type = $entity_manager->getDefinition($target_entity_type_id);
776 $entity_type_id = $field_storage->getTargetEntityTypeId();
777 $entity_type = $entity_manager->getDefinition($entity_type_id);
778 $target_base_table = $target_entity_type->getDataTable() ?: $target_entity_type->getBaseTable();
779 $field_name = $field_storage->getName();
781 // Provide a relationship for the entity type with the entity reference
784 '@label' => $target_entity_type->getLabel(),
785 '@field_name' => $field_name,
787 $data[$table_name][$field_name]['relationship'] = [
788 'title' => t('@label referenced from @field_name', $args),
789 'label' => t('@field_name: @label', $args),
790 'group' => $entity_type->getLabel(),
791 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $field_storage->getBundles())]),
793 'base' => $target_base_table,
794 'entity type' => $target_entity_type_id,
795 'base field' => $target_entity_type->getKey('id'),
796 'relationship field' => $field_name . '_target_id',
799 // Provide a reverse relationship for the entity type that is referenced by
801 $args['@entity'] = $entity_type->getLabel();
802 $args['@label'] = $target_entity_type->getLowercaseLabel();
803 $pseudo_field_name = 'reverse__' . $entity_type_id . '__' . $field_name;
804 $data[$target_base_table][$pseudo_field_name]['relationship'] = [
805 'title' => t('@entity using @field_name', $args),
806 'label' => t('@field_name', ['@field_name' => $field_name]),
807 'group' => $target_entity_type->getLabel(),
808 'help' => t('Relate each @entity with a @field_name set to the @label.', $args),
809 'id' => 'entity_reverse',
810 'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
811 'entity_type' => $entity_type_id,
812 'base field' => $entity_type->getKey('id'),
813 'field_name' => $field_name,
814 'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
815 'field field' => $field_name . '_target_id',
818 'field' => 'deleted',