sourceStorage = $source_storage; $this->targetStorage = $target_storage; $this->configManager = $config_manager; $this->changelist[StorageInterface::DEFAULT_COLLECTION] = $this->getEmptyChangelist(); } /** * {@inheritdoc} */ public function getSourceStorage($collection = StorageInterface::DEFAULT_COLLECTION) { if (!isset($this->sourceStorages[$collection])) { if ($collection == StorageInterface::DEFAULT_COLLECTION) { $this->sourceStorages[$collection] = $this->sourceStorage; } else { $this->sourceStorages[$collection] = $this->sourceStorage->createCollection($collection); } } return $this->sourceStorages[$collection]; } /** * {@inheritdoc} */ public function getTargetStorage($collection = StorageInterface::DEFAULT_COLLECTION) { if (!isset($this->targetStorages[$collection])) { if ($collection == StorageInterface::DEFAULT_COLLECTION) { $this->targetStorages[$collection] = $this->targetStorage; } else { $this->targetStorages[$collection] = $this->targetStorage->createCollection($collection); } } return $this->targetStorages[$collection]; } /** * {@inheritdoc} */ public function getEmptyChangelist() { return array( 'create' => array(), 'update' => array(), 'delete' => array(), 'rename' => array(), ); } /** * {@inheritdoc} */ public function getChangelist($op = NULL, $collection = StorageInterface::DEFAULT_COLLECTION) { if ($op) { return $this->changelist[$collection][$op]; } return $this->changelist[$collection]; } /** * Adds changes to the changelist. * * @param string $collection * The storage collection to add changes for. * @param string $op * The change operation performed. Either delete, create, rename, or update. * @param array $changes * Array of changes to add to the changelist. * @param array $sort_order * Array to sort that can be used to sort the changelist. This array must * contain all the items that are in the change list. */ protected function addChangeList($collection, $op, array $changes, array $sort_order = NULL) { // Only add changes that aren't already listed. $changes = array_diff($changes, $this->changelist[$collection][$op]); $this->changelist[$collection][$op] = array_merge($this->changelist[$collection][$op], $changes); if (isset($sort_order)) { $count = count($this->changelist[$collection][$op]); // Sort the changelist in the same order as the $sort_order array and // ensure the array is keyed from 0. $this->changelist[$collection][$op] = array_values(array_intersect($sort_order, $this->changelist[$collection][$op])); if ($count != count($this->changelist[$collection][$op])) { throw new \InvalidArgumentException(String::format('Sorting the @op changelist should not change its length.', array('@op' => $op))); } } } /** * {@inheritdoc} */ public function createChangelist() { foreach ($this->getAllCollectionNames() as $collection) { $this->changelist[$collection] = $this->getEmptyChangelist(); $this->getAndSortConfigData($collection); $this->addChangelistCreate($collection); $this->addChangelistUpdate($collection); $this->addChangelistDelete($collection); // Only collections that support configuration entities can have renames. if ($this->configManager->supportsConfigurationEntities($collection)) { $this->addChangelistRename($collection); } // Only need data whilst calculating changelists. Free up the memory. $this->sourceData = NULL; $this->targetData = NULL; } return $this; } /** * Creates the delete changelist. * * The list of deletes is sorted so that dependencies are deleted after * configuration entities that depend on them. For example, field instances * should be deleted after fields. * * @param string $collection * The storage collection to operate on. */ protected function addChangelistDelete($collection) { $deletes = array_diff(array_reverse($this->targetNames[$collection]), $this->sourceNames[$collection]); $this->addChangeList($collection, 'delete', $deletes); } /** * Creates the create changelist. * * The list of creates is sorted so that dependencies are created before * configuration entities that depend on them. For example, fields * should be created before field instances. * * @param string $collection * The storage collection to operate on. */ protected function addChangelistCreate($collection) { $creates = array_diff($this->sourceNames[$collection], $this->targetNames[$collection]); $this->addChangeList($collection, 'create', $creates); } /** * Creates the update changelist. * * The list of updates is sorted so that dependencies are created before * configuration entities that depend on them. For example, fields * should be updated before field instances. * * @param string $collection * The storage collection to operate on. */ protected function addChangelistUpdate($collection) { $recreates = array(); foreach (array_intersect($this->sourceNames[$collection], $this->targetNames[$collection]) as $name) { if ($this->sourceData[$collection][$name] !== $this->targetData[$collection][$name]) { if (isset($this->sourceData[$collection][$name]['uuid']) && $this->sourceData[$collection][$name]['uuid'] != $this->targetData[$collection][$name]['uuid']) { // The entity has the same file as an existing entity but the UUIDs do // not match. This means that the entity has been recreated so config // synchronisation should do the same. $recreates[] = $name; } else { $this->addChangeList($collection, 'update', array($name)); } } } if (!empty($recreates)) { // Recreates should become deletes and creates. Deletes should be ordered // so that dependencies are deleted first. $this->addChangeList($collection, 'create', $recreates, $this->sourceNames[$collection]); $this->addChangeList($collection, 'delete', $recreates, array_reverse($this->targetNames[$collection])); } } /** * Creates the rename changelist. * * The list of renames is created from the different source and target names * with same UUID. These changes will be removed from the create and delete * lists. * * @param string $collection * The storage collection to operate on. */ protected function addChangelistRename($collection) { // Renames will be present in both the create and delete lists. $create_list = $this->getChangelist('create', $collection); $delete_list = $this->getChangelist('delete', $collection); if (empty($create_list) || empty($delete_list)) { return; } $create_uuids = array(); foreach ($this->sourceData[$collection] as $id => $data) { if (isset($data['uuid']) && in_array($id, $create_list)) { $create_uuids[$data['uuid']] = $id; } } if (empty($create_uuids)) { return; } $renames = array(); // Renames should be ordered so that dependencies are renamed last. This // ensures that if there is logic in the configuration entity class to keep // names in sync it will still work. $this->targetNames is in the desired // order due to the use of configuration dependencies in // \Drupal\Core\Config\StorageComparer::getAndSortConfigData(). // Node type is a good example of a configuration entity that renames other // configuration when it is renamed. // @see \Drupal\node\Entity\NodeType::postSave() foreach ($this->targetNames[$collection] as $name) { $data = $this->targetData[$collection][$name]; if (isset($data['uuid']) && isset($create_uuids[$data['uuid']])) { // Remove the item from the create list. $this->removeFromChangelist($collection, 'create', $create_uuids[$data['uuid']]); // Remove the item from the delete list. $this->removeFromChangelist($collection, 'delete', $name); // Create the rename name. $renames[] = $this->createRenameName($name, $create_uuids[$data['uuid']]); } } $this->addChangeList($collection, 'rename', $renames); } /** * Removes the entry from the given operation changelist for the given name. * * @param string $collection * The storage collection to operate on. * @param string $op * The changelist to act on. Either delete, create, rename or update. * @param string $name * The name of the configuration to remove. */ protected function removeFromChangelist($collection, $op, $name) { $key = array_search($name, $this->changelist[$collection][$op]); if ($key !== FALSE) { unset($this->changelist[$collection][$op][$key]); } } /** * {@inheritdoc} */ public function moveRenameToUpdate($rename, $collection = StorageInterface::DEFAULT_COLLECTION) { $names = $this->extractRenameNames($rename); $this->removeFromChangelist($collection, 'rename', $rename); $this->addChangeList($collection, 'update', array($names['new_name']), $this->sourceNames[$collection]); } /** * {@inheritdoc} */ public function reset() { $this->changelist = array(StorageInterface::DEFAULT_COLLECTION => $this->getEmptyChangelist()); $this->sourceNames = $this->targetNames = array(); return $this->createChangelist(); } /** * {@inheritdoc} */ public function hasChanges() { foreach ($this->getAllCollectionNames() as $collection) { foreach (array('delete', 'create', 'update', 'rename') as $op) { if (!empty($this->changelist[$collection][$op])) { return TRUE; } } } return FALSE; } /** * {@inheritdoc} */ public function validateSiteUuid() { $source = $this->sourceStorage->read('system.site'); $target = $this->targetStorage->read('system.site'); return $source['uuid'] === $target['uuid']; } /** * Gets and sorts configuration data from the source and target storages. */ protected function getAndSortConfigData($collection) { $source_storage = $this->getSourceStorage($collection); $target_storage = $this->getTargetStorage($collection); $target_names = $target_storage->listAll(); $source_names = $source_storage->listAll(); $this->targetData[$collection] = $target_storage->readMultiple($target_names); $this->sourceData[$collection] = $source_storage->readMultiple($source_names); // If the collection only supports simple configuration do not use // configuration dependencies. if ($this->configManager->supportsConfigurationEntities($collection)) { $dependency_manager = new ConfigDependencyManager(); $this->targetNames[$collection] = $dependency_manager->setData($this->targetData[$collection])->sortAll(); $this->sourceNames[$collection] = $dependency_manager->setData($this->sourceData[$collection])->sortAll(); } else { $this->targetNames[$collection] = $target_names; $this->sourceNames[$collection] = $source_names; } } /** * Creates a rename name from the old and new names for the object. * * @param string $old_name * The old configuration object name. * @param string $new_name * The new configuration object name. * * @return string * The configuration change name that encodes both the old and the new name. * * @see \Drupal\Core\Config\StorageComparerInterface::extractRenameNames() */ protected function createRenameName($name1, $name2) { return $name1 . '::' . $name2; } /** * {@inheritdoc} */ public function extractRenameNames($name) { $names = explode('::', $name, 2); return array( 'old_name' => $names[0], 'new_name' => $names[1], ); } /** * {@inheritdoc} */ public function getAllCollectionNames($include_default = TRUE) { $collections = array_unique(array_merge($this->sourceStorage->getAllCollectionNames(), $this->targetStorage->getAllCollectionNames())); if ($include_default) { array_unshift($collections, StorageInterface::DEFAULT_COLLECTION); } return $collections; } }