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) Drupal 8.0.0, 2015-11-19
---------------------- ------------------------
- Dramatically improved the front end: - Significantly improved the front end:
* Made all built-in themes responsive. * Made all built-in themes responsive.
* Added support for responsive images. * 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 * Added Twig as the default template engine and converted all .tpl.php
templates and theme functions to .html.twig. templates and theme functions to .html.twig.
* Removed the PHPTemplate engine. * 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 Classy as a base theme to maintain CSS classes and wrappers.
* Added Stable as the default base theme to maintain backwards compatibility * Added Stable as the default base theme to maintain backwards compatibility
for core template and CSS changes, because templates and CSS outside 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. * Redesigned several key elements of the Seven theme.
* Added support for HTML5 elements. * 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. * Included Backbone.js and Underscore.js JavaScript frameworks.
* Updated to jQuery 2.1.4. * Updated to jQuery 2.1.4.
* Updated to jQuery UI 1.11.4. * Updated to jQuery UI 1.11.4.
* Removed jquery.bbq. * Removed jquery.bbq.
* Removed the Garland theme from core. * 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 * Improved the asset library system to manage CSS and JavaScript files and
their dependencies. Allowing for smaller AJAX request payloads. their dependencies. Allowing for smaller AJAX request payloads.
* jQuery is no longer loaded on all pages, only when another asset needs it. * 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. * Implemented SMACSS-style categorization for CSS files.
* Removed most support for Internet Explorer 8 and below. * Removed most support for Internet Explorer 8 and below.
* Added Modernizr for making styling changes based on browser support. * Added Modernizr for making styling changes based on browser support.
* All page template variables converted to blocks. * All page template variables converted to blocks (title, breadcrumb,
- Added tour module. Provides highly contextual tips for UI elements. branding, etc).
- Improved entity system. * Added the Breakpoint module to manage breakpoints of responsive designs.
* Added support for saving and deleting entities through the controller. * Introduced native Schema.org output in pages.
* Base entity fields (such as labels) support widgets, formatters and * Made use of semantic HTML 5 tags when possible. This also makes form input
translation. on mobile devices much easier for users.
* Form modes introduced, similar to display modes. * Redesigned icons to look good on high resolution (retina) displays too.
* Entities are now classed objects, implementing EntityInterface. - Made the site administration experience simpler:
* Drupal now understands the concept of a "default" revision, tracked * Redesigned the installer.
independently from the latest revision, allowing for the creation of * Visually updated and extended the Seven (administration) theme.
drafts while the current revision stays published. * Made the administration toolbar responsive and touch friendly.
* All entity types, not just nodes, now have support for revisions. * 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. - Refactored routing system based on Symfony2 components.
- Reworked menu links, local actions, and local tasks based upon the new routing - Made declarative information (libraries, permissions, routes etc.) use YAML
system. 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. - Added plugin system to standardize implementation of several core APIs.
- Configuration: - Introduced a new configuration management system:
* Added a centralized file-based configuration system. * Added a centralized configuration system with export and import
* Allows module authors to provide configuration in a standard format. functionality.
* Implements functionality to get, set, add and remove configuration. * Allowed module authors to provide configuration in a YAML file format.
* Includes ability to override configuration values with language variants * Implemented functionality to get, set, add, and remove configuration.
and other runtime values. * Provided the ability to override configuration values with language
* Supports configuration schema, dependencies, and validation to maintain variants and other runtime values.
data-integrity between deployments and updates. * 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: - 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 * Added the CKEditor WYSIWYG editor. Clean markup guaranteed thanks to tight
integration with the filter system. integration with the filter system.
* Includes uploading, aligning and captioning of images. * Made uploading, aligning, and captioning of images possible in the editor.
* Correspondingly modernized the default text formats. * Modernized the default text formats.
* Provides a drag-and-drop configuration UI, which automatically updates the * Added a drag-and-drop configuration UI, which automatically updates the
HTML filter settings, making configuring text formats trivial for typical HTML filter settings, making configuring text formats trivial for typical
use cases. use cases.
* Added align and caption filters that can be applied to any element: * Added align and caption filters that can be applied to any element:
images, blockquotes, code snippets, videos… 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: - Included the following Symfony2 components:
* ClassLoader - PSR-0-compatible autoload routines. * ClassLoader - PSR-0-compatible autoload routines.
* DependencyInjection - Flexible dependency injection container. * DependencyInjection - Flexible dependency injection container.
@ -86,16 +117,14 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
* Blog * Blog
* Dashboard * Dashboard
* OpenID * OpenID
* PHP Filter
* Poll * Poll
* Profile * Profile
* Trigger * 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 XML-RPC functionality from core.
- Removed user signatures support from core. - Removed user signatures support from core.
- Universally Unique IDentifier (UUID): - Added ability to generate and validate Universally Unique IDentifiers (UUIDs).
* Support for generating and validating UUIDs. - Tremendously improved language support all around:
- Tremendously improved language support all around.
* Great language improvements for users: * Great language improvements for users:
* Improved language selection with user preference detection in the * Improved language selection with user preference detection in the
installer based on browser settings. 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.). configuration with translatable values (blocks, views, fields, etc.).
* Added language options to block visibility. * Added language options to block visibility.
* Much improved language APIs for developers: * 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 * Made it possible for users to have a preferred language separate from
their user entity language. their user entity language.
* The text formatter from t() is now available as FormattableMarkup. * 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. menu items and contextual links.
* Removed textgroups support from interface translation in favor of * Removed textgroups support from interface translation in favor of
native configuration language support. 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 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: - New field types added to core:
- Email * Email
- Link * Link
- Phone number * Telephone number
- Entity reference * Entity reference
- Date * Date
- Comment (allows comment threads on entity types other than node). - Made commenting more flexible:
- Added local image input filter, to enable secure image posting. * 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: - Added Views and Views UI module to core:
* Various core listings: /node, /admin/content/node, /admin/people etc. are * Added simple bulk operations functionality to Views.
now served by views. * Converted various core listings to views, including /node,
* REST API support built in. /admin/content/node, /admin/people, and several blocks.
* Rewrote caching integration for better performance. * Built in REST API support.
- Custom blocks are now fieldable, revisionable, and translatable entities. * Rewrote caching integration for better performance.
- An accessible modal API based on improvements made in collaboration with the * Made it possible to configure responsive tables in Views.
jQuery UI team and the Views team. - Greatly improved block management:
- Fieldable contact forms allowing site-builders to easily build custom forms * Made custom blocks fieldable, revisionable, and translatable entities.
for soliciting feedback from users. * 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 Web Services module package.
* Added a RESTful web services provider module. * Added a RESTful web services provider module.
* Added a serialization module using the Symfony serialization component. * Added a serialization module using the Symfony serialization component.
* Added a Hypertext Application Language (HAL) serialization module. * Added a Hypertext Application Language (HAL) serialization module.
* Added a HTTP Basic authentication provider module. * Added a HTTP Basic authentication provider module.
- Significant performance/scalability improvements: - Improved performance/scalability significantly:
* Cache tags, which allow content to be invalidated accurately and instantly, * Introduced cache tags, which allow content to be invalidated accurately
including reverse proxies and CDNs. and instantly, including for reverse proxies and CDNs.
* Cache contexts, which allow content to be cached correctly, and placeholdered * Added cache contexts, which allow content to be cached correctly, and
to improve cache hit rates. placeholdered to improve cache hit rates.
* Cacheability bubbling, which allows strict tracking of assets and * Implemented cacheability bubbling, which allows strict tracking of assets
cacheability throughout page rendering. and cacheability throughout page rendering.
* Page caching has been factored out to its own module and is enabled by * Factored out page caching to its own module and enabled it by default.
default. * Added the Dynamic Page Cache module for authenticated page caching and
* Authenticated page caching has been added to core via the Dynamic Page Cache enabled it by default.
module and is enabled by default. * Added APCu, memory, and PHP file caching backends to core, alongside
* APCu, memory, and PHP file caching backends added to core, alongside support support for a chained, consistent cache backend to support correctly using
for a chained, consistent cache backend to support correctly using fast fast local cache implementations with multiple web servers.
local cache implementations with multiple web servers. - Removed support for MyISAM, when using MySQL.
- When using MySQL, the MyISAM engine is no longer supported.
- Testing improvements - Testing improvements
* Added PHPUnit for proper unit testing, see * Added PHPUnit for proper unit testing, see
https://phpunit.de/manual/4.8/en/index.html so you can run tests via 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 * Added KernelTestBase to provide a fast API testing of integration of
different components different components
* Core branch nightly tests include PHP 5.5, 5.6, 7, sqlite and PostgreSQL. * 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 Drupal 7.0, 2011-01-05
---------------------- ----------------------

