diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php index 110fcee..ff45a17 100644 --- a/core/lib/Drupal/Core/Entity/EntityType.php +++ b/core/lib/Drupal/Core/Entity/EntityType.php @@ -306,6 +306,14 @@ public function hasKey($key) { /** * {@inheritdoc} */ + public function setKey($key, $value) { + $this->entity_keys[$key] = $value; + return $this; + } + + /** + * {@inheritdoc} + */ public function id() { return $this->id; } diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php index e655df7..b73de24 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php @@ -141,6 +141,18 @@ public function getKey($key); public function hasKey($key); /** + * Sets a specific entity key. + * + * @param string $key + * The name of the entity key to return. + * @param string $value + * The new value of the key. + * + * @return $this + */ + public function setKey($key, $value); + + /** * Indicates whether entities should be statically cached. * * @return bool diff --git a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php index 4f30d47..ba8ab66 100644 --- a/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php +++ b/core/lib/Drupal/Core/Entity/Schema/ContentEntitySchemaHandler.php @@ -26,7 +26,7 @@ class ContentEntitySchemaHandler implements EntitySchemaHandlerInterface { /** * The storage field definitions for this entity type. * - * @var \Drupal\Core\Field\FieldDefinitionInterface[] + * @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] */ protected $fieldStorageDefinitions; diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php index 48745d8..73b88d2 100644 --- a/core/modules/file/src/Entity/File.php +++ b/core/modules/file/src/Entity/File.php @@ -25,7 +25,7 @@ * "storage" = "Drupal\file\FileStorage", * "access" = "Drupal\file\FileAccessControlHandler", * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", - * "views_data" = "Drupal\file\FileViewsData", + * "views_data" = "Drupal\file\FileViewsData" * }, * base_table = "file_managed", * entity_keys = { diff --git a/core/modules/file/src/FileViewsData.php b/core/modules/file/src/FileViewsData.php index 1c6e524..5c5fee7 100644 --- a/core/modules/file/src/FileViewsData.php +++ b/core/modules/file/src/FileViewsData.php @@ -6,32 +6,29 @@ */ namespace Drupal\file; - -use Drupal\views\EntityViewsDataInterface; +use Drupal\Component\Utility\NestedArray; +use Drupal\views\EntityViewsData; /** * Provides views data for the file entity type. */ -class FileViewsData implements EntityViewsDataInterface { +class FileViewsData extends EntityViewsData { /** * {@inheritdoc} */ public function getViewsData() { - $data = array(); + $parent_views_data = parent::getViewsData(); // Sets 'group' index for file_managed table. - $data['file_managed']['table']['group'] = t('File'); // Advertise this table as a possible base table. $data['file_managed']['table']['base'] = array( - 'field' => 'fid', - 'title' => t('File'), + // @TODO There is no corresponding information in entity metadata. 'help' => t("Files maintained by Drupal and various modules."), 'defaults' => array( 'field' => 'filename' ), ); - $data['file_managed']['table']['entity type'] = 'file'; $data['file_managed']['table']['wizard_id'] = 'file_managed'; // Describes fid field in file_managed table. @@ -47,12 +44,6 @@ public function getViewsData() { 'name field' => 'filename', 'numeric' => TRUE, ), - 'filter' => array( - 'id' => 'numeric', - ), - 'sort' => array( - 'id' => 'standard', - ), 'relationship' => array( 'title' => t('File usage'), 'help' => t('Relate file entities to their usage.'), @@ -66,56 +57,23 @@ public function getViewsData() { // Describes filename field in file_managed table. $data['file_managed']['filename'] = array( - 'title' => t('Name'), - 'help' => t('The name of the file.'), 'field' => array( 'id' => 'file', ), - 'sort' => array( - 'id' => 'standard', - ), - 'filter' => array( - 'id' => 'string', - ), - 'argument' => array( - 'id' => 'string', - ), ); // Describes uri field in file_managed table. $data['file_managed']['uri'] = array( - 'title' => t('Path'), - 'help' => t('The path of the file.'), 'field' => array( 'id' => 'file_uri', ), - 'sort' => array( - 'id' => 'standard', - ), - 'filter' => array( - 'id' => 'string', - ), - 'argument' => array( - 'id' => 'string', - ), ); // Describes filemime field in file_managed table. $data['file_managed']['filemime'] = array( - 'title' => t('Mime type'), - 'help' => t('The mime type of the file.'), 'field' => array( 'id' => 'file_filemime', ), - 'sort' => array( - 'id' => 'standard', - ), - 'filter' => array( - 'id' => 'string', - ), - 'argument' => array( - 'id' => 'string', - ), ); // Describes extension field in file_managed table. @@ -131,73 +89,26 @@ public function getViewsData() { // Describes filesize field in file_managed table. $data['file_managed']['filesize'] = array( - 'title' => t('Size'), - 'help' => t('The size of the file.'), 'field' => array( 'id' => 'file_size', ), - 'sort' => array( - 'id' => 'standard', - ), - 'filter' => array( - 'id' => 'numeric', - ), ); // Describes status field in file_managed table. $data['file_managed']['status'] = array( - 'title' => t('Status'), - 'help' => t('The status of the file.'), 'field' => array( 'id' => 'file_status', ), - 'sort' => array( - 'id' => 'standard', - ), 'filter' => array( 'id' => 'file_status', ), ); - // Describes created field in file_managed table. - $data['file_managed']['created'] = array( - 'title' => t('Upload date'), - 'help' => t('The date the file was uploaded.'), - 'field' => array( - 'id' => 'date', - ), - 'sort' => array( - 'id' => 'date', - ), - 'filter' => array( - 'id' => 'date', - ), - ); - - // Describes changed field in file_managed table. - $data['file_managed']['changed'] = array( - 'title' => t('Modified date'), - 'help' => t('The date the file was last changed.'), - 'field' => array( - 'id' => 'date', - ), - 'sort' => array( - 'id' => 'date', - ), - 'filter' => array( - 'id' => 'date', - ), - ); - // Describes uid field in file_managed table. $data['file_managed']['uid'] = array( - 'title' => t('User who uploaded'), - 'help' => t('The user that uploaded the file.'), 'relationship' => array( 'title' => t('User who uploaded'), 'label' => t('User who uploaded'), - 'base' => 'users', - 'base field' => 'uid', ), ); @@ -495,8 +406,7 @@ public function getViewsData() { ), ); - return $data; + return NestedArray::mergeDeep($parent_views_data, $data); } } - diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php index 2470c8c..e791497 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php @@ -29,7 +29,8 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler" + * "translation" = "Drupal\content_translation\ContentTranslationHandler", + * "views_data" = "Drupal\views\EntityViewsData" * }, * base_table = "entity_test", * fieldable = TRUE, diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php index a61f41b..bbdedaa 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMul.php @@ -24,7 +24,8 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler" + * "translation" = "Drupal\content_translation\ContentTranslationHandler", + * "views_data" = "Drupal\views\EntityViewsData" * }, * base_table = "entity_test_mul", * data_table = "entity_test_mul_property_data", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php index f4ee307..10d3730 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php @@ -23,7 +23,8 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler" + * "translation" = "Drupal\content_translation\ContentTranslationHandler", + * "views_data" = "Drupal\views\EntityViewsData" * }, * base_table = "entity_test_mulrev", * data_table = "entity_test_mulrev_property_data", diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php index 5f74c59..2ebc858 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php @@ -23,7 +23,8 @@ * "default" = "Drupal\entity_test\EntityTestForm", * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, - * "translation" = "Drupal\content_translation\ContentTranslationHandler" + * "translation" = "Drupal\content_translation\ContentTranslationHandler", + * "views_data" = "Drupal\views\EntityViewsData" * }, * base_table = "entity_test_rev", * revision_table = "entity_test_rev_revision", diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php new file mode 100644 index 0000000..3533964 --- /dev/null +++ b/core/modules/views/src/EntityViewsData.php @@ -0,0 +1,318 @@ +entityType = $entity_type; + $this->entityManager = $entity_manager; + $this->storageController = $storage_controller; + $this->moduleHandler = $module_handler; + $this->setStringTranslation($translation_manager); + $this->typedDataManager = $typed_data_manager; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + return new static( + $entity_type, + $container->get('entity.manager')->getController($entity_type->id(), 'storage'), + $container->get('entity.manager'), + $container->get('module_handler'), + $container->get('string_translation'), + $container->get('typed_data_manager') + ); + } + + /** + * Gets the field storage definitions. + * + * @return \Drupal\Core\Field\FieldStorageDefinitionInterface[] + */ + protected function getFieldStorageDefinitions() { + if (!isset($this->fieldStorageDefinitions)) { + $this->fieldStorageDefinitions = $this->entityManager->getFieldStorageDefinitions($this->entityType->id()); + } + return $this->fieldStorageDefinitions; + } + + /** + * {@inheritdoc} + */ + public function getViewsData() { + $data = array(); + + // @todo In theory we should use the data table as base table, as this would + // save one pointless join (and one more for every relationship). + $base_table = $this->entityType->getBaseTable(); + $base_field = $this->entityType->getKey('id'); + $data_table = $this->entityType->getDataTable(); + $revision_table = $this->entityType->getRevisionTable(); + $revision_data_table = $this->entityType->getRevisionDataTable(); + $revision_field = $this->entityType->getKey('revision'); + + // Setup base information of the views data. + $data[$base_table]['table']['entity type'] = $this->entityType->id(); + $data[$base_table]['table']['group'] = $this->entityType->getLabel(); + $data[$base_table]['table']['base'] = array( + 'field' => $base_field, + 'title' => $this->entityType->getLabel(), + ); + + // Setup relations to the revisions/property data. + if ($data_table) { + $data[$data_table]['table']['join'][$base_table] = array( + 'left_field' => $base_field, + 'field' => $base_field + ); + + } + if ($revision_table) { + $data[$revision_table]['table']['entity_type_id'] = $this->entityType->id(); + $data[$revision_table]['table']['group'] = $this->entityType->getLabel(); + $data[$revision_table]['table']['base'] = array( + 'field' => $revision_field, + 'title' => $this->t('@entity_type revisions', array('@entity_type' => $this->entityType->getLabel())), + ); + // Join the revision table to the base table. + $data[$revision_table]['table']['join'][$base_table] = array( + 'left_field' => $base_field, + 'field' => $base_field, + ); + + if ($revision_data_table) { + $data[$revision_data_table]['table']['join'][$revision_table] = array( + 'left_field' => $revision_field, + 'field' => $revision_field, + ); + } + } + + // Load all typed data definitions of all fields. This should cover each of + // the entity base, revision, data tables. + $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id()); + if ($table_mapping = $this->storageController->getTableMapping()) { + // Iterate over each table we have so far and collect field data for each. + // Based on whether the field is in the field_definitions provided by the + // entity manager. + // @todo We should better just rely on information coming from the entity + // storage. + foreach ($table_mapping->getTableNames() as $table) { + foreach ($table_mapping->getFieldNames($table) as $field_name) { + $this->mapFieldDefinition($field_name, $field_definitions[$field_name], $table_mapping, $data[$table]); + } + } + } + + return $data; + } + + protected function mapFieldDefinition($field_name, FieldDefinitionInterface $field_definition, TableMappingInterface $table_mapping, &$views_table_data) { + // Create a dummy instance to retrieve property definitions. + $field_column_mapping = $table_mapping->getColumnNames($field_name); + $field_schema = $this->getFieldStorageDefinitions()[$field_name]->getSchema(); + + $field_definition_type = $field_definition->getType(); + // Add all properties to views table data. + $first = TRUE; + foreach ($field_column_mapping as $field_column_name => $schema_field_name) { + $schema = $field_schema['columns'][$field_column_name]; + if ($first) { + $first = FALSE; + $views_table_data[$field_name] = $this->mapSingleFieldViewsData($field_definition_type, $schema_field_name, $field_definition); + } + else { + $views_table_data["$field_name.$field_column_name"] = $this->mapSingleFieldViewsData($schema['type'], $schema_field_name, $field_definition); + } + } + } + + /** + * Provides a mapping from typed data plugin types to view plugin types. + * + * @return array + * The modified views data field definition. + */ + protected function mapSingleFieldViewsData($data_type, $schema_field_name, FieldDefinitionInterface $field_definition) { + $views_field = array(); + + $views_field['title'] = $field_definition->getLabel() . " ($schema_field_name)"; + + if ($description = $field_definition->getDescription()) { + $views_field['help'] = $description; + } + + // @todo Allow field types to customize this. + switch ($data_type) { + case 'int': + case 'integer': + case 'smallint': + case 'tinyint': + case 'mediumint': + case 'float': + case 'double': + case 'decimal': + $views_field['field']['id'] = 'numeric'; + $views_field['argument']['id'] = 'numeric'; + $views_field['filter']['id'] = 'numeric'; + $views_field['sort']['id'] = 'standard'; + break; + case 'char': + case 'string': + case 'varchar': + case 'tinytext': + case 'text': + case 'mediumtext': + case 'longtext': + $views_field['field']['id'] = 'standard'; + $views_field['argument']['id'] = 'string'; + $views_field['filter']['id'] = 'string'; + $views_field['sort']['id'] = 'standard'; + break; + case 'boolean': + $views_field['field']['id'] = 'boolean'; + $views_field['argument']['id'] = 'numeric'; + $views_field['filter']['id'] = 'boolean'; + $views_field['sort']['id'] = 'standard'; + break; + case 'uuid': + $views_field['field']['id'] = 'standard'; + $views_field['argument']['id'] = 'string'; + $views_field['filter']['id'] = 'string'; + $views_field['sort']['id'] = 'standard'; + break; + case 'language': + $views_field['field']['id'] = 'language'; + $views_field['argument']['id'] = 'language'; + $views_field['filter']['id'] = 'language'; + $views_field['sort']['id'] = 'standard'; + break; + case 'created': + case 'changed': + $views_field['field']['id'] = 'date'; + $views_field['argument']['id'] = 'date'; + $views_field['filter']['id'] = 'date'; + $views_field['sort']['id'] = 'date'; + break; + case 'entity_reference': + // @todo No idea to determine how to find out whether the field is a number/string ID. + // @todo Should the actual field handler respect that this is just renders a number + // @todo Create an optional entity field handler, that can render the + // entity. + $views_field['field']['id'] = 'numeric'; + $views_field['argument']['id'] = 'numeric'; + $views_field['filter']['id'] = 'numeric'; + $views_field['sort']['id'] = 'standard'; + if ($entity_type_id = $field_definition->getItemDefinition()->getSetting('target_type')) { + $entity_type = $this->entityManager->getDefinition($entity_type_id); + $views_field['relationship'] = array( + 'base' => $this->getViewsTableForEntityType($entity_type), + 'base field' => $entity_type->getKey('id'), + 'label' => $entity_type->getLabel(), + 'title' => $entity_type->getLabel(), + 'id' => 'standard', + ); + } + break; + case 'uri': + $views_field['field']['id'] = 'standard'; + $views_field['argument']['id'] = 'string'; + $views_field['filter']['id'] = 'string'; + $views_field['sort']['id'] = 'standard'; + break; + default: + $views_field['field']['id'] = 'standard'; + $views_field['argument']['id'] = 'standard'; + $views_field['filter']['id'] = 'standard'; + $views_field['sort']['id'] = 'standard'; + } + + return $views_field; + } + + public function getViewsTableForEntityType(EntityTypeInterface $entity_type) { + return $entity_type->getBaseTable(); + } + +} diff --git a/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php b/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php new file mode 100644 index 0000000..5f0a8c7 --- /dev/null +++ b/core/modules/views/tests/Drupal/views/Tests/EntityViewsDataTest.php @@ -0,0 +1,567 @@ +entityStorage = $this->getMockBuilder('Drupal\Core\Entity\ContentEntityDatabaseStorage') + ->disableOriginalConstructor() + ->getMock(); + $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); + + $this->baseEntityType = new EntityType([ + 'base_table' => 'entity_test', + 'id' => 'entity_test', + 'label' => 'Entity test', + 'entity_keys' => ['id' => 'id'], + ]); + + $this->translationManager = $this->getStringTranslationStub(); + $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $this->typedDataManager = $this->getMockBuilder('Drupal\Core\TypedData\TypedDataManager') + ->disableOriginalConstructor() + ->getMock(); + + $this->viewsData = new TestEntityViewsData($this->baseEntityType, $this->entityStorage, $this->entityManager, $this->moduleHandler, $this->translationManager, $this->typedDataManager); + + $field_type_manager = $this->getMockBuilder('Drupal\Core\Field\FieldTypePluginManager') + ->disableOriginalConstructor() + ->getMock(); + $field_type_manager->expects($this->any()) + ->method('getDefaultSettings') + ->willReturn([]); + $field_type_manager->expects($this->any()) + ->method('getDefaultInstanceSettings') + ->willReturn([]); + + $container = new ContainerBuilder(); + $container->set('plugin.manager.field.field_type', $field_type_manager); + $container->set('entity.manager', $this->entityManager); + \Drupal::setContainer($container); + } + + /** + * Tests base tables. + */ + public function testBaseTables() { + $data = $this->viewsData->getViewsData(); + + $this->assertEquals('entity_test', $data['entity_test']['table']['entity type']); + $this->assertEquals('Entity test', $data['entity_test']['table']['group']); + + $this->assertEquals('id', $data['entity_test']['table']['base']['field']); + $this->assertEquals('Entity test', $data['entity_test']['table']['base']['title']); + + $this->assertFalse(isset($data['entity_test_mul_property_data'])); + $this->assertFalse(isset($data['revision_table'])); + $this->assertFalse(isset($data['revision_data_table'])); + } + + + /** + * Tests data_table support. + */ + public function testDataTable() { + $entity_type = $this->baseEntityType->set('data_table', 'entity_test_mul_property_data') + ->set('id', 'entity_test_mul'); + + $this->viewsData->setEntityType($entity_type); + + // Tests the join definition between the base and the data table. + $data = $this->viewsData->getViewsData(); + $field_views_data = $data['entity_test_mul_property_data']; + + // Ensure the join information is set up properly. + $this->assertCount(1, $field_views_data['table']['join']); + $this->assertEquals(['entity_test' => ['left_field' => 'id', 'field' => 'id']], $field_views_data['table']['join']); + $this->assertFalse(isset($data['revision_table'])); + $this->assertFalse(isset($data['revision_data_table'])); + } + + /** + * Tests revision table support. + */ + public function testRevisionTable() { + $entity_type = $this->baseEntityType + ->set('revision_table', 'entity_test_mulrev_revision') + ->set('revision_data_table', 'entity_test_mulrev_property_revision') + ->set('id', 'entity_test_mulrev') + ->setKey('revision', 'revision_id') + ; + $this->viewsData->setEntityType($entity_type); + + $data = $this->viewsData->getViewsData(); + + // Ensure the join information is set up properly. + // Tests the join definition between the base and the revision table. + $revision_data = $data['entity_test_mulrev_revision']; + $this->assertCount(1, $revision_data['table']['join']); + $this->assertEquals(['entity_test' => ['left_field' => 'id', 'field' => 'id']], $revision_data['table']['join']); + $revision_data = $data['entity_test_mulrev_property_revision']; + $this->assertCount(1, $revision_data['table']['join']); + $this->assertEquals(['entity_test_mulrev_revision' => ['left_field' => 'revision_id', 'field' => 'revision_id']], $revision_data['table']['join']); + $this->assertFalse(isset($data['data_table'])); + } + + /** + * Helper method to mock all store definitions. + */ + protected function setupFieldStorageDefinition() { + $id_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $id_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(IntegerItem::schema($id_field_storage_definition)); + $uuid_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $uuid_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(UuidItem::schema($uuid_field_storage_definition)); + $type_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $type_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(StringItem::schema($type_field_storage_definition)); + $langcode_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $langcode_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(LanguageItem::schema($langcode_field_storage_definition)); + $name_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $name_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(StringItem::schema($name_field_storage_definition)); + + // Setup the user_id entity reference field. + $this->entityManager->expects($this->any()) + ->method('getDefinition') + ->willReturnMap([ + ['user', TRUE, static::userEntityInfo()], + ] + ); + $user_id_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $user_id_field_storage_definition->expects($this->any()) + ->method('getSetting') + ->with('target_type') + ->willReturn('user'); + $user_id_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(EntityReferenceItem::schema($user_id_field_storage_definition)); + + $revision_id_field_storage_definition = $this->getMock('Drupal\Core\Field\FieldStorageDefinitionInterface'); + $revision_id_field_storage_definition->expects($this->any()) + ->method('getSchema') + ->willReturn(IntegerItem::schema($revision_id_field_storage_definition)); + + $this->entityManager->expects($this->any()) + ->method('getFieldStorageDefinitions') + ->willReturn([ + 'id' => $id_field_storage_definition, + 'uuid' => $uuid_field_storage_definition, + 'type' => $type_field_storage_definition, + 'langcode' => $langcode_field_storage_definition, + 'name' => $name_field_storage_definition, + 'user_id' => $user_id_field_storage_definition, + 'revision_id' => $revision_id_field_storage_definition, + ]); + } + + /** + * Tests fields on the base table. + */ + public function testBaseTableFields() { + $base_field_definitions = EntityTest::baseFieldDefinitions($this->baseEntityType); + $this->entityManager->expects($this->once()) + ->method('getBaseFieldDefinitions') + ->with('entity_test') + ->willReturn($base_field_definitions); + + // Setup the table mapping. + $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping->expects($this->any()) + ->method('getTableNames') + ->willReturn(['entity_test']); + $table_mapping->expects($this->any()) + ->method('getColumnNames') + ->willReturnMap([ + ['id', ['value' => 'id']], + ['uuid', ['value' => 'uuid']], + ['type', ['value' => 'type']], + ['langcode', ['value' => 'langcode']], + ['name', ['value' => 'name']], + ['user_id', ['target_id' => 'user_id']], + ]); + $table_mapping->expects($this->any()) + ->method('getFieldNames') + ->willReturnMap([ + ['entity_test', ['id', 'uuid', 'type', 'langcode', 'name', 'user_id']] + ]); + + $this->entityStorage->expects($this->once()) + ->method('getTableMapping') + ->willReturn($table_mapping); + + $this->setupFieldStorageDefinition(); + + $this->viewsData->setSchemaFields(['entity_test' => ['id', 'uuid', 'type', 'langcode', 'name', 'user_id']]); + $data = $this->viewsData->getViewsData(); + + $this->assertNumericField($data['entity_test']['id']); + $this->assertUuidField($data['entity_test']['uuid']); + $this->assertStringField($data['entity_test']['type']); + $this->assertLanguageField($data['entity_test']['langcode']); + $this->assertStringField($data['entity_test']['name']); + + $this->assertEntityReferenceField($data['entity_test']['user_id']); + $relationship = $data['entity_test']['user_id']['relationship']; + $this->assertEquals('users', $relationship['base']); + $this->assertEquals('uid', $relationship['base field']); + } + + /** + * Tests fields on the data table. + */ + public function testDataTableFields() { + $entity_type = $this->baseEntityType + ->set('data_table', 'entity_test_mul_property_data') + ->set('base_table', 'entity_test_mul') + ->set('id', 'entity_test_mul'); + $base_field_definitions = EntityTestMul::baseFieldDefinitions($this->baseEntityType); + $this->entityManager->expects($this->once()) + ->method('getBaseFieldDefinitions') + ->with('entity_test_mul') + ->willReturn($base_field_definitions); + + $this->viewsData->setSchemaFields([ + 'entity_test_mul' => ['id', 'uuid', 'type', 'langcode'], + 'entity_test_mul_property_data' => ['id', 'langcode', 'name', 'user_id'], + ]); + + ; + $this->viewsData->setEntityType($entity_type); + + // Setup the table mapping. + $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping->expects($this->any()) + ->method('getTableNames') + ->willReturn(['entity_test_mul', 'entity_test_mul_property_data']); + $table_mapping->expects($this->any()) + ->method('getColumnNames') + ->willReturnMap([ + ['id', ['value' => 'id']], + ['uuid', ['value' => 'uuid']], + ['type', ['value' => 'type']], + ['langcode', ['value' => 'langcode']], + ['name', ['value' => 'name']], + ['user_id', ['target_id' => 'user_id']], + ]); + $table_mapping->expects($this->any()) + ->method('getFieldNames') + ->willReturnMap([ + ['entity_test_mul', ['id', 'uuid', 'type', 'langcode']], + ['entity_test_mul_property_data', ['id', 'langcode', 'name', 'user_id']], + ]); + + $this->entityStorage->expects($this->once()) + ->method('getTableMapping') + ->willReturn($table_mapping); + + $this->setupFieldStorageDefinition(); + + $data = $this->viewsData->getViewsData(); + + // Check the base fields. + $this->assertNumericField($data['entity_test_mul']['id']); + $this->assertUuidField($data['entity_test_mul']['uuid']); + $this->assertStringField($data['entity_test_mul']['type']); + $this->assertLanguageField($data['entity_test_mul']['langcode']); + // Also ensure that field_data only fields don't appear on the base table. + $this->assertFalse(isset($data['entity_test_mul']['name'])); + $this->assertFalse(isset($data['entity_test_mul']['user_id'])); + + // Check the data fields. + $this->assertNumericField($data['entity_test_mul_property_data']['id']); + $this->assertLanguageField($data['entity_test_mul_property_data']['langcode']); + $this->assertStringField($data['entity_test_mul_property_data']['name']); + + $this->assertEntityReferenceField($data['entity_test_mul_property_data']['user_id']); + $relationship = $data['entity_test_mul_property_data']['user_id']['relationship']; + $this->assertEquals('users', $relationship['base']); + $this->assertEquals('uid', $relationship['base field']); + } + + /** + * Tests fields on the revision table. + */ + public function testRevisionTableFields() { + $entity_type = $this->baseEntityType + ->set('base_table', 'entity_test_mulrev') + ->set('revision_table', 'entity_test_mulrev_revision') + ->set('data_table', 'entity_test_mulrev_property_data') + ->set('revision_data_table', 'entity_test_mulrev_property_revision') + ->set('id', 'entity_test_mulrev'); + $base_field_definitions = EntityTestMulRev::baseFieldDefinitions($this->baseEntityType); + $this->entityManager->expects($this->once()) + ->method('getBaseFieldDefinitions') + ->with('entity_test_mulrev') + ->willReturn($base_field_definitions); + + $this->viewsData->setSchemaFields([ + 'entity_test_mulrev' => ['id', 'revision_id', 'uuid', 'type'], + 'entity_test_mulrev_revision' => ['id', 'revision_id', 'langcode'], + 'entity_test_mulrev_property_data' => ['id', 'revision_id', 'langcode', 'name', 'user_id'], + 'entity_test_mulrev_property_revision' => ['id', 'revision_id', 'langcode', 'name', 'user_id'], + ]); + + ; + $this->viewsData->setEntityType($entity_type); + + // Setup the table mapping. + $table_mapping = $this->getMock('Drupal\Core\Entity\Sql\TableMappingInterface'); + $table_mapping->expects($this->any()) + ->method('getTableNames') + ->willReturn(['entity_test_mulrev', 'entity_test_mulrev_revision', 'entity_test_mulrev_property_data', 'entity_test_mulrev_property_revision']); + $table_mapping->expects($this->any()) + ->method('getColumnNames') + ->willReturnMap([ + ['id', ['value' => 'id']], + ['uuid', ['value' => 'uuid']], + ['type', ['value' => 'type']], + ['langcode', ['value' => 'langcode']], + ['name', ['value' => 'name']], + ['user_id', ['target_id' => 'user_id']], + ['revision_id', ['value' => 'id']], + ]); + $table_mapping->expects($this->any()) + ->method('getFieldNames') + ->willReturnMap([ + ['entity_test_mulrev', ['id', 'revision_id', 'uuid', 'type']], + ['entity_test_mulrev_revision', ['id', 'revision_id', 'langcode']], + ['entity_test_mulrev_property_data', ['id', 'revision_id', 'langcode', 'name', 'user_id']], + ['entity_test_mulrev_property_revision', ['id', 'revision_id', 'langcode', 'name', 'user_id']], + ]); + + $this->entityStorage->expects($this->once()) + ->method('getTableMapping') + ->willReturn($table_mapping); + + $this->setupFieldStorageDefinition(); + + $data = $this->viewsData->getViewsData(); + + // Check the base fields. + $this->assertNumericField($data['entity_test_mulrev']['id']); + $this->assertNumericField($data['entity_test_mulrev']['revision_id']); + $this->assertUuidField($data['entity_test_mulrev']['uuid']); + $this->assertStringField($data['entity_test_mulrev']['type']); + + // Also ensure that field_data only fields don't appear on the base table. + $this->assertFalse(isset($data['entity_test_mulrev']['name'])); + $this->assertFalse(isset($data['entity_test_mulrev']['langcode'])); + $this->assertFalse(isset($data['entity_test_mulrev']['user_id'])); + + // Check the revision fields. + $this->assertNumericField($data['entity_test_mulrev_revision']['id']); + $this->assertNumericField($data['entity_test_mulrev_revision']['revision_id']); + $this->assertLanguageField($data['entity_test_mulrev_revision']['langcode']); + // Also ensure that field_data only fields don't appear on the revision table. + $this->assertFalse(isset($data['entity_test_mulrev_revision']['name'])); + $this->assertFalse(isset($data['entity_test_mulrev_revision']['user_id'])); + + // Check the data fields. + $this->assertNumericField($data['entity_test_mulrev_property_data']['id']); + $this->assertLanguageField($data['entity_test_mulrev_property_data']['langcode']); + $this->assertStringField($data['entity_test_mulrev_property_data']['name']); + + $this->assertEntityReferenceField($data['entity_test_mulrev_property_data']['user_id']); + $relationship = $data['entity_test_mulrev_property_data']['user_id']['relationship']; + $this->assertEquals('users', $relationship['base']); + $this->assertEquals('uid', $relationship['base field']); + + // Check the property data fields. + $this->assertNumericField($data['entity_test_mulrev_property_revision']['id']); + $this->assertLanguageField($data['entity_test_mulrev_property_revision']['langcode']); + $this->assertStringField($data['entity_test_mulrev_property_revision']['name']); + + $this->assertEntityReferenceField($data['entity_test_mulrev_property_revision']['user_id']); + $relationship = $data['entity_test_mulrev_property_revision']['user_id']['relationship']; + $this->assertEquals('users', $relationship['base']); + $this->assertEquals('uid', $relationship['base field']); + } + + /** + * Tests views data for a string field. + * + * @param $data + * The views data to check. + */ + public function assertStringField($data) { + $this->assertEquals('standard', $data['field']['id']); + $this->assertEquals('string', $data['filter']['id']); + $this->assertEquals('string', $data['argument']['id']); + $this->assertEquals('standard', $data['sort']['id']); + } + + /** + * Tests views data for a UUID field. + * + * @param array $data + * The views data to check. + */ + public function assertUuidField($data) { + // @todo Can we provide additional support for UUIDs in views? + $this->assertEquals('standard', $data['field']['id']); + $this->assertEquals('string', $data['filter']['id']); + $this->assertEquals('string', $data['argument']['id']); + $this->assertEquals('standard', $data['sort']['id']); + } + + /** + * Tests views data for a numeric field. + * + * @param array $data + * The views data to check. + */ + public function assertNumericField($data) { + $this->assertEquals('numeric', $data['field']['id']); + $this->assertEquals('numeric', $data['filter']['id']); + $this->assertEquals('numeric', $data['argument']['id']); + $this->assertEquals('standard', $data['sort']['id']); + } + + /** + * Tests views data for a language field. + * + * @param array $data + * The views data to check. + */ + public function assertLanguageField($data) { + $this->assertEquals('language', $data['field']['id']); + $this->assertEquals('language', $data['filter']['id']); + $this->assertEquals('language', $data['argument']['id']); + $this->assertEquals('standard', $data['sort']['id']); + } + + public function assertEntityReferenceField($data) { + $this->assertEquals('numeric', $data['field']['id']); + $this->assertEquals('numeric', $data['filter']['id']); + $this->assertEquals('numeric', $data['argument']['id']); + $this->assertEquals('standard', $data['sort']['id']); + } + + /** + * Returns entity info for the user entity. + * + * @return array + */ + public static function userEntityInfo() { + return new ContentEntityType([ + 'id' => 'user', + 'class' => 'Drupal\user\Entity\User', + 'label' => 'User', + 'base_table' => 'users', + 'entity_keys' => [ + 'id' => 'uid', + 'uuid' => 'uuid', + ], + ]); + } + +} + +class TestEntityViewsData extends EntityViewsData { + + protected $schemaFields = []; + + public function setSchemaFields($fields) { + $this->schemaFields = $fields; + } + + /** + * {@inheritdoc} + */ + protected function drupalSchemaFieldsSql($table) { + return isset($this->schemaFields[$table]) ? $this->schemaFields[$table] : []; + } + + public function setEntityType(EntityTypeInterface $entity_type) { + $this->entityType = $entity_type; + } + +} + +} + +namespace { + use Drupal\Component\Utility\String; + + if (!function_exists('t')) { + function t($string, array $args = []) { + return String::format($string, $args); + } + } +}