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

@ -1,8 +1,9 @@
Drupal 8.0.x, xxxx-xx-xx (development version)
----------------------
- Dramatically improved the front end:
Drupal 8.0.0, 2015-11-19
------------------------
- Significantly improved the front end:
* Made all built-in themes responsive.
* Added support for responsive images.
* Made built-in tables responsive with three levels of column importance.
* Added Twig as the default template engine and converted all .tpl.php
templates and theme functions to .html.twig.
* Removed the PHPTemplate engine.
@ -10,17 +11,16 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
* Added Classy as a base theme to maintain CSS classes and wrappers.
* Added Stable as the default base theme to maintain backwards compatibility
for core template and CSS changes, because templates and CSS outside
Stable can be improved in minor releases (8.1.0, 8.2.0).
Stable can be improved in minor releases (8.1.0, 8.2.0, etc.).
* Redesigned several key elements of the Seven theme.
* Added support for HTML5 elements.
* Included the HTML5 Shiv library to support HTML5 elements in IE 8 and
below.
* Included Backbone.js and Underscore.js JavaScript frameworks.
* Updated to jQuery 2.1.4.
* Updated to jQuery UI 1.11.4.
* Removed jquery.bbq.
* Removed the Garland theme from core.
* Removed the Overlay module from core.
* Removed the Overlay module from core and replaced it with a simple,
dynamic "Back to site" link.
* Improved the asset library system to manage CSS and JavaScript files and
their dependencies. Allowing for smaller AJAX request payloads.
* jQuery is no longer loaded on all pages, only when another asset needs it.
@ -29,41 +29,72 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
* Implemented SMACSS-style categorization for CSS files.
* Removed most support for Internet Explorer 8 and below.
* Added Modernizr for making styling changes based on browser support.
* All page template variables converted to blocks.
- Added tour module. Provides highly contextual tips for UI elements.
- Improved entity system.
* Added support for saving and deleting entities through the controller.
* Base entity fields (such as labels) support widgets, formatters and
translation.
* Form modes introduced, similar to display modes.
* Entities are now classed objects, implementing EntityInterface.
* Drupal now understands the concept of a "default" revision, tracked
independently from the latest revision, allowing for the creation of
drafts while the current revision stays published.
* All entity types, not just nodes, now have support for revisions.
* All page template variables converted to blocks (title, breadcrumb,
branding, etc).
* Added the Breakpoint module to manage breakpoints of responsive designs.
* Introduced native Schema.org output in pages.
* Made use of semantic HTML 5 tags when possible. This also makes form input
on mobile devices much easier for users.
* Redesigned icons to look good on high resolution (retina) displays too.
- Made the site administration experience simpler:
* Redesigned the installer.
* Visually updated and extended the Seven (administration) theme.
* Made the administration toolbar responsive and touch friendly.
* Added search to the module listing and made the page easier to read.
* Added the tour module to provide highly contextual tips for UI elements.
- Improved the entity system:
* Added a full CRUD API for entities.
* Improved the field API and entity query API.
* Added support for widgets, formatters, and translation to base entity
fields (such as labels).
* Made view modes configurable for reusable display variants.
* Introduced form modes for reusable form variants.
* Added ability to handle a "default" revision that may not be the latest.
* All content entity types (custom blocks, terms, comments, etc.), not just
nodes, have support for revisions.
* Database schema of content entities is automatically generated based on
entity type and field definitions.
- Added the Typed Data system to manage complex types.
- Refactored routing system based on Symfony2 components.
- Reworked menu links, local actions, and local tasks based upon the new routing
system.
- Made declarative information (libraries, permissions, routes etc.) use YAML
files for definitions instead of PHP.
- Improved the menu handling systems:
* Moved custom menu item handling to its own module.
* Reworked menu links, local actions, and local tasks based upon the new
routing system.
- Added plugin system to standardize implementation of several core APIs.
- Configuration:
* Added a centralized file-based configuration system.
* Allows module authors to provide configuration in a standard format.
* Implements functionality to get, set, add and remove configuration.
* Includes ability to override configuration values with language variants
and other runtime values.
* Supports configuration schema, dependencies, and validation to maintain
data-integrity between deployments and updates.
- Introduced a new configuration management system:
* Added a centralized configuration system with export and import
functionality.
* Allowed module authors to provide configuration in a YAML file format.
* Implemented functionality to get, set, add, and remove configuration.
* Provided the ability to override configuration values with language
variants and other runtime values.
* Added configuration schema, dependencies, and validation to maintain
data integrity between deployments and updates.
* Support added for both global configuration and configuration entities.
- Improved authoring experience:
* Redesigned the content creation and editing form.
* Content preview is now displayed on the frontend.
* Added the CKEditor WYSIWYG editor. Clean markup guaranteed thanks to tight
integration with the filter system.
* Includes uploading, aligning and captioning of images.
* Correspondingly modernized the default text formats.
* Provides a drag-and-drop configuration UI, which automatically updates the
* Made uploading, aligning, and captioning of images possible in the editor.
* Modernized the default text formats.
* Added a drag-and-drop configuration UI, which automatically updates the
HTML filter settings, making configuring text formats trivial for typical
use cases.
* Added align and caption filters that can be applied to any element:
images, blockquotes, code snippets, videos…
* In-place editing of any entity: nodes, blocks…
* Made possible to in-place edit any entity: nodes, blocks…
* Added the Text Editor module to help map other editors to text formats.
- Improved media management:
* Added ability to configure when unused files get deleted with the option
to keep them, useful for media libraries.
* Added a customizable view under the content administration screen that
lists all files uploaded on the system.
* Made uploads immediate when selecting files in file fields.
* Added ability to upload multiple files at once.
* Added local image input filter, to enable secure image posting.
- Included the following Symfony2 components:
* ClassLoader - PSR-0-compatible autoload routines.
* DependencyInjection - Flexible dependency injection container.
@ -86,16 +117,14 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
* Blog
* Dashboard
* OpenID
* PHP Filter
* Poll
* Profile
* Trigger
- Removed the Statistics module's accesslog functionality and reports from core.
- Removed the Statistics module's accesslog functionality and reports.
- Removed XML-RPC functionality from core.
- Removed user signatures support from core.
- Universally Unique IDentifier (UUID):
* Support for generating and validating UUIDs.
- Tremendously improved language support all around.
- Added ability to generate and validate Universally Unique IDentifiers (UUIDs).
- Tremendously improved language support all around:
* Great language improvements for users:
* Improved language selection with user preference detection in the
installer based on browser settings.
@ -143,12 +172,6 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
configuration with translatable values (blocks, views, fields, etc.).
* Added language options to block visibility.
* Much improved language APIs for developers:
* Added simple APIs and hooks to save/delete/update languages.
* New Language class wraps language information, used universally.
* Unified database schemas and APIs to make it easier to spot where
language codes are referenced.
* Made the language negotiation system APIs more consistent for
developers.
* Made it possible for users to have a preferred language separate from
their user entity language.
* The text formatter from t() is now available as FormattableMarkup.
@ -157,49 +180,54 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
menu items and contextual links.
* Removed textgroups support from interface translation in favor of
native configuration language support.
* Added configuration schema system to support generating translation
forms for any configuration.
* Reworked Gettext PO support to use pluggable read/write handlers.
* Added language select form element in the Form API.
* Added a transliteration API. (Only used for machine names in core.)
* Added a language fallback capability to the interface translation API.
- New field types added to core:
- Email
- Link
- Phone number
- Entity reference
- Date
- Comment (allows comment threads on entity types other than node).
- Added local image input filter, to enable secure image posting.
* Email
* Link
* Telephone number
* Entity reference
* Date
- Made commenting more flexible:
* Added the notion of comment types (for reviews, greetings, and so on),
each of which can be configured with a different set of fields.
* Made commenting a field to allow comment threads on entity types other
than nodes.
- Added Views and Views UI module to core:
* Various core listings: /node, /admin/content/node, /admin/people etc. are
now served by views.
* REST API support built in.
* Added simple bulk operations functionality to Views.
* Converted various core listings to views, including /node,
/admin/content/node, /admin/people, and several blocks.
* Built in REST API support.
* Rewrote caching integration for better performance.
- Custom blocks are now fieldable, revisionable, and translatable entities.
- An accessible modal API based on improvements made in collaboration with the
jQuery UI team and the Views team.
- Fieldable contact forms allowing site-builders to easily build custom forms
for soliciting feedback from users.
* Made it possible to configure responsive tables in Views.
- Greatly improved block management:
* Made custom blocks fieldable, revisionable, and translatable entities.
* Added the notion of custom block types.
* Added the ability to place the same block in multiple locations.
* Introduced a block library with categorized blocks.
- Introduced an accessible modal API based on improvements made in collaboration
with the jQuery UI team.
- Made it possible to add fields to contact forms allowing site-builders to
easily build custom forms for soliciting feedback from users.
- Added a Web Services module package.
* Added a RESTful web services provider module.
* Added a serialization module using the Symfony serialization component.
* Added a Hypertext Application Language (HAL) serialization module.
* Added a HTTP Basic authentication provider module.
- Significant performance/scalability improvements:
* Cache tags, which allow content to be invalidated accurately and instantly,
including reverse proxies and CDNs.
* Cache contexts, which allow content to be cached correctly, and placeholdered
to improve cache hit rates.
* Cacheability bubbling, which allows strict tracking of assets and
cacheability throughout page rendering.
* Page caching has been factored out to its own module and is enabled by
default.
* Authenticated page caching has been added to core via the Dynamic Page Cache
module and is enabled by default.
* APCu, memory, and PHP file caching backends added to core, alongside support
for a chained, consistent cache backend to support correctly using fast
local cache implementations with multiple web servers.
- When using MySQL, the MyISAM engine is no longer supported.
- Improved performance/scalability significantly:
* Introduced cache tags, which allow content to be invalidated accurately
and instantly, including for reverse proxies and CDNs.
* Added cache contexts, which allow content to be cached correctly, and
placeholdered to improve cache hit rates.
* Implemented cacheability bubbling, which allows strict tracking of assets
and cacheability throughout page rendering.
* Factored out page caching to its own module and enabled it by default.
* Added the Dynamic Page Cache module for authenticated page caching and
enabled it by default.
* Added APCu, memory, and PHP file caching backends to core, alongside
support for a chained, consistent cache backend to support correctly using
fast local cache implementations with multiple web servers.
- Removed support for MyISAM, when using MySQL.
- Testing improvements
* Added PHPUnit for proper unit testing, see
https://phpunit.de/manual/4.8/en/index.html so you can run tests via
@ -209,6 +237,32 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
* Added KernelTestBase to provide a fast API testing of integration of
different components
* Core branch nightly tests include PHP 5.5, 5.6, 7, sqlite and PostgreSQL.
- Added the migrate module (experimental) with support for migrating content and
configuration from earlier Drupal versions.
- Introduced support for Composer.
- Moved the automated cron execution functionality to its own module.
- Refactored IP address based banning functionality to its own module.
- Security improvements:
* Removed PHP filter, including the ability to use PHP for block visibility.
* Managing fields for each entity type is now a separate permission.
* PDO drivers other than MySQL are now limited to executing single
statements to limit SQL injection vectors.
* Added an autoescape API to prevent cross-site scripting in many of the
places where Drupal outputs HTML.
* Hardened user session and session ID handling.
* Automated CSRF protection in route definitions.
* Clickjacking protection enabled by default.
* Made the core JavaScript API compatible with Content Security Policy
(CSP).
* Trusted host patterns enforced for requests preventing cache and link
poisoning.
- Switched to semantic versioning with significant updates planned every 6
months in 8.1, 8.2, etc.
- Numerous other important changes and additions. See
https://www.drupal.org/list-changes/drupal for a detailed list.
- Numerous bug fixes.
- Numerous API documentation improvements.
- Additional automated test coverage.
Drupal 7.0, 2011-01-05
----------------------

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.0.0-dev-2015-11-17';
const VERSION = '8.0.0';
/**
* Core API compatibility.

View file

@ -511,8 +511,6 @@ class ConfigImporter {
* If the configuration is already importing.
*/
public function initialize() {
$this->createExtensionChangelist();
// Ensure that the changes have been validated.
$this->validate();
@ -710,8 +708,10 @@ class ConfigImporter {
* @throws \Drupal\Core\Config\ConfigImporterException
* Exception thrown if the validate event logged any errors.
*/
protected function validate() {
public function validate() {
if (!$this->validated) {
// Create the list of installs and uninstalls.
$this->createExtensionChangelist();
// Validate renames.
foreach ($this->getUnprocessedConfiguration('rename') as $name) {
$names = $this->storageComparer->extractRenameNames($name);

View file

@ -69,9 +69,6 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
// Always add a default 'uuid' key.
$this->entity_keys['uuid'] = 'uuid';
$this->entity_keys['langcode'] = 'langcode';
if (isset($this->handlers['storage'])) {
$this->checkStorageClass($this->handlers['storage']);
}
$this->handlers += array(
'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage',
);
@ -135,23 +132,12 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
/**
* {@inheritdoc}
*
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
*
* @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
* Exception thrown when the provided class is not an instance of
* \Drupal\Core\Config\Entity\ConfigEntityStorage.
*/
public function setStorageClass($class) {
$this->checkStorageClass($class);
parent::setStorageClass($class);
}
/**
* Checks that the provided class is an instance of ConfigEntityStorage.
*
* @param string $class
* The class to check.
*
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
*/
protected function checkStorageClass($class) {
if (!is_a($class, 'Drupal\Core\Config\Entity\ConfigEntityStorage', TRUE)) {
throw new ConfigEntityStorageClassException("$class is not \\Drupal\\Core\\Config\\Entity\\ConfigEntityStorage or it does not extend it");

View file

@ -808,6 +808,13 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
return !empty($this->translations[$langcode]['status']);
}
/**
* {@inheritdoc}
*/
public function isNewTranslation() {
return $this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_CREATED;
}
/**
* {@inheritdoc}
*/
@ -822,37 +829,11 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
throw new \InvalidArgumentException("The entity cannot be translated since it is language neutral ({$this->defaultLangcode}).");
}
// Instantiate a new empty entity so default values will be populated in the
// specified language.
$entity_type = $this->getEntityType();
$default_values = array(
$entity_type->getKey('bundle') => $this->bundle(),
$this->langcodeKey => $langcode,
);
$entity = $this->entityManager()
->getStorage($this->getEntityTypeId())
->create($default_values);
foreach ($entity as $name => $field) {
if (!isset($values[$name]) && !$field->isEmpty()) {
$values[$name] = $field->getValue();
}
}
$values[$this->langcodeKey] = $langcode;
$values[$this->defaultLangcodeKey] = FALSE;
// Initialize the translation object.
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = $this->entityManager()->getStorage($this->getEntityTypeId());
$this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
$translation = $this->getTranslation($langcode);
$definitions = $translation->getFieldDefinitions();
foreach ($values as $name => $value) {
if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
$translation->values[$name][$langcode] = $value;
}
}
return $translation;
return $storage->createTranslation($this, $langcode, $values);
}
/**

View file

@ -13,7 +13,10 @@ use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class ContentEntityStorageBase extends EntityStorageBase implements DynamicallyFieldableEntityStorageInterface {
/**
* Base class for content entity storage handlers.
*/
abstract class ContentEntityStorageBase extends EntityStorageBase implements ContentEntityStorageInterface, DynamicallyFieldableEntityStorageInterface {
/**
* The entity bundle key.
@ -87,14 +90,33 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
$bundle = $values[$this->bundleKey];
}
$entity = new $this->entityClass(array(), $this->entityTypeId, $bundle);
$this->initFieldValues($entity, $values);
return $entity;
}
/**
* Initializes field values.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* An entity object.
* @param array $values
* (optional) An associative array of initial field values keyed by field
* name. If none is provided default values will be applied.
* @param array $field_names
* (optional) An associative array of field names to be initialized. If none
* is provided all fields will be initialized.
*/
protected function initFieldValues(ContentEntityInterface $entity, array $values = [], array $field_names = []) {
// Populate field values.
foreach ($entity as $name => $field) {
if (!$field_names || isset($field_names[$name])) {
if (isset($values[$name])) {
$entity->$name = $values[$name];
}
elseif (!array_key_exists($name, $values)) {
$entity->get($name)->applyDefaultValue();
}
}
unset($values[$name]);
}
@ -102,7 +124,23 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
foreach ($values as $name => $value) {
$entity->$name = $value;
}
return $entity;
// Make sure modules can alter field initial values.
$this->invokeHook('field_values_init', $entity);
}
/**
* {@inheritdoc}
*/
public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []) {
$translation = $entity->getTranslation($langcode);
$definitions = array_filter($translation->getFieldDefinitions(), function(FieldDefinitionInterface $definition) { return $definition->isTranslatable(); });
$field_names = array_map(function(FieldDefinitionInterface $definition) { return $definition->getName(); }, $definitions);
$values[$this->langcodeKey] = $langcode;
$values[$this->getEntityType()->getKey('default_langcode')] = FALSE;
$this->initFieldValues($translation, $values, $field_names);
$this->invokeHook('translation_create', $entity);
return $translation;
}
/**

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\ContentEntityStorageInterface.
*/
namespace Drupal\Core\Entity;
/**
* A storage that supports content entity types.
*/
interface ContentEntityStorageInterface extends EntityStorageInterface {
/**
* Constructs a new entity translation object, without permanently saving it.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity object being translated.
* @param string $langcode
* The translation language code.
* @param array $values
* (optional) An associative array of initial field values keyed by field
* name. If none is provided default values will be applied.
*
* @return \Drupal\Core\Entity\ContentEntityInterface
* A new entity translation object.
*/
public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []);
}

View file

@ -30,4 +30,20 @@ class ContentEntityType extends EntityType implements ContentEntityTypeInterface
return 'content';
}
/**
* {@inheritdoc}
*
* @see \Drupal\Core\Entity\ContentEntityStorageInterface.
*
* @throws \InvalidArgumentException
* If the provided class does not implement
* \Drupal\Core\Entity\ContentEntityStorageInterface.
*/
protected function checkStorageClass($class) {
$required_interface = ContentEntityStorageInterface::class;
if (!is_subclass_of($class, $required_interface)) {
throw new \InvalidArgumentException("$class does not implement $required_interface");
}
}
}

View file

@ -276,6 +276,9 @@ class EntityType implements EntityTypeInterface {
$this->handlers += array(
'access' => 'Drupal\Core\Entity\EntityAccessControlHandler',
);
if (isset($this->handlers['storage'])) {
$this->checkStorageClass($this->handlers['storage']);
}
// Automatically add the EntityChanged constraint if the entity type tracks
// the changed time.
@ -459,9 +462,20 @@ class EntityType implements EntityTypeInterface {
* {@inheritdoc}
*/
public function setStorageClass($class) {
$this->checkStorageClass($class);
$this->handlers['storage'] = $class;
}
/**
* Checks that the provided class is an instance of ConfigEntityStorage.
*
* @param string $class
* The class to check.
*/
protected function checkStorageClass($class) {
// Nothing to check by default.
}
/**
* {@inheritdoc}
*/

View file

@ -0,0 +1,25 @@
<?php
/**
* @file
* Contains \Drupal\Core\Entity\KeyValueStore\KeyValueContentEntityStorage.
*/
namespace Drupal\Core\Entity\KeyValueStore;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityStorageInterface;
/**
* Provides a key value backend for content entities.
*/
class KeyValueContentEntityStorage extends KeyValueEntityStorage implements ContentEntityStorageInterface {
/**
* {@inheritdoc}
*/
public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []) {
// @todo
}
}

View file

@ -780,10 +780,9 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
}
/**
* Act on a newly created entity.
* Acts when creating a new entity.
*
* This hook runs after a new entity object has just been instantiated. It can
* be used to set initial values, e.g. to provide defaults.
* This hook runs after a new entity object has just been instantiated.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
@ -792,16 +791,13 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
* @see hook_ENTITY_TYPE_create()
*/
function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
if ($entity instanceof FieldableEntityInterface && !$entity->foo->value) {
$entity->foo->value = 'some_initial_value';
}
\Drupal::logger('example')->info('Entity created: @label', ['@label' => $entity->label()]);
}
/**
* Act on a newly created entity of a specific type.
* Acts when creating a new entity of a specific type.
*
* This hook runs after a new entity object has just been instantiated. It can
* be used to set initial values, e.g. to provide defaults.
* This hook runs after a new entity object has just been instantiated.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object.
@ -810,9 +806,7 @@ function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
* @see hook_entity_create()
*/
function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) {
if (!$entity->foo->value) {
$entity->foo->value = 'some_initial_value';
}
\Drupal::logger('example')->info('ENTITY_TYPE created: @label', ['@label' => $entity->label()]);
}
/**
@ -1011,6 +1005,38 @@ function hook_ENTITY_TYPE_update(Drupal\Core\Entity\EntityInterface $entity) {
->execute();
}
/**
* Acts when creating a new entity translation.
*
* This hook runs after a new entity translation object has just been
* instantiated.
*
* @param \Drupal\Core\Entity\EntityInterface $translation
* The entity object.
*
* @ingroup entity_crud
* @see hook_ENTITY_TYPE_translation_create()
*/
function hook_entity_translation_create(\Drupal\Core\Entity\EntityInterface $translation) {
\Drupal::logger('example')->info('Entity translation created: @label', ['@label' => $translation->label()]);
}
/**
* Acts when creating a new entity translation of a specific type.
*
* This hook runs after a new entity translation object has just been
* instantiated.
*
* @param \Drupal\Core\Entity\EntityInterface $translation
* The entity object.
*
* @ingroup entity_crud
* @see hook_entity_translation_create()
*/
function hook_ENTITY_TYPE_translation_create(\Drupal\Core\Entity\EntityInterface $translation) {
\Drupal::logger('example')->info('ENTITY_TYPE translation created: @label', ['@label' => $translation->label()]);
}
/**
* Respond to creation of a new entity translation.
*
@ -1886,6 +1912,44 @@ function hook_entity_field_access_alter(array &$grants, array $context) {
}
}
/**
* Acts when initializing a fieldable entity object.
*
* This hook runs after a new entity object or a new entity translation object
* has just been instantiated. It can be used to set initial values, e.g. to
* provide defaults.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity object.
*
* @ingroup entity_crud
* @see hook_ENTITY_TYPE_field_values_init()
*/
function hook_entity_field_values_init(\Drupal\Core\Entity\FieldableEntityInterface $entity) {
if ($entity instanceof \Drupal\Core\Entity\ContentEntityInterface && !$entity->foo->value) {
$entity->foo->value = 'some_initial_value';
}
}
/**
* Acts when initializing a fieldable entity object.
*
* This hook runs after a new entity object or a new entity translation object
* has just been instantiated. It can be used to set initial values, e.g. to
* provide defaults.
*
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
* The entity object.
*
* @ingroup entity_crud
* @see hook_entity_field_values_init()
*/
function hook_ENTITY_TYPE_field_values_init(\Drupal\Core\Entity\FieldableEntityInterface $entity) {
if (!$entity->foo->value) {
$entity->foo->value = 'some_initial_value';
}
}
/**
* Exposes "pseudo-field" components on content entities.
*

View file

@ -28,6 +28,14 @@ interface TranslatableInterface {
*/
public function isDefaultTranslation();
/**
* Checks whether the translation is new.
*
* @return bool
* TRUE if the translation is new, FALSE otherwise.
*/
public function isNewTranslation();
/**
* Returns the languages the data is translated to.
*

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{
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']);
batch_set($batch);
}
$entity->save();
drupal_set_message($this->t('The @entity_type %label was imported.', array('@entity_type' => $entity->getEntityTypeId(), '%label' => $entity->label())));
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');
}
catch (\Exception $e) {
drupal_set_message($e->getMessage(), '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 . '.');
}
}

View file

@ -699,6 +699,17 @@ $settings['container_yamls'][] = __DIR__ . '/services.yml';
* example.org, with all subdomains included.
*/
/**
* Include the Pantheon-specific settings file.
*
* n.b. The settings.pantheon.php file makes some changes
* that affect all envrionments that this site
* exists in. Always include this file, even in
* a local development environment, to insure that
* the site settings remain consistent.
*/
include __DIR__ . "/settings.pantheon.php";
/**
* Load local development override configuration, if available.
*
@ -709,6 +720,6 @@ $settings['container_yamls'][] = __DIR__ . '/services.yml';
*
* Keep this code block at the end of this file to take full effect.
*/
# if (file_exists(__DIR__ . '/settings.local.php')) {
# include __DIR__ . '/settings.local.php';
# }
if (file_exists(__DIR__ . '/settings.local.php')) {
include __DIR__ . '/settings.local.php';
}

View file

@ -47,20 +47,6 @@ else {
);
}
/**
* Override the $install_state variable to let Drupal know that the settings are verified
* since they are being passed directly by the Pantheon.
*
* Issue: https://github.com/pantheon-systems/drops-8/issues/9
*
*/
if (
isset($_ENV['PANTHEON_ENVIRONMENT']) &&
$is_installer_url &&
(php_sapi_name() != "cli")
) {
$GLOBALS['install_state']['settings_verified'] = TRUE;
}
/**
* Allow Drupal 8 to Cleanly Redirect to Install.php For New Sites.
@ -73,7 +59,7 @@ if (
if (
isset($_ENV['PANTHEON_ENVIRONMENT']) &&
!$is_installer_url &&
(!is_dir(__DIR__ . '/files/styles')) &&
(isset($_SERVER['PANTHEON_DATABASE_STATE']) && ($_SERVER['PANTHEON_DATABASE_STATE'] == 'empty')) &&
(empty($GLOBALS['install_state'])) &&
(php_sapi_name() != "cli")
) {