Update to Drupal 8.0.0. For more information, see https://www.drupal.org/node/2619030

This commit is contained in:
Pantheon Automation 2015-11-19 07:04:44 -08:00 committed by Greg Anderson
parent 7784f4c23d
commit 25a6735fb3
49 changed files with 1394 additions and 281 deletions

View file

@ -7,12 +7,12 @@
namespace Drupal\aggregator;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
/**
* Defines an interface for aggregator feed entity storage classes.
*/
interface FeedStorageInterface extends EntityStorageInterface {
interface FeedStorageInterface extends ContentEntityStorageInterface {
/**
* Returns the fids of feeds that need to be refreshed.

View file

@ -7,12 +7,12 @@
namespace Drupal\aggregator;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
/**
* Defines an interface for aggregator item entity storage classes.
*/
interface ItemStorageInterface extends EntityStorageInterface {
interface ItemStorageInterface extends ContentEntityStorageInterface {
/**
* Returns the count of the items in a feed.

View file

@ -36,25 +36,24 @@ class BlockContentViewsData extends EntityViewsData {
),
);
// Advertise this table as a possible base table.
$data['block_content_revision']['table']['base']['help'] = $this->t('Block Content revision is a history of changes to block content.');
$data['block_content_revision']['table']['base']['defaults']['title'] = 'info';
$data['block_content_field_revision']['table']['base']['help'] = $this->t('Block Content revision is a history of changes to block content.');
$data['block_content_field_revision']['table']['base']['defaults']['title'] = 'info';
// @todo EntityViewsData should add these relationships by default.
// https://www.drupal.org/node/2410275
$data['block_content_revision']['id']['relationship']['id'] = 'standard';
$data['block_content_revision']['id']['relationship']['base'] = 'block_content';
$data['block_content_revision']['id']['relationship']['base field'] = 'id';
$data['block_content_revision']['id']['relationship']['title'] = $this->t('Block Content');
$data['block_content_revision']['id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
$data['block_content_field_revision']['id']['relationship']['id'] = 'standard';
$data['block_content_field_revision']['id']['relationship']['base'] = 'block_content_field_data';
$data['block_content_field_revision']['id']['relationship']['base field'] = 'id';
$data['block_content_field_revision']['id']['relationship']['title'] = $this->t('Block Content');
$data['block_content_field_revision']['id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
$data['block_content_revision']['revision_id']['relationship']['id'] = 'standard';
$data['block_content_revision']['revision_id']['relationship']['base'] = 'block_content';
$data['block_content_revision']['revision_id']['relationship']['base field'] = 'revision_id';
$data['block_content_revision']['revision_id']['relationship']['title'] = $this->t('Block Content');
$data['block_content_revision']['revision_id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
$data['block_content_field_revision']['revision_id']['relationship']['id'] = 'standard';
$data['block_content_field_revision']['revision_id']['relationship']['base'] = 'block_content_field_data';
$data['block_content_field_revision']['revision_id']['relationship']['base field'] = 'revision_id';
$data['block_content_field_revision']['revision_id']['relationship']['title'] = $this->t('Block Content');
$data['block_content_field_revision']['revision_id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
return $data;
}
}

View file

@ -60,7 +60,7 @@ class RevisionRelationshipsTest extends ViewTestBase {
$column_map = array(
'revision_id' => 'revision_id',
'id_1' => 'id_1',
'block_content_block_content_revision_id' => 'block_content_block_content_revision_id',
'block_content_field_data_block_content_field_revision_id' => 'block_content_field_data_block_content_field_revision_id',
);
// Here should be two rows.
@ -70,12 +70,12 @@ class RevisionRelationshipsTest extends ViewTestBase {
array(
'revision_id' => '1',
'id_1' => '1',
'block_content_block_content_revision_id' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
),
array(
'revision_id' => '2',
'id_1' => '1',
'block_content_block_content_revision_id' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
),
);
$this->assertIdenticalResultset($view_id, $resultset_id, $column_map);
@ -87,7 +87,7 @@ class RevisionRelationshipsTest extends ViewTestBase {
array(
'revision_id' => '2',
'id_1' => '1',
'block_content_block_content_revision_id' => '1',
'block_content_field_data_block_content_field_revision_id' => '1',
),
);
$this->assertIdenticalResultset($view_revision_id, $resultset_revision_id, $column_map);

View file

@ -8,7 +8,7 @@ label: null
module: views
description: ''
tag: ''
base_table: block_content_revision
base_table: block_content_field_revision
base_field: revision_id
core: '8'
display:
@ -17,28 +17,28 @@ display:
relationships:
id:
id: id
table: block_content_revision
table: block_content_field_revision
field: id
required: true
plugin_id: standard
fields:
revision_id:
id: revision_id
table: block_content_revision
table: block_content_field_revision
field: revision_id
plugin_id: field
entity_type: block_content
entity_field: revision_id
id_1:
id: id_1
table: block_content_revision
table: block_content_field_revision
field: id
plugin_id: field
entity_type: block_content
entity_field: id
id:
id: id
table: block_content
table: block_content_field_data
field: id
relationship: id
plugin_id: field
@ -47,11 +47,20 @@ display:
arguments:
id:
id: id
table: block_content_revision
table: block_content_field_revision
field: id
plugin_id: numeric
entity_type: block_content
entity_field: id
sorts:
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
order: ASC
plugin_id: field
entity_type: block_content
entity_field: revision_id
display_plugin: default
display_title: Master
id: default

View file

@ -8,7 +8,7 @@ label: null
module: views
description: ''
tag: ''
base_table: block_content_revision
base_table: block_content_field_revision
base_field: revision_id
core: '8'
display:
@ -17,7 +17,7 @@ display:
relationships:
revision_id:
id: revision_id
table: block_content_revision
table: block_content_field_revision
field: revision_id
required: true
entity_type: block_content
@ -26,21 +26,21 @@ display:
fields:
revision_id:
id: revision_id
table: block_content_revision
table: block_content_field_revision
field: revision_id
plugin_id: field
entity_type: block_content
entity_field: revision_id
id_1:
id: id_1
table: block_content_revision
table: block_content_field_revision
field: id
plugin_id: field
entity_type: block_content
entity_field: id
id:
id: id
table: block_content
table: block_content_field_data
field: id
relationship: revision_id
plugin_id: field
@ -49,11 +49,20 @@ display:
arguments:
id:
id: id
table: block_content_revision
table: block_content_field_revision
field: id
plugin_id: block_content_id
entity_type: block_content
entity_field: id
sorts:
revision_id:
id: revision_id
table: block_content_field_revision
field: revision_id
order: ASC
plugin_id: field
entity_type: block_content
entity_field: revision_id
display_extenders: { }
display_plugin: default
display_title: Master

View file

@ -8,13 +8,13 @@
namespace Drupal\comment;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
/**
* Defines an interface for comment entity storage classes.
*/
interface CommentStorageInterface extends EntityStorageInterface {
interface CommentStorageInterface extends ContentEntityStorageInterface {
/**
* Gets the maximum encoded thread value for the top level comments.

View file

@ -338,7 +338,7 @@ display:
arguments:
nid:
id: nid
table: node
table: node_field_data
field: nid
relationship: node
group_type: group

View file

@ -8,12 +8,24 @@
namespace Drupal\config\Form;
use Drupal\Component\Serialization\Yaml;
use Drupal\config\StorageReplaceDataWrapper;
use Drupal\Core\Config\ConfigImporter;
use Drupal\Core\Config\ConfigImporterException;
use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageComparer;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Provides a form for importing a single configuration file.
@ -34,6 +46,62 @@ class ConfigSingleImportForm extends ConfirmFormBase {
*/
protected $configStorage;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The configuration manager.
*
* @var \Drupal\Core\Config\ConfigManagerInterface;
*/
protected $configManager;
/**
* The database lock object.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The typed config manager.
*
* @var \Drupal\Core\Config\TypedConfigManagerInterface
*/
protected $typedConfigManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The theme handler.
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* The module installer.
*
* @var \Drupal\Core\Extension\ModuleInstallerInterface
*/
protected $moduleInstaller;
/**
* If the config exists, this is that object. Otherwise, FALSE.
*
@ -55,10 +123,36 @@ class ConfigSingleImportForm extends ConfirmFormBase {
* The entity manager.
* @param \Drupal\Core\Config\StorageInterface $config_storage
* The config storage.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
* The event dispatcher used to notify subscribers of config import events.
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
* The configuration manager.
* @param \Drupal\Core\Lock\LockBackendInterface $lock
* The lock backend to ensure multiple imports do not occur at the same time.
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
* The typed configuration manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
* The module installer.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage) {
public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler) {
$this->entityManager = $entity_manager;
$this->configStorage = $config_storage;
$this->renderer = $renderer;
// Services necessary for \Drupal\Core\Config\ConfigImporter.
$this->eventDispatcher = $event_dispatcher;
$this->configManager = $config_manager;
$this->lock = $lock;
$this->typedConfigManager = $typed_config;
$this->moduleHandler = $module_handler;
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
}
/**
@ -67,7 +161,15 @@ class ConfigSingleImportForm extends ConfirmFormBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager'),
$container->get('config.storage')
$container->get('config.storage'),
$container->get('renderer'),
$container->get('event_dispatcher'),
$container->get('config.manager'),
$container->get('lock.persistent'),
$container->get('config.typed'),
$container->get('module_handler'),
$container->get('module_installer'),
$container->get('theme_handler')
);
}
@ -204,6 +306,8 @@ class ConfigSingleImportForm extends ConfirmFormBase {
$form_state->setErrorByName('import', $this->t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => $id_key, '@entity_type' => $definition->getLabel())));
return;
}
$config_name = $definition->getConfigPrefix() . '.' . $data[$id_key];
// If there is an existing entity, ensure matching ID and UUID.
if ($entity = $entity_storage->load($data[$id_key])) {
$this->configExists = $entity;
@ -222,10 +326,53 @@ class ConfigSingleImportForm extends ConfirmFormBase {
}
}
else {
$config = $this->config($form_state->getValue('config_name'));
$config_name = $form_state->getValue('config_name');
$config = $this->config($config_name);
$this->configExists = !$config->isNew() ? $config : FALSE;
}
// Use ConfigImporter validation.
if (!$form_state->getErrors()) {
$source_storage = new StorageReplaceDataWrapper($this->configStorage);
$source_storage->replaceData($config_name, $data);
$storage_comparer = new StorageComparer(
$source_storage,
$this->configStorage,
$this->configManager
);
if (!$storage_comparer->createChangelist()->hasChanges()) {
$form_state->setErrorByName('import', $this->t('There are no changes to import.'));
}
else {
$config_importer = new ConfigImporter(
$storage_comparer,
$this->eventDispatcher,
$this->configManager,
$this->lock,
$this->typedConfigManager,
$this->moduleHandler,
$this->moduleInstaller,
$this->themeHandler,
$this->getStringTranslation()
);
try {
$config_importer->validate();
$form_state->set('config_importer', $config_importer);
}
catch (ConfigImporterException $e) {
// There are validation errors.
$item_list = [
'#theme' => 'item_list',
'#items' => $config_importer->getErrors(),
'#title' => $this->t('The configuration cannot be imported because it failed validation for the following reasons:'),
];
$form_state->setErrorByName('import', $this->renderer->render($item_list));
}
}
}
// Store the decoded version of the submitted import.
$form_state->setValueForElement($form['import'], $data);
}
@ -241,26 +388,34 @@ class ConfigSingleImportForm extends ConfirmFormBase {
return;
}
// If a simple configuration file was added, set the data and save.
if ($this->data['config_type'] === 'system.simple') {
$this->configFactory()->getEditable($this->data['config_name'])->setData($this->data['import'])->save();
drupal_set_message($this->t('The %name configuration was imported.', array('%name' => $this->data['config_name'])));
/** @var \Drupal\Core\Config\ConfigImporter $config_importer */
$config_importer = $form_state->get('config_importer');
if ($config_importer->alreadyImporting()) {
drupal_set_message($this->t('Another request may be importing configuration already.'), 'error');
}
// For a config entity, create an entity and save it.
else {
else{
try {
$entity_storage = $this->entityManager->getStorage($this->data['config_type']);
if ($this->configExists) {
$entity = $entity_storage->updateFromStorageRecord($this->configExists, $this->data['import']);
$sync_steps = $config_importer->initialize();
$batch = [
'operations' => [],
'finished' => [ConfigSync::class, 'finishBatch'],
'title' => $this->t('Importing configuration'),
'init_message' => $this->t('Starting configuration import.'),
'progress_message' => $this->t('Completed @current step of @total.'),
'error_message' => $this->t('Configuration import has encountered an error.'),
];
foreach ($sync_steps as $sync_step) {
$batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]];
}
else {
$entity = $entity_storage->createFromStorageRecord($this->data['import']);
}
$entity->save();
drupal_set_message($this->t('The @entity_type %label was imported.', array('@entity_type' => $entity->getEntityTypeId(), '%label' => $entity->label())));
batch_set($batch);
}
catch (\Exception $e) {
drupal_set_message($e->getMessage(), 'error');
catch (ConfigImporterException $e) {
// There are validation errors.
drupal_set_message($this->t('The configuration import failed for the following reasons:'), 'error');
foreach ($config_importer->getErrors() as $message) {
drupal_set_message($message, 'error');
}
}
}
}

View file

@ -0,0 +1,204 @@
<?php
/**
* @file
* Contains \Drupal\config\StorageReplaceDataWrapper.
*/
namespace Drupal\config;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
/**
* Wraps a configuration storage to allow replacing specific configuration data.
*/
class StorageReplaceDataWrapper implements StorageInterface {
use DependencySerializationTrait;
/**
* The configuration storage to be wrapped.
*
* @var \Drupal\Core\Config\StorageInterface
*/
protected $storage;
/**
* The configuration replacement data, keyed by configuration object name.
*
* @var array
*/
protected $replacementData = [];
/**
* The storage collection.
*
* @var string
*/
protected $collection;
/**
* Constructs a new StorageReplaceDataWrapper.
*
* @param \Drupal\Core\Config\StorageInterface $storage
* A configuration storage to be used to read and write configuration.
* @param string $collection
* (optional) The collection to store configuration in. Defaults to the
* default collection.
*/
public function __construct(StorageInterface $storage, $collection = StorageInterface::DEFAULT_COLLECTION) {
$this->storage = $storage;
$this->collection = $collection;
}
/**
* {@inheritdoc}
*/
public function exists($name) {
return isset($this->replacementData[$this->collection][$name]) || $this->storage->exists($name);
}
/**
* {@inheritdoc}
*/
public function read($name) {
if (isset($this->replacementData[$this->collection][$name])) {
return $this->replacementData[$this->collection][$name];
}
return $this->storage->read($name);
}
/**
* {@inheritdoc}
*/
public function readMultiple(array $names) {
$data = $this->storage->readMultiple(($names));
foreach ($names as $name) {
if (isset($this->replacementData[$this->collection][$name])) {
$data[$name] = $this->replacementData[$this->collection][$name];
}
}
return $data;
}
/**
* {@inheritdoc}
*/
public function write($name, array $data) {
if (isset($this->replacementData[$this->collection][$name])) {
unset($this->replacementData[$this->collection][$name]);
}
return $this->storage->write($name, $data);
}
/**
* {@inheritdoc}
*/
public function delete($name) {
if (isset($this->replacementData[$this->collection][$name])) {
unset($this->replacementData[$this->collection][$name]);
}
return $this->storage->delete($name);
}
/**
* {@inheritdoc}
*/
public function rename($name, $new_name) {
if (isset($this->replacementData[$this->collection][$name])) {
$this->replacementData[$this->collection][$new_name] = $this->replacementData[$this->collection][$name];
unset($this->replacementData[$this->collection][$name]);
}
return $this->rename($name, $new_name);
}
/**
* {@inheritdoc}
*/
public function encode($data) {
return $this->storage->encode($data);
}
/**
* {@inheritdoc}
*/
public function decode($raw) {
return $this->storage->decode($raw);
}
/**
* {@inheritdoc}
*/
public function listAll($prefix = '') {
$names = $this->storage->listAll($prefix);
$additional_names = [];
if ($prefix === '') {
$additional_names = array_keys($this->replacementData[$this->collection]);
}
else {
foreach (array_keys($this->replacementData[$this->collection]) as $name) {
if (strpos($name, $prefix) === 0) {
$additional_names[] = $name;
}
}
}
if (!empty($additional_names)) {
$names = array_unique(array_merge($names, $additional_names));
}
return $names;
}
/**
* {@inheritdoc}
*/
public function deleteAll($prefix = '') {
if ($prefix === '') {
$this->replacementData[$this->collection] = [];
}
else {
foreach (array_keys($this->replacementData[$this->collection]) as $name) {
if (strpos($name, $prefix) === 0) {
unset($this->replacementData[$this->collection][$name]);
}
}
}
return $this->storage->deleteAll($prefix);
}
/**
* {@inheritdoc}
*/
public function createCollection($collection) {
$this->collection = $collection;
return $this->storage->createCollection($collection);
}
/**
* {@inheritdoc}
*/
public function getAllCollectionNames() {
return $this->storage->getAllCollectionNames();
}
/**
* {@inheritdoc}
*/
public function getCollectionName() {
return $this->collection;
}
/**
* Replaces the configuration object data with the supplied data.
*
* @param $name
* The configuration object name whose data to replace.
* @param array $data
* The configuration data.
*
* @return $this
*/
public function replaceData($name, array $data) {
$this->replacementData[$this->collection][$name] = $data;
return $this;
}
}

View file

@ -66,7 +66,7 @@ EOD;
$this->assertIdentical($entity->label(), 'First');
$this->assertIdentical($entity->id(), 'first');
$this->assertTrue($entity->status());
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
$this->assertRaw(t('The configuration was imported successfully.'));
// Attempt an import with an existing ID but missing UUID.
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
@ -82,8 +82,7 @@ EOD;
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Are you sure you want to create a new %name @type?', array('%name' => 'custom_id', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('custom_id');
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
$this->assertRaw(t('The configuration was imported successfully.'));
// Perform an import with a unique ID and UUID.
$import = <<<EOD
@ -103,7 +102,7 @@ EOD;
$this->assertRaw(t('Are you sure you want to create a new %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('second');
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
$this->assertRaw(t('The configuration was imported successfully.'));
$this->assertIdentical($entity->label(), 'Second');
$this->assertIdentical($entity->id(), 'second');
$this->assertFalse($entity->status());
@ -126,8 +125,27 @@ EOD;
$this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('second');
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
$this->assertRaw(t('The configuration was imported successfully.'));
$this->assertIdentical($entity->label(), 'Second updated');
// Try to perform an update which adds missing dependencies.
$import = <<<EOD
id: second
uuid: $second_uuid
label: 'Second updated'
weight: 0
style: ''
status: '0'
dependencies:
module:
- does_not_exist
EOD;
$edit = array(
'config_type' => 'config_test',
'import' => $import,
);
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertRaw(t('Configuration %name depends on the %owner module that will not be installed after import.', ['%name' => 'config_test.dynamic.second', '%owner' => 'does_not_exist']));
}
/**
@ -150,6 +168,20 @@ EOD;
$this->drupalPostForm(NULL, array(), t('Confirm'));
$this->drupalGet('');
$this->assertText('Test simple import');
// Ensure that ConfigImporter validation is running when importing simple
// configuration.
$config_data = $this->config('core.extension')->get();
// Simulate uninstalling the Config module.
unset($config_data['module']['config']);
$edit = array(
'config_type' => 'system.simple',
'config_name' => 'core.extension',
'import' => Yaml::encode($config_data),
);
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
$this->assertText(t('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.'));
}
/**

View file

@ -27,7 +27,7 @@ display:
field: nid
id: nid
relationship: none
table: node
table: node_field_data
plugin_id: numeric
pager:
options:
@ -39,7 +39,7 @@ display:
id: nid
order: ASC
relationship: none
table: node
table: node_field_data
plugin_id: numeric
display_plugin: default
display_title: Master

View file

@ -24,7 +24,7 @@ display:
nid:
field: nid
id: nid
table: node
table: node_field_data
plugin_id: node
filters:
field_date_value:
@ -38,7 +38,7 @@ display:
id: nid
order: ASC
relationship: none
table: node
table: node_field_data
plugin_id: numeric
pager:
type: full

View file

@ -24,7 +24,7 @@ display:
nid:
field: nid
id: nid
table: node
table: node_field_data
plugin_id: node
sorts:
field_date_value:
@ -39,7 +39,7 @@ display:
id: nid
order: ASC
relationship: none
table: node
table: node_field_data
plugin_id: numeric
pager:
type: full

View file

@ -9,7 +9,7 @@ label: test_view_fieldapi
module: views
description: ''
tag: default
base_table: node
base_table: node_field_data
base_field: nid
core: '8'
display:
@ -21,7 +21,7 @@ display:
nid:
field: nid
id: nid
table: node
table: node_field_data
plugin_id: field
entity_type: node
entity_field: nid

View file

@ -7,12 +7,12 @@
namespace Drupal\file;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
/**
* Defines an interface for file entity storage classes.
*/
interface FileStorageInterface extends EntityStorageInterface {
interface FileStorageInterface extends ContentEntityStorageInterface {
/**
* Determines total disk space used by a single user or the whole filesystem.

View file

@ -7,14 +7,14 @@
namespace Drupal\node;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines an interface for node entity storage classes.
*/
interface NodeStorageInterface extends EntityStorageInterface {
interface NodeStorageInterface extends ContentEntityStorageInterface {
/**
* Gets a list of node revision IDs for a specific node.

View file

@ -240,7 +240,7 @@ class NodeViewsData extends EntityViewsData {
'title' => t('Content'),
'label' => t('Get the actual content from a content revision.'),
),
) + $data['node_revision']['vid'];
) + $data['node_field_revision']['vid'];
$data['node_field_revision']['langcode']['help'] = t('The language the original content is in.');

View file

@ -56,7 +56,7 @@ display:
fields:
nid:
id: nid
table: node
table: node_field_data
field: nid
relationship: none
group_type: group

View file

@ -41,7 +41,7 @@ display:
fields:
nid:
id: nid
table: node
table: node_field_data
field: nid
plugin_id: field
entity_type: node

View file

@ -41,7 +41,7 @@ display:
fields:
nid:
id: nid
table: node
table: node_field_data
field: nid
plugin_id: field
entity_type: node

View file

@ -328,6 +328,7 @@ class EntityTranslationTest extends EntityLanguageTestBase {
// Verify that we obtain the entity object itself when we attempt to
// retrieve a translation referring to it.
$translation = $entity->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED);
$this->assertFalse($translation->isNewTranslation(), 'Existing translations are not marked as new.');
$this->assertIdentical($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
$entity->{$langcode_key}->value = $default_langcode;
$translation = $entity->getTranslation($default_langcode);
@ -353,6 +354,7 @@ class EntityTranslationTest extends EntityLanguageTestBase {
$entity->name->value = $name;
$name_translated = $langcode . '_' . $this->randomMachineName();
$translation = $entity->addTranslation($langcode);
$this->assertTrue($translation->isNewTranslation(), 'Newly added translations are marked as new.');
$this->assertNotIdentical($entity, $translation, 'The entity and the translation object differ from one another.');
$this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.');
$this->assertEqual($translation->language()->getId(), $langcode, 'The translation language matches the specified one.');

View file

@ -11,7 +11,7 @@
function keyvalue_test_entity_type_alter(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
if (isset($entity_types['entity_test_label'])) {
$entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage');
$entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueContentEntityStorage');
$entity_keys = $entity_types['entity_test_label']->getKeys();
$entity_types['entity_test_label']->set('entity_keys', $entity_keys + array('uuid' => 'uuid'));
}

View file

@ -8,12 +8,12 @@
namespace Drupal\taxonomy;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
/**
* Defines an interface for taxonomy_term entity storage classes.
*/
interface TermStorageInterface extends EntityStorageInterface {
interface TermStorageInterface extends ContentEntityStorageInterface {
/**
* Removed reference to terms from term_hierarchy.

View file

@ -59,7 +59,7 @@ display:
subquery_namespace: ''
subquery_order: DESC
subquery_regenerate: true
subquery_sort: node.nid
subquery_sort: node_field_data.nid
subquery_view: ''
table: taxonomy_term_field_data
plugin_id: groupwise_max

View file

@ -156,7 +156,7 @@ display:
plugin_id: field
nid:
id: nid
table: node
table: node_field_data
field: nid
entity_type: node
entity_field: nid

View file

@ -7,13 +7,13 @@
namespace Drupal\user;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines an interface for user entity storage classes.
*/
interface UserStorageInterface extends EntityStorageInterface{
interface UserStorageInterface extends ContentEntityStorageInterface {
/**
* Update the last login timestamp of the user.

View file

@ -116,11 +116,29 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
public function getViewsData() {
$data = [];
$base_table = $this->entityType->getBaseTable();
$base_table = $this->entityType->getBaseTable() ?: $this->entityType->id();
$revisionable = $this->entityType->isRevisionable();
$base_field = $this->entityType->getKey('id');
$data_table = $this->entityType->getDataTable();
$revision_table = $this->entityType->getRevisionTable();
$revision_data_table = $this->entityType->getRevisionDataTable();
$revision_table = '';
if ($revisionable) {
$revision_table = $this->entityType->getRevisionTable() ?: $this->entityType->id() . '_revision';
}
$translatable = $this->entityType->isTranslatable();
$data_table = '';
if ($translatable) {
$data_table = $this->entityType->getDataTable() ?: $this->entityType->id() . '_field_data';
}
// Some entity types do not have a revision data table defined, but still
// have a revision table name set in
// \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() so we
// apply the same kind of logic.
$revision_data_table = '';
if ($revisionable && $translatable) {
$revision_data_table = $this->entityType->getRevisionDataTable() ?: $this->entityType->id() . '_field_revision';
}
$revision_field = $this->entityType->getKey('revision');
// Setup base information of the views data.
@ -213,6 +231,10 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
// the entity base, revision, data tables.
$field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id());
if ($table_mapping = $this->storage->getTableMapping()) {
// Fetch all fields that can appear in both the base table and the data
// table.
$entity_keys = $this->entityType->getKeys();
$duplicate_fields = array_intersect_key($entity_keys, array_flip(['id', 'revision', 'bundle']));
// 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.
@ -221,6 +243,12 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
// @todo https://www.drupal.org/node/2337511
foreach ($table_mapping->getTableNames() as $table) {
foreach ($table_mapping->getFieldNames($table) as $field_name) {
// To avoid confusing duplication in the user interface, for fields
// that are on both base and data tables, only add them on the data
// table (same for revision vs. revision data).
if ($data_table && ($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields)) {
continue;
}
$this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]);
}
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @file
* Contains \Drupal\views\Tests\Update\FieldHandlersUpdateTest.
*/
namespace Drupal\views\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\views\Entity\View;
/**
* Tests the upgrade path for views field handlers.
*
* @see views_post_update_cleanup_duplicate_views_data()
*
* @group Update
*/
class FieldHandlersUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../tests/fixtures/update/duplicate-field-handler.php',
];
}
/**
* Tests that field handlers are updated properly.
*/
public function testViewsUpdate8004() {
$this->runUpdates();
// Load and initialize our test view.
$view = View::load('test_duplicate_field_handlers');
$data = $view->toArray();
// Check that the field is using the expected base table.
$this->assertEqual('node_field_data', $data['display']['default']['display_options']['fields']['nid']['table']);
$this->assertEqual('node_field_data', $data['display']['default']['display_options']['filters']['type']['table']);
$this->assertEqual('node_field_data', $data['display']['default']['display_options']['sorts']['vid']['table']);
$this->assertEqual('node_field_data', $data['display']['default']['display_options']['arguments']['nid']['table']);
}
}

View file

@ -0,0 +1,11 @@
<?php
$connection = Drupal\Core\Database\Database::getConnection();
$connection->insert('config')
->fields(array(
'collection' => '',
'name' => 'views.view.test_duplicate_field_handlers',
'data' => serialize(\Drupal\Component\Serialization\Yaml::decode(file_get_contents('core/modules/views/tests/modules/views_test_config/test_views/views.view.test_duplicate_field_handlers.yml'))),
))
->execute();

View file

@ -0,0 +1,246 @@
langcode: en
status: true
dependencies:
config:
- node.type.article
module:
- node
- user
id: test_duplicate_field_handlers
label: 'Test Duplicate Field Handlers'
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: none
options:
items_per_page: null
offset: 0
style:
type: default
row:
type: fields
fields:
nid:
id: nid
table: node
field: nid
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: number_integer
settings:
thousand_separator: ''
prefix_suffix: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: node
entity_field: nid
plugin_id: field
filters:
type:
id: type
table: node
field: type
relationship: none
group_type: group
admin_label: ''
operator: in
value:
article: article
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: node
entity_field: type
plugin_id: bundle
sorts:
vid:
id: vid
table: node
field: vid
relationship: none
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
entity_type: node
entity_field: vid
plugin_id: standard
title: 'Test Duplicate Field Handlers'
header: { }
footer: { }
empty: { }
relationships: { }
arguments:
nid:
id: nid
table: node
field: nid
relationship: none
group_type: group
admin_label: ''
default_action: ignore
exception:
value: all
title_enable: false
title: All
title_enable: false
title: ''
default_argument_type: fixed
default_argument_options:
argument: ''
default_argument_skip_url: false
summary_options:
base_path: ''
count: true
items_per_page: 25
override: false
summary:
sort_order: asc
number_of_records: 0
format: default_summary
specify_validation: false
validate:
type: none
fail: 'not found'
validate_options: { }
break_phrase: false
not: false
entity_type: node
entity_field: nid
plugin_id: numeric
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- 'user.node_grants:view'
- user.permissions
cacheable: false
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test-duplicate-field-handlers
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- 'user.node_grants:view'
- user.permissions
cacheable: false

View file

@ -130,7 +130,7 @@ display:
fields:
nid:
id: nid
table: node
table: node_field_data
field: nid
relationship: none
group_type: group

View file

@ -93,7 +93,12 @@ class EntityViewsDataTest extends UnitTestCase {
'base_table' => 'entity_test',
'id' => 'entity_test',
'label' => 'Entity test',
'entity_keys' => ['id' => 'id', 'langcode' => 'langcode'],
'entity_keys' => [
'id' => 'id',
'langcode' => 'langcode',
'bundle' => 'type',
'revision' => 'revision_id',
],
'provider' => 'entity_test',
'list_cache_contexts' => ['entity_test_list_cache_context'],
]);
@ -190,6 +195,7 @@ class EntityViewsDataTest extends UnitTestCase {
$entity_type = $this->baseEntityType
->set('data_table', 'entity_test_mul_property_data')
->set('id', 'entity_test_mul')
->set('translatable', TRUE)
->setKey('label', 'label');
$this->viewsData->setEntityType($entity_type);
@ -259,6 +265,7 @@ class EntityViewsDataTest extends UnitTestCase {
->set('revision_table', 'entity_test_mulrev_revision')
->set('revision_data_table', 'entity_test_mulrev_property_revision')
->set('id', 'entity_test_mulrev')
->set('translatable', TRUE)
->setKey('revision', 'revision_id')
;
$this->viewsData->setEntityType($entity_type);
@ -297,6 +304,7 @@ class EntityViewsDataTest extends UnitTestCase {
->set('revision_table', 'entity_test_mulrev_revision')
->set('revision_data_table', 'entity_test_mulrev_property_revision')
->set('id', 'entity_test_mulrev')
->set('translatable', TRUE)
->setKey('revision', 'revision_id')
;
$this->viewsData->setEntityType($entity_type);
@ -316,7 +324,7 @@ class EntityViewsDataTest extends UnitTestCase {
$revision_data = $data['entity_test_mulrev_property_revision'];
$this->assertCount(2, $revision_data['table']['join']);
$this->assertEquals([
'entity_test' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER'],
'entity_test_mulrev_field_data' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER'],
'entity_test_mulrev_revision' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER'],
], $revision_data['table']['join']);
$this->assertFalse(isset($data['data_table']));
@ -526,10 +534,19 @@ class EntityViewsDataTest extends UnitTestCase {
$table_mapping->expects($this->any())
->method('getFieldNames')
->willReturnMap([
['entity_test_mul', ['id', 'uuid', 'type', 'langcode']],
['entity_test_mul_property_data', ['id', 'langcode', 'name', 'description', 'homepage', 'user_id']],
['entity_test_mul', ['uuid']],
['entity_test_mul_property_data', ['id', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']],
]);
$table_mapping->expects($this->any())
->method('getFieldTableName')
->willReturnCallback(function($field) {
if ($field == 'uuid') {
return 'entity_test_mul';
}
return 'entity_test_mul_property_data';
});
$this->entityStorage->expects($this->once())
->method('getTableMapping')
->willReturn($table_mapping);
@ -547,17 +564,13 @@ class EntityViewsDataTest extends UnitTestCase {
$data = $this->viewsData->getViewsData();
// Check the base fields.
$this->assertNumericField($data['entity_test_mul']['id']);
$this->assertField($data['entity_test_mul']['id'], 'id');
$this->assertFalse(isset($data['entity_test_mul']['id']));
$this->assertFalse(isset($data['entity_test_mul']['type']));
$this->assertUuidField($data['entity_test_mul']['uuid']);
$this->assertField($data['entity_test_mul']['uuid'], 'uuid');
$this->assertBundleField($data['entity_test_mul']['type']);
$this->assertField($data['entity_test_mul']['type'], 'type');
$this->assertFalse(isset($data['entity_test_mul']['type']['relationship']));
$this->assertLanguageField($data['entity_test_mul']['langcode']);
$this->assertField($data['entity_test_mul']['langcode'], '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']['description']));
@ -570,6 +583,9 @@ class EntityViewsDataTest extends UnitTestCase {
$this->assertNumericField($data['entity_test_mul_property_data']['id']);
$this->assertField($data['entity_test_mul_property_data']['id'], 'id');
$this->assertBundleField($data['entity_test_mul_property_data']['type']);
$this->assertField($data['entity_test_mul_property_data']['type'], 'type');
$this->assertLanguageField($data['entity_test_mul_property_data']['langcode']);
$this->assertField($data['entity_test_mul_property_data']['langcode'], 'langcode');
$this->assertEquals('Translation language', $data['entity_test_mul_property_data']['langcode']['title']);
@ -600,7 +616,8 @@ class EntityViewsDataTest extends UnitTestCase {
->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');
->set('id', 'entity_test_mulrev')
->set('translatable', TRUE);
$base_field_definitions = $this->setupBaseFields(EntityTestMulRev::baseFieldDefinitions($this->baseEntityType));
$user_base_field_definitions = [
'uid' => BaseFieldDefinition::create('integer')
@ -645,6 +662,15 @@ class EntityViewsDataTest extends UnitTestCase {
['entity_test_mulrev_property_revision', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']],
]);
$table_mapping->expects($this->any())
->method('getFieldTableName')
->willReturnCallback(function($field) {
if ($field == 'uuid') {
return 'entity_test_mulrev';
}
return 'entity_test_mulrev_property_data';
});
$this->entityStorage->expects($this->once())
->method('getTableMapping')
->willReturn($table_mapping);
@ -654,14 +680,11 @@ class EntityViewsDataTest extends UnitTestCase {
$data = $this->viewsData->getViewsData();
// Check the base fields.
$this->assertNumericField($data['entity_test_mulrev']['id']);
$this->assertField($data['entity_test_mulrev']['id'], 'id');
$this->assertNumericField($data['entity_test_mulrev']['revision_id']);
$this->assertField($data['entity_test_mulrev']['revision_id'], 'revision_id');
$this->assertFalse(isset($data['entity_test_mulrev']['id']));
$this->assertFalse(isset($data['entity_test_mulrev']['type']));
$this->assertFalse(isset($data['entity_test_mulrev']['revision_id']));
$this->assertUuidField($data['entity_test_mulrev']['uuid']);
$this->assertField($data['entity_test_mulrev']['uuid'], 'uuid');
$this->assertStringField($data['entity_test_mulrev']['type']);
$this->assertField($data['entity_test_mulrev']['type'], 'type');
// Also ensure that field_data only fields don't appear on the base table.
$this->assertFalse(isset($data['entity_test_mulrev']['name']));
@ -672,17 +695,12 @@ class EntityViewsDataTest extends UnitTestCase {
$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->assertField($data['entity_test_mulrev_revision']['id'], 'id');
$this->assertNumericField($data['entity_test_mulrev_revision']['revision_id']);
$this->assertField($data['entity_test_mulrev_revision']['revision_id'], 'revision_id');
$this->assertLanguageField($data['entity_test_mulrev_revision']['langcode']);
$this->assertField($data['entity_test_mulrev_revision']['langcode'], 'langcode');
$this->assertEquals('Original language', $data['entity_test_mulrev_revision']['langcode']['title']);
// Check the revision fields. The revision ID should only appear in the data
// table.
$this->assertFalse(isset($data['entity_test_mulrev_revision']['revision_id']));
// Also ensure that field_data only fields don't appear on the revision table.
$this->assertFalse(isset($data['entity_test_mulrev_revision']['id']));
$this->assertFalse(isset($data['entity_test_mulrev_revision']['name']));
$this->assertFalse(isset($data['entity_test_mulrev_revision']['description']));
$this->assertFalse(isset($data['entity_test_mulrev_revision']['description__value']));
@ -693,6 +711,8 @@ class EntityViewsDataTest extends UnitTestCase {
// Check the data fields.
$this->assertNumericField($data['entity_test_mulrev_property_data']['id']);
$this->assertField($data['entity_test_mulrev_property_data']['id'], 'id');
$this->assertNumericField($data['entity_test_mulrev_property_data']['revision_id']);
$this->assertField($data['entity_test_mulrev_property_data']['revision_id'], 'revision_id');
$this->assertLanguageField($data['entity_test_mulrev_property_data']['langcode']);
$this->assertField($data['entity_test_mulrev_property_data']['langcode'], 'langcode');
$this->assertStringField($data['entity_test_mulrev_property_data']['name']);

View file

@ -5,6 +5,9 @@
* Post update functions for Views.
*/
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\views\Views;
/**
* @addtogroup updates-8.0.0-beta
* @{
@ -31,6 +34,99 @@ function views_post_update_update_cacheability_metadata() {
}
/**
* Update some views fields that were previously duplicated.
*/
function views_post_update_cleanup_duplicate_views_data() {
$config_factory = \Drupal::configFactory();
$ids = [];
$message = NULL;
$data_tables = [];
$base_tables = [];
$revision_tables = [];
$entities_by_table = [];
$duplicate_fields = [];
$handler_types = Views::getHandlerTypes();
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = \Drupal::service('entity_type.manager');
// This will allow us to create an index of all entity types of the site.
foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) {
// Store the entity keyed by base table. If it has a data table, use that as
// well.
if ($data_table = $entity_type->getDataTable()) {
$entities_by_table[$data_table] = $entity_type;
}
if ($base_table = $entity_type->getBaseTable()) {
$entities_by_table[$base_table] = $entity_type;
}
// The following code basically contains the same kind of logic as
// \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() to
// prefetch all tables (base, data, revision, and revision data).
$base_tables[$entity_type_id] = $entity_type->getBaseTable() ?: $entity_type->id();
$revisionable = $entity_type->isRevisionable();
$revision_table = '';
if ($revisionable) {
$revision_table = $entity_type->getRevisionTable() ?: $entity_type->id() . '_revision';
}
$revision_tables[$entity_type_id] = $revision_table;
$translatable = $entity_type->isTranslatable();
$data_table = '';
// For example the data table just exists, when the entity type is
// translatable.
if ($translatable) {
$data_table = $entity_type->getDataTable() ?: $entity_type->id() . '_field_data';
}
$data_tables[$entity_type_id] = $data_table;
$duplicate_fields[$entity_type_id] = array_intersect_key($entity_type->getKeys(), array_flip(['id', 'revision', 'bundle']));
}
foreach ($config_factory->listAll('views.view.') as $view_config_name) {
$changed = FALSE;
$view = $config_factory->getEditable($view_config_name);
$displays = $view->get('display');
if (isset($entities_by_table[$view->get('base_table')])) {
$entity_type = $entities_by_table[$view->get('base_table')];
$entity_type_id = $entity_type->id();
$data_table = $data_tables[$entity_type_id];
$base_table = $base_tables[$entity_type_id];
$revision_table = $revision_tables[$entity_type_id];
if ($data_table) {
foreach ($displays as $display_name => &$display) {
foreach ($handler_types as $handler_type) {
if (!empty($display['display_options'][$handler_type['plural']])) {
foreach ($display['display_options'][$handler_type['plural']] as $field_name => &$field) {
$table = $field['table'];
if (($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields[$entity_type_id])) {
$field['table'] = $data_table;
$changed = TRUE;
}
}
}
}
}
}
}
if ($changed) {
$view->set('display', $displays);
$view->save();
$ids[] = $view->get('id');
}
}
if (!empty($ids)) {
$message = new TranslatableMarkup('Updated tables for field handlers for views: @ids', ['@ids' => implode(', ', array_unique($ids))]);
}
return $message;
}
/**
* @} End of "addtogroup updates-8.0.0-beta".
*/

View file

@ -10,6 +10,7 @@ namespace Drupal\views_ui\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\ViewExecutable;
/**
@ -20,12 +21,17 @@ use Drupal\views\ViewExecutable;
*/
class HandlerTest extends UITestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('node_test_views');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_view_empty', 'test_view_broken', 'node');
public static $testViews = array('test_view_empty', 'test_view_broken', 'node', 'test_node_view');
/**
* {@inheritdoc}
@ -34,6 +40,7 @@ class HandlerTest extends UITestBase {
parent::setUp();
$this->drupalPlaceBlock('page_title_block');
ViewTestData::createTestViews(get_class($this), array('node_test_views'));
}
/**
@ -218,4 +225,37 @@ class HandlerTest extends UITestBase {
}
}
/**
* Ensures that neither node type or node ID appears multiple times.
*
* @see \Drupal\views\EntityViewsData
*/
public function testNoDuplicateFields() {
$handler_types = ['field', 'filter', 'sort', 'argument'];
foreach ($handler_types as $handler_type) {
$add_handler_url = 'admin/structure/views/nojs/add-handler/test_node_view/default/' . $handler_type;
$this->drupalGet($add_handler_url);
$this->assertNoDuplicateField('Node ID', 'Content');
$this->assertNoDuplicateField('Node ID', 'Content revision');
$this->assertNoDuplicateField('Type', 'Content');
$this->assertNoDuplicateField('UUID', 'Content');
$this->assertNoDuplicateField('Revision ID', 'Content');
$this->assertNoDuplicateField('Revision ID', 'Content revision');
}
}
/**
* Asserts that fields only appear once.
*
* @param string $field_name
* The field name.
* @param string $entity_type
* The entity type to which the field belongs.
*/
public function assertNoDuplicateField($field_name, $entity_type) {
$elements = $this->xpath('//td[.=:entity_type]/preceding-sibling::td[@class="title" and .=:title]', [':title' => $field_name, ':entity_type' => $entity_type]);
$this->assertEqual(1, count($elements), $field_name . ' appears just once in ' . $entity_type . '.');
}
}