diff --git a/core/lib/Drupal/Component/Plugin/composer.json b/core/lib/Drupal/Component/Plugin/composer.json index 09179e5..c15e205 100644 --- a/core/lib/Drupal/Component/Plugin/composer.json +++ b/core/lib/Drupal/Component/Plugin/composer.json @@ -1,4 +1,4 @@ -{ +n{ "name": "drupal/plugin", "description": "Base building block for a scalable and extensible plug-in system for PHP components and application framework extensions.", "keywords": ["drupal", "plugin", "plugins"], diff --git a/core/lib/Drupal/Core/Config/ConfigEvents.php b/core/lib/Drupal/Core/Config/ConfigEvents.php index 7fa22a5..7f83ecb 100644 --- a/core/lib/Drupal/Core/Config/ConfigEvents.php +++ b/core/lib/Drupal/Core/Config/ConfigEvents.php @@ -58,4 +58,14 @@ */ const COLLECTION_INFO = 'config.collection_info'; + /** + * Event fired when after importing config to allow modules to handle content. + * + * Some configuration might depend on content entities - this allows modules + * to react to a config import event and create the required content. + * + * @see \Drupal\Core\Config\ConfigImporter::import(). + */ + const CONTENT_DEPENDENCIES = 'config.importer.content_dependencies'; + } diff --git a/core/lib/Drupal/Core/Config/ConfigImporter.php b/core/lib/Drupal/Core/Config/ConfigImporter.php index a13a414..b0336c8 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporter.php +++ b/core/lib/Drupal/Core/Config/ConfigImporter.php @@ -283,7 +283,10 @@ public function hasUnprocessedConfigurationChanges() { * Defaults to the default collection. * * @return array - * An array containing a list of processed changes. + * An array containing a list of processed changes keyed by the operations + * 'create', 'delete', 'rename' and 'update'. Each entry in the array is an + * array of \Drupal\Core\Config\Config objects or FALSE if the item was + * deleted or does not exist. */ public function getProcessedConfiguration($collection = StorageInterface::DEFAULT_COLLECTION) { return $this->processedConfiguration[$collection]; @@ -298,9 +301,12 @@ public function getProcessedConfiguration($collection = StorageInterface::DEFAUL * The change operation performed, either delete, create, rename, or update. * @param string $name * The name of the configuration processed. + * @param \Drupal\Core\Config\Config|bool $config + * The processed configuration object or FALSE if the object was deleted or + * doesn't exist. */ - protected function setProcessedConfiguration($collection, $op, $name) { - $this->processedConfiguration[$collection][$op][] = $name; + protected function setProcessedConfiguration($collection, $op, $name, Config $config) { + $this->processedConfiguration[$collection][$op][$name] = $config; } /** @@ -317,7 +323,7 @@ protected function setProcessedConfiguration($collection, $op, $name) { * An array of configuration names. */ public function getUnprocessedConfiguration($op, $collection = StorageInterface::DEFAULT_COLLECTION) { - return array_diff($this->storageComparer->getChangelist($op, $collection), $this->processedConfiguration[$collection][$op]); + return array_diff($this->storageComparer->getChangelist($op, $collection), array_keys($this->processedConfiguration[$collection][$op])); } /** @@ -532,6 +538,8 @@ public function initialize() { $sync_steps[] = 'processExtensions'; } $sync_steps[] = 'processConfigurations'; + // Add a sync content step. + $sync_steps[] = 'processContentDependencies'; // Allow modules to add new steps to configuration synchronization. $this->moduleHandler->alter('config_import_steps', $sync_steps, $this); @@ -603,6 +611,16 @@ protected function processConfigurations(array &$context) { } /** + * Processes content dependencies. + */ + protected function processContentDependencies(array &$context) { + $event = new ConfigImporterEvent($this, $context); + $this->eventDispatcher->dispatch(ConfigEvents::CONTENT_DEPENDENCIES, $event); + // Update the context object from any changes made to it during the event. + $context = $event->getContext(); + } + + /** * Finishes the batch. * * @param array $context. @@ -729,7 +747,7 @@ protected function processConfiguration($collection, $op, $name) { $this->logError($this->t('Unexpected error during import with operation @op for @name: @message', array('@op' => $op, '@name' => $name, '@message' => $e->getMessage()))); // Error for that operation was logged, mark it as processed so that // the import can continue. - $this->setProcessedConfiguration($collection, $op, $name); + $this->setProcessedConfiguration($collection, $op, $name, FALSE); } } @@ -819,7 +837,7 @@ protected function checkOp($collection, $op, $name) { if (!$target_exists) { // The configuration has already been deleted. For example, a field // is automatically deleted if all the instances are. - $this->setProcessedConfiguration($collection, $op, $name); + $this->setProcessedConfiguration($collection, $op, $name, FALSE); return FALSE; } break; @@ -849,7 +867,7 @@ protected function checkOp($collection, $op, $name) { // Mark as processed so that the synchronisation continues. Once the // the current synchronisation is complete it will show up as a // create. - $this->setProcessedConfiguration($collection, $op, $name); + $this->setProcessedConfiguration($collection, $op, $name, FALSE); return FALSE; } break; @@ -885,7 +903,7 @@ protected function importConfig($collection, $op, $name) { $config->setData($data ? $data : array()); $config->save(); } - $this->setProcessedConfiguration($collection, $op, $name); + $this->setProcessedConfiguration($collection, $op, $name, $config); } /** @@ -939,7 +957,7 @@ protected function importInvokeOwner($collection, $op, $name) { throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type))); } $entity_storage->$method($name, $new_config, $old_config); - $this->setProcessedConfiguration($collection, $op, $name); + $this->setProcessedConfiguration($collection, $op, $name, $new_config); return TRUE; } return FALSE; @@ -985,7 +1003,7 @@ protected function importInvokeRename($collection, $rename_name) { throw new EntityStorageException(String::format('The entity storage "@storage" for the "@entity_type" entity type does not support imports', array('@storage' => get_class($entity_storage), '@entity_type' => $entity_type_id))); } $entity_storage->importRename($names['old_name'], $new_config, $old_config); - $this->setProcessedConfiguration($collection, 'rename', $rename_name); + $this->setProcessedConfiguration($collection, 'rename', $rename_name, $new_config); return TRUE; } diff --git a/core/lib/Drupal/Core/Config/ConfigImporterEvent.php b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php index caef42e..dc1f73e 100644 --- a/core/lib/Drupal/Core/Config/ConfigImporterEvent.php +++ b/core/lib/Drupal/Core/Config/ConfigImporterEvent.php @@ -18,13 +18,23 @@ class ConfigImporterEvent extends Event { protected $configImporter; /** + * Batch context. + * + * @var array + */ + protected $context = array(); + + /** * Constructs ConfigImporterEvent. * * @param \Drupal\Core\Config\ConfigImporter $config_importer * A config import object to notify listeners about. + * @param array $context + * (optional) Batch context. */ - public function __construct(ConfigImporter $config_importer) { + public function __construct(ConfigImporter $config_importer, array $context = array()) { $this->configImporter = $config_importer; + $this->context = $context; } /** @@ -37,4 +47,27 @@ public function getConfigImporter() { return $this->configImporter; } + /** + * Gets the batch context. + * + * @return array + * The batch context if it exists. + */ + public function getContext() { + return $this->context; + } + + /** + * Sets the batch context. + * + * @param array $context + * The new batch context. + * + * @return $this + */ + public function setContext($context) { + $this->context = $context; + return $this; + } + } diff --git a/core/modules/config/src/Tests/ConfigEventsTest.php b/core/modules/config/src/Tests/ConfigEventsTest.php index 93ef627..3e0a9a2 100644 --- a/core/modules/config/src/Tests/ConfigEventsTest.php +++ b/core/modules/config/src/Tests/ConfigEventsTest.php @@ -56,6 +56,11 @@ function testConfigEvents() { $this->assertIdentical($event['current_config_data'], array()); $this->assertIdentical($event['raw_config_data'], array()); $this->assertIdentical($event['original_config_data'], array('key' => 'updated')); + + // Test config dependencies event. + $config->set('dependencies.content', ['entity_test:entity_test:8A521545-F2EC-4BBF-9722-8DAB4F4FCBD7']); + $config->save(); + $this->assertIdentical(\Drupal::state()->get('config_import_test.content_dependencies'), ['entity_test:entity_test:8A521545-F2EC-4BBF-9722-8DAB4F4FCBD7']); } /** diff --git a/core/modules/config/src/Tests/ConfigImporterTest.php b/core/modules/config/src/Tests/ConfigImporterTest.php index fd69055..2c881e7 100644 --- a/core/modules/config/src/Tests/ConfigImporterTest.php +++ b/core/modules/config/src/Tests/ConfigImporterTest.php @@ -32,7 +32,12 @@ class ConfigImporterTest extends DrupalUnitTestBase { * * @var array */ - public static $modules = array('config_test', 'system', 'config_import_test'); + public static $modules = array( + 'config_test', + 'system', + 'config_import_test', + 'entity_test', + ); protected function setUp() { parent::setUp(); diff --git a/core/modules/config/tests/config_import_test/src/EventSubscriber.php b/core/modules/config/tests/config_import_test/src/EventSubscriber.php index 0be4f97..480893c 100644 --- a/core/modules/config/tests/config_import_test/src/EventSubscriber.php +++ b/core/modules/config/tests/config_import_test/src/EventSubscriber.php @@ -52,6 +52,27 @@ public function onConfigImporterValidate(ConfigImporterEvent $event) { } /** + * Reacts to the ConfigEvents::CONTENT_DEPENDENCIES event. + * + * @param \Drupal\Core\Config\ConfigImporterEvent $event + * The config import event. + */ + public function onConfigImporterContentEntities(ConfigImporterEvent $event) { + $importer = $event->getConfigImporter(); + foreach ($importer->getProcessedConfiguration() as $operation => $configurations) { + /* @var \Drupal\Core\Config\Config|bool $config_object */ + foreach ($configurations as $config_id => $config_object) { + if ($dependencies = $config_object->get('dependencies.content')) { + $this->state->set('config_import_test.content_dependencies', $dependencies); + $context = $event->getContext(); + $context['hi_there'] = 'indeed'; + $event->setContext($context); + } + } + } + } + + /** * Reacts to a config save and records information in state for testing. * * @param \Drupal\Core\Config\ConfigCrudEvent $event @@ -106,6 +127,7 @@ static function getSubscribedEvents() { $events[ConfigEvents::SAVE][] = array('onConfigSave', 40); $events[ConfigEvents::DELETE][] = array('onConfigDelete', 40); $events[ConfigEvents::IMPORT_VALIDATE] = array('onConfigImporterValidate'); + $events[ConfigEvents::CONTENT_DEPENDENCIES] = array('onConfigImporterContentEntities'); return $events; }