Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 34 |
CRAP | |
0.00% |
0 / 402 |
| ConfigImporter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 34 |
10920 | |
0.00% |
0 / 402 |
| __construct | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 14 |
|||
| logError | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| getErrors | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| getStorageComparer | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| reset | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 9 |
|||
| getEmptyExtensionsProcessedList | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 11 |
|||
| hasUnprocessedConfigurationChanges | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 9 |
|||
| getProcessedConfiguration | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| setProcessedConfiguration | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| getUnprocessedConfiguration | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| getProcessedExtensions | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| setProcessedExtension | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| createExtensionChangelist | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 8 |
|||
| anonymous function | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| getExtensionChangelist | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 5 |
|||
| getUnprocessedExtensions | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 6 |
|||
| import | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 11 |
|||
| doSyncStep | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 10 |
|||
| initialize | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 22 |
|||
| processExtensions | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 12 |
|||
| processConfigurations | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 31 |
|||
| processMissingContent | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 23 |
|||
| finish | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 6 |
|||
| getNextExtensionOperation | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 14 |
|||
| getNextConfigurationOperation | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 14 |
|||
| validate | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 23 |
|||
| processConfiguration | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 14 |
|||
| processExtension | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 20 |
|||
| checkOp | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 43 |
|||
| importConfig | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 17 |
|||
| importInvokeOwner | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 24 |
|||
| importInvokeRename | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 19 |
|||
| alreadyImporting | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
| reInjectMe | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 8 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Config\ConfigImporter. | |
| */ | |
| namespace Drupal\Core\Config; | |
| use Drupal\Core\Config\Importer\MissingContentEvent; | |
| use Drupal\Core\Extension\ModuleHandlerInterface; | |
| use Drupal\Core\Extension\ModuleInstallerInterface; | |
| use Drupal\Core\Extension\ThemeHandlerInterface; | |
| use Drupal\Core\Config\Entity\ImportableEntityStorageInterface; | |
| use Drupal\Core\DependencyInjection\DependencySerializationTrait; | |
| use Drupal\Core\Entity\EntityStorageException; | |
| use Drupal\Core\Lock\LockBackendInterface; | |
| use Drupal\Core\StringTranslation\StringTranslationTrait; | |
| use Drupal\Core\StringTranslation\TranslationInterface; | |
| use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
| /** | |
| * Defines a configuration importer. | |
| * | |
| * A config importer imports the changes into the configuration system. To | |
| * determine which changes to import a StorageComparer in used. | |
| * | |
| * @see \Drupal\Core\Config\StorageComparerInterface | |
| * | |
| * The ConfigImporter has a identifier which is used to construct event names. | |
| * The events fired during an import are: | |
| * - ConfigEvents::IMPORT_VALIDATE: Events listening can throw a | |
| * \Drupal\Core\Config\ConfigImporterException to prevent an import from | |
| * occurring. | |
| * @see \Drupal\Core\EventSubscriber\ConfigImportSubscriber | |
| * - ConfigEvents::IMPORT: Events listening can react to a successful import. | |
| * @see \Drupal\Core\EventSubscriber\ConfigSnapshotSubscriber | |
| * | |
| * @see \Drupal\Core\Config\ConfigImporterEvent | |
| */ | |
| class ConfigImporter { | |
| use StringTranslationTrait; | |
| use DependencySerializationTrait; | |
| /** | |
| * The name used to identify the lock. | |
| */ | |
| const LOCK_NAME = 'config_importer'; | |
| /** | |
| * The storage comparer used to discover configuration changes. | |
| * | |
| * @var \Drupal\Core\Config\StorageComparerInterface | |
| */ | |
| protected $storageComparer; | |
| /** | |
| * The event dispatcher used to notify subscribers. | |
| * | |
| * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface | |
| */ | |
| protected $eventDispatcher; | |
| /** | |
| * The configuration manager. | |
| * | |
| * @var \Drupal\Core\Config\ConfigManagerInterface | |
| */ | |
| protected $configManager; | |
| /** | |
| * The used lock backend instance. | |
| * | |
| * @var \Drupal\Core\Lock\LockBackendInterface | |
| */ | |
| protected $lock; | |
| /** | |
| * The typed config manager. | |
| * | |
| * @var \Drupal\Core\Config\TypedConfigManagerInterface | |
| */ | |
| protected $typedConfigManager; | |
| /** | |
| * List of configuration file changes processed by the import(). | |
| * | |
| * @var array | |
| */ | |
| protected $processedConfiguration; | |
| /** | |
| * List of extension changes processed by the import(). | |
| * | |
| * @var array | |
| */ | |
| protected $processedExtensions; | |
| /** | |
| * List of extension changes to be processed by the import(). | |
| * | |
| * @var array | |
| */ | |
| protected $extensionChangelist; | |
| /** | |
| * Indicates changes to import have been validated. | |
| * | |
| * @var bool | |
| */ | |
| protected $validated; | |
| /** | |
| * The module handler. | |
| * | |
| * @var \Drupal\Core\Extension\ModuleHandlerInterface | |
| */ | |
| protected $moduleHandler; | |
| /** | |
| * The theme handler. | |
| * | |
| * @var \Drupal\Core\Extension\ThemeHandlerInterface | |
| */ | |
| protected $themeHandler; | |
| /** | |
| * Flag set to import system.theme during processing theme install and uninstalls. | |
| * | |
| * @var bool | |
| */ | |
| protected $processedSystemTheme = FALSE; | |
| /** | |
| * A log of any errors encountered. | |
| * | |
| * If errors are logged during the validation event the configuration | |
| * synchronization will not occur. If errors occur during an import then best | |
| * efforts are made to complete the synchronization. | |
| * | |
| * @var array | |
| */ | |
| protected $errors = array(); | |
| /** | |
| * The total number of extensions to process. | |
| * | |
| * @var int | |
| */ | |
| protected $totalExtensionsToProcess = 0; | |
| /** | |
| * The total number of configuration objects to process. | |
| * | |
| * @var int | |
| */ | |
| protected $totalConfigurationToProcess = 0; | |
| /** | |
| * The module installer. | |
| * | |
| * @var \Drupal\Core\Extension\ModuleInstallerInterface | |
| */ | |
| protected $moduleInstaller; | |
| /** | |
| * Constructs a configuration import object. | |
| * | |
| * @param \Drupal\Core\Config\StorageComparerInterface $storage_comparer | |
| * A storage comparer object used to determine configuration changes and | |
| * access the source and target storage objects. | |
| * @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 | |
| * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation | |
| * The string translation service. | |
| */ | |
| public function __construct(StorageComparerInterface $storage_comparer, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, LockBackendInterface $lock, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, TranslationInterface $string_translation) { | |
| $this->storageComparer = $storage_comparer; | |
| $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; | |
| $this->stringTranslation = $string_translation; | |
| foreach ($this->storageComparer->getAllCollectionNames() as $collection) { | |
| $this->processedConfiguration[$collection] = $this->storageComparer->getEmptyChangelist(); | |
| } | |
| $this->processedExtensions = $this->getEmptyExtensionsProcessedList(); | |
| } | |
| /** | |
| * Logs an error message. | |
| * | |
| * @param string $message | |
| * The message to log. | |
| */ | |
| public function logError($message) { | |
| $this->errors[] = $message; | |
| } | |
| /** | |
| * Returns error messages created while running the import. | |
| * | |
| * @return array | |
| * List of messages. | |
| */ | |
| public function getErrors() { | |
| return $this->errors; | |
| } | |
| /** | |
| * Gets the configuration storage comparer. | |
| * | |
| * @return \Drupal\Core\Config\StorageComparerInterface | |
| * Storage comparer object used to calculate configuration changes. | |
| */ | |
| public function getStorageComparer() { | |
| return $this->storageComparer; | |
| } | |
| /** | |
| * Resets the storage comparer and processed list. | |
| * | |
| * @return \Drupal\Core\Config\ConfigImporter | |
| * The ConfigImporter instance. | |
| */ | |
| public function reset() { | |
| $this->storageComparer->reset(); | |
| // Empty all the lists. | |
| foreach ($this->storageComparer->getAllCollectionNames() as $collection) { | |
| $this->processedConfiguration[$collection] = $this->storageComparer->getEmptyChangelist(); | |
| } | |
| $this->extensionChangelist = $this->processedExtensions = $this->getEmptyExtensionsProcessedList(); | |
| $this->validated = FALSE; | |
| $this->processedSystemTheme = FALSE; | |
| return $this; | |
| } | |
| /** | |
| * Gets an empty list of extensions to process. | |
| * | |
| * @return array | |
| * An empty list of extensions to process. | |
| */ | |
| protected function getEmptyExtensionsProcessedList() { | |
| return array( | |
| 'module' => array( | |
| 'install' => array(), | |
| 'uninstall' => array(), | |
| ), | |
| 'theme' => array( | |
| 'install' => array(), | |
| 'uninstall' => array(), | |
| ), | |
| ); | |
| } | |
| /** | |
| * Checks if there are any unprocessed configuration changes. | |
| * | |
| * @return bool | |
| * TRUE if there are changes to process and FALSE if not. | |
| */ | |
| public function hasUnprocessedConfigurationChanges() { | |
| foreach ($this->storageComparer->getAllCollectionNames() as $collection) { | |
| foreach (array('delete', 'create', 'rename', 'update') as $op) { | |
| if (count($this->getUnprocessedConfiguration($op, $collection))) { | |
| return TRUE; | |
| } | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * Gets list of processed changes. | |
| * | |
| * @param string $collection | |
| * (optional) The configuration collection to get processed changes for. | |
| * Defaults to the default collection. | |
| * | |
| * @return array | |
| * An array containing a list of processed changes. | |
| */ | |
| public function getProcessedConfiguration($collection = StorageInterface::DEFAULT_COLLECTION) { | |
| return $this->processedConfiguration[$collection]; | |
| } | |
| /** | |
| * Sets a change as processed. | |
| * | |
| * @param string $collection | |
| * The configuration collection to set a change as processed for. | |
| * @param string $op | |
| * The change operation performed, either delete, create, rename, or update. | |
| * @param string $name | |
| * The name of the configuration processed. | |
| */ | |
| protected function setProcessedConfiguration($collection, $op, $name) { | |
| $this->processedConfiguration[$collection][$op][] = $name; | |
| } | |
| /** | |
| * Gets a list of unprocessed changes for a given operation. | |
| * | |
| * @param string $op | |
| * The change operation to get the unprocessed list for, either delete, | |
| * create, rename, or update. | |
| * @param string $collection | |
| * (optional) The configuration collection to get unprocessed changes for. | |
| * Defaults to the default collection. | |
| * | |
| * @return array | |
| * 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]); | |
| } | |
| /** | |
| * Gets list of processed extension changes. | |
| * | |
| * @return array | |
| * An array containing a list of processed extension changes. | |
| */ | |
| public function getProcessedExtensions() { | |
| return $this->processedExtensions; | |
| } | |
| /** | |
| * Sets an extension change as processed. | |
| * | |
| * @param string $type | |
| * The type of extension, either 'theme' or 'module'. | |
| * @param string $op | |
| * The change operation performed, either install or uninstall. | |
| * @param string $name | |
| * The name of the extension processed. | |
| */ | |
| protected function setProcessedExtension($type, $op, $name) { | |
| $this->processedExtensions[$type][$op][] = $name; | |
| } | |
| /** | |
| * Populates the extension change list. | |
| */ | |
| protected function createExtensionChangelist() { | |
| // Create an empty changelist. | |
| $this->extensionChangelist = $this->getEmptyExtensionsProcessedList(); | |
| // Read the extensions information to determine changes. | |
| $current_extensions = $this->storageComparer->getTargetStorage()->read('core.extension'); | |
| $new_extensions = $this->storageComparer->getSourceStorage()->read('core.extension'); | |
| // If there is no extension information in sync then exit. This is probably | |
| // due to an empty sync directory. | |
| if (!$new_extensions) { | |
| return; | |
| } | |
| // Get a list of modules with dependency weights as values. | |
| $module_data = system_rebuild_module_data(); | |
| // Set the actual module weights. | |
| $module_list = array_combine(array_keys($module_data), array_keys($module_data)); | |
| $module_list = array_map(function ($module) use ($module_data) { | |
| return $module_data[$module]->sort; | |
| }, $module_list); | |
| // Determine which modules to uninstall. | |
| $uninstall = array_keys(array_diff_key($current_extensions['module'], $new_extensions['module'])); | |
| // Sort the list of newly uninstalled extensions by their weights, so that | |
| // dependencies are uninstalled last. Extensions of the same weight are | |
| // sorted in reverse alphabetical order, to ensure the order is exactly | |
| // opposite from installation. For example, this module list: | |
| // array( | |
| // 'actions' => 0, | |
| // 'ban' => 0, | |
| // 'options' => -2, | |
| // 'text' => -1, | |
| // ); | |
| // will result in the following sort order: | |
| // -2 options | |
| // -1 text | |
| // 0 0 ban | |
| // 0 1 actions | |
| // @todo Move this sorting functionality to the extension system. | |
| array_multisort(array_values($module_list), SORT_ASC, array_keys($module_list), SORT_DESC, $module_list); | |
| $this->extensionChangelist['module']['uninstall'] = array_intersect(array_keys($module_list), $uninstall); | |
| // Determine which modules to install. | |
| $install = array_keys(array_diff_key($new_extensions['module'], $current_extensions['module'])); | |
| // Ensure that installed modules are sorted in exactly the reverse order | |
| // (with dependencies installed first, and modules of the same weight sorted | |
| // in alphabetical order). | |
| $module_list = array_reverse($module_list); | |
| $this->extensionChangelist['module']['install'] = array_intersect(array_keys($module_list), $install); | |
| // Work out what themes to install and to uninstall. | |
| $this->extensionChangelist['theme']['install'] = array_keys(array_diff_key($new_extensions['theme'], $current_extensions['theme'])); | |
| $this->extensionChangelist['theme']['uninstall'] = array_keys(array_diff_key($current_extensions['theme'], $new_extensions['theme'])); | |
| } | |
| /** | |
| * Gets a list changes for extensions. | |
| * | |
| * @param string $type | |
| * The type of extension, either 'theme' or 'module'. | |
| * @param string $op | |
| * The change operation to get the unprocessed list for, either install | |
| * or uninstall. | |
| * | |
| * @return array | |
| * An array of extension names. | |
| */ | |
| public function getExtensionChangelist($type, $op = NULL) { | |
| if ($op) { | |
| return $this->extensionChangelist[$type][$op]; | |
| } | |
| return $this->extensionChangelist[$type]; | |
| } | |
| /** | |
| * Gets a list of unprocessed changes for extensions. | |
| * | |
| * @param string $type | |
| * The type of extension, either 'theme' or 'module'. | |
| * | |
| * @return array | |
| * An array of extension names. | |
| */ | |
| protected function getUnprocessedExtensions($type) { | |
| $changelist = $this->getExtensionChangelist($type); | |
| return array( | |
| 'install' => array_diff($changelist['install'], $this->processedExtensions[$type]['install']), | |
| 'uninstall' => array_diff($changelist['uninstall'], $this->processedExtensions[$type]['uninstall']), | |
| ); | |
| } | |
| /** | |
| * Imports the changelist to the target storage. | |
| * | |
| * @return \Drupal\Core\Config\ConfigImporter | |
| * The ConfigImporter instance. | |
| * | |
| * @throws \Drupal\Core\Config\ConfigException | |
| */ | |
| public function import() { | |
| if ($this->hasUnprocessedConfigurationChanges()) { | |
| $sync_steps = $this->initialize(); | |
| foreach ($sync_steps as $step) { | |
| $context = array(); | |
| do { | |
| $this->doSyncStep($step, $context); | |
| } while ($context['finished'] < 1); | |
| } | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Calls a config import step. | |
| * | |
| * @param string|callable $sync_step | |
| * The step to do. Either a method on the ConfigImporter class or a | |
| * callable. | |
| * @param array $context | |
| * A batch context array. If the config importer is not running in a batch | |
| * the only array key that is used is $context['finished']. A process needs | |
| * to set $context['finished'] = 1 when it is done. | |
| * | |
| * @throws \InvalidArgumentException | |
| * Exception thrown if the $sync_step can not be called. | |
| */ | |
| public function doSyncStep($sync_step, &$context) { | |
| if (!is_array($sync_step) && method_exists($this, $sync_step)) { | |
| $this->$sync_step($context); | |
| } | |
| elseif (is_callable($sync_step)) { | |
| call_user_func_array($sync_step, array(&$context, $this)); | |
| } | |
| else { | |
| throw new \InvalidArgumentException('Invalid configuration synchronization step'); | |
| } | |
| } | |
| /** | |
| * Initializes the config importer in preparation for processing a batch. | |
| * | |
| * @return array | |
| * An array of \Drupal\Core\Config\ConfigImporter method names and callables | |
| * that are invoked to complete the import. If there are modules or themes | |
| * to process then an extra step is added. | |
| * | |
| * @throws \Drupal\Core\Config\ConfigImporterException | |
| * If the configuration is already importing. | |
| */ | |
| public function initialize() { | |
| // Ensure that the changes have been validated. | |
| $this->validate(); | |
| if (!$this->lock->acquire(static::LOCK_NAME)) { | |
| // Another process is synchronizing configuration. | |
| throw new ConfigImporterException(sprintf('%s is already importing', static::LOCK_NAME)); | |
| } | |
| $sync_steps = array(); | |
| $modules = $this->getUnprocessedExtensions('module'); | |
| foreach (array('install', 'uninstall') as $op) { | |
| $this->totalExtensionsToProcess += count($modules[$op]); | |
| } | |
| $themes = $this->getUnprocessedExtensions('theme'); | |
| foreach (array('install', 'uninstall') as $op) { | |
| $this->totalExtensionsToProcess += count($themes[$op]); | |
| } | |
| // We have extensions to process. | |
| if ($this->totalExtensionsToProcess > 0) { | |
| $sync_steps[] = 'processExtensions'; | |
| } | |
| $sync_steps[] = 'processConfigurations'; | |
| $sync_steps[] = 'processMissingContent'; | |
| // Allow modules to add new steps to configuration synchronization. | |
| $this->moduleHandler->alter('config_import_steps', $sync_steps, $this); | |
| $sync_steps[] = 'finish'; | |
| return $sync_steps; | |
| } | |
| /** | |
| * Processes extensions as a batch operation. | |
| * | |
| * @param array $context. | |
| * The batch context. | |
| */ | |
| protected function processExtensions(array &$context) { | |
| $operation = $this->getNextExtensionOperation(); | |
| if (!empty($operation)) { | |
| $this->processExtension($operation['type'], $operation['op'], $operation['name']); | |
| $context['message'] = t('Synchronizing extensions: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name'])); | |
| $processed_count = count($this->processedExtensions['module']['install']) + count($this->processedExtensions['module']['uninstall']); | |
| $processed_count += count($this->processedExtensions['theme']['uninstall']) + count($this->processedExtensions['theme']['install']); | |
| $context['finished'] = $processed_count / $this->totalExtensionsToProcess; | |
| } | |
| else { | |
| $context['finished'] = 1; | |
| } | |
| } | |
| /** | |
| * Processes configuration as a batch operation. | |
| * | |
| * @param array $context. | |
| * The batch context. | |
| */ | |
| protected function processConfigurations(array &$context) { | |
| // The first time this is called we need to calculate the total to process. | |
| // This involves recalculating the changelist which will ensure that if | |
| // extensions have been processed any configuration affected will be taken | |
| // into account. | |
| if ($this->totalConfigurationToProcess == 0) { | |
| $this->storageComparer->reset(); | |
| foreach ($this->storageComparer->getAllCollectionNames() as $collection) { | |
| foreach (array('delete', 'create', 'rename', 'update') as $op) { | |
| $this->totalConfigurationToProcess += count($this->getUnprocessedConfiguration($op, $collection)); | |
| } | |
| } | |
| } | |
| $operation = $this->getNextConfigurationOperation(); | |
| if (!empty($operation)) { | |
| if ($this->checkOp($operation['collection'], $operation['op'], $operation['name'])) { | |
| $this->processConfiguration($operation['collection'], $operation['op'], $operation['name']); | |
| } | |
| if ($operation['collection'] == StorageInterface::DEFAULT_COLLECTION) { | |
| $context['message'] = $this->t('Synchronizing configuration: @op @name.', array('@op' => $operation['op'], '@name' => $operation['name'])); | |
| } | |
| else { | |
| $context['message'] = $this->t('Synchronizing configuration: @op @name in @collection.', array('@op' => $operation['op'], '@name' => $operation['name'], '@collection' => $operation['collection'])); | |
| } | |
| $processed_count = 0; | |
| foreach ($this->storageComparer->getAllCollectionNames() as $collection) { | |
| foreach (array('delete', 'create', 'rename', 'update') as $op) { | |
| $processed_count += count($this->processedConfiguration[$collection][$op]); | |
| } | |
| } | |
| $context['finished'] = $processed_count / $this->totalConfigurationToProcess; | |
| } | |
| else { | |
| $context['finished'] = 1; | |
| } | |
| } | |
| /** | |
| * Handles processing of missing content. | |
| * | |
| * @param array $context | |
| * Standard batch context. | |
| */ | |
| protected function processMissingContent(array &$context) { | |
| $sandbox = &$context['sandbox']['config']; | |
| if (!isset($sandbox['missing_content'])) { | |
| $missing_content = $this->configManager->findMissingContentDependencies(); | |
| $sandbox['missing_content']['data'] = $missing_content; | |
| $sandbox['missing_content']['total'] = count($missing_content); | |
| } | |
| else { | |
| $missing_content = $sandbox['missing_content']['data']; | |
| } | |
| if (!empty($missing_content)) { | |
| $event = new MissingContentEvent($missing_content); | |
| // Fire an event to allow listeners to create the missing content. | |
| $this->eventDispatcher->dispatch(ConfigEvents::IMPORT_MISSING_CONTENT, $event); | |
| $sandbox['missing_content']['data'] = $event->getMissingContent(); | |
| } | |
| $current_count = count($sandbox['missing_content']['data']); | |
| if ($current_count) { | |
| $context['message'] = $this->t('Resolving missing content'); | |
| $context['finished'] = ($sandbox['missing_content']['total'] - $current_count) / $sandbox['missing_content']['total']; | |
| } | |
| else { | |
| $context['finished'] = 1; | |
| } | |
| } | |
| /** | |
| * Finishes the batch. | |
| * | |
| * @param array $context. | |
| * The batch context. | |
| */ | |
| protected function finish(array &$context) { | |
| $this->eventDispatcher->dispatch(ConfigEvents::IMPORT, new ConfigImporterEvent($this)); | |
| // The import is now complete. | |
| $this->lock->release(static::LOCK_NAME); | |
| $this->reset(); | |
| $context['message'] = t('Finalizing configuration synchronization.'); | |
| $context['finished'] = 1; | |
| } | |
| /** | |
| * Gets the next extension operation to perform. | |
| * | |
| * @return array|bool | |
| * An array containing the next operation and extension name to perform it | |
| * on. If there is nothing left to do returns FALSE; | |
| */ | |
| protected function getNextExtensionOperation() { | |
| foreach (array('module', 'theme') as $type) { | |
| foreach (array('install', 'uninstall') as $op) { | |
| $unprocessed = $this->getUnprocessedExtensions($type); | |
| if (!empty($unprocessed[$op])) { | |
| return array( | |
| 'op' => $op, | |
| 'type' => $type, | |
| 'name' => array_shift($unprocessed[$op]), | |
| ); | |
| } | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * Gets the next configuration operation to perform. | |
| * | |
| * @return array|bool | |
| * An array containing the next operation and configuration name to perform | |
| * it on. If there is nothing left to do returns FALSE; | |
| */ | |
| protected function getNextConfigurationOperation() { | |
| // The order configuration operations is processed is important. Deletes | |
| // have to come first so that recreates can work. | |
| foreach ($this->storageComparer->getAllCollectionNames() as $collection) { | |
| foreach (array('delete', 'create', 'rename', 'update') as $op) { | |
| $config_names = $this->getUnprocessedConfiguration($op, $collection); | |
| if (!empty($config_names)) { | |
| return array( | |
| 'op' => $op, | |
| 'name' => array_shift($config_names), | |
| 'collection' => $collection, | |
| ); | |
| } | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * Dispatches validate event for a ConfigImporter object. | |
| * | |
| * Events should throw a \Drupal\Core\Config\ConfigImporterException to | |
| * prevent an import from occurring. | |
| * | |
| * @throws \Drupal\Core\Config\ConfigImporterException | |
| * Exception thrown if the validate event logged any errors. | |
| */ | |
| 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); | |
| $old_entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']); | |
| $new_entity_type_id = $this->configManager->getEntityTypeIdByName($names['new_name']); | |
| if ($old_entity_type_id != $new_entity_type_id) { | |
| $this->logError($this->t('Entity type mismatch on rename. @old_type not equal to @new_type for existing configuration @old_name and staged configuration @new_name.', array('@old_type' => $old_entity_type_id, '@new_type' => $new_entity_type_id, '@old_name' => $names['old_name'], '@new_name' => $names['new_name']))); | |
| } | |
| // Has to be a configuration entity. | |
| if (!$old_entity_type_id) { | |
| $this->logError($this->t('Rename operation for simple configuration. Existing configuration @old_name and staged configuration @new_name.', array('@old_name' => $names['old_name'], '@new_name' => $names['new_name']))); | |
| } | |
| } | |
| $this->eventDispatcher->dispatch(ConfigEvents::IMPORT_VALIDATE, new ConfigImporterEvent($this)); | |
| if (count($this->getErrors())) { | |
| throw new ConfigImporterException('There were errors validating the config synchronization.'); | |
| } | |
| else { | |
| $this->validated = TRUE; | |
| } | |
| } | |
| return $this; | |
| } | |
| /** | |
| * Processes a configuration change. | |
| * | |
| * @param string $collection | |
| * The configuration collection to process changes for. | |
| * @param string $op | |
| * The change operation. | |
| * @param string $name | |
| * The name of the configuration to process. | |
| * | |
| * @throws \Exception | |
| * Thrown when the import process fails, only thrown when no importer log is | |
| * set, otherwise the exception message is logged and the configuration | |
| * is skipped. | |
| */ | |
| protected function processConfiguration($collection, $op, $name) { | |
| try { | |
| $processed = FALSE; | |
| if ($collection == StorageInterface::DEFAULT_COLLECTION) { | |
| $processed = $this->importInvokeOwner($collection, $op, $name); | |
| } | |
| if (!$processed) { | |
| $this->importConfig($collection, $op, $name); | |
| } | |
| } | |
| catch (\Exception $e) { | |
| $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); | |
| } | |
| } | |
| /** | |
| * Processes an extension change. | |
| * | |
| * @param string $type | |
| * The type of extension, either 'module' or 'theme'. | |
| * @param string $op | |
| * The change operation. | |
| * @param string $name | |
| * The name of the extension to process. | |
| */ | |
| protected function processExtension($type, $op, $name) { | |
| // Set the config installer to use the sync directory instead of the | |
| // extensions own default config directories. | |
| \Drupal::service('config.installer') | |
| ->setSyncing(TRUE) | |
| ->setSourceStorage($this->storageComparer->getSourceStorage()); | |
| if ($type == 'module') { | |
| $this->moduleInstaller->$op(array($name), FALSE); | |
| // Installing a module can cause a kernel boot therefore reinject all the | |
| // services. | |
| $this->reInjectMe(); | |
| // During a module install or uninstall the container is rebuilt and the | |
| // module handler is called from drupal_get_complete_schema(). This causes | |
| // the container's instance of the module handler not to have loaded all | |
| // the enabled modules. | |
| $this->moduleHandler->loadAll(); | |
| } | |
| if ($type == 'theme') { | |
| // Theme uninstalls possible remove default or admin themes therefore we | |
| // need to import this before doing any. If there are no uninstalls and | |
| // the default or admin theme is changing this will be picked up whilst | |
| // processing configuration. | |
| if ($op == 'uninstall' && $this->processedSystemTheme === FALSE) { | |
| $this->importConfig(StorageInterface::DEFAULT_COLLECTION, 'update', 'system.theme'); | |
| $this->configManager->getConfigFactory()->reset('system.theme'); | |
| $this->processedSystemTheme = TRUE; | |
| } | |
| $this->themeHandler->$op(array($name)); | |
| } | |
| $this->setProcessedExtension($type, $op, $name); | |
| \Drupal::service('config.installer') | |
| ->setSyncing(FALSE); | |
| } | |
| /** | |
| * Checks that the operation is still valid. | |
| * | |
| * During a configuration import secondary writes and deletes are possible. | |
| * This method checks that the operation is still valid before processing a | |
| * configuration change. | |
| * | |
| * @param string $collection | |
| * The configuration collection. | |
| * @param string $op | |
| * The change operation. | |
| * @param string $name | |
| * The name of the configuration to process. | |
| * | |
| * @return bool | |
| * TRUE is to continue processing, FALSE otherwise. | |
| * | |
| * @throws \Drupal\Core\Config\ConfigImporterException | |
| */ | |
| protected function checkOp($collection, $op, $name) { | |
| if ($op == 'rename') { | |
| $names = $this->storageComparer->extractRenameNames($name); | |
| $target_exists = $this->storageComparer->getTargetStorage($collection)->exists($names['new_name']); | |
| if ($target_exists) { | |
| // If the target exists, the rename has already occurred as the | |
| // result of a secondary configuration write. Change the operation | |
| // into an update. This is the desired behavior since renames often | |
| // have to occur together. For example, renaming a node type must | |
| // also result in renaming its fields and entity displays. | |
| $this->storageComparer->moveRenameToUpdate($name); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| $target_exists = $this->storageComparer->getTargetStorage($collection)->exists($name); | |
| switch ($op) { | |
| case 'delete': | |
| 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); | |
| return FALSE; | |
| } | |
| break; | |
| case 'create': | |
| if ($target_exists) { | |
| // If the target already exists, use the entity storage to delete it | |
| // again, if is a simple config, delete it directly. | |
| if ($entity_type_id = $this->configManager->getEntityTypeIdByName($name)) { | |
| $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type_id); | |
| $entity_type = $this->configManager->getEntityManager()->getDefinition($entity_type_id); | |
| $entity = $entity_storage->load($entity_storage->getIDFromConfigName($name, $entity_type->getConfigPrefix())); | |
| $entity->delete(); | |
| $this->logError($this->t('Deleted and replaced configuration entity "@name"', array('@name' => $name))); | |
| } | |
| else { | |
| $this->storageComparer->getTargetStorage($collection)->delete($name); | |
| $this->logError($this->t('Deleted and replaced configuration "@name"', array('@name' => $name))); | |
| } | |
| return TRUE; | |
| } | |
| break; | |
| case 'update': | |
| if (!$target_exists) { | |
| $this->logError($this->t('Update target "@name" is missing.', array('@name' => $name))); | |
| // Mark as processed so that the synchronization continues. Once the | |
| // the current synchronization is complete it will show up as a | |
| // create. | |
| $this->setProcessedConfiguration($collection, $op, $name); | |
| return FALSE; | |
| } | |
| break; | |
| } | |
| return TRUE; | |
| } | |
| /** | |
| * Writes a configuration change from the source to the target storage. | |
| * | |
| * @param string $collection | |
| * The configuration collection. | |
| * @param string $op | |
| * The change operation. | |
| * @param string $name | |
| * The name of the configuration to process. | |
| */ | |
| protected function importConfig($collection, $op, $name) { | |
| // Allow config factory overriders to use a custom configuration object if | |
| // they are responsible for the collection. | |
| $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection); | |
| if ($overrider) { | |
| $config = $overrider->createConfigObject($name, $collection); | |
| } | |
| else { | |
| $config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager); | |
| } | |
| if ($op == 'delete') { | |
| $config->delete(); | |
| } | |
| else { | |
| $data = $this->storageComparer->getSourceStorage($collection)->read($name); | |
| $config->setData($data ? $data : array()); | |
| $config->save(); | |
| } | |
| $this->setProcessedConfiguration($collection, $op, $name); | |
| } | |
| /** | |
| * Invokes import* methods on configuration entity storage. | |
| * | |
| * Allow modules to take over configuration change operations for higher-level | |
| * configuration data. | |
| * | |
| * @todo Add support for other extension types; e.g., themes etc. | |
| * | |
| * @param string $collection | |
| * The configuration collection. | |
| * @param string $op | |
| * The change operation to get the unprocessed list for, either delete, | |
| * create, rename, or update. | |
| * @param string $name | |
| * The name of the configuration to process. | |
| * | |
| * @return bool | |
| * TRUE if the configuration was imported as a configuration entity. FALSE | |
| * otherwise. | |
| * | |
| * @throws \Drupal\Core\Entity\EntityStorageException | |
| * Thrown if the data is owned by an entity type, but the entity storage | |
| * does not support imports. | |
| */ | |
| protected function importInvokeOwner($collection, $op, $name) { | |
| // Renames are handled separately. | |
| if ($op == 'rename') { | |
| return $this->importInvokeRename($collection, $name); | |
| } | |
| // Validate the configuration object name before importing it. | |
| // Config::validateName($name); | |
| if ($entity_type = $this->configManager->getEntityTypeIdByName($name)) { | |
| $old_config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager); | |
| if ($old_data = $this->storageComparer->getTargetStorage($collection)->read($name)) { | |
| $old_config->initWithData($old_data); | |
| } | |
| $data = $this->storageComparer->getSourceStorage($collection)->read($name); | |
| $new_config = new Config($name, $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager); | |
| if ($data !== FALSE) { | |
| $new_config->setData($data); | |
| } | |
| $method = 'import' . ucfirst($op); | |
| $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type); | |
| // Call to the configuration entity's storage to handle the configuration | |
| // change. | |
| if (!($entity_storage instanceof ImportableEntityStorageInterface)) { | |
| throw new EntityStorageException(sprintf('The entity storage "%s" for the "%s" entity type does not support imports', get_class($entity_storage), $entity_type)); | |
| } | |
| $entity_storage->$method($name, $new_config, $old_config); | |
| $this->setProcessedConfiguration($collection, $op, $name); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| * Imports a configuration entity rename. | |
| * | |
| * @param string $collection | |
| * The configuration collection. | |
| * @param string $rename_name | |
| * The rename configuration name, as provided by | |
| * \Drupal\Core\Config\StorageComparer::createRenameName(). | |
| * | |
| * @return bool | |
| * TRUE if the configuration was imported as a configuration entity. FALSE | |
| * otherwise. | |
| * | |
| * @throws \Drupal\Core\Entity\EntityStorageException | |
| * Thrown if the data is owned by an entity type, but the entity storage | |
| * does not support imports. | |
| * | |
| * @see \Drupal\Core\Config\ConfigImporter::createRenameName() | |
| */ | |
| protected function importInvokeRename($collection, $rename_name) { | |
| $names = $this->storageComparer->extractRenameNames($rename_name); | |
| $entity_type_id = $this->configManager->getEntityTypeIdByName($names['old_name']); | |
| $old_config = new Config($names['old_name'], $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager); | |
| if ($old_data = $this->storageComparer->getTargetStorage($collection)->read($names['old_name'])) { | |
| $old_config->initWithData($old_data); | |
| } | |
| $data = $this->storageComparer->getSourceStorage($collection)->read($names['new_name']); | |
| $new_config = new Config($names['new_name'], $this->storageComparer->getTargetStorage($collection), $this->eventDispatcher, $this->typedConfigManager); | |
| if ($data !== FALSE) { | |
| $new_config->setData($data); | |
| } | |
| $entity_storage = $this->configManager->getEntityManager()->getStorage($entity_type_id); | |
| // Call to the configuration entity's storage to handle the configuration | |
| // change. | |
| if (!($entity_storage instanceof ImportableEntityStorageInterface)) { | |
| throw new EntityStorageException(sprintf("The entity storage '%s' for the '%s' entity type does not support imports", get_class($entity_storage), $entity_type_id)); | |
| } | |
| $entity_storage->importRename($names['old_name'], $new_config, $old_config); | |
| $this->setProcessedConfiguration($collection, 'rename', $rename_name); | |
| return TRUE; | |
| } | |
| /** | |
| * Determines if a import is already running. | |
| * | |
| * @return bool | |
| * TRUE if an import is already running, FALSE if not. | |
| */ | |
| public function alreadyImporting() { | |
| return !$this->lock->lockMayBeAvailable(static::LOCK_NAME); | |
| } | |
| /** | |
| * Gets all the service dependencies from \Drupal. | |
| * | |
| * Since the ConfigImporter handles module installation the kernel and the | |
| * container can be rebuilt and altered during processing. It is necessary to | |
| * keep the services used by the importer in sync. | |
| */ | |
| protected function reInjectMe() { | |
| $this->_serviceIds = array(); | |
| $vars = get_object_vars($this); | |
| foreach ($vars as $key => $value) { | |
| if (is_object($value) && isset($value->_serviceId)) { | |
| $this->$key = \Drupal::service($value->_serviceId); | |
| } | |
| } | |
| } | |
| } |