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
					
				
					 49 changed files with 1394 additions and 281 deletions
				
			
		|  | @ -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. | ||||
|   * 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. | ||||
|     * 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. | ||||
|     * 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,13 +90,32 @@ 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 (isset($values[$name])) { | ||||
|         $entity->$name = $values[$name]; | ||||
|       } | ||||
|       elseif (!array_key_exists($name, $values)) { | ||||
|         $entity->get($name)->applyDefaultValue(); | ||||
|       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 { | ||||
|     else{ | ||||
|       try { | ||||
|         $entity_storage = $this->entityManager->getStorage($this->data['config_type']); | ||||
|         if ($this->configExists) { | ||||
|           $entity = $entity_storage->updateFromStorageRecord($this->configExists, $this->data['import']); | ||||
|         $sync_steps = $config_importer->initialize(); | ||||
|         $batch = [ | ||||
|           'operations' => [], | ||||
|           'finished' => [ConfigSync::class, 'finishBatch'], | ||||
|           'title' => $this->t('Importing configuration'), | ||||
|           'init_message' => $this->t('Starting configuration import.'), | ||||
|           'progress_message' => $this->t('Completed @current step of @total.'), | ||||
|           'error_message' => $this->t('Configuration import has encountered an error.'), | ||||
|         ]; | ||||
|         foreach ($sync_steps as $sync_step) { | ||||
|           $batch['operations'][] = [[ConfigSync::class, 'processBatch'], [$config_importer, $sync_step]]; | ||||
|         } | ||||
|         else { | ||||
|           $entity = $entity_storage->createFromStorageRecord($this->data['import']); | ||||
|         } | ||||
|         $entity->save(); | ||||
|         drupal_set_message($this->t('The @entity_type %label was imported.', array('@entity_type' => $entity->getEntityTypeId(), '%label' => $entity->label()))); | ||||
| 
 | ||||
|         batch_set($batch); | ||||
|       } | ||||
|       catch (\Exception $e) { | ||||
|         drupal_set_message($e->getMessage(), 'error'); | ||||
|       catch (ConfigImporterException $e) { | ||||
|         // There are validation errors.
 | ||||
|         drupal_set_message($this->t('The configuration import failed for the following reasons:'), 'error'); | ||||
|         foreach ($config_importer->getErrors() as $message) { | ||||
|           drupal_set_message($message, 'error'); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  |  | |||
							
								
								
									
										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
	
	 Pantheon Automation
						Pantheon Automation