View file

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

View file

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

View file

@ -69,9 +69,6 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
// Always add a default 'uuid' key. // Always add a default 'uuid' key.
$this->entity_keys['uuid'] = 'uuid'; $this->entity_keys['uuid'] = 'uuid';
$this->entity_keys['langcode'] = 'langcode'; $this->entity_keys['langcode'] = 'langcode';
if (isset($this->handlers['storage'])) {
$this->checkStorageClass($this->handlers['storage']);
}
$this->handlers += array( $this->handlers += array(
'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage', 'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage',
); );
@ -135,23 +132,12 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
* *
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
*
* @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException * @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
* Exception thrown when the provided class is not an instance of * Exception thrown when the provided class is not an instance of
* \Drupal\Core\Config\Entity\ConfigEntityStorage. * \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) { protected function checkStorageClass($class) {
if (!is_a($class, 'Drupal\Core\Config\Entity\ConfigEntityStorage', TRUE)) { 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"); 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']); return !empty($this->translations[$langcode]['status']);
} }
/**
* {@inheritdoc}
*/
public function isNewTranslation() {
return $this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_CREATED;
}
/** /**
* {@inheritdoc} * {@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})."); 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 // Initialize the translation object.
// specified language. /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$entity_type = $this->getEntityType(); $storage = $this->entityManager()->getStorage($this->getEntityTypeId());
$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;
$this->translations[$langcode]['status'] = static::TRANSLATION_CREATED; $this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
$translation = $this->getTranslation($langcode); return $storage->createTranslation($this, $langcode, $values);
$definitions = $translation->getFieldDefinitions();
foreach ($values as $name => $value) {
if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
$translation->values[$name][$langcode] = $value;
}
}
return $translation;
} }
/** /**

View file

@ -13,7 +13,10 @@ use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; 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. * The entity bundle key.
@ -87,13 +90,32 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
$bundle = $values[$this->bundleKey]; $bundle = $values[$this->bundleKey];
} }
$entity = new $this->entityClass(array(), $this->entityTypeId, $bundle); $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) { foreach ($entity as $name => $field) {
if (isset($values[$name])) { if (!$field_names || isset($field_names[$name])) {
$entity->$name = $values[$name]; if (isset($values[$name])) {
} $entity->$name = $values[$name];
elseif (!array_key_exists($name, $values)) { }
$entity->get($name)->applyDefaultValue(); elseif (!array_key_exists($name, $values)) {
$entity->get($name)->applyDefaultValue();
}
} }
unset($values[$name]); unset($values[$name]);
} }
@ -102,7 +124,23 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
foreach ($values as $name => $value) { foreach ($values as $name => $value) {
$entity->$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'; 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( $this->handlers += array(
'access' => 'Drupal\Core\Entity\EntityAccessControlHandler', '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 // Automatically add the EntityChanged constraint if the entity type tracks
// the changed time. // the changed time.
@ -459,9 +462,20 @@ class EntityType implements EntityTypeInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function setStorageClass($class) { public function setStorageClass($class) {
$this->checkStorageClass($class);
$this->handlers['storage'] = $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} * {@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 * This hook runs after a new entity object has just been instantiated.
* be used to set initial values, e.g. to provide defaults.
* *
* @param \Drupal\Core\Entity\EntityInterface $entity * @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object. * The entity object.
@ -792,16 +791,13 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
* @see hook_ENTITY_TYPE_create() * @see hook_ENTITY_TYPE_create()
*/ */
function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) { function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
if ($entity instanceof FieldableEntityInterface && !$entity->foo->value) { \Drupal::logger('example')->info('Entity created: @label', ['@label' => $entity->label()]);
$entity->foo->value = 'some_initial_value';
}
} }
/** /**
* 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 * This hook runs after a new entity object has just been instantiated.
* be used to set initial values, e.g. to provide defaults.
* *
* @param \Drupal\Core\Entity\EntityInterface $entity * @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object. * The entity object.
@ -810,9 +806,7 @@ function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
* @see hook_entity_create() * @see hook_entity_create()
*/ */
function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) { function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) {
if (!$entity->foo->value) { \Drupal::logger('example')->info('ENTITY_TYPE created: @label', ['@label' => $entity->label()]);
$entity->foo->value = 'some_initial_value';
}
} }
/** /**
@ -1011,6 +1005,38 @@ function hook_ENTITY_TYPE_update(Drupal\Core\Entity\EntityInterface $entity) {
->execute(); ->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. * 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. * Exposes "pseudo-field" components on content entities.
* *

View file

@ -28,6 +28,14 @@ interface TranslatableInterface {
*/ */
public function isDefaultTranslation(); 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. * Returns the languages the data is translated to.
* *

