Issue #1740492 by dawehner, damiankloip, dasjo, xjm: Implement a default entity views data handler.

8.0.x
Alex Pott 2014-09-12 19:44:03 +01:00
parent 7f69cb4456
commit 35b3d0faeb
11 changed files with 1227 additions and 552 deletions

View File

@ -28,7 +28,7 @@ class SqlContentEntityStorageSchema implements EntitySchemaHandlerInterface {
/**
* The storage field definitions for this entity type.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface[]
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
protected $fieldStorageDefinitions;

View File

@ -7,118 +7,47 @@
namespace Drupal\file;
use Drupal\views\EntityViewsDataInterface;
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();
// Sets 'group' index for file_managed table.
$data['file_managed']['table']['group'] = t('File');
$data = parent::getViewsData();
// Advertise this table as a possible base table.
$data['file_managed']['table']['base'] = array(
'field' => 'fid',
'title' => t('File'),
'help' => t("Files maintained by Drupal and various modules."),
'defaults' => array(
'field' => 'filename'
),
);
$data['file_managed']['table']['entity type'] = 'file';
// @TODO There is no corresponding information in entity metadata.
$data['file_managed']['table']['base']['help'] = t('Files maintained by Drupal and various modules.');
$data['file_managed']['table']['base']['defaults']['field'] = 'filename';
$data['file_managed']['table']['wizard_id'] = 'file_managed';
// Describes fid field in file_managed table.
$data['file_managed']['fid'] = array(
'title' => t('File ID'),
'help' => t('The ID of the file.'),
'field' => array(
'id' => 'file',
),
'argument' => array(
'id' => 'file_fid',
// The field to display in the summary.
'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.'),
'id' => 'standard',
'base' => 'file_usage',
'base field' => 'fid',
'field' => 'fid',
'label' => t('File usage'),
),
$data['file_managed']['fid']['field']['id'] ='file';
$data['file_managed']['fid']['argument'] = array(
'id' => 'file_fid',
// The field to display in the summary.
'name field' => 'filename',
'numeric' => TRUE,
);
$data['file_managed']['fid']['relationship'] = array(
'title' => t('File usage'),
'help' => t('Relate file entities to their usage.'),
'id' => 'standard',
'base' => 'file_usage',
'base field' => 'fid',
'field' => 'fid',
'label' => t('File usage'),
);
// 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',
),
);
$data['file_managed']['filename']['field']['id'] = 'file';
// 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',
),
);
$data['file_managed']['uri']['field']['id'] = 'file_uri';
// 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',
),
);
$data['file_managed']['filemime']['field']['id'] = 'file_filemime';
// Describes extension field in file_managed table.
$data['file_managed']['extension'] = array(
'title' => t('Extension'),
'help' => t('The extension of the file.'),
@ -129,79 +58,14 @@ class FileViewsData implements EntityViewsDataInterface {
),
);
// 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',
),
);
$data['file_managed']['filesize']['field']['id'] = 'file_size';
// 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',
),
);
$data['file_managed']['status']['field']['id'] = 'file_status';
$data['file_managed']['status']['filter']['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',
),
);
$data['file_managed']['uid']['relationship']['title'] = t('User who uploaded');
$data['file_managed']['uid']['relationship']['label'] = t('User who uploaded');
// 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',
),
);
// Sets 'group' index for file_usage table.
$data['file_usage']['table']['group'] = t('File Usage');
// Provide field-type-things to several base tables; on the core files table
@ -499,4 +363,3 @@ class FileViewsData implements EntityViewsDataInterface {
}
}

View File

@ -7,169 +7,48 @@
namespace Drupal\node;
use Drupal\views\EntityViewsData;
use Drupal\views\EntityViewsDataInterface;
/**
* Provides the views data for the node entity type.
*/
class NodeViewsData implements EntityViewsDataInterface {
class NodeViewsData extends EntityViewsData implements EntityViewsDataInterface {
/**
* {@inheritdoc}
*/
public function getViewsData() {
// Define the base group of this table. Fields that don't have a group defined
// will go into this field by default.
$data['node']['table']['group'] = t('Content');
$data = parent::getViewsData();
// Advertise this table as a possible base table.
$data['node']['table']['base'] = array(
'field' => 'nid',
'title' => t('Content'),
'weight' => -10,
'access query tag' => 'node_access',
'defaults' => array(
'field' => 'title',
),
);
$data['node']['table']['entity type'] = 'node';
$data['node']['table']['base']['weight'] = -10;
$data['node']['table']['base']['access query tag'] = 'node_access';
$data['node']['table']['wizard_id'] = 'node';
$data['node_field_data']['table']['group'] = t('Content');
$data['node_field_data']['table']['entity type'] = 'node';
$data['node_field_data']['table']['join']['node'] = array(
'type' => 'INNER',
'left_field' => 'nid',
'field' => 'nid',
);
$data['node']['nid']['field']['id'] = 'node';
$data['node']['nid']['field']['argument'] = [
'id' => 'node_nid',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'nid',
];
$data['node']['nid'] = array(
'title' => t('Nid'),
'help' => t('The node ID.'),
'field' => array(
'id' => 'node',
),
'argument' => array(
'id' => 'node_nid',
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'nid',
),
'filter' => array(
'id' => 'numeric',
),
'sort' => array(
'id' => 'standard',
),
);
$data['node_field_data']['title']['field']['id'] = 'node';
$data['node_field_data']['title']['field']['link_to_node default'] = TRUE;
// This definition has more items in it than it needs to as an example.
$data['node_field_data']['title'] = array(
'title' => t('Title'),
'help' => t('The content title.'),
'field' => array(
// This is the real field which could be left out since it is the same.
'field' => 'title',
// This is the UI group which could be left out since it is the same.
'group' => t('Content'),
'id' => 'node',
'link_to_node default' => TRUE,
),
'sort' => array(
'id' => 'standard',
),
'filter' => array(
'id' => 'string',
),
'argument' => array(
'id' => 'string',
),
);
$data['node_field_data']['type']['field']['id'] = 'node_type';
$data['node_field_data']['type']['argument']['id'] = 'node_type';
$data['node_field_data']['created'] = array(
'title' => t('Post date'),
'help' => t('The date the content was posted.'),
'field' => array(
'id' => 'date',
),
'sort' => array(
'id' => 'date'
),
'filter' => array(
'id' => 'date',
),
);
$data['node_field_data']['langcode']['help'] = t('The language of the content or translation.');
$data['node_field_data']['langcode']['field']['id'] = 'node_language';
$data['node_field_data']['changed'] = array(
'title' => t('Updated date'),
'help' => t('The date the content was last updated.'),
'field' => array(
'id' => 'date',
),
'sort' => array(
'id' => 'date'
),
'filter' => array(
'id' => 'date',
),
);
$data['node_field_data']['type'] = array(
'title' => t('Type'),
'help' => t('The content type (for example, "blog entry", "forum post", "story", etc).'),
'field' => array(
'id' => 'node_type',
),
'sort' => array(
'id' => 'standard',
),
'filter' => array(
'id' => 'bundle',
),
'argument' => array(
'id' => 'node_type',
),
);
if (\Drupal::moduleHandler()->moduleExists('language')) {
$data['node_field_data']['langcode'] = array(
'title' => t('Translation language'),
'help' => t('The language of the content or translation.'),
'field' => array(
'id' => 'node_language',
),
'filter' => array(
'id' => 'language',
),
'argument' => array(
'id' => 'language',
),
'sort' => array(
'id' => 'standard',
),
);
}
$data['node_field_data']['status'] = array(
'title' => t('Published status'),
'help' => t('Whether or not the content is published.'),
'field' => array(
'id' => 'boolean',
'output formats' => array(
'published-notpublished' => array(t('Published'), t('Not published')),
),
),
'filter' => array(
'id' => 'boolean',
'label' => t('Published status'),
'type' => 'yes-no',
// Use status = 1 instead of status <> 0 in WHERE statement.
'use_equal' => TRUE,
),
'sort' => array(
'id' => 'standard',
),
);
$data['node_field_data']['status']['field']['output formats'] = [
'published-notpublished' => array(t('Published'), t('Not published')),
];
$data['node_field_data']['status']['filter']['label'] = t('Published status');
$data['node_field_data']['status']['filter']['type'] = 'yes-no';
// Use status = 1 instead of status <> 0 in WHERE statement.
$data['node_field_data']['status']['filter']['use_equal'] = TRUE;
$data['node_field_data']['status_extra'] = array(
'title' => t('Published status or admin user'),
@ -181,44 +60,18 @@ class NodeViewsData implements EntityViewsDataInterface {
),
);
$data['node_field_data']['promote'] = array(
'title' => t('Promoted to front page status'),
'help' => t('Whether or not the content is promoted to the front page.'),
'field' => array(
'id' => 'boolean',
'output formats' => array(
'promoted-notpromoted' => array(t('Promoted'), t('Not promoted')),
),
),
'filter' => array(
'id' => 'boolean',
'label' => t('Promoted to front page status'),
'type' => 'yes-no',
),
'sort' => array(
'id' => 'standard',
),
);
$data['node_field_data']['promote']['field']['output formats'] = [
'promoted-notpromoted' => array(t('Promoted'), t('Not promoted')),
];
$data['node_field_data']['promote']['filter']['label'] = t('Promoted to front page status');
$data['node_field_data']['promote']['filter']['type'] = 'yes-no';
$data['node_field_data']['sticky'] = array(
'title' => t('Sticky status'),
'help' => t('Whether or not the content is sticky.'),
'field' => array(
'id' => 'boolean',
'output formats' => array(
'sticky' => array(t('Sticky'), t('Not sticky')),
),
),
'filter' => array(
'id' => 'boolean',
'label' => t('Sticky status'),
'type' => 'yes-no',
),
'sort' => array(
'id' => 'standard',
'help' => t('Whether or not the content is sticky. To list sticky content first, set this to descending.'),
),
);
$data['node_field_data']['sticky']['field']['output formats'] = [
'sticky' => array(t('Sticky'), t('Not sticky')),
];
$data['node_field_data']['sticky']['filter']['label'] = t('Sticky status');
$data['node_field_data']['sticky']['filter']['type'] = 'yes-no';
$data['node_field_data']['sticky']['sort']['help'] = t('Whether or not the content is sticky. To list sticky content first, set this to descending.');
if (\Drupal::moduleHandler()->moduleExists('content_translation')) {
$data['node']['translation_link'] = array(
@ -264,6 +117,8 @@ class NodeViewsData implements EntityViewsDataInterface {
// Bogus fields for aliasing purposes.
// @todo Add similar support to any date field
// @see https://drupal.org/node/2337507
$data['node_field_data']['created_fulldate'] = array(
'title' => t('Created date'),
'help' => t('Date in the form of CCYYMMDD.'),
@ -372,27 +227,12 @@ class NodeViewsData implements EntityViewsDataInterface {
),
);
$data['node_field_data']['uid'] = array(
'title' => t('Author uid'),
'help' => t('The user authoring the content. If you need more fields than the uid add the content: author relationship'),
'relationship' => array(
'title' => t('Content author'),
'help' => t('Relate content to the user who created it.'),
'id' => 'standard',
'base' => 'users',
'field' => 'uid',
'label' => t('author'),
),
'filter' => array(
'id' => 'user_name',
),
'argument' => array(
'id' => 'numeric',
),
'field' => array(
'id' => 'user',
),
);
$data['node_field_data']['uid']['help'] = t('The user authoring the content. If you need more fields than the uid add the content: author relationship');
$data['node_field_data']['uid']['filter']['id'] = 'user_name';
$data['node_field_data']['uid']['field']['id'] = 'user';
$data['node_field_data']['uid']['relationship']['title'] = t('Content author');
$data['node_field_data']['uid']['relationship']['help'] = t('Relate content to the user who created it.');
$data['node_field_data']['uid']['relationship']['label'] = t('author');
$data['node']['node_listing_empty'] = array(
'title' => t('Empty Node Frontpage behavior'),
@ -402,83 +242,36 @@ class NodeViewsData implements EntityViewsDataInterface {
),
);
$data['node_field_data']['uid_revision'] = array(
'title' => t('User has a revision'),
'help' => t('All nodes where a certain user has a revision'),
'real field' => 'nid',
'filter' => array(
'id' => 'node_uid_revision',
),
'argument' => array(
'id' => 'node_uid_revision',
),
);
$data['node_field_data']['uid_revision']['title'] = t('User has a revision');
$data['node_field_data']['uid_revision']['help'] = t('All nodes where a certain user has a revision');
$data['node_field_data']['uid_revision']['real field'] = 'nid';
$data['node_field_data']['uid_revision']['filter']['id'] = 'node_uid_revision';
$data['node_field_data']['uid_revision']['argument']['id'] = 'node_uid_revision';
$data['node_revision']['table']['entity type'] = 'node';
// Define the base group of this table. Fields that don't have a group defined
// will go into this field by default.
$data['node_revision']['table']['group'] = t('Content revision');
$data['node_revision']['table']['wizard_id'] = 'node_revision';
// Advertise this table as a possible base table.
$data['node_revision']['table']['base'] = array(
'field' => 'vid',
'title' => t('Content revision'),
'help' => t('Content revision is a history of changes to content.'),
'defaults' => array(
'field' => 'title',
),
);
$data['node_revision']['table']['base']['help'] = t('Content revision is a history of changes to content.');
$data['node_revision']['table']['base']['defaults']['title'] = 'title';
// For other base tables, explain how we join.
$data['node_revision']['table']['join'] = array(
'node' => array(
'left_field' => 'vid',
'field' => 'vid',
),
);
$data['node_revision']['nid'] = array(
'title' => t('Nid'),
'help' => t('The revision NID of the content revision.'),
'field' => array(
'id' => 'standard',
),
'argument' => array(
'id' => 'node_nid',
'numeric' => TRUE,
),
'filter' => array(
'id' => 'numeric',
),
'sort' => array(
'id' => 'standard',
),
'relationship' => array(
'id' => 'standard',
'base' => 'node',
'base field' => 'nid',
'title' => t('Content'),
'label' => t('Get the actual content from a content revision.'),
),
);
$data['node_revision']['nid']['argument'] = [
'id' => 'node_nid',
'numeric' => TRUE,
];
// @todo the NID field needs different behaviour on revision/non-revision
// tables. It would be neat if this could be encoded in the base field
// definition.
$data['node_revision']['nid']['relationship']['id'] = 'standard';
$data['node_revision']['nid']['relationship']['base'] = 'node';
$data['node_revision']['nid']['relationship']['base field'] = 'nid';
$data['node_revision']['nid']['relationship']['title'] = t('Content');
$data['node_revision']['nid']['relationship']['label'] = t('Get the actual content from a content revision.');
$data['node_revision']['vid'] = array(
'title' => t('Vid'),
'help' => t('The revision ID of the content revision.'),
'field' => array(
'id' => 'standard',
),
'argument' => array(
'id' => 'node_vid',
'numeric' => TRUE,
),
'filter' => array(
'id' => 'numeric',
),
'sort' => array(
'id' => 'standard',
),
'relationship' => array(
'id' => 'standard',
'base' => 'node',
@ -486,119 +279,29 @@ class NodeViewsData implements EntityViewsDataInterface {
'title' => t('Content'),
'label' => t('Get the actual content from a content revision.'),
),
);
) + $data['node_revision']['vid'];
if (\Drupal::moduleHandler()->moduleExists('language')) {
$data['node_revision']['langcode'] = array(
'title' => t('Original language'),
'help' => t('The language the original content is in.'),
'field' => array(
'id' => 'node_language',
),
'filter' => array(
'id' => 'language',
),
'argument' => array(
'id' => 'language',
),
'sort' => array(
'id' => 'standard',
),
);
}
$data['node_revision']['langcode']['help'] = t('The language the original content is in.');
$data['node_revision']['langcode']['field']['id'] = 'node_language';
$data['node_revision']['revision_log'] = array(
'title' => t('Log message'),
'help' => t('The log message entered when the revision was created.'),
'field' => array(
'id' => 'xss',
),
'filter' => array(
'id' => 'string',
),
);
$data['node_revision']['revision_log']['field']['id'] = 'xss';
$data['node_revision']['revision_uid'] = array(
'title' => t('User'),
'help' => t('Relate a content revision to the user who created the revision.'),
'relationship' => array(
'id' => 'standard',
'base' => 'users',
'base field' => 'uid',
'label' => t('revision user'),
),
);
$data['node_revision']['revision_uid']['help'] = t('Relate a content revision to the user who created the revision.');
$data['node_revision']['revision_uid']['relationship']['label'] = t('revision user');
$data['node_field_revision']['table']['entity type'] = 'node';
// Define the base group of this table. Fields that don't have a group defined
// will go into this field by default.
$data['node_field_revision']['table']['group'] = t('Content revision');
$data['node_field_revision']['table']['wizard_id'] = 'node_field_revision';
// For other base tables, explain how we join.
$data['node_field_revision']['table']['join'] = array(
'node' => array(
'left_field' => 'vid',
'field' => 'vid',
),
'node_revision' => array(
'left_field' => 'vid',
'field' => 'vid',
),
);
$data['node_field_revision']['table']['join']['node']['left_field'] = 'vid';
$data['node_field_revision']['table']['join']['node']['field'] = 'vid';
$data['node_field_revision']['status'] = array(
'title' => t('Published'),
'help' => t('Whether or not the content is published.'),
'field' => array(
'id' => 'boolean',
'output formats' => array(
'published-notpublished' => array(t('Published'), t('Not published')),
),
),
'filter' => array(
'id' => 'boolean',
'label' => t('Published'),
'type' => 'yes-no',
// Use status = 1 instead of status <> 0 in WHERE statement.
'use_equal' => TRUE,
),
'sort' => array(
'id' => 'standard',
),
);
$data['node_field_revision']['status']['field']['output formats'] = [
'published-notpublished' => [t('Published'), t('Not published')],
];
$data['node_field_revision']['status']['filter']['label'] = t('Published');
$data['node_field_revision']['status']['filter']['type'] = 'yes-no';
$data['node_field_revision']['status']['filter']['use_equal'] = TRUE;
$data['node_field_revision']['title'] = array(
'title' => t('Title'),
'help' => t('The content title.'),
'field' => array(
'field' => 'title',
'id' => 'node_revision',
),
'sort' => array(
'id' => 'standard',
),
'filter' => array(
'id' => 'string',
),
'argument' => array(
'id' => 'string',
),
);
$data['node_field_revision']['changed'] = array(
'title' => t('Updated date'),
'help' => t('The date the content was last updated.'),
'field' => array(
'id' => 'date',
),
'sort' => array(
'id' => 'date'
),
'filter' => array(
'id' => 'date',
),
);
$data['node_field_revision']['title']['field']['id'] = 'node_revision';
$data['node_revision']['link_to_revision'] = array(
'field' => array(
@ -727,6 +430,4 @@ class NodeViewsData implements EntityViewsDataInterface {
return $data;
}
}

View File

@ -310,7 +310,8 @@ use Drupal\Core\Render\Element;
* config entities will use \Drupal\Core\Config\Entity\ConfigEntityStorage.
* You can extend one of these classes to provide custom behavior.
* - views_data: A class implementing \Drupal\views\EntityViewsDataInterface
* to provide views data for the entity type.
* to provide views data for the entity type. You can autogenerate most of
* the views data by extending \Drupal\views\EntityViewsData.
* - For content entities, the annotation will refer to a number of database
* tables and their fields. These annotation properties, such as 'base_table',
* 'data_table', 'entity_keys', etc., are documented on

View File

@ -29,7 +29,8 @@ use Drupal\user\UserInterface;
* "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,

View File

@ -24,7 +24,8 @@ use Drupal\entity_test\Entity\EntityTest;
* "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",

View File

@ -23,7 +23,8 @@ use Drupal\entity_test\Entity\EntityTestRev;
* "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",

View File

@ -23,7 +23,8 @@ use Drupal\entity_test\Entity\EntityTest;
* "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",

View File

@ -0,0 +1,446 @@
<?php
/**
* @file
* Contains \Drupal\views\EntityViewsData.
*/
namespace Drupal\views;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Entity\Sql\TableMappingInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides generic views integration for entities.
*/
class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterface {
use StringTranslationTrait;
/**
* Entity type for this views controller instance.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The storage used for this entity type.
*
* @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface
*/
protected $storage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The translation manager.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* The field storage definitions for all base fields of the entity type.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
protected $fieldStorageDefinitions;
/**
* Constructs an EntityViewsData object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type to provide views integration for.
* @param \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage_controller
* The storage controller used for this entity type.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The translation manager.
*/
function __construct(EntityTypeInterface $entity_type, SqlEntityStorageInterface $storage_controller, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager) {
$this->entityType = $entity_type;
$this->entityManager = $entity_manager;
$this->storage = $storage_controller;
$this->moduleHandler = $module_handler;
$this->setStringTranslation($translation_manager);
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$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 = [];
// @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).
// @see https://drupal.org/node/2337509
$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'] = [
'field' => $base_field,
'title' => $this->entityType->getLabel(),
];
if ($label_key = $this->entityType->getKey('label')) {
if ($data_table) {
$data[$base_table]['table']['base']['defaults'] = array(
'field' => $label_key,
'table' => $data_table,
);
}
else {
$data[$base_table]['table']['base']['defaults'] = array(
'field' => $label_key,
);
}
}
// Setup relations to the revisions/property data.
if ($data_table) {
$data[$data_table]['table']['join'][$base_table] = [
'left_field' => $base_field,
'field' => $base_field,
'type' => 'INNER'
];
$data[$data_table]['table']['entity type'] = $this->entityType->id();
$data[$data_table]['table']['group'] = $this->entityType->getLabel();
}
if ($revision_table) {
$data[$revision_table]['table']['entity type'] = $this->entityType->id();
$data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $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' => $revision_field,
'field' => $revision_field,
'type' => 'INNER',
);
if ($revision_data_table) {
$data[$revision_data_table]['table']['entity type'] = $this->entityType->id();
$data[$revision_data_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
$data[$revision_data_table]['table']['join'][$revision_table] = array(
'left_field' => $revision_field,
'field' => $revision_field,
'type' => 'INNER',
);
}
}
// 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->storage->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.
// @todo https://drupal.org/node/2337511
foreach ($table_mapping->getTableNames() as $table) {
foreach ($table_mapping->getFieldNames($table) as $field_name) {
$this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]);
}
}
}
return $data;
}
/**
* Puts the views data for a single field onto the views data.
*
* @param string $table
* The table of the field to handle.
* @param string $field_name
* The name of the field to handle.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition defined in Entity::baseFieldDefinitions()
* @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping
* The table mapping information
* @param array $table_data
* A reference to a specific entity table (for example data_table) inside
* the views data.
*/
protected function mapFieldDefinition($table, $field_name, FieldDefinitionInterface $field_definition, TableMappingInterface $table_mapping, &$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];
// We want to both have an entry in the views data for the actual field,
// but also each additional schema field, for example the file
// description.
// @todo Introduce a concept of the "main" schema field for a field item.
// This would be the FID for a file reference for example.
// @see https://www.drupal.org/node/2337517
if ($first) {
$first = FALSE;
$table_data[$field_name] = $this->mapSingleFieldViewsData($table, $field_definition_type, $schema_field_name, $field_definition, TRUE);
}
else {
$table_data["$field_name.$field_column_name"] = $this->mapSingleFieldViewsData($table, $schema['type'], $schema_field_name, $field_definition, FALSE);
}
}
}
/**
* Provides the views data for a given data type and schema field.
*
* @param string $table
* The table of the field to handle.
* @param string $data_type
* The data type to generate views data for, for example "int". The data
* type comes directly from the schema definition of each field item.
* @param string $schema_field_name
* The schema field name.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param bool $first
* Is it the first column of the schema.
*
* @return array
* The modified views data field definition.
*/
protected function mapSingleFieldViewsData($table, $data_type, $schema_field_name, FieldDefinitionInterface $field_definition, $first) {
$views_field = array();
// Provide a nicer, less verbose label for the first field.
if ($first) {
$views_field['title'] = $field_definition->getLabel();
}
else {
$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.
// @see https://www.drupal.org/node/2337515
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 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.
// @see https://www.drupal.org/node/2322949
$views_field['field']['id'] = 'standard';
$views_field['argument']['id'] = 'standard';
$views_field['filter']['id'] = 'standard';
$views_field['sort']['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';
}
$process_method = 'processViewsDataFor' . Container::camelize($data_type);
if (method_exists($this, $process_method)) {
$this->{$process_method}($table, $field_definition, $views_field);
}
return $views_field;
}
/**
* Processes the views data for a language field.
*
* @param string $table
* The table the language field is added to.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param array $views_field
* The views field data.
*/
protected function processViewsDataForLanguage($table, FieldDefinitionInterface $field_definition, array &$views_field) {
// Apply special titles for the langcode field.
if ($field_definition->getName() == 'langcode') {
if ($table == $this->entityType->getDataTable() || $table == $this->entityType->getBaseTable()) {
$views_field['title'] = $this->t('Translation language');
}
if ($table == $this->entityType->getRevisionDataTable() || $table == $this->entityType->getRevisionTable()) {
$views_field['title'] = $this->t('Original language');
}
}
}
/**
* Processes the views data for an entity reference field.
*
* @param string $table
* The table the language field is added to.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param array $views_field
* The views field data.
*/
protected function processViewsDataForEntityReference($table, FieldDefinitionInterface $field_definition, array &$views_field) {
if ($entity_type_id = $field_definition->getItemDefinition()->getSetting('target_type')) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
if ($entity_type instanceof ContentEntityType) {
$views_field['relationship'] = [
'base' => $this->getViewsTableForEntityType($entity_type),
'base field' => $entity_type->getKey('id'),
'label' => $entity_type->getLabel(),
'title' => $entity_type->getLabel(),
'id' => 'standard',
];
$views_field['field']['id'] = 'numeric';
$views_field['argument']['id'] = 'numeric';
$views_field['filter']['id'] = 'numeric';
$views_field['sort']['id'] = 'standard';
}
else {
$views_field['field']['id'] = 'standard';
$views_field['argument']['id'] = 'string';
$views_field['filter']['id'] = 'string';
$views_field['sort']['id'] = 'standard';
}
}
if ($field_definition->getName() == $this->entityType->getKey('bundle')) {
// @todo Use the other bundle handlers, once
// https://www.drupal.org/node/2322949 is in.
$views_field['filter']['id'] = 'bundle';
}
}
/**
* Gets the table of an entity type to be used as base table in views.
*
* @todo Given that the base_table is pretty much useless as you often have to
* join to the data table anyway, it could make a lot of sense to start with
* the data table right from the beginning.
* @see https://drupal.org/node/2337509
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return string
* The name of the base table in views.
*/
protected function getViewsTableForEntityType(EntityTypeInterface $entity_type) {
return $entity_type->getBaseTable();
}
}

View File

@ -0,0 +1,659 @@
<?php
use Drupal\Core\Entity\EntityType;
/**
* @file
* Contains \Drupal\views\Tests\EntityViewsDataTest.
*/
namespace Drupal\views\Tests {
use Drupal\Core\Config\Entity\ConfigEntityType;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityType;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem;
use Drupal\Core\Field\Plugin\Field\FieldType\LanguageItem;
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
use Drupal\Core\Field\Plugin\Field\FieldType\UuidItem;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestMul;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\Tests\UnitTestCase;
use Drupal\views\EntityViewsData;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @coversDefaultClass \Drupal\views\EntityViewsData
* @group Views
*/
class EntityViewsDataTest extends UnitTestCase {
/**
* Entity info to use in this test.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $baseEntityType;
/**
* The mocked entity storage.
*
* @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityStorage;
/**
* The mocked entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $entityManager;
/**
* The mocked module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $moduleHandler;
/**
* The mocked translation manager.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $translationManager;
/**
* The tested entity views controller.
*
* @var \Drupal\views\Tests\TestEntityViewsData
*/
protected $viewsData;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->entityStorage = $this->getMockBuilder('Drupal\Core\Entity\Sql\SqlContentEntityStorage')
->disableOriginalConstructor()
->getMock();
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->baseEntityType = new TestEntityType([
'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->viewsData = new TestEntityViewsData($this->baseEntityType, $this->entityStorage, $this->entityManager, $this->moduleHandler, $this->translationManager);
$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);
}
/**
* Helper method to setup base fields.
*
* @param \Drupal\Core\Field\BaseFieldDefinition[] $base_fields
* The base fields which are adapted.
*
* @return \Drupal\Core\Field\BaseFieldDefinition[]
* The setup base fields.
*/
protected function setupBaseFields(array $base_fields) {
foreach ($base_fields as $name => $base_field) {
$base_field->setName($name);
}
return $base_fields;
}
/**
* 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']['table']['defaults']));
$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')
->setKey('label', 'label');
$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'];
$this->assertEquals('entity_test_mul', $data['entity_test_mul_property_data']['table']['entity type']);
$this->assertEquals('Entity test', $data['entity_test_mul_property_data']['table']['group']);
$this->assertEquals(['field' => 'label', 'table' => 'entity_test_mul_property_data'], $data['entity_test']['table']['base']['defaults']);
// 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', 'type' => 'INNER']], $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();
$this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_revision']['table']['entity type']);
$this->assertEquals('entity_test_mulrev', $data['entity_test_mulrev_property_revision']['table']['entity type']);
$this->assertEquals('Entity test revision', $data['entity_test_mulrev_revision']['table']['group']);
// 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' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER']], $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', 'type' => 'INNER']], $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 = $this->setupBaseFields(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->assertEquals('Translation language', $data['entity_test']['langcode']['title']);
$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')
->setKey('bundle', 'type')
;
$base_field_definitions = $this->setupBaseFields(EntityTestMul::baseFieldDefinitions($this->baseEntityType));
$base_field_definitions['type'] = BaseFieldDefinition::create('entity_reference')
->setLabel('entity test type')
->setSettings(array('target_type' => 'entity_test_bundle'))
->setTranslatable(TRUE);
$base_field_definitions = $this->setupBaseFields($base_field_definitions);
$entity_test_type = new ConfigEntityType(['id' => 'entity_test_bundle']);
$user_entity_type = new ContentEntityType(['id' => 'user', 'base_table' => 'users', 'entity_keys' => ['id' => 'uid']]);
$this->entityManager->expects($this->any())
->method('getDefinition')
->willReturnMap([
['entity_test_bundle', TRUE, $entity_test_type],
['user', TRUE, $user_entity_type],
]);
$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->assertBundleField($data['entity_test_mul']['type']);
$this->assertFalse(isset($data['entity_test_mul']['type']['relationship']));
$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->assertEquals('Translation language', $data['entity_test_mul_property_data']['langcode']['title']);
$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 = $this->setupBaseFields(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']);
$this->assertEquals('Original language', $data['entity_test_mulrev_revision']['langcode']['title']);
// 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->assertEquals('Original language', $data['entity_test_mulrev_property_revision']['langcode']['title']);
$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.
*/
protected 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.
*/
protected 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.
*/
protected 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.
*/
protected 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']);
}
/**
* Tests views data for a entity reference field.
*/
protected 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']);
}
/**
* Tests views data for a bundle field.
*/
protected function assertBundleField($data) {
$this->assertEquals('standard', $data['field']['id']);
$this->assertEquals('bundle', $data['filter']['id']);
$this->assertEquals('string', $data['argument']['id']);
$this->assertEquals('standard', $data['sort']['id']);
}
/**
* Returns entity info for the user entity.
*
* @return array
*/
protected 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;
}
}
class TestEntityType extends EntityType {
/**
* Sets a specific entity key.
*
* @param string $key
* The name of the entity key.
* @param string $value
* The new value of the key.
*
* @return $this
*/
public function setKey($key, $value) {
$this->entity_keys[$key] = $value;
return $this;
}
}
}
namespace {
use Drupal\Component\Utility\String;
if (!function_exists('t')) {
function t($string, array $args = []) {
return String::format($string, $args);
}
}
}

View File

@ -26,9 +26,10 @@ use Drupal\Core\Language\LanguageInterface;
* by implementing hook_views_data_alter(). To provide views data for an
* entity, create a class implementing
* \Drupal\views\EntityViewsDataInterface and reference this in the
* "views_data" annotation in the entity class. See the
* @link entity_api Entity API topic @endlink for more information about
* entities.
* "views_data" annotation in the entity class. You can autogenerate big parts
* of the ingration if you extend the \Drupal\views\EntityViewsData base
* class. See the @link entity_api Entity API topic @endlink for more
* information about entities.
* - Implement hooks: A few operations in Views can be influenced by hooks.
* See the @link Views hooks topic @endlink for a list.
* - Theming: See the @link views_templates Views templates topic @endlink