Update to Drupal 8.0.0. For more information, see https://www.drupal.org/node/2619030
This commit is contained in:
parent
7784f4c23d
commit
25a6735fb3
|
@ -1,8 +1,9 @@
|
|||
Drupal 8.0.x, xxxx-xx-xx (development version)
|
||||
----------------------
|
||||
- Dramatically improved the front end:
|
||||
Drupal 8.0.0, 2015-11-19
|
||||
------------------------
|
||||
- Significantly improved the front end:
|
||||
* Made all built-in themes responsive.
|
||||
* Added support for responsive images.
|
||||
* Made built-in tables responsive with three levels of column importance.
|
||||
* Added Twig as the default template engine and converted all .tpl.php
|
||||
templates and theme functions to .html.twig.
|
||||
* Removed the PHPTemplate engine.
|
||||
|
@ -10,17 +11,16 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
|
|||
* Added Classy as a base theme to maintain CSS classes and wrappers.
|
||||
* Added Stable as the default base theme to maintain backwards compatibility
|
||||
for core template and CSS changes, because templates and CSS outside
|
||||
Stable can be improved in minor releases (8.1.0, 8.2.0 …).
|
||||
Stable can be improved in minor releases (8.1.0, 8.2.0, etc.).
|
||||
* Redesigned several key elements of the Seven theme.
|
||||
* Added support for HTML5 elements.
|
||||
* Included the HTML5 Shiv library to support HTML5 elements in IE 8 and
|
||||
below.
|
||||
* Included Backbone.js and Underscore.js JavaScript frameworks.
|
||||
* Updated to jQuery 2.1.4.
|
||||
* Updated to jQuery UI 1.11.4.
|
||||
* Removed jquery.bbq.
|
||||
* Removed the Garland theme from core.
|
||||
* Removed the Overlay module from core.
|
||||
* Removed the Overlay module from core and replaced it with a simple,
|
||||
dynamic "Back to site" link.
|
||||
* Improved the asset library system to manage CSS and JavaScript files and
|
||||
their dependencies. Allowing for smaller AJAX request payloads.
|
||||
* jQuery is no longer loaded on all pages, only when another asset needs it.
|
||||
|
@ -29,41 +29,72 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
|
|||
* Implemented SMACSS-style categorization for CSS files.
|
||||
* Removed most support for Internet Explorer 8 and below.
|
||||
* Added Modernizr for making styling changes based on browser support.
|
||||
* All page template variables converted to blocks.
|
||||
- Added tour module. Provides highly contextual tips for UI elements.
|
||||
- Improved entity system.
|
||||
* Added support for saving and deleting entities through the controller.
|
||||
* Base entity fields (such as labels) support widgets, formatters and
|
||||
translation.
|
||||
* Form modes introduced, similar to display modes.
|
||||
* Entities are now classed objects, implementing EntityInterface.
|
||||
* Drupal now understands the concept of a "default" revision, tracked
|
||||
independently from the latest revision, allowing for the creation of
|
||||
drafts while the current revision stays published.
|
||||
* All entity types, not just nodes, now have support for revisions.
|
||||
* All page template variables converted to blocks (title, breadcrumb,
|
||||
branding, etc).
|
||||
* Added the Breakpoint module to manage breakpoints of responsive designs.
|
||||
* Introduced native Schema.org output in pages.
|
||||
* Made use of semantic HTML 5 tags when possible. This also makes form input
|
||||
on mobile devices much easier for users.
|
||||
* Redesigned icons to look good on high resolution (retina) displays too.
|
||||
- Made the site administration experience simpler:
|
||||
* Redesigned the installer.
|
||||
* Visually updated and extended the Seven (administration) theme.
|
||||
* Made the administration toolbar responsive and touch friendly.
|
||||
* Added search to the module listing and made the page easier to read.
|
||||
* Added the tour module to provide highly contextual tips for UI elements.
|
||||
- Improved the entity system:
|
||||
* Added a full CRUD API for entities.
|
||||
* Improved the field API and entity query API.
|
||||
* Added support for widgets, formatters, and translation to base entity
|
||||
fields (such as labels).
|
||||
* Made view modes configurable for reusable display variants.
|
||||
* Introduced form modes for reusable form variants.
|
||||
* Added ability to handle a "default" revision that may not be the latest.
|
||||
* All content entity types (custom blocks, terms, comments, etc.), not just
|
||||
nodes, have support for revisions.
|
||||
* Database schema of content entities is automatically generated based on
|
||||
entity type and field definitions.
|
||||
- Added the Typed Data system to manage complex types.
|
||||
- Refactored routing system based on Symfony2 components.
|
||||
- Reworked menu links, local actions, and local tasks based upon the new routing
|
||||
system.
|
||||
- Made declarative information (libraries, permissions, routes etc.) use YAML
|
||||
files for definitions instead of PHP.
|
||||
- Improved the menu handling systems:
|
||||
* Moved custom menu item handling to its own module.
|
||||
* Reworked menu links, local actions, and local tasks based upon the new
|
||||
routing system.
|
||||
- Added plugin system to standardize implementation of several core APIs.
|
||||
- Configuration:
|
||||
* Added a centralized file-based configuration system.
|
||||
* Allows module authors to provide configuration in a standard format.
|
||||
* Implements functionality to get, set, add and remove configuration.
|
||||
* Includes ability to override configuration values with language variants
|
||||
and other runtime values.
|
||||
* Supports configuration schema, dependencies, and validation to maintain
|
||||
data-integrity between deployments and updates.
|
||||
- Introduced a new configuration management system:
|
||||
* Added a centralized configuration system with export and import
|
||||
functionality.
|
||||
* Allowed module authors to provide configuration in a YAML file format.
|
||||
* Implemented functionality to get, set, add, and remove configuration.
|
||||
* Provided the ability to override configuration values with language
|
||||
variants and other runtime values.
|
||||
* Added configuration schema, dependencies, and validation to maintain
|
||||
data integrity between deployments and updates.
|
||||
* Support added for both global configuration and configuration entities.
|
||||
- Improved authoring experience:
|
||||
* Redesigned the content creation and editing form.
|
||||
* Content preview is now displayed on the frontend.
|
||||
* Added the CKEditor WYSIWYG editor. Clean markup guaranteed thanks to tight
|
||||
integration with the filter system.
|
||||
* Includes uploading, aligning and captioning of images.
|
||||
* Correspondingly modernized the default text formats.
|
||||
* Provides a drag-and-drop configuration UI, which automatically updates the
|
||||
* Made uploading, aligning, and captioning of images possible in the editor.
|
||||
* Modernized the default text formats.
|
||||
* Added a drag-and-drop configuration UI, which automatically updates the
|
||||
HTML filter settings, making configuring text formats trivial for typical
|
||||
use cases.
|
||||
* Added align and caption filters that can be applied to any element:
|
||||
images, blockquotes, code snippets, videos…
|
||||
* In-place editing of any entity: nodes, blocks…
|
||||
* Made possible to in-place edit any entity: nodes, blocks…
|
||||
* Added the Text Editor module to help map other editors to text formats.
|
||||
- Improved media management:
|
||||
* Added ability to configure when unused files get deleted with the option
|
||||
to keep them, useful for media libraries.
|
||||
* Added a customizable view under the content administration screen that
|
||||
lists all files uploaded on the system.
|
||||
* Made uploads immediate when selecting files in file fields.
|
||||
* Added ability to upload multiple files at once.
|
||||
* Added local image input filter, to enable secure image posting.
|
||||
- Included the following Symfony2 components:
|
||||
* ClassLoader - PSR-0-compatible autoload routines.
|
||||
* DependencyInjection - Flexible dependency injection container.
|
||||
|
@ -86,16 +117,14 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
|
|||
* Blog
|
||||
* Dashboard
|
||||
* OpenID
|
||||
* PHP Filter
|
||||
* Poll
|
||||
* Profile
|
||||
* Trigger
|
||||
- Removed the Statistics module's accesslog functionality and reports from core.
|
||||
- Removed the Statistics module's accesslog functionality and reports.
|
||||
- Removed XML-RPC functionality from core.
|
||||
- Removed user signatures support from core.
|
||||
- Universally Unique IDentifier (UUID):
|
||||
* Support for generating and validating UUIDs.
|
||||
- Tremendously improved language support all around.
|
||||
- Added ability to generate and validate Universally Unique IDentifiers (UUIDs).
|
||||
- Tremendously improved language support all around:
|
||||
* Great language improvements for users:
|
||||
* Improved language selection with user preference detection in the
|
||||
installer based on browser settings.
|
||||
|
@ -143,12 +172,6 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
|
|||
configuration with translatable values (blocks, views, fields, etc.).
|
||||
* Added language options to block visibility.
|
||||
* Much improved language APIs for developers:
|
||||
* Added simple APIs and hooks to save/delete/update languages.
|
||||
* New Language class wraps language information, used universally.
|
||||
* Unified database schemas and APIs to make it easier to spot where
|
||||
language codes are referenced.
|
||||
* Made the language negotiation system APIs more consistent for
|
||||
developers.
|
||||
* Made it possible for users to have a preferred language separate from
|
||||
their user entity language.
|
||||
* The text formatter from t() is now available as FormattableMarkup.
|
||||
|
@ -157,49 +180,54 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
|
|||
menu items and contextual links.
|
||||
* Removed textgroups support from interface translation in favor of
|
||||
native configuration language support.
|
||||
* Added configuration schema system to support generating translation
|
||||
forms for any configuration.
|
||||
* Reworked Gettext PO support to use pluggable read/write handlers.
|
||||
* Added language select form element in the Form API.
|
||||
* Added a transliteration API. (Only used for machine names in core.)
|
||||
* Added a language fallback capability to the interface translation API.
|
||||
- New field types added to core:
|
||||
- Email
|
||||
- Link
|
||||
- Phone number
|
||||
- Entity reference
|
||||
- Date
|
||||
- Comment (allows comment threads on entity types other than node).
|
||||
- Added local image input filter, to enable secure image posting.
|
||||
* Email
|
||||
* Link
|
||||
* Telephone number
|
||||
* Entity reference
|
||||
* Date
|
||||
- Made commenting more flexible:
|
||||
* Added the notion of comment types (for reviews, greetings, and so on),
|
||||
each of which can be configured with a different set of fields.
|
||||
* Made commenting a field to allow comment threads on entity types other
|
||||
than nodes.
|
||||
- Added Views and Views UI module to core:
|
||||
* Various core listings: /node, /admin/content/node, /admin/people etc. are
|
||||
now served by views.
|
||||
* REST API support built in.
|
||||
* Added simple bulk operations functionality to Views.
|
||||
* Converted various core listings to views, including /node,
|
||||
/admin/content/node, /admin/people, and several blocks.
|
||||
* Built in REST API support.
|
||||
* Rewrote caching integration for better performance.
|
||||
- Custom blocks are now fieldable, revisionable, and translatable entities.
|
||||
- An accessible modal API based on improvements made in collaboration with the
|
||||
jQuery UI team and the Views team.
|
||||
- Fieldable contact forms allowing site-builders to easily build custom forms
|
||||
for soliciting feedback from users.
|
||||
* Made it possible to configure responsive tables in Views.
|
||||
- Greatly improved block management:
|
||||
* Made custom blocks fieldable, revisionable, and translatable entities.
|
||||
* Added the notion of custom block types.
|
||||
* Added the ability to place the same block in multiple locations.
|
||||
* Introduced a block library with categorized blocks.
|
||||
- Introduced an accessible modal API based on improvements made in collaboration
|
||||
with the jQuery UI team.
|
||||
- Made it possible to add fields to contact forms allowing site-builders to
|
||||
easily build custom forms for soliciting feedback from users.
|
||||
- Added a Web Services module package.
|
||||
* Added a RESTful web services provider module.
|
||||
* Added a serialization module using the Symfony serialization component.
|
||||
* Added a Hypertext Application Language (HAL) serialization module.
|
||||
* Added a HTTP Basic authentication provider module.
|
||||
- Significant performance/scalability improvements:
|
||||
* Cache tags, which allow content to be invalidated accurately and instantly,
|
||||
including reverse proxies and CDNs.
|
||||
* Cache contexts, which allow content to be cached correctly, and placeholdered
|
||||
to improve cache hit rates.
|
||||
* Cacheability bubbling, which allows strict tracking of assets and
|
||||
cacheability throughout page rendering.
|
||||
* Page caching has been factored out to its own module and is enabled by
|
||||
default.
|
||||
* Authenticated page caching has been added to core via the Dynamic Page Cache
|
||||
module and is enabled by default.
|
||||
* APCu, memory, and PHP file caching backends added to core, alongside support
|
||||
for a chained, consistent cache backend to support correctly using fast
|
||||
local cache implementations with multiple web servers.
|
||||
- When using MySQL, the MyISAM engine is no longer supported.
|
||||
- Improved performance/scalability significantly:
|
||||
* Introduced cache tags, which allow content to be invalidated accurately
|
||||
and instantly, including for reverse proxies and CDNs.
|
||||
* Added cache contexts, which allow content to be cached correctly, and
|
||||
placeholdered to improve cache hit rates.
|
||||
* Implemented cacheability bubbling, which allows strict tracking of assets
|
||||
and cacheability throughout page rendering.
|
||||
* Factored out page caching to its own module and enabled it by default.
|
||||
* Added the Dynamic Page Cache module for authenticated page caching and
|
||||
enabled it by default.
|
||||
* Added APCu, memory, and PHP file caching backends to core, alongside
|
||||
support for a chained, consistent cache backend to support correctly using
|
||||
fast local cache implementations with multiple web servers.
|
||||
- Removed support for MyISAM, when using MySQL.
|
||||
- Testing improvements
|
||||
* Added PHPUnit for proper unit testing, see
|
||||
https://phpunit.de/manual/4.8/en/index.html so you can run tests via
|
||||
|
@ -209,6 +237,32 @@ Drupal 8.0.x, xxxx-xx-xx (development version)
|
|||
* Added KernelTestBase to provide a fast API testing of integration of
|
||||
different components
|
||||
* Core branch nightly tests include PHP 5.5, 5.6, 7, sqlite and PostgreSQL.
|
||||
- Added the migrate module (experimental) with support for migrating content and
|
||||
configuration from earlier Drupal versions.
|
||||
- Introduced support for Composer.
|
||||
- Moved the automated cron execution functionality to its own module.
|
||||
- Refactored IP address based banning functionality to its own module.
|
||||
- Security improvements:
|
||||
* Removed PHP filter, including the ability to use PHP for block visibility.
|
||||
* Managing fields for each entity type is now a separate permission.
|
||||
* PDO drivers other than MySQL are now limited to executing single
|
||||
statements to limit SQL injection vectors.
|
||||
* Added an autoescape API to prevent cross-site scripting in many of the
|
||||
places where Drupal outputs HTML.
|
||||
* Hardened user session and session ID handling.
|
||||
* Automated CSRF protection in route definitions.
|
||||
* Clickjacking protection enabled by default.
|
||||
* Made the core JavaScript API compatible with Content Security Policy
|
||||
(CSP).
|
||||
* Trusted host patterns enforced for requests preventing cache and link
|
||||
poisoning.
|
||||
- Switched to semantic versioning with significant updates planned every 6
|
||||
months in 8.1, 8.2, etc.
|
||||
- Numerous other important changes and additions. See
|
||||
https://www.drupal.org/list-changes/drupal for a detailed list.
|
||||
- Numerous bug fixes.
|
||||
- Numerous API documentation improvements.
|
||||
- Additional automated test coverage.
|
||||
|
||||
Drupal 7.0, 2011-01-05
|
||||
----------------------
|
||||
|
|
|
@ -81,7 +81,7 @@ class Drupal {
|
|||
/**
|
||||
* The current system version.
|
||||
*/
|
||||
const VERSION = '8.0.0-dev-2015-11-17';
|
||||
const VERSION = '8.0.0';
|
||||
|
||||
/**
|
||||
* Core API compatibility.
|
||||
|
|
|
@ -511,8 +511,6 @@ class ConfigImporter {
|
|||
* If the configuration is already importing.
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->createExtensionChangelist();
|
||||
|
||||
// Ensure that the changes have been validated.
|
||||
$this->validate();
|
||||
|
||||
|
@ -710,8 +708,10 @@ class ConfigImporter {
|
|||
* @throws \Drupal\Core\Config\ConfigImporterException
|
||||
* Exception thrown if the validate event logged any errors.
|
||||
*/
|
||||
protected function validate() {
|
||||
public function validate() {
|
||||
if (!$this->validated) {
|
||||
// Create the list of installs and uninstalls.
|
||||
$this->createExtensionChangelist();
|
||||
// Validate renames.
|
||||
foreach ($this->getUnprocessedConfiguration('rename') as $name) {
|
||||
$names = $this->storageComparer->extractRenameNames($name);
|
||||
|
|
|
@ -69,9 +69,6 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
|
|||
// Always add a default 'uuid' key.
|
||||
$this->entity_keys['uuid'] = 'uuid';
|
||||
$this->entity_keys['langcode'] = 'langcode';
|
||||
if (isset($this->handlers['storage'])) {
|
||||
$this->checkStorageClass($this->handlers['storage']);
|
||||
}
|
||||
$this->handlers += array(
|
||||
'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage',
|
||||
);
|
||||
|
@ -135,23 +132,12 @@ class ConfigEntityType extends EntityType implements ConfigEntityTypeInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
|
||||
*
|
||||
* @throws \Drupal\Core\Config\Entity\Exception\ConfigEntityStorageClassException
|
||||
* Exception thrown when the provided class is not an instance of
|
||||
* \Drupal\Core\Config\Entity\ConfigEntityStorage.
|
||||
*/
|
||||
public function setStorageClass($class) {
|
||||
$this->checkStorageClass($class);
|
||||
parent::setStorageClass($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the provided class is an instance of ConfigEntityStorage.
|
||||
*
|
||||
* @param string $class
|
||||
* The class to check.
|
||||
*
|
||||
* @see \Drupal\Core\Config\Entity\ConfigEntityStorage.
|
||||
*/
|
||||
protected function checkStorageClass($class) {
|
||||
if (!is_a($class, 'Drupal\Core\Config\Entity\ConfigEntityStorage', TRUE)) {
|
||||
throw new ConfigEntityStorageClassException("$class is not \\Drupal\\Core\\Config\\Entity\\ConfigEntityStorage or it does not extend it");
|
||||
|
|
|
@ -808,6 +808,13 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
return !empty($this->translations[$langcode]['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isNewTranslation() {
|
||||
return $this->translations[$this->activeLangcode]['status'] == static::TRANSLATION_CREATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -822,37 +829,11 @@ abstract class ContentEntityBase extends Entity implements \IteratorAggregate, C
|
|||
throw new \InvalidArgumentException("The entity cannot be translated since it is language neutral ({$this->defaultLangcode}).");
|
||||
}
|
||||
|
||||
// Instantiate a new empty entity so default values will be populated in the
|
||||
// specified language.
|
||||
$entity_type = $this->getEntityType();
|
||||
|
||||
$default_values = array(
|
||||
$entity_type->getKey('bundle') => $this->bundle(),
|
||||
$this->langcodeKey => $langcode,
|
||||
);
|
||||
$entity = $this->entityManager()
|
||||
->getStorage($this->getEntityTypeId())
|
||||
->create($default_values);
|
||||
|
||||
foreach ($entity as $name => $field) {
|
||||
if (!isset($values[$name]) && !$field->isEmpty()) {
|
||||
$values[$name] = $field->getValue();
|
||||
}
|
||||
}
|
||||
$values[$this->langcodeKey] = $langcode;
|
||||
$values[$this->defaultLangcodeKey] = FALSE;
|
||||
|
||||
// Initialize the translation object.
|
||||
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
|
||||
$storage = $this->entityManager()->getStorage($this->getEntityTypeId());
|
||||
$this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
|
||||
$translation = $this->getTranslation($langcode);
|
||||
$definitions = $translation->getFieldDefinitions();
|
||||
|
||||
foreach ($values as $name => $value) {
|
||||
if (isset($definitions[$name]) && $definitions[$name]->isTranslatable()) {
|
||||
$translation->values[$name][$langcode] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $translation;
|
||||
return $storage->createTranslation($this, $langcode, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,7 +13,10 @@ use Drupal\Core\Field\FieldDefinitionInterface;
|
|||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
abstract class ContentEntityStorageBase extends EntityStorageBase implements DynamicallyFieldableEntityStorageInterface {
|
||||
/**
|
||||
* Base class for content entity storage handlers.
|
||||
*/
|
||||
abstract class ContentEntityStorageBase extends EntityStorageBase implements ContentEntityStorageInterface, DynamicallyFieldableEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* The entity bundle key.
|
||||
|
@ -87,14 +90,33 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
$bundle = $values[$this->bundleKey];
|
||||
}
|
||||
$entity = new $this->entityClass(array(), $this->entityTypeId, $bundle);
|
||||
$this->initFieldValues($entity, $values);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes field values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
|
||||
* An entity object.
|
||||
* @param array $values
|
||||
* (optional) An associative array of initial field values keyed by field
|
||||
* name. If none is provided default values will be applied.
|
||||
* @param array $field_names
|
||||
* (optional) An associative array of field names to be initialized. If none
|
||||
* is provided all fields will be initialized.
|
||||
*/
|
||||
protected function initFieldValues(ContentEntityInterface $entity, array $values = [], array $field_names = []) {
|
||||
// Populate field values.
|
||||
foreach ($entity as $name => $field) {
|
||||
if (!$field_names || isset($field_names[$name])) {
|
||||
if (isset($values[$name])) {
|
||||
$entity->$name = $values[$name];
|
||||
}
|
||||
elseif (!array_key_exists($name, $values)) {
|
||||
$entity->get($name)->applyDefaultValue();
|
||||
}
|
||||
}
|
||||
unset($values[$name]);
|
||||
}
|
||||
|
||||
|
@ -102,7 +124,23 @@ abstract class ContentEntityStorageBase extends EntityStorageBase implements Dyn
|
|||
foreach ($values as $name => $value) {
|
||||
$entity->$name = $value;
|
||||
}
|
||||
return $entity;
|
||||
|
||||
// Make sure modules can alter field initial values.
|
||||
$this->invokeHook('field_values_init', $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createTranslation(ContentEntityInterface $entity, $langcode, array $values = []) {
|
||||
$translation = $entity->getTranslation($langcode);
|
||||
$definitions = array_filter($translation->getFieldDefinitions(), function(FieldDefinitionInterface $definition) { return $definition->isTranslatable(); });
|
||||
$field_names = array_map(function(FieldDefinitionInterface $definition) { return $definition->getName(); }, $definitions);
|
||||
$values[$this->langcodeKey] = $langcode;
|
||||
$values[$this->getEntityType()->getKey('default_langcode')] = FALSE;
|
||||
$this->initFieldValues($translation, $values, $field_names);
|
||||
$this->invokeHook('translation_create', $entity);
|
||||
return $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 = []);
|
||||
|
||||
}
|
|
@ -30,4 +30,20 @@ class ContentEntityType extends EntityType implements ContentEntityTypeInterface
|
|||
return 'content';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\Core\Entity\ContentEntityStorageInterface.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* If the provided class does not implement
|
||||
* \Drupal\Core\Entity\ContentEntityStorageInterface.
|
||||
*/
|
||||
protected function checkStorageClass($class) {
|
||||
$required_interface = ContentEntityStorageInterface::class;
|
||||
if (!is_subclass_of($class, $required_interface)) {
|
||||
throw new \InvalidArgumentException("$class does not implement $required_interface");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -276,6 +276,9 @@ class EntityType implements EntityTypeInterface {
|
|||
$this->handlers += array(
|
||||
'access' => 'Drupal\Core\Entity\EntityAccessControlHandler',
|
||||
);
|
||||
if (isset($this->handlers['storage'])) {
|
||||
$this->checkStorageClass($this->handlers['storage']);
|
||||
}
|
||||
|
||||
// Automatically add the EntityChanged constraint if the entity type tracks
|
||||
// the changed time.
|
||||
|
@ -459,9 +462,20 @@ class EntityType implements EntityTypeInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function setStorageClass($class) {
|
||||
$this->checkStorageClass($class);
|
||||
$this->handlers['storage'] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the provided class is an instance of ConfigEntityStorage.
|
||||
*
|
||||
* @param string $class
|
||||
* The class to check.
|
||||
*/
|
||||
protected function checkStorageClass($class) {
|
||||
// Nothing to check by default.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -780,10 +780,9 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Act on a newly created entity.
|
||||
* Acts when creating a new entity.
|
||||
*
|
||||
* This hook runs after a new entity object has just been instantiated. It can
|
||||
* be used to set initial values, e.g. to provide defaults.
|
||||
* This hook runs after a new entity object has just been instantiated.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object.
|
||||
|
@ -792,16 +791,13 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
|
|||
* @see hook_ENTITY_TYPE_create()
|
||||
*/
|
||||
function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
if ($entity instanceof FieldableEntityInterface && !$entity->foo->value) {
|
||||
$entity->foo->value = 'some_initial_value';
|
||||
}
|
||||
\Drupal::logger('example')->info('Entity created: @label', ['@label' => $entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on a newly created entity of a specific type.
|
||||
* Acts when creating a new entity of a specific type.
|
||||
*
|
||||
* This hook runs after a new entity object has just been instantiated. It can
|
||||
* be used to set initial values, e.g. to provide defaults.
|
||||
* This hook runs after a new entity object has just been instantiated.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity object.
|
||||
|
@ -810,9 +806,7 @@ function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
|
|||
* @see hook_entity_create()
|
||||
*/
|
||||
function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) {
|
||||
if (!$entity->foo->value) {
|
||||
$entity->foo->value = 'some_initial_value';
|
||||
}
|
||||
\Drupal::logger('example')->info('ENTITY_TYPE created: @label', ['@label' => $entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1011,6 +1005,38 @@ function hook_ENTITY_TYPE_update(Drupal\Core\Entity\EntityInterface $entity) {
|
|||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts when creating a new entity translation.
|
||||
*
|
||||
* This hook runs after a new entity translation object has just been
|
||||
* instantiated.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $translation
|
||||
* The entity object.
|
||||
*
|
||||
* @ingroup entity_crud
|
||||
* @see hook_ENTITY_TYPE_translation_create()
|
||||
*/
|
||||
function hook_entity_translation_create(\Drupal\Core\Entity\EntityInterface $translation) {
|
||||
\Drupal::logger('example')->info('Entity translation created: @label', ['@label' => $translation->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts when creating a new entity translation of a specific type.
|
||||
*
|
||||
* This hook runs after a new entity translation object has just been
|
||||
* instantiated.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $translation
|
||||
* The entity object.
|
||||
*
|
||||
* @ingroup entity_crud
|
||||
* @see hook_entity_translation_create()
|
||||
*/
|
||||
function hook_ENTITY_TYPE_translation_create(\Drupal\Core\Entity\EntityInterface $translation) {
|
||||
\Drupal::logger('example')->info('ENTITY_TYPE translation created: @label', ['@label' => $translation->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to creation of a new entity translation.
|
||||
*
|
||||
|
@ -1886,6 +1912,44 @@ function hook_entity_field_access_alter(array &$grants, array $context) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts when initializing a fieldable entity object.
|
||||
*
|
||||
* This hook runs after a new entity object or a new entity translation object
|
||||
* has just been instantiated. It can be used to set initial values, e.g. to
|
||||
* provide defaults.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @ingroup entity_crud
|
||||
* @see hook_ENTITY_TYPE_field_values_init()
|
||||
*/
|
||||
function hook_entity_field_values_init(\Drupal\Core\Entity\FieldableEntityInterface $entity) {
|
||||
if ($entity instanceof \Drupal\Core\Entity\ContentEntityInterface && !$entity->foo->value) {
|
||||
$entity->foo->value = 'some_initial_value';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acts when initializing a fieldable entity object.
|
||||
*
|
||||
* This hook runs after a new entity object or a new entity translation object
|
||||
* has just been instantiated. It can be used to set initial values, e.g. to
|
||||
* provide defaults.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||
* The entity object.
|
||||
*
|
||||
* @ingroup entity_crud
|
||||
* @see hook_entity_field_values_init()
|
||||
*/
|
||||
function hook_ENTITY_TYPE_field_values_init(\Drupal\Core\Entity\FieldableEntityInterface $entity) {
|
||||
if (!$entity->foo->value) {
|
||||
$entity->foo->value = 'some_initial_value';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes "pseudo-field" components on content entities.
|
||||
*
|
||||
|
|
|
@ -28,6 +28,14 @@ interface TranslatableInterface {
|
|||
*/
|
||||
public function isDefaultTranslation();
|
||||
|
||||
/**
|
||||
* Checks whether the translation is new.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the translation is new, FALSE otherwise.
|
||||
*/
|
||||
public function isNewTranslation();
|
||||
|
||||
/**
|
||||
* Returns the languages the data is translated to.
|
||||
*
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator feed entity storage classes.
|
||||
*/
|
||||
interface FeedStorageInterface extends EntityStorageInterface {
|
||||
interface FeedStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Returns the fids of feeds that need to be refreshed.
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator item entity storage classes.
|
||||
*/
|
||||
interface ItemStorageInterface extends EntityStorageInterface {
|
||||
interface ItemStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Returns the count of the items in a feed.
|
||||
|
|
|
@ -36,25 +36,24 @@ class BlockContentViewsData extends EntityViewsData {
|
|||
),
|
||||
);
|
||||
// Advertise this table as a possible base table.
|
||||
$data['block_content_revision']['table']['base']['help'] = $this->t('Block Content revision is a history of changes to block content.');
|
||||
$data['block_content_revision']['table']['base']['defaults']['title'] = 'info';
|
||||
$data['block_content_field_revision']['table']['base']['help'] = $this->t('Block Content revision is a history of changes to block content.');
|
||||
$data['block_content_field_revision']['table']['base']['defaults']['title'] = 'info';
|
||||
|
||||
// @todo EntityViewsData should add these relationships by default.
|
||||
// https://www.drupal.org/node/2410275
|
||||
$data['block_content_revision']['id']['relationship']['id'] = 'standard';
|
||||
$data['block_content_revision']['id']['relationship']['base'] = 'block_content';
|
||||
$data['block_content_revision']['id']['relationship']['base field'] = 'id';
|
||||
$data['block_content_revision']['id']['relationship']['title'] = $this->t('Block Content');
|
||||
$data['block_content_revision']['id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
|
||||
$data['block_content_field_revision']['id']['relationship']['id'] = 'standard';
|
||||
$data['block_content_field_revision']['id']['relationship']['base'] = 'block_content_field_data';
|
||||
$data['block_content_field_revision']['id']['relationship']['base field'] = 'id';
|
||||
$data['block_content_field_revision']['id']['relationship']['title'] = $this->t('Block Content');
|
||||
$data['block_content_field_revision']['id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
|
||||
|
||||
$data['block_content_revision']['revision_id']['relationship']['id'] = 'standard';
|
||||
$data['block_content_revision']['revision_id']['relationship']['base'] = 'block_content';
|
||||
$data['block_content_revision']['revision_id']['relationship']['base field'] = 'revision_id';
|
||||
$data['block_content_revision']['revision_id']['relationship']['title'] = $this->t('Block Content');
|
||||
$data['block_content_revision']['revision_id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
|
||||
$data['block_content_field_revision']['revision_id']['relationship']['id'] = 'standard';
|
||||
$data['block_content_field_revision']['revision_id']['relationship']['base'] = 'block_content_field_data';
|
||||
$data['block_content_field_revision']['revision_id']['relationship']['base field'] = 'revision_id';
|
||||
$data['block_content_field_revision']['revision_id']['relationship']['title'] = $this->t('Block Content');
|
||||
$data['block_content_field_revision']['revision_id']['relationship']['label'] = $this->t('Get the actual block content from a block content revision.');
|
||||
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ class RevisionRelationshipsTest extends ViewTestBase {
|
|||
$column_map = array(
|
||||
'revision_id' => 'revision_id',
|
||||
'id_1' => 'id_1',
|
||||
'block_content_block_content_revision_id' => 'block_content_block_content_revision_id',
|
||||
'block_content_field_data_block_content_field_revision_id' => 'block_content_field_data_block_content_field_revision_id',
|
||||
);
|
||||
|
||||
// Here should be two rows.
|
||||
|
@ -70,12 +70,12 @@ class RevisionRelationshipsTest extends ViewTestBase {
|
|||
array(
|
||||
'revision_id' => '1',
|
||||
'id_1' => '1',
|
||||
'block_content_block_content_revision_id' => '1',
|
||||
'block_content_field_data_block_content_field_revision_id' => '1',
|
||||
),
|
||||
array(
|
||||
'revision_id' => '2',
|
||||
'id_1' => '1',
|
||||
'block_content_block_content_revision_id' => '1',
|
||||
'block_content_field_data_block_content_field_revision_id' => '1',
|
||||
),
|
||||
);
|
||||
$this->assertIdenticalResultset($view_id, $resultset_id, $column_map);
|
||||
|
@ -87,7 +87,7 @@ class RevisionRelationshipsTest extends ViewTestBase {
|
|||
array(
|
||||
'revision_id' => '2',
|
||||
'id_1' => '1',
|
||||
'block_content_block_content_revision_id' => '1',
|
||||
'block_content_field_data_block_content_field_revision_id' => '1',
|
||||
),
|
||||
);
|
||||
$this->assertIdenticalResultset($view_revision_id, $resultset_revision_id, $column_map);
|
||||
|
|
|
@ -8,7 +8,7 @@ label: null
|
|||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_revision
|
||||
base_table: block_content_field_revision
|
||||
base_field: revision_id
|
||||
core: '8'
|
||||
display:
|
||||
|
@ -17,28 +17,28 @@ display:
|
|||
relationships:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
required: true
|
||||
plugin_id: standard
|
||||
fields:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
id_1:
|
||||
id: id_1
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
id:
|
||||
id: id
|
||||
table: block_content
|
||||
table: block_content_field_data
|
||||
field: id
|
||||
relationship: id
|
||||
plugin_id: field
|
||||
|
@ -47,11 +47,20 @@ display:
|
|||
arguments:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: numeric
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
sorts:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
order: ASC
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
display_plugin: default
|
||||
display_title: Master
|
||||
id: default
|
||||
|
|
|
@ -8,7 +8,7 @@ label: null
|
|||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_revision
|
||||
base_table: block_content_field_revision
|
||||
base_field: revision_id
|
||||
core: '8'
|
||||
display:
|
||||
|
@ -17,7 +17,7 @@ display:
|
|||
relationships:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
required: true
|
||||
entity_type: block_content
|
||||
|
@ -26,21 +26,21 @@ display:
|
|||
fields:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
id_1:
|
||||
id: id_1
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
id:
|
||||
id: id
|
||||
table: block_content
|
||||
table: block_content_field_data
|
||||
field: id
|
||||
relationship: revision_id
|
||||
plugin_id: field
|
||||
|
@ -49,11 +49,20 @@ display:
|
|||
arguments:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_revision
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: block_content_id
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
sorts:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
order: ASC
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
display_extenders: { }
|
||||
display_plugin: default
|
||||
display_title: Master
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
namespace Drupal\comment;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for comment entity storage classes.
|
||||
*/
|
||||
interface CommentStorageInterface extends EntityStorageInterface {
|
||||
interface CommentStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Gets the maximum encoded thread value for the top level comments.
|
||||
|
|
|
@ -338,7 +338,7 @@ display:
|
|||
arguments:
|
||||
nid:
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: node
|
||||
group_type: group
|
||||
|
|
|
@ -8,12 +8,24 @@
|
|||
namespace Drupal\config\Form;
|
||||
|
||||
use Drupal\Component\Serialization\Yaml;
|
||||
use Drupal\config\StorageReplaceDataWrapper;
|
||||
use Drupal\Core\Config\ConfigImporter;
|
||||
use Drupal\Core\Config\ConfigImporterException;
|
||||
use Drupal\Core\Config\ConfigManagerInterface;
|
||||
use Drupal\Core\Config\StorageComparer;
|
||||
use Drupal\Core\Config\StorageInterface;
|
||||
use Drupal\Core\Config\TypedConfigManagerInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Extension\ModuleInstallerInterface;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Drupal\Core\Form\ConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for importing a single configuration file.
|
||||
|
@ -34,6 +46,62 @@ class ConfigSingleImportForm extends ConfirmFormBase {
|
|||
*/
|
||||
protected $configStorage;
|
||||
|
||||
/**
|
||||
* The renderer service.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The event dispatcher.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* The configuration manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigManagerInterface;
|
||||
*/
|
||||
protected $configManager;
|
||||
|
||||
/**
|
||||
* The database lock object.
|
||||
*
|
||||
* @var \Drupal\Core\Lock\LockBackendInterface
|
||||
*/
|
||||
protected $lock;
|
||||
|
||||
/**
|
||||
* The typed config manager.
|
||||
*
|
||||
* @var \Drupal\Core\Config\TypedConfigManagerInterface
|
||||
*/
|
||||
protected $typedConfigManager;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The theme handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ThemeHandlerInterface
|
||||
*/
|
||||
protected $themeHandler;
|
||||
|
||||
/**
|
||||
* The module installer.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleInstallerInterface
|
||||
*/
|
||||
protected $moduleInstaller;
|
||||
|
||||
/**
|
||||
* If the config exists, this is that object. Otherwise, FALSE.
|
||||
*
|
||||
|
@ -55,10 +123,36 @@ class ConfigSingleImportForm extends ConfirmFormBase {
|
|||
* The entity manager.
|
||||
* @param \Drupal\Core\Config\StorageInterface $config_storage
|
||||
* The config storage.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer service.
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
||||
* The event dispatcher used to notify subscribers of config import events.
|
||||
* @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
||||
* The configuration manager.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend to ensure multiple imports do not occur at the same time.
|
||||
* @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
||||
* The typed configuration manager.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Extension\ModuleInstallerInterface $module_installer
|
||||
* The module installer.
|
||||
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
|
||||
* The theme handler.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage) {
|
||||
public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, RendererInterface $renderer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler) {
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->configStorage = $config_storage;
|
||||
$this->renderer = $renderer;
|
||||
|
||||
// Services necessary for \Drupal\Core\Config\ConfigImporter.
|
||||
$this->eventDispatcher = $event_dispatcher;
|
||||
$this->configManager = $config_manager;
|
||||
$this->lock = $lock;
|
||||
$this->typedConfigManager = $typed_config;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->moduleInstaller = $module_installer;
|
||||
$this->themeHandler = $theme_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,7 +161,15 @@ class ConfigSingleImportForm extends ConfirmFormBase {
|
|||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager'),
|
||||
$container->get('config.storage')
|
||||
$container->get('config.storage'),
|
||||
$container->get('renderer'),
|
||||
$container->get('event_dispatcher'),
|
||||
$container->get('config.manager'),
|
||||
$container->get('lock.persistent'),
|
||||
$container->get('config.typed'),
|
||||
$container->get('module_handler'),
|
||||
$container->get('module_installer'),
|
||||
$container->get('theme_handler')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -204,6 +306,8 @@ class ConfigSingleImportForm extends ConfirmFormBase {
|
|||
$form_state->setErrorByName('import', $this->t('Missing ID key "@id_key" for this @entity_type import.', array('@id_key' => $id_key, '@entity_type' => $definition->getLabel())));
|
||||
return;
|
||||
}
|
||||
|
||||
$config_name = $definition->getConfigPrefix() . '.' . $data[$id_key];
|
||||
// If there is an existing entity, ensure matching ID and UUID.
|
||||
if ($entity = $entity_storage->load($data[$id_key])) {
|
||||
$this->configExists = $entity;
|
||||
|
@ -222,10 +326,53 @@ class ConfigSingleImportForm extends ConfirmFormBase {
|
|||
}
|
||||
}
|
||||
else {
|
||||
$config = $this->config($form_state->getValue('config_name'));
|
||||
$config_name = $form_state->getValue('config_name');
|
||||
$config = $this->config($config_name);
|
||||
$this->configExists = !$config->isNew() ? $config : FALSE;
|
||||
}
|
||||
|
||||
// Use ConfigImporter validation.
|
||||
if (!$form_state->getErrors()) {
|
||||
$source_storage = new StorageReplaceDataWrapper($this->configStorage);
|
||||
$source_storage->replaceData($config_name, $data);
|
||||
$storage_comparer = new StorageComparer(
|
||||
$source_storage,
|
||||
$this->configStorage,
|
||||
$this->configManager
|
||||
);
|
||||
|
||||
if (!$storage_comparer->createChangelist()->hasChanges()) {
|
||||
$form_state->setErrorByName('import', $this->t('There are no changes to import.'));
|
||||
}
|
||||
else {
|
||||
$config_importer = new ConfigImporter(
|
||||
$storage_comparer,
|
||||
$this->eventDispatcher,
|
||||
$this->configManager,
|
||||
$this->lock,
|
||||
$this->typedConfigManager,
|
||||
$this->moduleHandler,
|
||||
$this->moduleInstaller,
|
||||
$this->themeHandler,
|
||||
$this->getStringTranslation()
|
||||
);
|
||||
|
||||
try {
|
||||
$config_importer->validate();
|
||||
$form_state->set('config_importer', $config_importer);
|
||||
}
|
||||
catch (ConfigImporterException $e) {
|
||||
// There are validation errors.
|
||||
$item_list = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $config_importer->getErrors(),
|
||||
'#title' => $this->t('The configuration cannot be imported because it failed validation for the following reasons:'),
|
||||
];
|
||||
$form_state->setErrorByName('import', $this->renderer->render($item_list));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the decoded version of the submitted import.
|
||||
$form_state->setValueForElement($form['import'], $data);
|
||||
}
|
||||
|
@ -241,26 +388,34 @@ class ConfigSingleImportForm extends ConfirmFormBase {
|
|||
return;
|
||||
}
|
||||
|
||||
// If a simple configuration file was added, set the data and save.
|
||||
if ($this->data['config_type'] === 'system.simple') {
|
||||
$this->configFactory()->getEditable($this->data['config_name'])->setData($this->data['import'])->save();
|
||||
drupal_set_message($this->t('The %name configuration was imported.', array('%name' => $this->data['config_name'])));
|
||||
/** @var \Drupal\Core\Config\ConfigImporter $config_importer */
|
||||
$config_importer = $form_state->get('config_importer');
|
||||
if ($config_importer->alreadyImporting()) {
|
||||
drupal_set_message($this->t('Another request may be importing configuration already.'), 'error');
|
||||
}
|
||||
// For a config entity, create an entity and save it.
|
||||
else{
|
||||
try {
|
||||
$entity_storage = $this->entityManager->getStorage($this->data['config_type']);
|
||||
if ($this->configExists) {
|
||||
$entity = $entity_storage->updateFromStorageRecord($this->configExists, $this->data['import']);
|
||||
$sync_steps = $config_importer->initialize();
|
||||
$batch = [
|
||||
'operations' => [],
|
||||
'finished' => [ConfigSync::class, 'finishBatch'],
|
||||
'title' => $this->t('Importing configuration'),
|
||||
'init_message' => $this->t('Starting configuration import.'),
|
||||
'progress_message' => $this->t('Completed @current step of @total.'),
|
||||
'error_message' => $this->t('Configuration import has encountered an error.'),
|
||||
];
|
||||
foreach ($sync_steps as $sync_step) {
|
||||
$batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]];
|
||||
}
|
||||
else {
|
||||
$entity = $entity_storage->createFromStorageRecord($this->data['import']);
|
||||
|
||||
batch_set($batch);
|
||||
}
|
||||
$entity->save();
|
||||
drupal_set_message($this->t('The @entity_type %label was imported.', array('@entity_type' => $entity->getEntityTypeId(), '%label' => $entity->label())));
|
||||
catch (ConfigImporterException $e) {
|
||||
// There are validation errors.
|
||||
drupal_set_message($this->t('The configuration import failed for the following reasons:'), 'error');
|
||||
foreach ($config_importer->getErrors() as $message) {
|
||||
drupal_set_message($message, 'error');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
drupal_set_message($e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
204
core/modules/config/src/StorageReplaceDataWrapper.php
Normal file
204
core/modules/config/src/StorageReplaceDataWrapper.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -66,7 +66,7 @@ EOD;
|
|||
$this->assertIdentical($entity->label(), 'First');
|
||||
$this->assertIdentical($entity->id(), 'first');
|
||||
$this->assertTrue($entity->status());
|
||||
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
|
||||
$this->assertRaw(t('The configuration was imported successfully.'));
|
||||
|
||||
// Attempt an import with an existing ID but missing UUID.
|
||||
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
|
||||
|
@ -82,8 +82,7 @@ EOD;
|
|||
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
|
||||
$this->assertRaw(t('Are you sure you want to create a new %name @type?', array('%name' => 'custom_id', '@type' => 'test configuration')));
|
||||
$this->drupalPostForm(NULL, array(), t('Confirm'));
|
||||
$entity = $storage->load('custom_id');
|
||||
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
|
||||
$this->assertRaw(t('The configuration was imported successfully.'));
|
||||
|
||||
// Perform an import with a unique ID and UUID.
|
||||
$import = <<<EOD
|
||||
|
@ -103,7 +102,7 @@ EOD;
|
|||
$this->assertRaw(t('Are you sure you want to create a new %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
|
||||
$this->drupalPostForm(NULL, array(), t('Confirm'));
|
||||
$entity = $storage->load('second');
|
||||
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
|
||||
$this->assertRaw(t('The configuration was imported successfully.'));
|
||||
$this->assertIdentical($entity->label(), 'Second');
|
||||
$this->assertIdentical($entity->id(), 'second');
|
||||
$this->assertFalse($entity->status());
|
||||
|
@ -126,8 +125,27 @@ EOD;
|
|||
$this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => 'second', '@type' => 'test configuration')));
|
||||
$this->drupalPostForm(NULL, array(), t('Confirm'));
|
||||
$entity = $storage->load('second');
|
||||
$this->assertRaw(t('The @entity_type %label was imported.', array('@entity_type' => 'config_test', '%label' => $entity->label())));
|
||||
$this->assertRaw(t('The configuration was imported successfully.'));
|
||||
$this->assertIdentical($entity->label(), 'Second updated');
|
||||
|
||||
// Try to perform an update which adds missing dependencies.
|
||||
$import = <<<EOD
|
||||
id: second
|
||||
uuid: $second_uuid
|
||||
label: 'Second updated'
|
||||
weight: 0
|
||||
style: ''
|
||||
status: '0'
|
||||
dependencies:
|
||||
module:
|
||||
- does_not_exist
|
||||
EOD;
|
||||
$edit = array(
|
||||
'config_type' => 'config_test',
|
||||
'import' => $import,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
|
||||
$this->assertRaw(t('Configuration %name depends on the %owner module that will not be installed after import.', ['%name' => 'config_test.dynamic.second', '%owner' => 'does_not_exist']));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,6 +168,20 @@ EOD;
|
|||
$this->drupalPostForm(NULL, array(), t('Confirm'));
|
||||
$this->drupalGet('');
|
||||
$this->assertText('Test simple import');
|
||||
|
||||
// Ensure that ConfigImporter validation is running when importing simple
|
||||
// configuration.
|
||||
$config_data = $this->config('core.extension')->get();
|
||||
// Simulate uninstalling the Config module.
|
||||
unset($config_data['module']['config']);
|
||||
$edit = array(
|
||||
'config_type' => 'system.simple',
|
||||
'config_name' => 'core.extension',
|
||||
'import' => Yaml::encode($config_data),
|
||||
);
|
||||
$this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import'));
|
||||
$this->assertText(t('Can not uninstall the Configuration module as part of a configuration synchronization through the user interface.'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,7 +27,7 @@ display:
|
|||
field: nid
|
||||
id: nid
|
||||
relationship: none
|
||||
table: node
|
||||
table: node_field_data
|
||||
plugin_id: numeric
|
||||
pager:
|
||||
options:
|
||||
|
@ -39,7 +39,7 @@ display:
|
|||
id: nid
|
||||
order: ASC
|
||||
relationship: none
|
||||
table: node
|
||||
table: node_field_data
|
||||
plugin_id: numeric
|
||||
display_plugin: default
|
||||
display_title: Master
|
||||
|
|
|
@ -24,7 +24,7 @@ display:
|
|||
nid:
|
||||
field: nid
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
plugin_id: node
|
||||
filters:
|
||||
field_date_value:
|
||||
|
@ -38,7 +38,7 @@ display:
|
|||
id: nid
|
||||
order: ASC
|
||||
relationship: none
|
||||
table: node
|
||||
table: node_field_data
|
||||
plugin_id: numeric
|
||||
pager:
|
||||
type: full
|
||||
|
|
|
@ -24,7 +24,7 @@ display:
|
|||
nid:
|
||||
field: nid
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
plugin_id: node
|
||||
sorts:
|
||||
field_date_value:
|
||||
|
@ -39,7 +39,7 @@ display:
|
|||
id: nid
|
||||
order: ASC
|
||||
relationship: none
|
||||
table: node
|
||||
table: node_field_data
|
||||
plugin_id: numeric
|
||||
pager:
|
||||
type: full
|
||||
|
|
|
@ -9,7 +9,7 @@ label: test_view_fieldapi
|
|||
module: views
|
||||
description: ''
|
||||
tag: default
|
||||
base_table: node
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: '8'
|
||||
display:
|
||||
|
@ -21,7 +21,7 @@ display:
|
|||
nid:
|
||||
field: nid
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
namespace Drupal\file;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for file entity storage classes.
|
||||
*/
|
||||
interface FileStorageInterface extends EntityStorageInterface {
|
||||
interface FileStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Determines total disk space used by a single user or the whole filesystem.
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
namespace Drupal\node;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for node entity storage classes.
|
||||
*/
|
||||
interface NodeStorageInterface extends EntityStorageInterface {
|
||||
interface NodeStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Gets a list of node revision IDs for a specific node.
|
||||
|
|
|
@ -240,7 +240,7 @@ class NodeViewsData extends EntityViewsData {
|
|||
'title' => t('Content'),
|
||||
'label' => t('Get the actual content from a content revision.'),
|
||||
),
|
||||
) + $data['node_revision']['vid'];
|
||||
) + $data['node_field_revision']['vid'];
|
||||
|
||||
$data['node_field_revision']['langcode']['help'] = t('The language the original content is in.');
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ display:
|
|||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
|
|
|
@ -41,7 +41,7 @@ display:
|
|||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
field: nid
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
|
|
|
@ -41,7 +41,7 @@ display:
|
|||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
field: nid
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
|
|
|
@ -328,6 +328,7 @@ class EntityTranslationTest extends EntityLanguageTestBase {
|
|||
// Verify that we obtain the entity object itself when we attempt to
|
||||
// retrieve a translation referring to it.
|
||||
$translation = $entity->getTranslation(LanguageInterface::LANGCODE_NOT_SPECIFIED);
|
||||
$this->assertFalse($translation->isNewTranslation(), 'Existing translations are not marked as new.');
|
||||
$this->assertIdentical($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
|
||||
$entity->{$langcode_key}->value = $default_langcode;
|
||||
$translation = $entity->getTranslation($default_langcode);
|
||||
|
@ -353,6 +354,7 @@ class EntityTranslationTest extends EntityLanguageTestBase {
|
|||
$entity->name->value = $name;
|
||||
$name_translated = $langcode . '_' . $this->randomMachineName();
|
||||
$translation = $entity->addTranslation($langcode);
|
||||
$this->assertTrue($translation->isNewTranslation(), 'Newly added translations are marked as new.');
|
||||
$this->assertNotIdentical($entity, $translation, 'The entity and the translation object differ from one another.');
|
||||
$this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.');
|
||||
$this->assertEqual($translation->language()->getId(), $langcode, 'The translation language matches the specified one.');
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
function keyvalue_test_entity_type_alter(array &$entity_types) {
|
||||
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
|
||||
if (isset($entity_types['entity_test_label'])) {
|
||||
$entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueEntityStorage');
|
||||
$entity_types['entity_test_label']->setStorageClass('Drupal\Core\Entity\KeyValueStore\KeyValueContentEntityStorage');
|
||||
$entity_keys = $entity_types['entity_test_label']->getKeys();
|
||||
$entity_types['entity_test_label']->set('entity_keys', $entity_keys + array('uuid' => 'uuid'));
|
||||
}
|
||||
|
|
|
@ -8,12 +8,12 @@
|
|||
namespace Drupal\taxonomy;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for taxonomy_term entity storage classes.
|
||||
*/
|
||||
interface TermStorageInterface extends EntityStorageInterface {
|
||||
interface TermStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Removed reference to terms from term_hierarchy.
|
||||
|
|
|
@ -59,7 +59,7 @@ display:
|
|||
subquery_namespace: ''
|
||||
subquery_order: DESC
|
||||
subquery_regenerate: true
|
||||
subquery_sort: node.nid
|
||||
subquery_sort: node_field_data.nid
|
||||
subquery_view: ''
|
||||
table: taxonomy_term_field_data
|
||||
plugin_id: groupwise_max
|
||||
|
|
|
@ -156,7 +156,7 @@ display:
|
|||
plugin_id: field
|
||||
nid:
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
field: nid
|
||||
entity_type: node
|
||||
entity_field: nid
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
namespace Drupal\user;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for user entity storage classes.
|
||||
*/
|
||||
interface UserStorageInterface extends EntityStorageInterface{
|
||||
interface UserStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Update the last login timestamp of the user.
|
||||
|
|
|
@ -116,11 +116,29 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
|
|||
public function getViewsData() {
|
||||
$data = [];
|
||||
|
||||
$base_table = $this->entityType->getBaseTable();
|
||||
$base_table = $this->entityType->getBaseTable() ?: $this->entityType->id();
|
||||
$revisionable = $this->entityType->isRevisionable();
|
||||
$base_field = $this->entityType->getKey('id');
|
||||
$data_table = $this->entityType->getDataTable();
|
||||
$revision_table = $this->entityType->getRevisionTable();
|
||||
$revision_data_table = $this->entityType->getRevisionDataTable();
|
||||
|
||||
$revision_table = '';
|
||||
if ($revisionable) {
|
||||
$revision_table = $this->entityType->getRevisionTable() ?: $this->entityType->id() . '_revision';
|
||||
}
|
||||
|
||||
$translatable = $this->entityType->isTranslatable();
|
||||
$data_table = '';
|
||||
if ($translatable) {
|
||||
$data_table = $this->entityType->getDataTable() ?: $this->entityType->id() . '_field_data';
|
||||
}
|
||||
|
||||
// Some entity types do not have a revision data table defined, but still
|
||||
// have a revision table name set in
|
||||
// \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() so we
|
||||
// apply the same kind of logic.
|
||||
$revision_data_table = '';
|
||||
if ($revisionable && $translatable) {
|
||||
$revision_data_table = $this->entityType->getRevisionDataTable() ?: $this->entityType->id() . '_field_revision';
|
||||
}
|
||||
$revision_field = $this->entityType->getKey('revision');
|
||||
|
||||
// Setup base information of the views data.
|
||||
|
@ -213,6 +231,10 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
|
|||
// the entity base, revision, data tables.
|
||||
$field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id());
|
||||
if ($table_mapping = $this->storage->getTableMapping()) {
|
||||
// Fetch all fields that can appear in both the base table and the data
|
||||
// table.
|
||||
$entity_keys = $this->entityType->getKeys();
|
||||
$duplicate_fields = array_intersect_key($entity_keys, array_flip(['id', 'revision', 'bundle']));
|
||||
// Iterate over each table we have so far and collect field data for each.
|
||||
// Based on whether the field is in the field_definitions provided by the
|
||||
// entity manager.
|
||||
|
@ -221,6 +243,12 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
|
|||
// @todo https://www.drupal.org/node/2337511
|
||||
foreach ($table_mapping->getTableNames() as $table) {
|
||||
foreach ($table_mapping->getFieldNames($table) as $field_name) {
|
||||
// To avoid confusing duplication in the user interface, for fields
|
||||
// that are on both base and data tables, only add them on the data
|
||||
// table (same for revision vs. revision data).
|
||||
if ($data_table && ($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields)) {
|
||||
continue;
|
||||
}
|
||||
$this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
}
|
11
core/modules/views/tests/fixtures/update/duplicate-field-handler.php
vendored
Normal file
11
core/modules/views/tests/fixtures/update/duplicate-field-handler.php
vendored
Normal 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();
|
|
@ -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
|
|
@ -130,7 +130,7 @@ display:
|
|||
fields:
|
||||
nid:
|
||||
id: nid
|
||||
table: node
|
||||
table: node_field_data
|
||||
field: nid
|
||||
relationship: none
|
||||
group_type: group
|
||||
|
|
|
@ -93,7 +93,12 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
'base_table' => 'entity_test',
|
||||
'id' => 'entity_test',
|
||||
'label' => 'Entity test',
|
||||
'entity_keys' => ['id' => 'id', 'langcode' => 'langcode'],
|
||||
'entity_keys' => [
|
||||
'id' => 'id',
|
||||
'langcode' => 'langcode',
|
||||
'bundle' => 'type',
|
||||
'revision' => 'revision_id',
|
||||
],
|
||||
'provider' => 'entity_test',
|
||||
'list_cache_contexts' => ['entity_test_list_cache_context'],
|
||||
]);
|
||||
|
@ -190,6 +195,7 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
$entity_type = $this->baseEntityType
|
||||
->set('data_table', 'entity_test_mul_property_data')
|
||||
->set('id', 'entity_test_mul')
|
||||
->set('translatable', TRUE)
|
||||
->setKey('label', 'label');
|
||||
|
||||
$this->viewsData->setEntityType($entity_type);
|
||||
|
@ -259,6 +265,7 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
->set('revision_table', 'entity_test_mulrev_revision')
|
||||
->set('revision_data_table', 'entity_test_mulrev_property_revision')
|
||||
->set('id', 'entity_test_mulrev')
|
||||
->set('translatable', TRUE)
|
||||
->setKey('revision', 'revision_id')
|
||||
;
|
||||
$this->viewsData->setEntityType($entity_type);
|
||||
|
@ -297,6 +304,7 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
->set('revision_table', 'entity_test_mulrev_revision')
|
||||
->set('revision_data_table', 'entity_test_mulrev_property_revision')
|
||||
->set('id', 'entity_test_mulrev')
|
||||
->set('translatable', TRUE)
|
||||
->setKey('revision', 'revision_id')
|
||||
;
|
||||
$this->viewsData->setEntityType($entity_type);
|
||||
|
@ -316,7 +324,7 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
$revision_data = $data['entity_test_mulrev_property_revision'];
|
||||
$this->assertCount(2, $revision_data['table']['join']);
|
||||
$this->assertEquals([
|
||||
'entity_test' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER'],
|
||||
'entity_test_mulrev_field_data' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER'],
|
||||
'entity_test_mulrev_revision' => ['left_field' => 'revision_id', 'field' => 'revision_id', 'type' => 'INNER'],
|
||||
], $revision_data['table']['join']);
|
||||
$this->assertFalse(isset($data['data_table']));
|
||||
|
@ -526,10 +534,19 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
$table_mapping->expects($this->any())
|
||||
->method('getFieldNames')
|
||||
->willReturnMap([
|
||||
['entity_test_mul', ['id', 'uuid', 'type', 'langcode']],
|
||||
['entity_test_mul_property_data', ['id', 'langcode', 'name', 'description', 'homepage', 'user_id']],
|
||||
['entity_test_mul', ['uuid']],
|
||||
['entity_test_mul_property_data', ['id', 'type', 'langcode', 'name', 'description', 'homepage', 'user_id']],
|
||||
]);
|
||||
|
||||
$table_mapping->expects($this->any())
|
||||
->method('getFieldTableName')
|
||||
->willReturnCallback(function($field) {
|
||||
if ($field == 'uuid') {
|
||||
return 'entity_test_mul';
|
||||
}
|
||||
return 'entity_test_mul_property_data';
|
||||
});
|
||||
|
||||
$this->entityStorage->expects($this->once())
|
||||
->method('getTableMapping')
|
||||
->willReturn($table_mapping);
|
||||
|
@ -547,17 +564,13 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
$data = $this->viewsData->getViewsData();
|
||||
|
||||
// Check the base fields.
|
||||
$this->assertNumericField($data['entity_test_mul']['id']);
|
||||
$this->assertField($data['entity_test_mul']['id'], 'id');
|
||||
$this->assertFalse(isset($data['entity_test_mul']['id']));
|
||||
$this->assertFalse(isset($data['entity_test_mul']['type']));
|
||||
$this->assertUuidField($data['entity_test_mul']['uuid']);
|
||||
$this->assertField($data['entity_test_mul']['uuid'], 'uuid');
|
||||
|
||||
$this->assertBundleField($data['entity_test_mul']['type']);
|
||||
$this->assertField($data['entity_test_mul']['type'], 'type');
|
||||
$this->assertFalse(isset($data['entity_test_mul']['type']['relationship']));
|
||||
|
||||
$this->assertLanguageField($data['entity_test_mul']['langcode']);
|
||||
$this->assertField($data['entity_test_mul']['langcode'], 'langcode');
|
||||
// Also ensure that field_data only fields don't appear on the base table.
|
||||
$this->assertFalse(isset($data['entity_test_mul']['name']));
|
||||
$this->assertFalse(isset($data['entity_test_mul']['description']));
|
||||
|
@ -570,6 +583,9 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
$this->assertNumericField($data['entity_test_mul_property_data']['id']);
|
||||
$this->assertField($data['entity_test_mul_property_data']['id'], 'id');
|
||||
|
||||
$this->assertBundleField($data['entity_test_mul_property_data']['type']);
|
||||
$this->assertField($data['entity_test_mul_property_data']['type'], 'type');
|
||||
|
||||
$this->assertLanguageField($data['entity_test_mul_property_data']['langcode']);
|
||||
$this->assertField($data['entity_test_mul_property_data']['langcode'], 'langcode');
|
||||
$this->assertEquals('Translation language', $data['entity_test_mul_property_data']['langcode']['title']);
|
||||
|
@ -600,7 +616,8 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
->set('revision_table', 'entity_test_mulrev_revision')
|
||||
->set('data_table', 'entity_test_mulrev_property_data')
|
||||
->set('revision_data_table', 'entity_test_mulrev_property_revision')
|
||||
->set('id', 'entity_test_mulrev');
|
||||
->set('id', 'entity_test_mulrev')
|
||||
->set('translatable', TRUE);
|
||||
$base_field_definitions = $this->setupBaseFields(EntityTestMulRev::baseFieldDefinitions($this->baseEntityType));
|
||||
$user_base_field_definitions = [
|
||||
'uid' => BaseFieldDefinition::create('integer')
|
||||
|
@ -645,6 +662,15 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
['entity_test_mulrev_property_revision', ['id', 'revision_id', 'langcode', 'name', 'description', 'homepage', 'user_id']],
|
||||
]);
|
||||
|
||||
$table_mapping->expects($this->any())
|
||||
->method('getFieldTableName')
|
||||
->willReturnCallback(function($field) {
|
||||
if ($field == 'uuid') {
|
||||
return 'entity_test_mulrev';
|
||||
}
|
||||
return 'entity_test_mulrev_property_data';
|
||||
});
|
||||
|
||||
$this->entityStorage->expects($this->once())
|
||||
->method('getTableMapping')
|
||||
->willReturn($table_mapping);
|
||||
|
@ -654,14 +680,11 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
$data = $this->viewsData->getViewsData();
|
||||
|
||||
// Check the base fields.
|
||||
$this->assertNumericField($data['entity_test_mulrev']['id']);
|
||||
$this->assertField($data['entity_test_mulrev']['id'], 'id');
|
||||
$this->assertNumericField($data['entity_test_mulrev']['revision_id']);
|
||||
$this->assertField($data['entity_test_mulrev']['revision_id'], 'revision_id');
|
||||
$this->assertFalse(isset($data['entity_test_mulrev']['id']));
|
||||
$this->assertFalse(isset($data['entity_test_mulrev']['type']));
|
||||
$this->assertFalse(isset($data['entity_test_mulrev']['revision_id']));
|
||||
$this->assertUuidField($data['entity_test_mulrev']['uuid']);
|
||||
$this->assertField($data['entity_test_mulrev']['uuid'], 'uuid');
|
||||
$this->assertStringField($data['entity_test_mulrev']['type']);
|
||||
$this->assertField($data['entity_test_mulrev']['type'], 'type');
|
||||
|
||||
// Also ensure that field_data only fields don't appear on the base table.
|
||||
$this->assertFalse(isset($data['entity_test_mulrev']['name']));
|
||||
|
@ -672,17 +695,12 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
$this->assertFalse(isset($data['entity_test_mulrev']['langcode']));
|
||||
$this->assertFalse(isset($data['entity_test_mulrev']['user_id']));
|
||||
|
||||
// Check the revision fields.
|
||||
$this->assertNumericField($data['entity_test_mulrev_revision']['id']);
|
||||
$this->assertField($data['entity_test_mulrev_revision']['id'], 'id');
|
||||
$this->assertNumericField($data['entity_test_mulrev_revision']['revision_id']);
|
||||
$this->assertField($data['entity_test_mulrev_revision']['revision_id'], 'revision_id');
|
||||
|
||||
$this->assertLanguageField($data['entity_test_mulrev_revision']['langcode']);
|
||||
$this->assertField($data['entity_test_mulrev_revision']['langcode'], 'langcode');
|
||||
$this->assertEquals('Original language', $data['entity_test_mulrev_revision']['langcode']['title']);
|
||||
// Check the revision fields. The revision ID should only appear in the data
|
||||
// table.
|
||||
$this->assertFalse(isset($data['entity_test_mulrev_revision']['revision_id']));
|
||||
|
||||
// Also ensure that field_data only fields don't appear on the revision table.
|
||||
$this->assertFalse(isset($data['entity_test_mulrev_revision']['id']));
|
||||
$this->assertFalse(isset($data['entity_test_mulrev_revision']['name']));
|
||||
$this->assertFalse(isset($data['entity_test_mulrev_revision']['description']));
|
||||
$this->assertFalse(isset($data['entity_test_mulrev_revision']['description__value']));
|
||||
|
@ -693,6 +711,8 @@ class EntityViewsDataTest extends UnitTestCase {
|
|||
// Check the data fields.
|
||||
$this->assertNumericField($data['entity_test_mulrev_property_data']['id']);
|
||||
$this->assertField($data['entity_test_mulrev_property_data']['id'], 'id');
|
||||
$this->assertNumericField($data['entity_test_mulrev_property_data']['revision_id']);
|
||||
$this->assertField($data['entity_test_mulrev_property_data']['revision_id'], 'revision_id');
|
||||
$this->assertLanguageField($data['entity_test_mulrev_property_data']['langcode']);
|
||||
$this->assertField($data['entity_test_mulrev_property_data']['langcode'], 'langcode');
|
||||
$this->assertStringField($data['entity_test_mulrev_property_data']['name']);
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
* Post update functions for Views.
|
||||
*/
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.0.0-beta
|
||||
* @{
|
||||
|
@ -31,6 +34,99 @@ function views_post_update_update_cacheability_metadata() {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update some views fields that were previously duplicated.
|
||||
*/
|
||||
function views_post_update_cleanup_duplicate_views_data() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$ids = [];
|
||||
$message = NULL;
|
||||
$data_tables = [];
|
||||
$base_tables = [];
|
||||
$revision_tables = [];
|
||||
$entities_by_table = [];
|
||||
$duplicate_fields = [];
|
||||
$handler_types = Views::getHandlerTypes();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
|
||||
$entity_type_manager = \Drupal::service('entity_type.manager');
|
||||
// This will allow us to create an index of all entity types of the site.
|
||||
foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) {
|
||||
// Store the entity keyed by base table. If it has a data table, use that as
|
||||
// well.
|
||||
if ($data_table = $entity_type->getDataTable()) {
|
||||
$entities_by_table[$data_table] = $entity_type;
|
||||
}
|
||||
if ($base_table = $entity_type->getBaseTable()) {
|
||||
$entities_by_table[$base_table] = $entity_type;
|
||||
}
|
||||
|
||||
// The following code basically contains the same kind of logic as
|
||||
// \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() to
|
||||
// prefetch all tables (base, data, revision, and revision data).
|
||||
$base_tables[$entity_type_id] = $entity_type->getBaseTable() ?: $entity_type->id();
|
||||
$revisionable = $entity_type->isRevisionable();
|
||||
|
||||
$revision_table = '';
|
||||
if ($revisionable) {
|
||||
$revision_table = $entity_type->getRevisionTable() ?: $entity_type->id() . '_revision';
|
||||
}
|
||||
$revision_tables[$entity_type_id] = $revision_table;
|
||||
|
||||
$translatable = $entity_type->isTranslatable();
|
||||
$data_table = '';
|
||||
// For example the data table just exists, when the entity type is
|
||||
// translatable.
|
||||
if ($translatable) {
|
||||
$data_table = $entity_type->getDataTable() ?: $entity_type->id() . '_field_data';
|
||||
}
|
||||
$data_tables[$entity_type_id] = $data_table;
|
||||
|
||||
$duplicate_fields[$entity_type_id] = array_intersect_key($entity_type->getKeys(), array_flip(['id', 'revision', 'bundle']));
|
||||
}
|
||||
|
||||
foreach ($config_factory->listAll('views.view.') as $view_config_name) {
|
||||
$changed = FALSE;
|
||||
$view = $config_factory->getEditable($view_config_name);
|
||||
|
||||
$displays = $view->get('display');
|
||||
if (isset($entities_by_table[$view->get('base_table')])) {
|
||||
$entity_type = $entities_by_table[$view->get('base_table')];
|
||||
$entity_type_id = $entity_type->id();
|
||||
$data_table = $data_tables[$entity_type_id];
|
||||
$base_table = $base_tables[$entity_type_id];
|
||||
$revision_table = $revision_tables[$entity_type_id];
|
||||
|
||||
if ($data_table) {
|
||||
foreach ($displays as $display_name => &$display) {
|
||||
foreach ($handler_types as $handler_type) {
|
||||
if (!empty($display['display_options'][$handler_type['plural']])) {
|
||||
foreach ($display['display_options'][$handler_type['plural']] as $field_name => &$field) {
|
||||
$table = $field['table'];
|
||||
if (($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields[$entity_type_id])) {
|
||||
$field['table'] = $data_table;
|
||||
$changed = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
$view->set('display', $displays);
|
||||
$view->save();
|
||||
$ids[] = $view->get('id');
|
||||
}
|
||||
}
|
||||
if (!empty($ids)) {
|
||||
$message = new TranslatableMarkup('Updated tables for field handlers for views: @ids', ['@ids' => implode(', ', array_unique($ids))]);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-beta".
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\views_ui\Tests;
|
|||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\views\ViewExecutable;
|
||||
|
||||
/**
|
||||
|
@ -20,12 +21,17 @@ use Drupal\views\ViewExecutable;
|
|||
*/
|
||||
class HandlerTest extends UITestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('node_test_views');
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('test_view_empty', 'test_view_broken', 'node');
|
||||
public static $testViews = array('test_view_empty', 'test_view_broken', 'node', 'test_node_view');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -34,6 +40,7 @@ class HandlerTest extends UITestBase {
|
|||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
ViewTestData::createTestViews(get_class($this), array('node_test_views'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,4 +225,37 @@ class HandlerTest extends UITestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that neither node type or node ID appears multiple times.
|
||||
*
|
||||
* @see \Drupal\views\EntityViewsData
|
||||
*/
|
||||
public function testNoDuplicateFields() {
|
||||
$handler_types = ['field', 'filter', 'sort', 'argument'];
|
||||
|
||||
foreach ($handler_types as $handler_type) {
|
||||
$add_handler_url = 'admin/structure/views/nojs/add-handler/test_node_view/default/' . $handler_type;
|
||||
$this->drupalGet($add_handler_url);
|
||||
|
||||
$this->assertNoDuplicateField('Node ID', 'Content');
|
||||
$this->assertNoDuplicateField('Node ID', 'Content revision');
|
||||
$this->assertNoDuplicateField('Type', 'Content');
|
||||
$this->assertNoDuplicateField('UUID', 'Content');
|
||||
$this->assertNoDuplicateField('Revision ID', 'Content');
|
||||
$this->assertNoDuplicateField('Revision ID', 'Content revision');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that fields only appear once.
|
||||
*
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
* @param string $entity_type
|
||||
* The entity type to which the field belongs.
|
||||
*/
|
||||
public function assertNoDuplicateField($field_name, $entity_type) {
|
||||
$elements = $this->xpath('//td[.=:entity_type]/preceding-sibling::td[@class="title" and .=:title]', [':title' => $field_name, ':entity_type' => $entity_type]);
|
||||
$this->assertEqual(1, count($elements), $field_name . ' appears just once in ' . $entity_type . '.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -699,6 +699,17 @@ $settings['container_yamls'][] = __DIR__ . '/services.yml';
|
|||
* example.org, with all subdomains included.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Include the Pantheon-specific settings file.
|
||||
*
|
||||
* n.b. The settings.pantheon.php file makes some changes
|
||||
* that affect all envrionments that this site
|
||||
* exists in. Always include this file, even in
|
||||
* a local development environment, to insure that
|
||||
* the site settings remain consistent.
|
||||
*/
|
||||
include __DIR__ . "/settings.pantheon.php";
|
||||
|
||||
/**
|
||||
* Load local development override configuration, if available.
|
||||
*
|
||||
|
@ -709,6 +720,6 @@ $settings['container_yamls'][] = __DIR__ . '/services.yml';
|
|||
*
|
||||
* Keep this code block at the end of this file to take full effect.
|
||||
*/
|
||||
# if (file_exists(__DIR__ . '/settings.local.php')) {
|
||||
# include __DIR__ . '/settings.local.php';
|
||||
# }
|
||||
if (file_exists(__DIR__ . '/settings.local.php')) {
|
||||
include __DIR__ . '/settings.local.php';
|
||||
}
|
||||
|
|
|
@ -47,20 +47,6 @@ else {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the $install_state variable to let Drupal know that the settings are verified
|
||||
* since they are being passed directly by the Pantheon.
|
||||
*
|
||||
* Issue: https://github.com/pantheon-systems/drops-8/issues/9
|
||||
*
|
||||
*/
|
||||
if (
|
||||
isset($_ENV['PANTHEON_ENVIRONMENT']) &&
|
||||
$is_installer_url &&
|
||||
(php_sapi_name() != "cli")
|
||||
) {
|
||||
$GLOBALS['install_state']['settings_verified'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow Drupal 8 to Cleanly Redirect to Install.php For New Sites.
|
||||
|
@ -73,7 +59,7 @@ if (
|
|||
if (
|
||||
isset($_ENV['PANTHEON_ENVIRONMENT']) &&
|
||||
!$is_installer_url &&
|
||||
(!is_dir(__DIR__ . '/files/styles')) &&
|
||||
(isset($_SERVER['PANTHEON_DATABASE_STATE']) && ($_SERVER['PANTHEON_DATABASE_STATE'] == 'empty')) &&
|
||||
(empty($GLOBALS['install_state'])) &&
|
||||
(php_sapi_name() != "cli")
|
||||
) {
|
||||
|
|
Reference in a new issue