View file

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

View file

@ -7,12 +7,12 @@
namespace Drupal\aggregator; namespace Drupal\aggregator;
use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\ContentEntityStorageInterface;
/** /**
* Defines an interface for aggregator item entity storage classes. * 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. * 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. // 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_field_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']['defaults']['title'] = 'info';
// @todo EntityViewsData should add these relationships by default. // @todo EntityViewsData should add these relationships by default.
// https://www.drupal.org/node/2410275 // https://www.drupal.org/node/2410275
$data['block_content_revision']['id']['relationship']['id'] = 'standard'; $data['block_content_field_revision']['id']['relationship']['id'] = 'standard';
$data['block_content_revision']['id']['relationship']['base'] = 'block_content'; $data['block_content_field_revision']['id']['relationship']['base'] = 'block_content_field_data';
$data['block_content_revision']['id']['relationship']['base field'] = 'id'; $data['block_content_field_revision']['id']['relationship']['base field'] = 'id';
$data['block_content_revision']['id']['relationship']['title'] = $this->t('Block Content'); $data['block_content_field_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']['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_field_revision']['revision_id']['relationship']['id'] = 'standard';
$data['block_content_revision']['revision_id']['relationship']['base'] = 'block_content'; $data['block_content_field_revision']['revision_id']['relationship']['base'] = 'block_content_field_data';
$data['block_content_revision']['revision_id']['relationship']['base field'] = 'revision_id'; $data['block_content_field_revision']['revision_id']['relationship']['base field'] = 'revision_id';
$data['block_content_revision']['revision_id']['relationship']['title'] = $this->t('Block Content'); $data['block_content_field_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']['label'] = $this->t('Get the actual block content from a block content revision.');
return $data; return $data;
} }
} }

View file

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

View file

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

View file

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

View file

@ -8,13 +8,13 @@
namespace Drupal\comment; namespace Drupal\comment;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\ContentEntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\FieldableEntityInterface;
/** /**
* Defines an interface for comment entity storage classes. * 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. * Gets the maximum encoded thread value for the top level comments.

View file

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

View file

@ -8,12 +8,24 @@
namespace Drupal\config\Form; namespace Drupal\config\Form;
use Drupal\Component\Serialization\Yaml; 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\StorageInterface;
use Drupal\Core\Config\TypedConfigManagerInterface;
use Drupal\Core\Entity\EntityManagerInterface; 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\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url; use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/** /**
* Provides a form for importing a single configuration file. * Provides a form for importing a single configuration file.
@ -34,6 +46,62 @@ class ConfigSingleImportForm extends ConfirmFormBase {
*/ */
protected $configStorage; 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. * If the config exists, this is that object. Otherwise, FALSE.
* *
@ -55,10 +123,36 @@ class ConfigSingleImportForm extends ConfirmFormBase {
* The entity manager. * The entity manager.
* @param \Drupal\Core\Config\StorageInterface $config_storage * @param \Drupal\Core\Config\StorageInterface $config_storage
* The 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->entityManager = $entity_manager;
$this->configStorage = $config_storage; $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) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('entity.manager'), $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()))); $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; return;
} }
$config_name = $definition->getConfigPrefix() . '.' . $data[$id_key];
// If there is an existing entity, ensure matching ID and UUID. // If there is an existing entity, ensure matching ID and UUID.
if ($entity = $entity_storage->load($data[$id_key])) { if ($entity = $entity_storage->load($data[$id_key])) {
$this->configExists = $entity; $this->configExists = $entity;
@ -222,10 +326,53 @@ class ConfigSingleImportForm extends ConfirmFormBase {
} }
} }
else { 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; $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. // Store the decoded version of the submitted import.
$form_state->setValueForElement($form['import'], $data); $form_state->setValueForElement($form['import'], $data);
} }
@ -241,26 +388,34 @@ class ConfigSingleImportForm extends ConfirmFormBase {
return; return;
} }
// If a simple configuration file was added, set the data and save. /** @var \Drupal\Core\Config\ConfigImporter $config_importer */
if ($this->data['config_type'] === 'system.simple') { $config_importer = $form_state->get('config_importer');
$this->configFactory()->getEditable($this->data['config_name'])->setData($this->data['import'])->save(); if ($config_importer->alreadyImporting()) {
drupal_set_message($this->t('The %name configuration was imported.', array('%name' => $this->data['config_name']))); 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 { try {
$entity_storage = $this->entityManager->getStorage($this->data['config_type']); $sync_steps = $config_importer->initialize();
if ($this->configExists) { $batch = [
$entity = $entity_storage->updateFromStorageRecord($this->configExists, $this->data['import']); '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 (\Exception $e) { catch (ConfigImporterException $e) {
drupal_set_message($e->getMessage(), 'error'); // 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->label(), 'First');
$this->assertIdentical($entity->id(), 'first'); $this->assertIdentical($entity->id(), 'first');
$this->assertTrue($entity->status()); $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. // Attempt an import with an existing ID but missing UUID.
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); $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->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->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')); $this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('custom_id'); $this->assertRaw(t('The configuration was imported successfully.'));
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
// Perform an import with a unique ID and UUID. // Perform an import with a unique ID and UUID.
$import = <<<EOD $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->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')); $this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('second'); $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->label(), 'Second');
$this->assertIdentical($entity->id(), 'second'); $this->assertIdentical($entity->id(), 'second');
$this->assertFalse($entity->status()); $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->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
$this->drupalPostForm(NULL, array(), t('Confirm')); $this->drupalPostForm(NULL, array(), t('Confirm'));
$entity = $storage->load('second'); $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'); $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->drupalPostForm(NULL, array(), t('Confirm'));
$this->drupalGet(''); $this->drupalGet('');
$this->assertText('Test simple import'); $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 field: nid
id: nid id: nid
relationship: none relationship: none
table: node table: node_field_data
plugin_id: numeric plugin_id: numeric
pager: pager:
options: options:
@ -39,7 +39,7 @@ display:
id: nid id: nid
order: ASC order: ASC
relationship: none relationship: none
table: node table: node_field_data
plugin_id: numeric plugin_id: numeric
display_plugin: default display_plugin: default
display_title: Master display_title: Master

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -41,7 +41,7 @@ display:
fields: fields:
nid: nid:
id: nid id: nid
table: node table: node_field_data
field: nid field: nid
plugin_id: field plugin_id: field
entity_type: node 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 // Verify that we obtain the entity object itself when we attempt to
// retrieve a translation referring to it. // retrieve a translation referring to it.
$translation = $entity->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED); $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.'); $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; $entity->{$langcode_key}->value = $default_langcode;
$translation = $entity->getTranslation($default_langcode); $translation = $entity->getTranslation($default_langcode);
@ -353,6 +354,7 @@ class EntityTranslationTest extends EntityLanguageTestBase {
$entity->name->value = $name; $entity->name->value = $name;
$name_translated = $langcode . '_' . $this->randomMachineName(); $name_translated = $langcode . '_' . $this->randomMachineName();
$translation = $entity->addTranslation($langcode); $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->assertNotIdentical($entity, $translation, 'The entity and the translation object differ from one another.');
$this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.'); $this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.');
$this->assertEqual($translation->language()->getId(), $langcode, 'The translation language matches the specified one.'); $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) { function keyvalue_test_entity_type_alter(array &$entity_types) {
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */ /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
if (isset($entity_types['entity_test_label'])) { 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_keys = $entity_types['entity_test_label']->getKeys();
$entity_types['entity_test_label']->set('entity_keys', $entity_keys + array('uuid' => 'uuid')); $entity_types['entity_test_label']->set('entity_keys', $entity_keys + array('uuid' => 'uuid'));
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -116,11 +116,29 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
public function getViewsData() { public function getViewsData() {
$data = []; $data = [];
$base_table = $this->entityType->getBaseTable(); $base_table = $this->entityType->getBaseTable() ?: $this->entityType->id();
$revisionable = $this->entityType->isRevisionable();
$base_field = $this->entityType->getKey('id'); $base_field = $this->entityType->getKey('id');
$data_table = $this->entityType->getDataTable();
$revision_table = $this->entityType->getRevisionTable(); $revision_table = '';
$revision_data_table = $this->entityType->getRevisionDataTable(); 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'); $revision_field = $this->entityType->getKey('revision');
// Setup base information of the views data. // Setup base information of the views data.
@ -213,6 +231,10 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
// the entity base, revision, data tables. // the entity base, revision, data tables.
$field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id()); $field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id());
if ($table_mapping = $this->storage->getTableMapping()) { 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. // 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 // Based on whether the field is in the field_definitions provided by the
// entity manager. // entity manager.
@ -221,6 +243,12 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
// @todo https://www.drupal.org/node/2337511 // @todo https://www.drupal.org/node/2337511
foreach ($table_mapping->getTableNames() as $table) { foreach ($table_mapping->getTableNames() as $table) {
foreach ($table_mapping->getFieldNames($table) as $field_name) { 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]); $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: fields:
nid: nid:
id: nid id: nid
table: node table: node_field_data
field: nid field: nid
relationship: none relationship: none
group_type: group group_type: group

View file

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

View file

@ -5,6 +5,9 @@
* Post update functions for Views. * Post update functions for Views.
*/ */
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\views\Views;
/** /**
* @addtogroup updates-8.0.0-beta * @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". * @} 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\Component\Utility\SafeMarkup;
use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig; use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\ViewExecutable; use Drupal\views\ViewExecutable;
/** /**
@ -20,12 +21,17 @@ use Drupal\views\ViewExecutable;
*/ */
class HandlerTest extends UITestBase { class HandlerTest extends UITestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('node_test_views');
/** /**
* Views used by this test. * Views used by this test.
* *
* @var array * @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} * {@inheritdoc}
@ -34,6 +40,7 @@ class HandlerTest extends UITestBase {
parent::setUp(); parent::setUp();
$this->drupalPlaceBlock('page_title_block'); $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. * 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. * 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. * Keep this code block at the end of this file to take full effect.
*/ */
# if (file_exists(__DIR__ . '/settings.local.php')) { if (file_exists(__DIR__ . '/settings.local.php')) {
# include __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. * Allow Drupal 8 to Cleanly Redirect to Install.php For New Sites.
@ -73,7 +59,7 @@ if (
if ( if (
isset($_ENV['PANTHEON_ENVIRONMENT']) && isset($_ENV['PANTHEON_ENVIRONMENT']) &&
!$is_installer_url && !$is_installer_url &&
(!is_dir(__DIR__ . '/files/styles')) && (isset($_SERVER['PANTHEON_DATABASE_STATE']) && ($_SERVER['PANTHEON_DATABASE_STATE'] == 'empty')) &&
(empty($GLOBALS['install_state'])) && (empty($GLOBALS['install_state'])) &&
(php_sapi_name() != "cli") (php_sapi_name() != "cli")
) { ) {