From 06bed5c7958b970ab4c13d570880821480cdf5f6 Mon Sep 17 00:00:00 2001 From: webchick <drupal@webchick.net> Date: Tue, 6 Jan 2015 09:20:24 -0800 Subject: [PATCH] Issue #2399931 by dawehner, yched: Generic entity api field handler should live in views module not in field module --- .../entity_reference.views.inc | 2 +- core/modules/field/field.module | 14 - core/modules/field/field.views.inc | 494 ------------------ core/modules/file/file.views.inc | 6 +- core/modules/image/image.views.inc | 6 +- core/modules/taxonomy/taxonomy.views.inc | 6 +- .../src/Plugin/views/argument/FieldList.php | 4 +- .../src/Plugin/views/argument/ListString.php | 4 +- .../src/Plugin/views/field/Field.php | 4 +- .../src/Plugin/views/filter/FieldList.php | 6 +- .../views/relationship/EntityReverse.php | 4 +- core/modules/views/views.api.php | 16 +- core/modules/views/views.module | 7 + core/modules/views/views.views.inc | 485 +++++++++++++++++ 14 files changed, 520 insertions(+), 538 deletions(-) delete mode 100644 core/modules/field/field.views.inc rename core/modules/{field => views}/src/Plugin/views/argument/FieldList.php (95%) rename core/modules/{field => views}/src/Plugin/views/argument/ListString.php (95%) rename core/modules/{field => views}/src/Plugin/views/field/Field.php (99%) rename core/modules/{field => views}/src/Plugin/views/filter/FieldList.php (76%) rename core/modules/{field => views}/src/Plugin/views/relationship/EntityReverse.php (96%) diff --git a/core/modules/entity_reference/entity_reference.views.inc b/core/modules/entity_reference/entity_reference.views.inc index 030841552b99..486fca131ac8 100644 --- a/core/modules/entity_reference/entity_reference.views.inc +++ b/core/modules/entity_reference/entity_reference.views.inc @@ -11,7 +11,7 @@ * Implements hook_field_views_data(). */ function entity_reference_field_views_data(FieldStorageConfigInterface $field_storage) { - $data = field_views_field_default_views_data($field_storage); + $data = views_field_default_views_data($field_storage); $entity_manager = \Drupal::entityManager(); $table_mapping = $entity_manager->getStorage($field_storage->getTargetEntityTypeId())->getTableMapping(); foreach ($data as $table_name => $table_data) { diff --git a/core/modules/field/field.module b/core/modules/field/field.module index 143e8c51f3a3..82865d2e0548 100644 --- a/core/modules/field/field.module +++ b/core/modules/field/field.module @@ -264,20 +264,6 @@ function _field_create_entity_from_ids($ids) { return entity_create($ids->entity_type, $id_properties); } -/** - * Implements hook_hook_info(). - */ -function field_hook_info() { - $hooks['field_views_data'] = array( - 'group' => 'views', - ); - $hooks['field_views_data_alter'] = array( - 'group' => 'views', - ); - - return $hooks; -} - /** * Implements hook_config_import_steps_alter(). */ diff --git a/core/modules/field/field.views.inc b/core/modules/field/field.views.inc deleted file mode 100644 index cc263668baa1..000000000000 --- a/core/modules/field/field.views.inc +++ /dev/null @@ -1,494 +0,0 @@ -<?php - -/** - * @file - * Provide Views data for field.module. - */ - -use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Entity\Sql\SqlContentEntityStorage; -use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\field\FieldStorageConfigInterface; -use Drupal\field\FieldConfigInterface; - -/** - * Implements hook_views_data(). - * - * Field modules can implement hook_field_views_data() to override the default - * behavior for adding fields. - */ -function field_views_data() { - $data = array(); - $module_handler = \Drupal::moduleHandler(); - - foreach (\Drupal::entityManager()->getStorage('field_storage_config')->loadMultiple() as $field_storage) { - if (_field_views_get_entity_type_storage($field_storage)) { - $result = (array) $module_handler->invoke($field_storage->module, 'field_views_data', array($field_storage)); - if (empty($result)) { - $result = field_views_field_default_views_data($field_storage); - } - $module_handler->alter('field_views_data', $result, $field_storage); - - if (is_array($result)) { - $data = NestedArray::mergeDeep($result, $data); - } - } - } - - return $data; -} - -/** - * Implements hook_views_data_alter(). - * - * Field modules can implement hook_field_views_data_views_data_alter() to - * alter the views data on a per field basis. This is weirdly named so as - * not to conflict with the \Drupal::moduleHandler()->alter('field_views_data') - * in field_views_data. - */ -function field_views_data_alter(&$data) { - foreach (\Drupal::entityManager()->getStorage('field_storage_config')->loadMultiple() as $field_storage) { - if (_field_views_get_entity_type_storage($field_storage)) { - $function = $field_storage->module . '_field_views_data_views_data_alter'; - if (function_exists($function)) { - $function($data, $field_storage); - } - } - } -} - -/** - * Determines whether the entity type the field appears in is SQL based. - * - * @param \Drupal\field\FieldStorageConfigInterface $field_storage - * The field storage definition. - * - * @return \Drupal\Core\Entity\Sql\SqlContentEntityStorage - * Returns the entity type storage if supported. - */ -function _field_views_get_entity_type_storage(FieldStorageConfigInterface $field_storage) { - $result = FALSE; - $entity_manager = \Drupal::entityManager(); - if ($entity_manager->hasDefinition($field_storage->getTargetEntityTypeId())) { - $storage = $entity_manager->getStorage($field_storage->getTargetEntityTypeId()); - $result = $storage instanceof SqlContentEntityStorage ? $storage : FALSE; - } - return $result; -} - -/** - * Returns the label of a certain field. - * - * Therefore it looks up in all bundles to find the most used field. - */ -function field_views_field_label($entity_type, $field_name) { - $label_counter = array(); - $all_labels = array(); - // Count the amount of fields per label per field storage. - foreach (array_keys(\Drupal::entityManager()->getBundleInfo($entity_type)) as $bundle) { - $bundle_fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle), function ($field_definition) { - return $field_definition instanceof FieldConfigInterface; - }); - if (isset($bundle_fields[$field_name])) { - $field = $bundle_fields[$field_name]; - $label = $field->getLabel(); - $label_counter[$label] = isset($label_counter[$label]) ? ++$label_counter[$label] : 1; - $all_labels[$label] = TRUE; - } - } - if (empty($label_counter)) { - return array($field_name, $all_labels); - } - // Sort the field labels by it most used label and return the most used one. - arsort($label_counter); - $label_counter = array_keys($label_counter); - return array($label_counter[0], $all_labels); -} - -/** - * Default views data implementation for a field. - * - * @param \Drupal\field\FieldStorageConfigInterface $field_storage - * The field definition. - * - * @return array - * The default views data for the field. - */ -function field_views_field_default_views_data(FieldStorageConfigInterface $field_storage) { - $data = array(); - - // Check the field type is available. - if (!\Drupal::service('plugin.manager.field.field_type')->hasDefinition($field_storage->getType())) { - return $data; - } - // Check the field storage has fields. - if (!$field_storage->getBundles()) { - return $data; - } - // Check whether the entity type storage is supported. - $storage = _field_views_get_entity_type_storage($field_storage); - if (!$storage) { - return $data; - } - - $field_name = $field_storage->getName(); - $field_columns = $field_storage->getColumns(); - - // Grab information about the entity type tables. - // We need to join to both the base table and the data table, if available. - $entity_manager = \Drupal::entityManager(); - $entity_type_id = $field_storage->getTargetEntityTypeId(); - $entity_type = $entity_manager->getDefinition($entity_type_id); - if (!$base_table = $entity_type->getBaseTable()) { - // We cannot do anything if for some reason there is no base table. - return $data; - } - $entity_tables = array($base_table => $entity_type_id); - // Some entities may not have a data table. - $data_table = $entity_type->getDataTable(); - if ($data_table) { - $entity_tables[$data_table] = $entity_type_id; - } - $entity_revision_table = $entity_type->getRevisionTable(); - $supports_revisions = $entity_type->hasKey('revision') && $entity_revision_table; - if ($supports_revisions) { - $entity_tables[$entity_revision_table] = $entity_type_id; - $entity_revision_data_table = $entity_type->getRevisionDataTable(); - if ($entity_revision_data_table) { - $entity_tables[$entity_revision_data_table] = $entity_type_id; - } - } - - // Description of the field tables. - // @todo Generalize this code to make it work with any table layout. See - // https://drupal.org/node/2079019. - $table_mapping = $storage->getTableMapping(); - $field_tables = array( - EntityStorageInterface::FIELD_LOAD_CURRENT => array( - 'table' => $table_mapping->getDedicatedDataTableName($field_storage), - 'alias' => "{$entity_type_id}__{$field_name}", - ), - ); - if ($supports_revisions) { - $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION] = array( - 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage), - 'alias' => "{$entity_type_id}_revision__{$field_name}", - ); - } - - // Build the relationships between the field table and the entity tables. - $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias']; - if ($data_table) { - // Tell Views how to join to the base table, via the data table. - $data[$table_alias]['table']['join'][$base_table] = array( - 'left_table' => $data_table, - 'left_field' => $entity_type->getKey('id'), - 'field' => 'entity_id', - 'extra' => array( - array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), - array('left_field' => 'langcode', 'field' => 'langcode'), - ), - ); - } - else { - // If there is no data table, just join directly. - $data[$table_alias]['table']['join'][$base_table] = array( - 'left_field' => $entity_type->getKey('id'), - 'field' => 'entity_id', - 'extra' => array( - array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), - ), - ); - } - - if ($supports_revisions) { - $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias']; - if ($entity_revision_data_table) { - // Tell Views how to join to the revision table, via the data table. - $data[$table_alias]['table']['join'][$entity_revision_table] = array( - 'left_table' => $entity_revision_data_table, - 'left_field' => $entity_type->getKey('revision'), - 'field' => 'revision_id', - 'extra' => array( - array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), - array('left_field' => 'langcode', 'field' => 'langcode'), - ), - ); - } - else { - // If there is no data table, just join directly. - $data[$table_alias]['table']['join'][$entity_revision_table] = array( - 'left_field' => $entity_type->getKey('revision'), - 'field' => 'revision_id', - 'extra' => array( - array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), - ), - ); - } - } - - $group_name = $entity_type->getLabel(); - // Get the list of bundles the field appears in. - $bundles_names = $field_storage->getBundles(); - // Build the list of additional fields to add to queries. - $add_fields = array('delta', 'langcode', 'bundle'); - foreach (array_keys($field_columns) as $column) { - $add_fields[] = $table_mapping->getFieldColumnName($field_storage, $column); - } - // Determine the label to use for the field. We don't have a label available - // at the field level, so we just go through all fields and take the one - // which is used the most frequently. - list($label, $all_labels) = field_views_field_label($entity_type_id, $field_name); - - // Expose data for the field as a whole. - foreach ($field_tables as $type => $table_info) { - $table = $table_info['table']; - $table_alias = $table_info['alias']; - - if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) { - $group = $group_name; - $field_alias = $field_name; - } - else { - $group = t('@group (historical data)', array('@group' => $group_name)); - $field_alias = $field_name . '-revision_id'; - } - - $data[$table_alias][$field_alias] = array( - 'group' => $group, - 'title' => $label, - 'title short' => $label, - 'help' => t('Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), - ); - - // Go through and create a list of aliases for all possible combinations of - // entity type + name. - $aliases = array(); - $also_known = array(); - foreach ($all_labels as $label_name => $true) { - if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) { - if ($label != $label_name) { - $aliases[] = array( - 'base' => $base_table, - 'group' => $group_name, - 'title' => $label_name, - 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)), - ); - $also_known[] = t('@group: @field', array('@group' => $group_name, '@field' => $label_name)); - } - } - elseif ($supports_revisions && $label != $label_name) { - $aliases[] = array( - 'base' => $table, - 'group' => t('@group (historical data)', array('@group' => $group_name)), - 'title' => $label_name, - 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)), - ); - $also_known[] = t('@group (historical data): @field', array('@group' => $group_name, '@field' => $label_name)); - } - } - if ($aliases) { - $data[$table_alias][$field_alias]['aliases'] = $aliases; - $data[$table_alias][$field_alias]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known))); - } - - $keys = array_keys($field_columns); - $real_field = reset($keys); - $data[$table_alias][$field_alias]['field'] = array( - 'table' => $table, - 'id' => 'field', - 'field_name' => $field_name, - 'entity_type' => $entity_type_id, - // Provide a real field for group by. - 'real field' => $field_alias . '_' . $real_field, - 'additional fields' => $add_fields, - 'entity_tables' => $entity_tables, - // Default the element type to div, let the UI change it if necessary. - 'element type' => 'div', - 'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION, - ); - } - - // Expose data for each field property individually. - foreach ($field_columns as $column => $attributes) { - $allow_sort = TRUE; - - // Identify likely filters and arguments for each column based on field type. - switch ($attributes['type']) { - case 'int': - case 'mediumint': - case 'tinyint': - case 'bigint': - case 'serial': - case 'numeric': - case 'float': - $filter = 'numeric'; - $argument = 'numeric'; - $sort = 'standard'; - break; - case 'text': - case 'blob': - // It does not make sense to sort by blob or text. - $allow_sort = FALSE; - default: - $filter = 'string'; - $argument = 'string'; - $sort = 'standard'; - break; - } - - if (count($field_columns) == 1 || $column == 'value') { - $title = t('@label (!name)', array('@label' => $label, '!name' => $field_name)); - $title_short = $label; - } - else { - $title = t('@label (!name:!column)', array('@label' => $label, '!name' => $field_name, '!column' => $column)); - $title_short = t('@label:!column', array('@label' => $label, '!column' => $column)); - } - - // Expose data for the property. - foreach ($field_tables as $type => $table_info) { - $table = $table_info['table']; - $table_alias = $table_info['alias']; - - if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) { - $group = $group_name; - } - else { - $group = t('@group (historical data)', array('@group' => $group_name)); - } - $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column); - - // Load all the fields from the table by default. - $additional_fields = $table_mapping->getAllColumns($table); - - $data[$table_alias][$column_real_name] = array( - 'group' => $group, - 'title' => $title, - 'title short' => $title_short, - 'help' => t('Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), - ); - - // Go through and create a list of aliases for all possible combinations of - // entity type + name. - $aliases = array(); - $also_known = array(); - foreach ($all_labels as $label_name => $true) { - if ($label != $label_name) { - if (count($field_columns) == 1 || $column == 'value') { - $alias_title = t('@label (!name)', array('@label' => $label_name, '!name' => $field_name)); - } - else { - $alias_title = t('@label (!name:!column)', array('@label' => $label_name, '!name' => $field_name, '!column' => $column)); - } - $aliases[] = array( - 'group' => $group_name, - 'title' => $alias_title, - 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $title)), - ); - $also_known[] = t('@group: @field', array('@group' => $group_name, '@field' => $title)); - } - } - if ($aliases) { - $data[$table_alias][$column_real_name]['aliases'] = $aliases; - $data[$table_alias][$column_real_name]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known))); - } - - $data[$table_alias][$column_real_name]['argument'] = array( - 'field' => $column_real_name, - 'table' => $table, - 'id' => $argument, - 'additional fields' => $additional_fields, - 'field_name' => $field_name, - 'entity_type' => $entity_type_id, - 'empty field name' => t('- No value -'), - ); - $data[$table_alias][$column_real_name]['filter'] = array( - 'field' => $column_real_name, - 'table' => $table, - 'id' => $filter, - 'additional fields' => $additional_fields, - 'field_name' => $field_name, - 'entity_type' => $entity_type_id, - 'allow empty' => TRUE, - ); - if (!empty($allow_sort)) { - $data[$table_alias][$column_real_name]['sort'] = array( - 'field' => $column_real_name, - 'table' => $table, - 'id' => $sort, - 'additional fields' => $additional_fields, - 'field_name' => $field_name, - 'entity_type' => $entity_type_id, - ); - } - - // Expose additional delta column for multiple value fields. - if ($field_storage->isMultiple()) { - $title_delta = t('@label (!name:delta)', array('@label' => $label, '!name' => $field_name)); - $title_short_delta = t('@label:delta', array('@label' => $label)); - - $data[$table_alias]['delta'] = array( - 'group' => $group, - 'title' => $title_delta, - 'title short' => $title_short_delta, - 'help' => t('Delta - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), - ); - $data[$table_alias]['delta']['field'] = array( - 'id' => 'numeric', - ); - $data[$table_alias]['delta']['argument'] = array( - 'field' => 'delta', - 'table' => $table, - 'id' => 'numeric', - 'additional fields' => $additional_fields, - 'empty field name' => t('- No value -'), - 'field_name' => $field_name, - 'entity_type' => $entity_type_id, - ); - $data[$table_alias]['delta']['filter'] = array( - 'field' => 'delta', - 'table' => $table, - 'id' => 'numeric', - 'additional fields' => $additional_fields, - 'field_name' => $field_name, - 'entity_type' => $entity_type_id, - 'allow empty' => TRUE, - ); - $data[$table_alias]['delta']['sort'] = array( - 'field' => 'delta', - 'table' => $table, - 'id' => 'standard', - 'additional fields' => $additional_fields, - 'field_name' => $field_name, - 'entity_type' => $entity_type_id, - ); - } - } - } - - return $data; -} - -/** - * Have a different filter handler for lists. This should allow to select values of the list. - */ -function list_field_views_data(FieldStorageConfigInterface $field_storage) { - $data = field_views_field_default_views_data($field_storage); - foreach ($data as $table_name => $table_data) { - foreach ($table_data as $field_name => $field_data) { - if (isset($field_data['filter']) && $field_name != 'delta') { - $data[$table_name][$field_name]['filter']['id'] = 'field_list'; - } - if (isset($field_data['argument']) && $field_name != 'delta') { - if ($field_storage->getType() == 'list_string') { - $data[$table_name][$field_name]['argument']['id'] = 'field_list_string'; - } - else { - $data[$table_name][$field_name]['argument']['id'] = 'field_list'; - } - } - } - } - return $data; -} diff --git a/core/modules/file/file.views.inc b/core/modules/file/file.views.inc index ee523b7fc186..a2edb20508df 100644 --- a/core/modules/file/file.views.inc +++ b/core/modules/file/file.views.inc @@ -13,10 +13,10 @@ * Views integration for file fields. Adds a file relationship to the default * field data. * - * @see field_views_field_default_views_data() + * @see views_field_default_views_data() */ function file_field_views_data(FieldStorageConfigInterface $field_storage) { - $data = field_views_field_default_views_data($field_storage); + $data = views_field_default_views_data($field_storage); foreach ($data as $table_name => $table_data) { // Add the relationship only on the fid field. $data[$table_name][$field_storage->getName() . '_target_id']['relationship'] = array( @@ -45,7 +45,7 @@ function file_field_views_data_views_data_alter(array &$data, FieldStorageConfig /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ $table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping(); - list($label) = field_views_field_label($entity_type_id, $field_name); + list($label) = views_entity_field_label($entity_type_id, $field_name); $data['file_managed'][$pseudo_field_name]['relationship'] = array( 'title' => t('@entity using @field', array('@entity' => $entity_type->getLabel(), '@field' => $label)), diff --git a/core/modules/image/image.views.inc b/core/modules/image/image.views.inc index 2424b1a896f1..36e5dfcc357e 100644 --- a/core/modules/image/image.views.inc +++ b/core/modules/image/image.views.inc @@ -13,10 +13,10 @@ * Views integration for image fields. Adds an image relationship to the default * field data. * - * @see field_views_field_default_views_data() + * @see views_field_default_views_data() */ function image_field_views_data(FieldStorageConfigInterface $field_storage) { - $data = field_views_field_default_views_data($field_storage); + $data = views_field_default_views_data($field_storage); foreach ($data as $table_name => $table_data) { // Add the relationship only on the target_id field. $data[$table_name][$field_storage->getName() . '_target_id']['relationship'] = array( @@ -44,7 +44,7 @@ function image_field_views_data_views_data_alter(array &$data, FieldStorageConfi /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ $table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping(); - list($label) = field_views_field_label($entity_type_id, $field_name); + list($label) = views_entity_field_label($entity_type_id, $field_name); $data['file_managed'][$pseudo_field_name]['relationship'] = array( 'title' => t('@entity using @field', array('@entity' => $entity_type->getLabel(), '@field' => $label)), diff --git a/core/modules/taxonomy/taxonomy.views.inc b/core/modules/taxonomy/taxonomy.views.inc index 4de1876c954d..fc7cc0b5d410 100644 --- a/core/modules/taxonomy/taxonomy.views.inc +++ b/core/modules/taxonomy/taxonomy.views.inc @@ -57,10 +57,10 @@ function taxonomy_views_data_alter(&$data) { * Views integration for taxonomy_term_reference fields. Adds a term relationship to the default * field data. * - * @see field_views_field_default_views_data() + * @see views_field_default_views_data() */ function taxonomy_field_views_data(FieldStorageConfigInterface $field_storage) { - $data = field_views_field_default_views_data($field_storage); + $data = views_field_default_views_data($field_storage); foreach ($data as $table_name => $table_data) { foreach ($table_data as $field_name => $field_data) { if (isset($field_data['filter']) && $field_name != 'delta') { @@ -98,7 +98,7 @@ function taxonomy_field_views_data_views_data_alter(array &$data, FieldStorageCo /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ $table_mapping = $entity_manager->getStorage($entity_type_id)->getTableMapping(); - list($label) = field_views_field_label($entity_type_id, $field_name); + list($label) = views_entity_field_label($entity_type_id, $field_name); $data['taxonomy_term_data'][$pseudo_field_name]['relationship'] = array( 'title' => t('@entity using @field', array('@entity' => $entity_type->getLabel(), '@field' => $label)), diff --git a/core/modules/field/src/Plugin/views/argument/FieldList.php b/core/modules/views/src/Plugin/views/argument/FieldList.php similarity index 95% rename from core/modules/field/src/Plugin/views/argument/FieldList.php rename to core/modules/views/src/Plugin/views/argument/FieldList.php index c93cccd95dbc..118b4cce02e0 100644 --- a/core/modules/field/src/Plugin/views/argument/FieldList.php +++ b/core/modules/views/src/Plugin/views/argument/FieldList.php @@ -2,10 +2,10 @@ /** * @file - * Definition of views_handler_argument_field_list. + * Contains \Drupal\views\Plugin\views\argument\FieldList. */ -namespace Drupal\field\Plugin\views\argument; +namespace Drupal\views\Plugin\views\argument; use Drupal\Component\Utility\String; use Drupal\Core\Field\AllowedTagsXssTrait; diff --git a/core/modules/field/src/Plugin/views/argument/ListString.php b/core/modules/views/src/Plugin/views/argument/ListString.php similarity index 95% rename from core/modules/field/src/Plugin/views/argument/ListString.php rename to core/modules/views/src/Plugin/views/argument/ListString.php index 1b8f9ef0af8b..f24ca79b42ad 100644 --- a/core/modules/field/src/Plugin/views/argument/ListString.php +++ b/core/modules/views/src/Plugin/views/argument/ListString.php @@ -2,10 +2,10 @@ /** * @file - * Definition of Drupal\field\Plugin\views\argument\ListString. + * Contains \Drupal\views\Plugin\views\argument\ListString. */ -namespace Drupal\field\Plugin\views\argument; +namespace Drupal\views\Plugin\views\argument; use Drupal\Component\Utility\String as UtilityString; use Drupal\Core\Field\AllowedTagsXssTrait; diff --git a/core/modules/field/src/Plugin/views/field/Field.php b/core/modules/views/src/Plugin/views/field/Field.php similarity index 99% rename from core/modules/field/src/Plugin/views/field/Field.php rename to core/modules/views/src/Plugin/views/field/Field.php index afb643d33b07..025ee0295075 100644 --- a/core/modules/field/src/Plugin/views/field/Field.php +++ b/core/modules/views/src/Plugin/views/field/Field.php @@ -2,10 +2,10 @@ /** * @file - * Definition of Drupal\field\Plugin\views\field\Field. + * Contains \Drupal\views\Plugin\views\field\Field. */ -namespace Drupal\field\Plugin\views\field; +namespace Drupal\views\Plugin\views\field; use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\Xss; diff --git a/core/modules/field/src/Plugin/views/filter/FieldList.php b/core/modules/views/src/Plugin/views/filter/FieldList.php similarity index 76% rename from core/modules/field/src/Plugin/views/filter/FieldList.php rename to core/modules/views/src/Plugin/views/filter/FieldList.php index f365fc4b6d0c..ff7ef179ccc4 100644 --- a/core/modules/field/src/Plugin/views/filter/FieldList.php +++ b/core/modules/views/src/Plugin/views/filter/FieldList.php @@ -2,12 +2,10 @@ /** * @file - * Definition of Drupal\field\Plugin\views\filter\FieldList. + * Contains \Drupal\views\Plugin\views\filter\FieldList. */ -namespace Drupal\field\Plugin\views\filter; - -use Drupal\views\Plugin\views\filter\ManyToOne; +namespace Drupal\views\Plugin\views\filter; /** * Filter handler which uses list-fields as options. diff --git a/core/modules/field/src/Plugin/views/relationship/EntityReverse.php b/core/modules/views/src/Plugin/views/relationship/EntityReverse.php similarity index 96% rename from core/modules/field/src/Plugin/views/relationship/EntityReverse.php rename to core/modules/views/src/Plugin/views/relationship/EntityReverse.php index fee9e790fbcc..49d552791d4c 100644 --- a/core/modules/field/src/Plugin/views/relationship/EntityReverse.php +++ b/core/modules/views/src/Plugin/views/relationship/EntityReverse.php @@ -2,10 +2,10 @@ /** * @file - * Definition of Drupal\field\Plugin\views\relationship\EntityReverse. + * Contains \Drupal\views\Plugin\views\relationship\EntityReverse. */ -namespace Drupal\field\Plugin\views\relationship; +namespace Drupal\views\Plugin\views\relationship; use Drupal\views\Plugin\ViewsHandlerManager; use Drupal\views\Plugin\views\display\DisplayPluginBase; diff --git a/core/modules/views/views.api.php b/core/modules/views/views.api.php index a0d27ca17ec7..120bed0725fd 100644 --- a/core/modules/views/views.api.php +++ b/core/modules/views/views.api.php @@ -431,7 +431,7 @@ function hook_views_data_alter(array &$data) { * in other modules. * * If no hook implementation exists, hook_views_data() falls back to - * field_views_field_default_views_data(). + * views_field_default_views_data(). * * @param \Drupal\field\FieldStorageConfigInterface $field_storage * The field storage config entity. @@ -440,12 +440,12 @@ function hook_views_data_alter(array &$data) { * An array of views data, in the same format as the return value of * hook_views_data(). * - * @see field_views_data() + * @see views_views_data() * @see hook_field_views_data_alter() * @see hook_field_views_data_views_data_alter() */ function hook_field_views_data(\Drupal\field\FieldStorageConfigInterface $field_storage) { - $data = field_views_field_default_views_data($field_storage); + $data = views_field_default_views_data($field_storage); foreach ($data as $table_name => $table_data) { // Add the relationship only on the target_id field. $data[$table_name][$field_storage->getName() . '_target_id']['relationship'] = array( @@ -464,7 +464,7 @@ function hook_field_views_data(\Drupal\field\FieldStorageConfigInterface $field_ * * This is called on all modules even if there is no hook_field_views_data() * implementation for the field, and therefore may be used to alter the - * default data that field_views_field_default_views_data() supplies for the + * default data that views_field_default_views_data() supplies for the * field storage. * * @param array $data @@ -473,7 +473,7 @@ function hook_field_views_data(\Drupal\field\FieldStorageConfigInterface $field_ * @param \Drupal\field\FieldStorageConfigInterface $field_storage * The field storage config entity. * - * @see field_views_data() + * @see views_views_data() * @see hook_field_views_data() * @see hook_field_views_data_views_data_alter() */ @@ -484,7 +484,7 @@ function hook_field_views_data_alter(array &$data, \Drupal\field\FieldStorageCon $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type_id; $table_mapping = \Drupal::entityManager()->getStorage($entity_type_id)->getTableMapping(); - list($label) = field_views_field_label($entity_type_id, $field_name); + list($label) = views_entity_field_label($entity_type_id, $field_name); $data['file_managed'][$pseudo_field_name]['relationship'] = array( 'title' => t('@entity using @field', array('@entity' => $entity_type->getLabel(), '@field' => $label)), @@ -531,7 +531,7 @@ function hook_field_views_data_alter(array &$data, \Drupal\field\FieldStorageCon * * @see hook_field_views_data() * @see hook_field_views_data_alter() - * @see field_views_data_alter() + * @see views_views_data_alter() */ function hook_field_views_data_views_data_alter(array &$data, \Drupal\field\FieldStorageConfigInterface $field) { $field_name = $field->getName(); @@ -539,7 +539,7 @@ function hook_field_views_data_views_data_alter(array &$data, \Drupal\field\Fiel $entity_type_id = $field->entity_type; $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id); $pseudo_field_name = 'reverse_' . $field_name . '_' . $entity_type_id; - list($label) = field_views_field_label($entity_type_id, $field_name); + list($label) = views_entity_field_label($entity_type_id, $field_name); $table_mapping = \Drupal::entityManager()->getStorage($entity_type_id)->getTableMapping(); // Views data for this field is in $data[$data_key]. diff --git a/core/modules/views/views.module b/core/modules/views/views.module index d291717d0192..128a7d1afb0a 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -593,6 +593,13 @@ function views_hook_info() { 'views_query_alter', ), array('group' => 'views_execution')); + $hooks['field_views_data'] = array( + 'group' => 'views', + ); + $hooks['field_views_data_alter'] = array( + 'group' => 'views', + ); + return $hooks; } diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc index 34167b0cb251..4c9971ab3c26 100644 --- a/core/modules/views/views.views.inc +++ b/core/modules/views/views.views.inc @@ -6,6 +6,10 @@ */ use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Entity\Sql\SqlContentEntityStorage; +use Drupal\field\FieldConfigInterface; +use Drupal\field\FieldStorageConfigInterface; use Drupal\system\ActionConfigEntityInterface; /** @@ -165,5 +169,486 @@ function views_views_data() { } } + + // Field modules can implement hook_field_views_data() to override the default + // behavior for adding fields. + $module_handler = \Drupal::moduleHandler(); + + $entity_manager = \Drupal::entityManager(); + if ($entity_manager->hasDefinition('field_storage_config')) { + foreach ($entity_manager->getStorage('field_storage_config')->loadMultiple() as $field_storage) { + if (_views_field_get_entity_type_storage($field_storage)) { + $result = (array) $module_handler->invoke($field_storage->module, 'field_views_data', array($field_storage)); + if (empty($result)) { + $result = views_field_default_views_data($field_storage); + } + $module_handler->alter('field_views_data', $result, $field_storage); + + if (is_array($result)) { + $data = NestedArray::mergeDeep($result, $data); + } + } + } + } + + return $data; +} + +/** + * Implements hook_views_data_alter(). + * + * Field modules can implement hook_field_views_data_views_data_alter() to + * alter the views data on a per field basis. This is weirdly named so as + * not to conflict with the \Drupal::moduleHandler()->alter('field_views_data') + * in views_views_data(). + */ +function views_views_data_alter(&$data) { + $entity_manager = \Drupal::entityManager(); + if (!$entity_manager->hasDefinition('field_storage_config')) { + return; + } + foreach ($entity_manager->getStorage('field_storage_config')->loadMultiple() as $field_storage) { + if (_views_field_get_entity_type_storage($field_storage)) { + $function = $field_storage->module . '_field_views_data_views_data_alter'; + if (function_exists($function)) { + $function($data, $field_storage); + } + } + } +} + +/** + * Determines whether the entity type the field appears in is SQL based. + * + * @param \Drupal\field\FieldStorageConfigInterface $field_storage + * The field storage definition. + * + * @return \Drupal\Core\Entity\Sql\SqlContentEntityStorage + * Returns the entity type storage if supported. + */ +function _views_field_get_entity_type_storage(FieldStorageConfigInterface $field_storage) { + $result = FALSE; + $entity_manager = \Drupal::entityManager(); + if ($entity_manager->hasDefinition($field_storage->getTargetEntityTypeId())) { + $storage = $entity_manager->getStorage($field_storage->getTargetEntityTypeId()); + $result = $storage instanceof SqlContentEntityStorage ? $storage : FALSE; + } + return $result; +} + +/** + * Returns the label of a certain field. + * + * Therefore it looks up in all bundles to find the most used field. + */ +function views_entity_field_label($entity_type, $field_name) { + $label_counter = array(); + $all_labels = array(); + // Count the amount of fields per label per field storage. + foreach (array_keys(\Drupal::entityManager()->getBundleInfo($entity_type)) as $bundle) { + $bundle_fields = array_filter(\Drupal::entityManager()->getFieldDefinitions($entity_type, $bundle), function ($field_definition) { + return $field_definition instanceof FieldConfigInterface; + }); + if (isset($bundle_fields[$field_name])) { + $field = $bundle_fields[$field_name]; + $label = $field->getLabel(); + $label_counter[$label] = isset($label_counter[$label]) ? ++$label_counter[$label] : 1; + $all_labels[$label] = TRUE; + } + } + if (empty($label_counter)) { + return array($field_name, $all_labels); + } + // Sort the field labels by it most used label and return the most used one. + arsort($label_counter); + $label_counter = array_keys($label_counter); + return array($label_counter[0], $all_labels); +} + +/** + * Default views data implementation for a field. + * + * @param \Drupal\field\FieldStorageConfigInterface $field_storage + * The field definition. + * + * @return array + * The default views data for the field. + */ +function views_field_default_views_data(FieldStorageConfigInterface $field_storage) { + $data = array(); + + // Check the field type is available. + if (!\Drupal::service('plugin.manager.field.field_type')->hasDefinition($field_storage->getType())) { + return $data; + } + // Check the field storage has fields. + if (!$field_storage->getBundles()) { + return $data; + } + // Check whether the entity type storage is supported. + $storage = _views_field_get_entity_type_storage($field_storage); + if (!$storage) { + return $data; + } + + $field_name = $field_storage->getName(); + $field_columns = $field_storage->getColumns(); + + // Grab information about the entity type tables. + // We need to join to both the base table and the data table, if available. + $entity_manager = \Drupal::entityManager(); + $entity_type_id = $field_storage->getTargetEntityTypeId(); + $entity_type = $entity_manager->getDefinition($entity_type_id); + if (!$base_table = $entity_type->getBaseTable()) { + // We cannot do anything if for some reason there is no base table. + return $data; + } + $entity_tables = array($base_table => $entity_type_id); + // Some entities may not have a data table. + $data_table = $entity_type->getDataTable(); + if ($data_table) { + $entity_tables[$data_table] = $entity_type_id; + } + $entity_revision_table = $entity_type->getRevisionTable(); + $supports_revisions = $entity_type->hasKey('revision') && $entity_revision_table; + if ($supports_revisions) { + $entity_tables[$entity_revision_table] = $entity_type_id; + $entity_revision_data_table = $entity_type->getRevisionDataTable(); + if ($entity_revision_data_table) { + $entity_tables[$entity_revision_data_table] = $entity_type_id; + } + } + + // Description of the field tables. + // @todo Generalize this code to make it work with any table layout. See + // https://drupal.org/node/2079019. + $table_mapping = $storage->getTableMapping(); + $field_tables = array( + EntityStorageInterface::FIELD_LOAD_CURRENT => array( + 'table' => $table_mapping->getDedicatedDataTableName($field_storage), + 'alias' => "{$entity_type_id}__{$field_name}", + ), + ); + if ($supports_revisions) { + $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION] = array( + 'table' => $table_mapping->getDedicatedRevisionTableName($field_storage), + 'alias' => "{$entity_type_id}_revision__{$field_name}", + ); + } + + // Build the relationships between the field table and the entity tables. + $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_CURRENT]['alias']; + if ($data_table) { + // Tell Views how to join to the base table, via the data table. + $data[$table_alias]['table']['join'][$base_table] = array( + 'left_table' => $data_table, + 'left_field' => $entity_type->getKey('id'), + 'field' => 'entity_id', + 'extra' => array( + array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), + array('left_field' => 'langcode', 'field' => 'langcode'), + ), + ); + } + else { + // If there is no data table, just join directly. + $data[$table_alias]['table']['join'][$base_table] = array( + 'left_field' => $entity_type->getKey('id'), + 'field' => 'entity_id', + 'extra' => array( + array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), + ), + ); + } + + if ($supports_revisions) { + $table_alias = $field_tables[EntityStorageInterface::FIELD_LOAD_REVISION]['alias']; + if ($entity_revision_data_table) { + // Tell Views how to join to the revision table, via the data table. + $data[$table_alias]['table']['join'][$entity_revision_table] = array( + 'left_table' => $entity_revision_data_table, + 'left_field' => $entity_type->getKey('revision'), + 'field' => 'revision_id', + 'extra' => array( + array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), + array('left_field' => 'langcode', 'field' => 'langcode'), + ), + ); + } + else { + // If there is no data table, just join directly. + $data[$table_alias]['table']['join'][$entity_revision_table] = array( + 'left_field' => $entity_type->getKey('revision'), + 'field' => 'revision_id', + 'extra' => array( + array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), + ), + ); + } + } + + $group_name = $entity_type->getLabel(); + // Get the list of bundles the field appears in. + $bundles_names = $field_storage->getBundles(); + // Build the list of additional fields to add to queries. + $add_fields = array('delta', 'langcode', 'bundle'); + foreach (array_keys($field_columns) as $column) { + $add_fields[] = $table_mapping->getFieldColumnName($field_storage, $column); + } + // Determine the label to use for the field. We don't have a label available + // at the field level, so we just go through all fields and take the one + // which is used the most frequently. + list($label, $all_labels) = views_entity_field_label($entity_type_id, $field_name); + + // Expose data for the field as a whole. + foreach ($field_tables as $type => $table_info) { + $table = $table_info['table']; + $table_alias = $table_info['alias']; + + if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) { + $group = $group_name; + $field_alias = $field_name; + } + else { + $group = t('@group (historical data)', array('@group' => $group_name)); + $field_alias = $field_name . '-revision_id'; + } + + $data[$table_alias][$field_alias] = array( + 'group' => $group, + 'title' => $label, + 'title short' => $label, + 'help' => t('Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), + ); + + // Go through and create a list of aliases for all possible combinations of + // entity type + name. + $aliases = array(); + $also_known = array(); + foreach ($all_labels as $label_name => $true) { + if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) { + if ($label != $label_name) { + $aliases[] = array( + 'base' => $base_table, + 'group' => $group_name, + 'title' => $label_name, + 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)), + ); + $also_known[] = t('@group: @field', array('@group' => $group_name, '@field' => $label_name)); + } + } + elseif ($supports_revisions && $label != $label_name) { + $aliases[] = array( + 'base' => $table, + 'group' => t('@group (historical data)', array('@group' => $group_name)), + 'title' => $label_name, + 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $label)), + ); + $also_known[] = t('@group (historical data): @field', array('@group' => $group_name, '@field' => $label_name)); + } + } + if ($aliases) { + $data[$table_alias][$field_alias]['aliases'] = $aliases; + $data[$table_alias][$field_alias]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known))); + } + + $keys = array_keys($field_columns); + $real_field = reset($keys); + $data[$table_alias][$field_alias]['field'] = array( + 'table' => $table, + 'id' => 'field', + 'field_name' => $field_name, + 'entity_type' => $entity_type_id, + // Provide a real field for group by. + 'real field' => $field_alias . '_' . $real_field, + 'additional fields' => $add_fields, + 'entity_tables' => $entity_tables, + // Default the element type to div, let the UI change it if necessary. + 'element type' => 'div', + 'is revision' => $type == EntityStorageInterface::FIELD_LOAD_REVISION, + ); + } + + // Expose data for each field property individually. + foreach ($field_columns as $column => $attributes) { + $allow_sort = TRUE; + + // Identify likely filters and arguments for each column based on field type. + switch ($attributes['type']) { + case 'int': + case 'mediumint': + case 'tinyint': + case 'bigint': + case 'serial': + case 'numeric': + case 'float': + $filter = 'numeric'; + $argument = 'numeric'; + $sort = 'standard'; + break; + case 'text': + case 'blob': + // It does not make sense to sort by blob or text. + $allow_sort = FALSE; + default: + $filter = 'string'; + $argument = 'string'; + $sort = 'standard'; + break; + } + + if (count($field_columns) == 1 || $column == 'value') { + $title = t('@label (!name)', array('@label' => $label, '!name' => $field_name)); + $title_short = $label; + } + else { + $title = t('@label (!name:!column)', array('@label' => $label, '!name' => $field_name, '!column' => $column)); + $title_short = t('@label:!column', array('@label' => $label, '!column' => $column)); + } + + // Expose data for the property. + foreach ($field_tables as $type => $table_info) { + $table = $table_info['table']; + $table_alias = $table_info['alias']; + + if ($type == EntityStorageInterface::FIELD_LOAD_CURRENT) { + $group = $group_name; + } + else { + $group = t('@group (historical data)', array('@group' => $group_name)); + } + $column_real_name = $table_mapping->getFieldColumnName($field_storage, $column); + + // Load all the fields from the table by default. + $additional_fields = $table_mapping->getAllColumns($table); + + $data[$table_alias][$column_real_name] = array( + 'group' => $group, + 'title' => $title, + 'title short' => $title_short, + 'help' => t('Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), + ); + + // Go through and create a list of aliases for all possible combinations of + // entity type + name. + $aliases = array(); + $also_known = array(); + foreach ($all_labels as $label_name => $true) { + if ($label != $label_name) { + if (count($field_columns) == 1 || $column == 'value') { + $alias_title = t('@label (!name)', array('@label' => $label_name, '!name' => $field_name)); + } + else { + $alias_title = t('@label (!name:!column)', array('@label' => $label_name, '!name' => $field_name, '!column' => $column)); + } + $aliases[] = array( + 'group' => $group_name, + 'title' => $alias_title, + 'help' => t('This is an alias of @group: @field.', array('@group' => $group_name, '@field' => $title)), + ); + $also_known[] = t('@group: @field', array('@group' => $group_name, '@field' => $title)); + } + } + if ($aliases) { + $data[$table_alias][$column_real_name]['aliases'] = $aliases; + $data[$table_alias][$column_real_name]['help'] .= ' ' . t('Also known as: !also.', array('!also' => implode(', ', $also_known))); + } + + $data[$table_alias][$column_real_name]['argument'] = array( + 'field' => $column_real_name, + 'table' => $table, + 'id' => $argument, + 'additional fields' => $additional_fields, + 'field_name' => $field_name, + 'entity_type' => $entity_type_id, + 'empty field name' => t('- No value -'), + ); + $data[$table_alias][$column_real_name]['filter'] = array( + 'field' => $column_real_name, + 'table' => $table, + 'id' => $filter, + 'additional fields' => $additional_fields, + 'field_name' => $field_name, + 'entity_type' => $entity_type_id, + 'allow empty' => TRUE, + ); + if (!empty($allow_sort)) { + $data[$table_alias][$column_real_name]['sort'] = array( + 'field' => $column_real_name, + 'table' => $table, + 'id' => $sort, + 'additional fields' => $additional_fields, + 'field_name' => $field_name, + 'entity_type' => $entity_type_id, + ); + } + + // Expose additional delta column for multiple value fields. + if ($field_storage->isMultiple()) { + $title_delta = t('@label (!name:delta)', array('@label' => $label, '!name' => $field_name)); + $title_short_delta = t('@label:delta', array('@label' => $label)); + + $data[$table_alias]['delta'] = array( + 'group' => $group, + 'title' => $title_delta, + 'title short' => $title_short_delta, + 'help' => t('Delta - Appears in: @bundles.', array('@bundles' => implode(', ', $bundles_names))), + ); + $data[$table_alias]['delta']['field'] = array( + 'id' => 'numeric', + ); + $data[$table_alias]['delta']['argument'] = array( + 'field' => 'delta', + 'table' => $table, + 'id' => 'numeric', + 'additional fields' => $additional_fields, + 'empty field name' => t('- No value -'), + 'field_name' => $field_name, + 'entity_type' => $entity_type_id, + ); + $data[$table_alias]['delta']['filter'] = array( + 'field' => 'delta', + 'table' => $table, + 'id' => 'numeric', + 'additional fields' => $additional_fields, + 'field_name' => $field_name, + 'entity_type' => $entity_type_id, + 'allow empty' => TRUE, + ); + $data[$table_alias]['delta']['sort'] = array( + 'field' => 'delta', + 'table' => $table, + 'id' => 'standard', + 'additional fields' => $additional_fields, + 'field_name' => $field_name, + 'entity_type' => $entity_type_id, + ); + } + } + } + + return $data; +} + +/** + * Have a different filter handler for lists. This should allow to select values of the list. + */ +function list_field_views_data(FieldStorageConfigInterface $field_storage) { + $data = views_field_default_views_data($field_storage); + foreach ($data as $table_name => $table_data) { + foreach ($table_data as $field_name => $field_data) { + if (isset($field_data['filter']) && $field_name != 'delta') { + $data[$table_name][$field_name]['filter']['id'] = 'field_list'; + } + if (isset($field_data['argument']) && $field_name != 'delta') { + if ($field_storage->getType() == 'list_string') { + $data[$table_name][$field_name]['argument']['id'] = 'field_list_string'; + } + else { + $data[$table_name][$field_name]['argument']['id'] = 'field_list'; + } + } + } + } return $data; } -- GitLab