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 'A non-existent config entity name returned by FieldStorageConfigInterface::getBundles(): entity type: %entity_type, bundle: %bundle, field name: %field',
380 '%entity_type' => $entity_type->id(),
381 '%bundle' => $bundle,
382 '%field' => $field_name,
388 // If the field is translatable on all the bundles, there will be a join on
390 if (!empty($translatable_configs) && empty($untranslatable_configs)) {
391 $translation_join_type = 'language';
393 // If the field is translatable only on certain bundles, there will be a join
394 // on langcode OR bundle name.
395 elseif (!empty($translatable_configs) && !empty($untranslatable_configs)) {
396 foreach ($untranslatable_configs as $config) {
397 $untranslatable_config_bundles[] = $config->getTargetBundle();
399 $translation_join_type = 'language_bundle';
402 // Build the relationships between the field table and the entity tables.
403 $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias'];
405 // Tell Views how to join to the base table, via the data table.
406 $data[$table_alias]['table']['join'][$data_table] = [
407 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
408 'left_field' => $entity_type->getKey('id'),
409 'field' => 'entity_id',
411 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
416 // If there is no data table, just join directly.
417 $data[$table_alias]['table']['join'][$base_table] = [
418 'table' => $table_mapping->getDedicatedDataTableName($field_storage),
419 'left_field' => $entity_type->getKey('id'),
420 'field' => 'entity_id',
422 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
427 if ($translation_join_type === 'language_bundle') {
428 $data[$table_alias]['table']['join'][$data_table]['join_id'] = 'field_or_language_join';
429 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
430 'left_field' => 'langcode',
431 'field' => 'langcode',
433 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
435 'value' => $untranslatable_config_bundles,
438 elseif ($translation_join_type === 'language') {
439 $data[$table_alias]['table']['join'][$data_table]['extra'][] = [
440 'left_field' => 'langcode',
441 'field' => 'langcode',
445 if ($supports_revisions) {
446 $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias'];
447 if ($entity_revision_data_table) {
448 // Tell Views how to join to the revision table, via the data table.
449 $data[$table_alias]['table']['join'][$entity_revision_data_table] = [
450 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
451 'left_field' => $entity_type->getKey('revision'),
452 'field' => 'revision_id',
454 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
459 // If there is no data table, just join directly.
460 $data[$table_alias]['table']['join'][$entity_revision_table] = [
461 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage),
462 'left_field' => $entity_type->getKey('revision'),
463 'field' => 'revision_id',
465 ['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
469 if ($translation_join_type === 'language_bundle') {
470 $data[$table_alias]['table']['join'][$entity_revision_data_table]['join_id'] = 'field_or_language_join';
471 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
472 'left_field' => 'langcode',
473 'field' => 'langcode',
475 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
476 'value' => $untranslatable_config_bundles,
480 elseif ($translation_join_type === 'language') {
481 $data[$table_alias]['table']['join'][$entity_revision_data_table]['extra'][] = [
482 'left_field' => 'langcode',
483 'field' => 'langcode',
488 $group_name = $entity_type->getLabel();
489 // Get the list of bundles the field appears in.
490 $bundles_names = $field_storage->getBundles();
491 // Build the list of additional fields to add to queries.
492 $add_fields = ['delta', 'langcode', 'bundle'];
493 foreach (array_keys($field_columns) as $column) {
494 $add_fields[] = $table_mapping->getFieldColumnName($field_storage, $column);
496 // Determine the label to use for the field. We don't have a label available
497 // at the field level, so we just go through all fields and take the one
498 // which is used the most frequently.
499 list($label, $all_labels) = views_entity_field_label($entity_type_id, $field_name);
501 // Expose data for the field as a whole.
502 foreach ($field_tables as $type => $table_info) {
503 $table = $table_info['table'];
504 $table_alias = $table_info['alias'];
506 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
507 $group = $group_name;
508 $field_alias = $field_name;
511 $group = t('@group (historical data)', ['@group' => $group_name]);
512 $field_alias = $field_name . '-revision_id';
515 $data[$table_alias][$field_alias] = [
518 'title short' => $label,
519 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
522 // Go through and create a list of aliases for all possible combinations of
523 // entity type + name.
526 foreach ($all_labels as $label_name => $true) {
527 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
528 if ($label != $label_name) {
530 'base' => $base_table,
531 'group' => $group_name,
532 'title' => $label_name,
533 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
535 $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $label_name]);
538 elseif ($supports_revisions && $label != $label_name) {
541 'group' => t('@group (historical data)', ['@group' => $group_name]),
542 'title' => $label_name,
543 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $label]),
545 $also_known[] = t('@group (historical data): @field', ['@group' => $group_name, '@field' => $label_name]);
549 $data[$table_alias][$field_alias]['aliases'] = $aliases;
550 // The $also_known variable contains markup that is HTML escaped and that
551 // loses safeness when imploded. The help text is used in #description
552 // and therefore XSS admin filtered by default. Escaped HTML is not
553 // altered by XSS filtering, therefore it is safe to just concatenate the
554 // strings. Afterwards we mark the entire string as safe, so it won't be
555 // escaped, no matter where it is used.
556 // Considering the dual use of this help data (both as metadata and as
557 // help text), other patterns such as use of #markup would not be correct
559 $data[$table_alias][$field_alias]['help'] = Markup::create($data[$table_alias][$field_alias]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
562 $keys = array_keys($field_columns);
563 $real_field = reset($keys);
564 $data[$table_alias][$field_alias]['field'] = [
567 'field_name' => $field_name,
568 'entity_type' => $entity_type_id,
569 // Provide a real field for group by.
570 'real field' => $field_alias . '_' . $real_field,
571 'additional fields' => $add_fields,
572 // Default the element type to div, let the UI change it if necessary.
573 'element type' => 'div',
574 'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION,
578 // Expose data for each field property individually.
579 foreach ($field_columns as $column => $attributes) {
582 // Identify likely filters and arguments for each column based on field type.
583 switch ($attributes['type']) {
592 $argument = 'numeric';
594 if ($field_storage->getType() == 'boolean') {
600 // It does not make sense to sort by blob or text.
604 $argument = 'string';
609 if (count($field_columns) == 1 || $column == 'value') {
610 $title = t('@label (@name)', ['@label' => $label, '@name' => $field_name]);
611 $title_short = $label;
614 $title = t('@label (@name:@column)', ['@label' => $label, '@name' => $field_name, '@column' => $column]);
615 $title_short = t('@label:@column', ['@label' => $label, '@column' => $column]);
618 // Expose data for the property.
619 foreach ($field_tables as $type => $table_info) {
620 $table = $table_info['table'];
621 $table_alias = $table_info['alias'];
623 if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) {
624 $group = $group_name;
627 $group = t('@group (historical data)', ['@group' => $group_name]);
629 $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column);
631 // Load all the fields from the table by default.
632 $additional_fields = $table_mapping->getAllColumns($table);
634 $data[$table_alias][$column_real_name] = [
637 'title short' => $title_short,
638 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
641 // Go through and create a list of aliases for all possible combinations of
642 // entity type + name.
645 foreach ($all_labels as $label_name => $true) {
646 if ($label != $label_name) {
647 if (count($field_columns) == 1 || $column == 'value') {
648 $alias_title = t('@label (@name)', ['@label' => $label_name, '@name' => $field_name]);
651 $alias_title = t('@label (@name:@column)', ['@label' => $label_name, '@name' => $field_name, '@column' => $column]);
654 'group' => $group_name,
655 'title' => $alias_title,
656 'help' => t('This is an alias of @group: @field.', ['@group' => $group_name, '@field' => $title]),
658 $also_known[] = t('@group: @field', ['@group' => $group_name, '@field' => $title]);
662 $data[$table_alias][$column_real_name]['aliases'] = $aliases;
663 // The $also_known variable contains markup that is HTML escaped and
664 // that loses safeness when imploded. The help text is used in
665 // #description and therefore XSS admin filtered by default. Escaped
666 // HTML is not altered by XSS filtering, therefore it is safe to just
667 // concatenate the strings. Afterwards we mark the entire string as
668 // safe, so it won't be escaped, no matter where it is used.
669 // Considering the dual use of this help data (both as metadata and as
670 // help text), other patterns such as use of #markup would not be
672 $data[$table_alias][$column_real_name]['help'] = Markup::create($data[$table_alias][$column_real_name]['help'] . ' ' . t('Also known as:') . ' ' . implode(', ', $also_known));
675 $data[$table_alias][$column_real_name]['argument'] = [
676 'field' => $column_real_name,
679 'additional fields' => $additional_fields,
680 'field_name' => $field_name,
681 'entity_type' => $entity_type_id,
682 'empty field name' => t('- No value -'),
684 $data[$table_alias][$column_real_name]['filter'] = [
685 'field' => $column_real_name,
688 'additional fields' => $additional_fields,
689 'field_name' => $field_name,
690 'entity_type' => $entity_type_id,
691 'allow empty' => TRUE,
693 if (!empty($allow_sort)) {
694 $data[$table_alias][$column_real_name]['sort'] = [
695 'field' => $column_real_name,
698 'additional fields' => $additional_fields,
699 'field_name' => $field_name,
700 'entity_type' => $entity_type_id,
704 // Set click sortable if there is a field definition.
705 if (isset($data[$table_alias][$field_name]['field'])) {
706 $data[$table_alias][$field_name]['field']['click sortable'] = $allow_sort;
709 // Expose additional delta column for multiple value fields.
710 if ($field_storage->isMultiple()) {
711 $title_delta = t('@label (@name:delta)', ['@label' => $label, '@name' => $field_name]);
712 $title_short_delta = t('@label:delta', ['@label' => $label]);
714 $data[$table_alias]['delta'] = [
716 'title' => $title_delta,
717 'title short' => $title_short_delta,
718 'help' => t('Delta - Appears in: @bundles.', ['@bundles' => implode(', ', $bundles_names)]),
720 $data[$table_alias]['delta']['field'] = [
723 $data[$table_alias]['delta']['argument'] = [
727 'additional fields' => $additional_fields,
728 'empty field name' => t('- No value -'),
729 'field_name' => $field_name,
730 'entity_type' => $entity_type_id,
732 $data[$table_alias]['delta']['filter'] = [
736 'additional fields' => $additional_fields,
737 'field_name' => $field_name,
738 'entity_type' => $entity_type_id,
739 'allow empty' => TRUE,
741 $data[$table_alias]['delta']['sort'] = [
745 'additional fields' => $additional_fields,
746 'field_name' => $field_name,
747 'entity_type' => $entity_type_id,
757 * Implements hook_field_views_data().
759 * The function implements the hook in behalf of 'core' because it adds a
760 * relationship and a reverse relationship to entity_reference field type, which
761 * is provided by core.
763 function core_field_views_data(FieldStorageConfigInterface $field_storage) {
764 $data = views_field_default_views_data($field_storage);
766 // The code below only deals with the Entity reference field type.
767 if ($field_storage->getType() != 'entity_reference') {
771 $entity_manager = \Drupal::entityManager();
772 $entity_type_id = $field_storage->getTargetEntityTypeId();
773 /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
774 $table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping();
776 foreach ($data as $table_name => $table_data) {
777 // Add a relationship to the target entity type.
778 $target_entity_type_id = $field_storage->getSetting('target_type');
779 $target_entity_type = $entity_manager->getDefinition($target_entity_type_id);
780 $entity_type_id = $field_storage->getTargetEntityTypeId();
781 $entity_type = $entity_manager->getDefinition($entity_type_id);
782 $target_base_table = $target_entity_type->getDataTable() ?: $target_entity_type->getBaseTable();
783 $field_name = $field_storage->getName();
785 // Provide a relationship for the entity type with the entity reference
788 '@label' => $target_entity_type->getLabel(),
789 '@field_name' => $field_name,
791 $data[$table_name][$field_name]['relationship'] = [
792 'title' => t('@label referenced from @field_name', $args),
793 'label' => t('@field_name: @label', $args),
794 'group' => $entity_type->getLabel(),
795 'help' => t('Appears in: @bundles.', ['@bundles' => implode(', ', $field_storage->getBundles())]),
797 'base' => $target_base_table,
798 'entity type' => $target_entity_type_id,
799 'base field' => $target_entity_type->getKey('id'),
800 'relationship field' => $field_name . '_target_id',
803 // Provide a reverse relationship for the entity type that is referenced by
805 $args['@entity'] = $entity_type->getLabel();
806 $args['@label'] = $target_entity_type->getLowercaseLabel();
807 $pseudo_field_name = 'reverse__' . $entity_type_id . '__' . $field_name;
808 $data[$target_base_table][$pseudo_field_name]['relationship'] = [
809 'title' => t('@entity using @field_name', $args),
810 'label' => t('@field_name', ['@field_name' => $field_name]),
811 'group' => $target_entity_type->getLabel(),
812 'help' => t('Relate each @entity with a @field_name set to the @label.', $args),
813 'id' => 'entity_reverse',
814 'base' => $entity_type->getDataTable() ?: $entity_type->getBaseTable(),
815 'entity_type' => $entity_type_id,
816 'base field' => $entity_type->getKey('id'),
817 'field_name' => $field_name,
818 'field table' => $table_mapping->getDedicatedDataTableName($field_storage),
819 'field field' => $field_name . '_target_id',
822 'field' => 'deleted',