Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Config;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\Crypt;
|
Chris@0
|
6 use Drupal\Core\Config\Entity\ConfigDependencyManager;
|
Chris@0
|
7 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
Chris@0
|
8
|
Chris@0
|
9 class ConfigInstaller implements ConfigInstallerInterface {
|
Chris@0
|
10
|
Chris@0
|
11 /**
|
Chris@0
|
12 * The configuration factory.
|
Chris@0
|
13 *
|
Chris@0
|
14 * @var \Drupal\Core\Config\ConfigFactoryInterface
|
Chris@0
|
15 */
|
Chris@0
|
16 protected $configFactory;
|
Chris@0
|
17
|
Chris@0
|
18 /**
|
Chris@0
|
19 * The active configuration storages, keyed by collection.
|
Chris@0
|
20 *
|
Chris@0
|
21 * @var \Drupal\Core\Config\StorageInterface[]
|
Chris@0
|
22 */
|
Chris@0
|
23 protected $activeStorages;
|
Chris@0
|
24
|
Chris@0
|
25 /**
|
Chris@0
|
26 * The typed configuration manager.
|
Chris@0
|
27 *
|
Chris@0
|
28 * @var \Drupal\Core\Config\TypedConfigManagerInterface
|
Chris@0
|
29 */
|
Chris@0
|
30 protected $typedConfig;
|
Chris@0
|
31
|
Chris@0
|
32 /**
|
Chris@0
|
33 * The configuration manager.
|
Chris@0
|
34 *
|
Chris@0
|
35 * @var \Drupal\Core\Config\ConfigManagerInterface
|
Chris@0
|
36 */
|
Chris@0
|
37 protected $configManager;
|
Chris@0
|
38
|
Chris@0
|
39 /**
|
Chris@0
|
40 * The event dispatcher.
|
Chris@0
|
41 *
|
Chris@0
|
42 * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
Chris@0
|
43 */
|
Chris@0
|
44 protected $eventDispatcher;
|
Chris@0
|
45
|
Chris@0
|
46 /**
|
Chris@0
|
47 * The configuration storage that provides the default configuration.
|
Chris@0
|
48 *
|
Chris@0
|
49 * @var \Drupal\Core\Config\StorageInterface
|
Chris@0
|
50 */
|
Chris@0
|
51 protected $sourceStorage;
|
Chris@0
|
52
|
Chris@0
|
53 /**
|
Chris@0
|
54 * Is configuration being created as part of a configuration sync.
|
Chris@0
|
55 *
|
Chris@0
|
56 * @var bool
|
Chris@0
|
57 */
|
Chris@0
|
58 protected $isSyncing = FALSE;
|
Chris@0
|
59
|
Chris@0
|
60 /**
|
Chris@0
|
61 * The name of the currently active installation profile.
|
Chris@0
|
62 *
|
Chris@0
|
63 * @var string
|
Chris@0
|
64 */
|
Chris@0
|
65 protected $installProfile;
|
Chris@0
|
66
|
Chris@0
|
67 /**
|
Chris@0
|
68 * Constructs the configuration installer.
|
Chris@0
|
69 *
|
Chris@0
|
70 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
Chris@0
|
71 * The configuration factory.
|
Chris@0
|
72 * @param \Drupal\Core\Config\StorageInterface $active_storage
|
Chris@0
|
73 * The active configuration storage.
|
Chris@0
|
74 * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
|
Chris@0
|
75 * The typed configuration manager.
|
Chris@0
|
76 * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
|
Chris@0
|
77 * The configuration manager.
|
Chris@0
|
78 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
|
Chris@0
|
79 * The event dispatcher.
|
Chris@0
|
80 * @param string $install_profile
|
Chris@0
|
81 * The name of the currently active installation profile.
|
Chris@0
|
82 */
|
Chris@0
|
83 public function __construct(ConfigFactoryInterface $config_factory, StorageInterface $active_storage, TypedConfigManagerInterface $typed_config, ConfigManagerInterface $config_manager, EventDispatcherInterface $event_dispatcher, $install_profile) {
|
Chris@0
|
84 $this->configFactory = $config_factory;
|
Chris@0
|
85 $this->activeStorages[$active_storage->getCollectionName()] = $active_storage;
|
Chris@0
|
86 $this->typedConfig = $typed_config;
|
Chris@0
|
87 $this->configManager = $config_manager;
|
Chris@0
|
88 $this->eventDispatcher = $event_dispatcher;
|
Chris@0
|
89 $this->installProfile = $install_profile;
|
Chris@0
|
90 }
|
Chris@0
|
91
|
Chris@0
|
92 /**
|
Chris@0
|
93 * {@inheritdoc}
|
Chris@0
|
94 */
|
Chris@0
|
95 public function installDefaultConfig($type, $name) {
|
Chris@0
|
96 $extension_path = $this->drupalGetPath($type, $name);
|
Chris@0
|
97 // Refresh the schema cache if the extension provides configuration schema
|
Chris@0
|
98 // or is a theme.
|
Chris@0
|
99 if (is_dir($extension_path . '/' . InstallStorage::CONFIG_SCHEMA_DIRECTORY) || $type == 'theme') {
|
Chris@0
|
100 $this->typedConfig->clearCachedDefinitions();
|
Chris@0
|
101 }
|
Chris@0
|
102
|
Chris@0
|
103 $default_install_path = $this->getDefaultConfigDirectory($type, $name);
|
Chris@0
|
104 if (is_dir($default_install_path)) {
|
Chris@0
|
105 if (!$this->isSyncing()) {
|
Chris@0
|
106 $storage = new FileStorage($default_install_path, StorageInterface::DEFAULT_COLLECTION);
|
Chris@0
|
107 $prefix = '';
|
Chris@0
|
108 }
|
Chris@0
|
109 else {
|
Chris@0
|
110 // The configuration importer sets the source storage on the config
|
Chris@0
|
111 // installer. The configuration importer handles all of the
|
Chris@0
|
112 // configuration entity imports. We only need to ensure that simple
|
Chris@0
|
113 // configuration is created when the extension is installed.
|
Chris@0
|
114 $storage = $this->getSourceStorage();
|
Chris@0
|
115 $prefix = $name . '.';
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 // Gets profile storages to search for overrides if necessary.
|
Chris@0
|
119 $profile_storages = $this->getProfileStorages($name);
|
Chris@0
|
120
|
Chris@0
|
121 // Gather information about all the supported collections.
|
Chris@0
|
122 $collection_info = $this->configManager->getConfigCollectionInfo();
|
Chris@0
|
123 foreach ($collection_info->getCollectionNames() as $collection) {
|
Chris@0
|
124 $config_to_create = $this->getConfigToCreate($storage, $collection, $prefix, $profile_storages);
|
Chris@0
|
125 // If we're installing a profile ensure configuration that is overriding
|
Chris@0
|
126 // is excluded.
|
Chris@0
|
127 if ($name == $this->drupalGetProfile()) {
|
Chris@0
|
128 $existing_configuration = $this->getActiveStorages($collection)->listAll();
|
Chris@0
|
129 $config_to_create = array_diff_key($config_to_create, array_flip($existing_configuration));
|
Chris@0
|
130 }
|
Chris@0
|
131 if (!empty($config_to_create)) {
|
Chris@0
|
132 $this->createConfiguration($collection, $config_to_create);
|
Chris@0
|
133 }
|
Chris@0
|
134 }
|
Chris@0
|
135 }
|
Chris@0
|
136
|
Chris@0
|
137 // During a drupal installation optional configuration is installed at the
|
Chris@0
|
138 // end of the installation process.
|
Chris@0
|
139 // @see install_install_profile()
|
Chris@0
|
140 if (!$this->isSyncing() && !$this->drupalInstallationAttempted()) {
|
Chris@0
|
141 $optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
|
Chris@0
|
142 if (is_dir($optional_install_path)) {
|
Chris@0
|
143 // Install any optional config the module provides.
|
Chris@0
|
144 $storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION);
|
Chris@0
|
145 $this->installOptionalConfig($storage, '');
|
Chris@0
|
146 }
|
Chris@0
|
147 // Install any optional configuration entities whose dependencies can now
|
Chris@0
|
148 // be met. This searches all the installed modules config/optional
|
Chris@0
|
149 // directories.
|
Chris@0
|
150 $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, FALSE, $this->installProfile);
|
Chris@0
|
151 $this->installOptionalConfig($storage, [$type => $name]);
|
Chris@0
|
152 }
|
Chris@0
|
153
|
Chris@0
|
154 // Reset all the static caches and list caches.
|
Chris@0
|
155 $this->configFactory->reset();
|
Chris@0
|
156 }
|
Chris@0
|
157
|
Chris@0
|
158 /**
|
Chris@0
|
159 * {@inheritdoc}
|
Chris@0
|
160 */
|
Chris@0
|
161 public function installOptionalConfig(StorageInterface $storage = NULL, $dependency = []) {
|
Chris@0
|
162 $profile = $this->drupalGetProfile();
|
Chris@17
|
163 $enabled_extensions = $this->getEnabledExtensions();
|
Chris@17
|
164 $existing_config = $this->getActiveStorages()->listAll();
|
Chris@17
|
165
|
Chris@17
|
166 // Create the storages to read configuration from.
|
Chris@0
|
167 if (!$storage) {
|
Chris@0
|
168 // Search the install profile's optional configuration too.
|
Chris@0
|
169 $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION, TRUE, $this->installProfile);
|
Chris@0
|
170 // The extension install storage ensures that overrides are used.
|
Chris@0
|
171 $profile_storage = NULL;
|
Chris@0
|
172 }
|
Chris@0
|
173 elseif (!empty($profile)) {
|
Chris@0
|
174 // Creates a profile storage to search for overrides.
|
Chris@0
|
175 $profile_install_path = $this->drupalGetPath('module', $profile) . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
|
Chris@0
|
176 $profile_storage = new FileStorage($profile_install_path, StorageInterface::DEFAULT_COLLECTION);
|
Chris@0
|
177 }
|
Chris@0
|
178 else {
|
Chris@0
|
179 // Profile has not been set yet. For example during the first steps of the
|
Chris@0
|
180 // installer or during unit tests.
|
Chris@0
|
181 $profile_storage = NULL;
|
Chris@0
|
182 }
|
Chris@0
|
183
|
Chris@17
|
184 // Build the list of possible configuration to create.
|
Chris@17
|
185 $list = $storage->listAll();
|
Chris@17
|
186 if ($profile_storage && !empty($dependency)) {
|
Chris@17
|
187 // Only add the optional profile configuration into the list if we are
|
Chris@17
|
188 // have a dependency to check. This ensures that optional profile
|
Chris@17
|
189 // configuration is not unexpectedly re-created after being deleted.
|
Chris@17
|
190 $list = array_unique(array_merge($list, $profile_storage->listAll()));
|
Chris@17
|
191 }
|
Chris@0
|
192
|
Chris@17
|
193 // Filter the list of configuration to only include configuration that
|
Chris@17
|
194 // should be created.
|
Chris@0
|
195 $list = array_filter($list, function ($config_name) use ($existing_config) {
|
Chris@0
|
196 // Only list configuration that:
|
Chris@0
|
197 // - does not already exist
|
Chris@0
|
198 // - is a configuration entity (this also excludes config that has an
|
Chris@0
|
199 // implicit dependency on modules that are not yet installed)
|
Chris@0
|
200 return !in_array($config_name, $existing_config) && $this->configManager->getEntityTypeIdByName($config_name);
|
Chris@0
|
201 });
|
Chris@0
|
202
|
Chris@0
|
203 $all_config = array_merge($existing_config, $list);
|
Chris@0
|
204 $all_config = array_combine($all_config, $all_config);
|
Chris@0
|
205 $config_to_create = $storage->readMultiple($list);
|
Chris@0
|
206 // Check to see if the corresponding override storage has any overrides or
|
Chris@0
|
207 // new configuration that can be installed.
|
Chris@0
|
208 if ($profile_storage) {
|
Chris@0
|
209 $config_to_create = $profile_storage->readMultiple($list) + $config_to_create;
|
Chris@0
|
210 }
|
Chris@0
|
211 // Sort $config_to_create in the order of the least dependent first.
|
Chris@0
|
212 $dependency_manager = new ConfigDependencyManager();
|
Chris@0
|
213 $dependency_manager->setData($config_to_create);
|
Chris@0
|
214 $config_to_create = array_merge(array_flip($dependency_manager->sortAll()), $config_to_create);
|
Chris@17
|
215 if (!empty($dependency)) {
|
Chris@17
|
216 // In order to work out dependencies we need the full config graph.
|
Chris@17
|
217 $dependency_manager->setData($this->getActiveStorages()->readMultiple($existing_config) + $config_to_create);
|
Chris@17
|
218 $dependencies = $dependency_manager->getDependentEntities(key($dependency), reset($dependency));
|
Chris@17
|
219 }
|
Chris@0
|
220
|
Chris@0
|
221 foreach ($config_to_create as $config_name => $data) {
|
Chris@0
|
222 // Remove configuration where its dependencies cannot be met.
|
Chris@0
|
223 $remove = !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config);
|
Chris@17
|
224 // Remove configuration that is not dependent on $dependency, if it is
|
Chris@17
|
225 // defined.
|
Chris@0
|
226 if (!$remove && !empty($dependency)) {
|
Chris@17
|
227 $remove = !isset($dependencies[$config_name]);
|
Chris@0
|
228 }
|
Chris@0
|
229
|
Chris@0
|
230 if ($remove) {
|
Chris@0
|
231 // Remove from the list of configuration to create.
|
Chris@0
|
232 unset($config_to_create[$config_name]);
|
Chris@0
|
233 // Remove from the list of all configuration. This ensures that any
|
Chris@0
|
234 // configuration that depends on this configuration is also removed.
|
Chris@0
|
235 unset($all_config[$config_name]);
|
Chris@0
|
236 }
|
Chris@0
|
237 }
|
Chris@17
|
238
|
Chris@17
|
239 // Create the optional configuration if there is any left after filtering.
|
Chris@0
|
240 if (!empty($config_to_create)) {
|
Chris@0
|
241 $this->createConfiguration(StorageInterface::DEFAULT_COLLECTION, $config_to_create, TRUE);
|
Chris@0
|
242 }
|
Chris@0
|
243 }
|
Chris@0
|
244
|
Chris@0
|
245 /**
|
Chris@0
|
246 * Gets configuration data from the provided storage to create.
|
Chris@0
|
247 *
|
Chris@0
|
248 * @param StorageInterface $storage
|
Chris@0
|
249 * The configuration storage to read configuration from.
|
Chris@0
|
250 * @param string $collection
|
Chris@0
|
251 * The configuration collection to use.
|
Chris@0
|
252 * @param string $prefix
|
Chris@0
|
253 * (optional) Limit to configuration starting with the provided string.
|
Chris@0
|
254 * @param \Drupal\Core\Config\StorageInterface[] $profile_storages
|
Chris@0
|
255 * An array of storage interfaces containing profile configuration to check
|
Chris@0
|
256 * for overrides.
|
Chris@0
|
257 *
|
Chris@0
|
258 * @return array
|
Chris@0
|
259 * An array of configuration data read from the source storage keyed by the
|
Chris@0
|
260 * configuration object name.
|
Chris@0
|
261 */
|
Chris@0
|
262 protected function getConfigToCreate(StorageInterface $storage, $collection, $prefix = '', array $profile_storages = []) {
|
Chris@0
|
263 if ($storage->getCollectionName() != $collection) {
|
Chris@0
|
264 $storage = $storage->createCollection($collection);
|
Chris@0
|
265 }
|
Chris@0
|
266 $data = $storage->readMultiple($storage->listAll($prefix));
|
Chris@0
|
267
|
Chris@0
|
268 // Check to see if the corresponding override storage has any overrides.
|
Chris@0
|
269 foreach ($profile_storages as $profile_storage) {
|
Chris@0
|
270 if ($profile_storage->getCollectionName() != $collection) {
|
Chris@0
|
271 $profile_storage = $profile_storage->createCollection($collection);
|
Chris@0
|
272 }
|
Chris@0
|
273 $data = $profile_storage->readMultiple(array_keys($data)) + $data;
|
Chris@0
|
274 }
|
Chris@0
|
275 return $data;
|
Chris@0
|
276 }
|
Chris@0
|
277
|
Chris@0
|
278 /**
|
Chris@0
|
279 * Creates configuration in a collection based on the provided list.
|
Chris@0
|
280 *
|
Chris@0
|
281 * @param string $collection
|
Chris@0
|
282 * The configuration collection.
|
Chris@0
|
283 * @param array $config_to_create
|
Chris@0
|
284 * An array of configuration data to create, keyed by name.
|
Chris@0
|
285 */
|
Chris@0
|
286 protected function createConfiguration($collection, array $config_to_create) {
|
Chris@0
|
287 // Order the configuration to install in the order of dependencies.
|
Chris@0
|
288 if ($collection == StorageInterface::DEFAULT_COLLECTION) {
|
Chris@0
|
289 $dependency_manager = new ConfigDependencyManager();
|
Chris@0
|
290 $config_names = $dependency_manager
|
Chris@0
|
291 ->setData($config_to_create)
|
Chris@0
|
292 ->sortAll();
|
Chris@0
|
293 }
|
Chris@0
|
294 else {
|
Chris@0
|
295 $config_names = array_keys($config_to_create);
|
Chris@0
|
296 }
|
Chris@0
|
297
|
Chris@0
|
298 foreach ($config_names as $name) {
|
Chris@0
|
299 // Allow config factory overriders to use a custom configuration object if
|
Chris@0
|
300 // they are responsible for the collection.
|
Chris@0
|
301 $overrider = $this->configManager->getConfigCollectionInfo()->getOverrideService($collection);
|
Chris@0
|
302 if ($overrider) {
|
Chris@0
|
303 $new_config = $overrider->createConfigObject($name, $collection);
|
Chris@0
|
304 }
|
Chris@0
|
305 else {
|
Chris@0
|
306 $new_config = new Config($name, $this->getActiveStorages($collection), $this->eventDispatcher, $this->typedConfig);
|
Chris@0
|
307 }
|
Chris@0
|
308 if ($config_to_create[$name] !== FALSE) {
|
Chris@0
|
309 $new_config->setData($config_to_create[$name]);
|
Chris@0
|
310 // Add a hash to configuration created through the installer so it is
|
Chris@0
|
311 // possible to know if the configuration was created by installing an
|
Chris@0
|
312 // extension and to track which version of the default config was used.
|
Chris@0
|
313 if (!$this->isSyncing() && $collection == StorageInterface::DEFAULT_COLLECTION) {
|
Chris@0
|
314 $new_config->set('_core.default_config_hash', Crypt::hashBase64(serialize($config_to_create[$name])));
|
Chris@0
|
315 }
|
Chris@0
|
316 }
|
Chris@0
|
317 if ($collection == StorageInterface::DEFAULT_COLLECTION && $entity_type = $this->configManager->getEntityTypeIdByName($name)) {
|
Chris@0
|
318 // If we are syncing do not create configuration entities. Pluggable
|
Chris@0
|
319 // configuration entities can have dependencies on modules that are
|
Chris@0
|
320 // not yet enabled. This approach means that any code that expects
|
Chris@0
|
321 // default configuration entities to exist will be unstable after the
|
Chris@0
|
322 // module has been enabled and before the config entity has been
|
Chris@0
|
323 // imported.
|
Chris@0
|
324 if ($this->isSyncing()) {
|
Chris@0
|
325 continue;
|
Chris@0
|
326 }
|
Chris@0
|
327 /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $entity_storage */
|
Chris@0
|
328 $entity_storage = $this->configManager
|
Chris@18
|
329 ->getEntityTypeManager()
|
Chris@0
|
330 ->getStorage($entity_type);
|
Chris@17
|
331
|
Chris@17
|
332 $id = $entity_storage->getIDFromConfigName($name, $entity_storage->getEntityType()->getConfigPrefix());
|
Chris@0
|
333 // It is possible that secondary writes can occur during configuration
|
Chris@0
|
334 // creation. Updates of such configuration are allowed.
|
Chris@0
|
335 if ($this->getActiveStorages($collection)->exists($name)) {
|
Chris@0
|
336 $entity = $entity_storage->load($id);
|
Chris@0
|
337 $entity = $entity_storage->updateFromStorageRecord($entity, $new_config->get());
|
Chris@0
|
338 }
|
Chris@0
|
339 else {
|
Chris@0
|
340 $entity = $entity_storage->createFromStorageRecord($new_config->get());
|
Chris@0
|
341 }
|
Chris@0
|
342 if ($entity->isInstallable()) {
|
Chris@0
|
343 $entity->trustData()->save();
|
Chris@17
|
344 if ($id !== $entity->id()) {
|
Chris@17
|
345 trigger_error(sprintf('The configuration name "%s" does not match the ID "%s"', $name, $entity->id()), E_USER_WARNING);
|
Chris@17
|
346 }
|
Chris@0
|
347 }
|
Chris@0
|
348 }
|
Chris@0
|
349 else {
|
Chris@0
|
350 $new_config->save(TRUE);
|
Chris@0
|
351 }
|
Chris@0
|
352 }
|
Chris@0
|
353 }
|
Chris@0
|
354
|
Chris@0
|
355 /**
|
Chris@0
|
356 * {@inheritdoc}
|
Chris@0
|
357 */
|
Chris@0
|
358 public function installCollectionDefaultConfig($collection) {
|
Chris@0
|
359 $storage = new ExtensionInstallStorage($this->getActiveStorages(StorageInterface::DEFAULT_COLLECTION), InstallStorage::CONFIG_INSTALL_DIRECTORY, $collection, $this->drupalInstallationAttempted(), $this->installProfile);
|
Chris@0
|
360 // Only install configuration for enabled extensions.
|
Chris@0
|
361 $enabled_extensions = $this->getEnabledExtensions();
|
Chris@0
|
362 $config_to_install = array_filter($storage->listAll(), function ($config_name) use ($enabled_extensions) {
|
Chris@17
|
363 $provider = mb_substr($config_name, 0, strpos($config_name, '.'));
|
Chris@0
|
364 return in_array($provider, $enabled_extensions);
|
Chris@0
|
365 });
|
Chris@0
|
366 if (!empty($config_to_install)) {
|
Chris@0
|
367 $this->createConfiguration($collection, $storage->readMultiple($config_to_install));
|
Chris@0
|
368 // Reset all the static caches and list caches.
|
Chris@0
|
369 $this->configFactory->reset();
|
Chris@0
|
370 }
|
Chris@0
|
371 }
|
Chris@0
|
372
|
Chris@0
|
373 /**
|
Chris@0
|
374 * {@inheritdoc}
|
Chris@0
|
375 */
|
Chris@0
|
376 public function setSourceStorage(StorageInterface $storage) {
|
Chris@0
|
377 $this->sourceStorage = $storage;
|
Chris@0
|
378 return $this;
|
Chris@0
|
379 }
|
Chris@0
|
380
|
Chris@0
|
381 /**
|
Chris@18
|
382 * {@inheritdoc}
|
Chris@0
|
383 */
|
Chris@0
|
384 public function getSourceStorage() {
|
Chris@0
|
385 return $this->sourceStorage;
|
Chris@0
|
386 }
|
Chris@0
|
387
|
Chris@0
|
388 /**
|
Chris@0
|
389 * Gets the configuration storage that provides the active configuration.
|
Chris@0
|
390 *
|
Chris@0
|
391 * @param string $collection
|
Chris@0
|
392 * (optional) The configuration collection. Defaults to the default
|
Chris@0
|
393 * collection.
|
Chris@0
|
394 *
|
Chris@0
|
395 * @return \Drupal\Core\Config\StorageInterface
|
Chris@0
|
396 * The configuration storage that provides the default configuration.
|
Chris@0
|
397 */
|
Chris@0
|
398 protected function getActiveStorages($collection = StorageInterface::DEFAULT_COLLECTION) {
|
Chris@0
|
399 if (!isset($this->activeStorages[$collection])) {
|
Chris@0
|
400 $this->activeStorages[$collection] = reset($this->activeStorages)->createCollection($collection);
|
Chris@0
|
401 }
|
Chris@0
|
402 return $this->activeStorages[$collection];
|
Chris@0
|
403 }
|
Chris@0
|
404
|
Chris@0
|
405 /**
|
Chris@0
|
406 * {@inheritdoc}
|
Chris@0
|
407 */
|
Chris@0
|
408 public function setSyncing($status) {
|
Chris@0
|
409 if (!$status) {
|
Chris@0
|
410 $this->sourceStorage = NULL;
|
Chris@0
|
411 }
|
Chris@0
|
412 $this->isSyncing = $status;
|
Chris@0
|
413 return $this;
|
Chris@0
|
414 }
|
Chris@0
|
415
|
Chris@0
|
416 /**
|
Chris@0
|
417 * {@inheritdoc}
|
Chris@0
|
418 */
|
Chris@0
|
419 public function isSyncing() {
|
Chris@0
|
420 return $this->isSyncing;
|
Chris@0
|
421 }
|
Chris@0
|
422
|
Chris@0
|
423 /**
|
Chris@0
|
424 * Finds pre-existing configuration objects for the provided extension.
|
Chris@0
|
425 *
|
Chris@0
|
426 * Extensions can not be installed if configuration objects exist in the
|
Chris@0
|
427 * active storage with the same names. This can happen in a number of ways,
|
Chris@0
|
428 * commonly:
|
Chris@0
|
429 * - if a user has created configuration with the same name as that provided
|
Chris@0
|
430 * by the extension.
|
Chris@0
|
431 * - if the extension provides default configuration that does not depend on
|
Chris@0
|
432 * it and the extension has been uninstalled and is about to the
|
Chris@0
|
433 * reinstalled.
|
Chris@0
|
434 *
|
Chris@0
|
435 * @return array
|
Chris@0
|
436 * Array of configuration object names that already exist keyed by
|
Chris@0
|
437 * collection.
|
Chris@0
|
438 */
|
Chris@0
|
439 protected function findPreExistingConfiguration(StorageInterface $storage) {
|
Chris@0
|
440 $existing_configuration = [];
|
Chris@0
|
441 // Gather information about all the supported collections.
|
Chris@0
|
442 $collection_info = $this->configManager->getConfigCollectionInfo();
|
Chris@0
|
443
|
Chris@0
|
444 foreach ($collection_info->getCollectionNames() as $collection) {
|
Chris@0
|
445 $config_to_create = array_keys($this->getConfigToCreate($storage, $collection));
|
Chris@0
|
446 $active_storage = $this->getActiveStorages($collection);
|
Chris@0
|
447 foreach ($config_to_create as $config_name) {
|
Chris@0
|
448 if ($active_storage->exists($config_name)) {
|
Chris@0
|
449 $existing_configuration[$collection][] = $config_name;
|
Chris@0
|
450 }
|
Chris@0
|
451 }
|
Chris@0
|
452 }
|
Chris@0
|
453 return $existing_configuration;
|
Chris@0
|
454 }
|
Chris@0
|
455
|
Chris@0
|
456 /**
|
Chris@0
|
457 * {@inheritdoc}
|
Chris@0
|
458 */
|
Chris@0
|
459 public function checkConfigurationToInstall($type, $name) {
|
Chris@0
|
460 if ($this->isSyncing()) {
|
Chris@0
|
461 // Configuration is assumed to already be checked by the config importer
|
Chris@0
|
462 // validation events.
|
Chris@0
|
463 return;
|
Chris@0
|
464 }
|
Chris@0
|
465 $config_install_path = $this->getDefaultConfigDirectory($type, $name);
|
Chris@0
|
466 if (!is_dir($config_install_path)) {
|
Chris@0
|
467 return;
|
Chris@0
|
468 }
|
Chris@0
|
469
|
Chris@0
|
470 $storage = new FileStorage($config_install_path, StorageInterface::DEFAULT_COLLECTION);
|
Chris@0
|
471
|
Chris@0
|
472 $enabled_extensions = $this->getEnabledExtensions();
|
Chris@0
|
473 // Add the extension that will be enabled to the list of enabled extensions.
|
Chris@0
|
474 $enabled_extensions[] = $name;
|
Chris@0
|
475 // Gets profile storages to search for overrides if necessary.
|
Chris@0
|
476 $profile_storages = $this->getProfileStorages($name);
|
Chris@0
|
477
|
Chris@0
|
478 // Check the dependencies of configuration provided by the module.
|
Chris@0
|
479 list($invalid_default_config, $missing_dependencies) = $this->findDefaultConfigWithUnmetDependencies($storage, $enabled_extensions, $profile_storages);
|
Chris@0
|
480 if (!empty($invalid_default_config)) {
|
Chris@0
|
481 throw UnmetDependenciesException::create($name, array_unique($missing_dependencies, SORT_REGULAR));
|
Chris@0
|
482 }
|
Chris@0
|
483
|
Chris@0
|
484 // Install profiles can not have config clashes. Configuration that
|
Chris@0
|
485 // has the same name as a module's configuration will be used instead.
|
Chris@0
|
486 if ($name != $this->drupalGetProfile()) {
|
Chris@0
|
487 // Throw an exception if the module being installed contains configuration
|
Chris@0
|
488 // that already exists. Additionally, can not continue installing more
|
Chris@0
|
489 // modules because those may depend on the current module being installed.
|
Chris@0
|
490 $existing_configuration = $this->findPreExistingConfiguration($storage);
|
Chris@0
|
491 if (!empty($existing_configuration)) {
|
Chris@0
|
492 throw PreExistingConfigException::create($name, $existing_configuration);
|
Chris@0
|
493 }
|
Chris@0
|
494 }
|
Chris@0
|
495 }
|
Chris@0
|
496
|
Chris@0
|
497 /**
|
Chris@0
|
498 * Finds default configuration with unmet dependencies.
|
Chris@0
|
499 *
|
Chris@0
|
500 * @param \Drupal\Core\Config\StorageInterface $storage
|
Chris@0
|
501 * The storage containing the default configuration.
|
Chris@0
|
502 * @param array $enabled_extensions
|
Chris@0
|
503 * A list of all the currently enabled modules and themes.
|
Chris@0
|
504 * @param \Drupal\Core\Config\StorageInterface[] $profile_storages
|
Chris@0
|
505 * An array of storage interfaces containing profile configuration to check
|
Chris@0
|
506 * for overrides.
|
Chris@0
|
507 *
|
Chris@0
|
508 * @return array
|
Chris@0
|
509 * An array containing:
|
Chris@0
|
510 * - A list of configuration that has unmet dependencies.
|
Chris@0
|
511 * - An array that will be filled with the missing dependency names, keyed
|
Chris@0
|
512 * by the dependents' names.
|
Chris@0
|
513 */
|
Chris@0
|
514 protected function findDefaultConfigWithUnmetDependencies(StorageInterface $storage, array $enabled_extensions, array $profile_storages = []) {
|
Chris@0
|
515 $missing_dependencies = [];
|
Chris@0
|
516 $config_to_create = $this->getConfigToCreate($storage, StorageInterface::DEFAULT_COLLECTION, '', $profile_storages);
|
Chris@0
|
517 $all_config = array_merge($this->configFactory->listAll(), array_keys($config_to_create));
|
Chris@0
|
518 foreach ($config_to_create as $config_name => $config) {
|
Chris@0
|
519 if ($missing = $this->getMissingDependencies($config_name, $config, $enabled_extensions, $all_config)) {
|
Chris@0
|
520 $missing_dependencies[$config_name] = $missing;
|
Chris@0
|
521 }
|
Chris@0
|
522 }
|
Chris@0
|
523 return [
|
Chris@0
|
524 array_intersect_key($config_to_create, $missing_dependencies),
|
Chris@0
|
525 $missing_dependencies,
|
Chris@0
|
526 ];
|
Chris@0
|
527 }
|
Chris@0
|
528
|
Chris@0
|
529 /**
|
Chris@0
|
530 * Validates an array of config data that contains dependency information.
|
Chris@0
|
531 *
|
Chris@0
|
532 * @param string $config_name
|
Chris@0
|
533 * The name of the configuration object that is being validated.
|
Chris@0
|
534 * @param array $data
|
Chris@0
|
535 * Configuration data.
|
Chris@0
|
536 * @param array $enabled_extensions
|
Chris@0
|
537 * A list of all the currently enabled modules and themes.
|
Chris@0
|
538 * @param array $all_config
|
Chris@0
|
539 * A list of all the active configuration names.
|
Chris@0
|
540 *
|
Chris@0
|
541 * @return bool
|
Chris@0
|
542 * TRUE if all dependencies are present, FALSE otherwise.
|
Chris@0
|
543 */
|
Chris@0
|
544 protected function validateDependencies($config_name, array $data, array $enabled_extensions, array $all_config) {
|
Chris@0
|
545 if (!isset($data['dependencies'])) {
|
Chris@0
|
546 // Simple config or a config entity without dependencies.
|
Chris@0
|
547 list($provider) = explode('.', $config_name, 2);
|
Chris@0
|
548 return in_array($provider, $enabled_extensions, TRUE);
|
Chris@0
|
549 }
|
Chris@0
|
550
|
Chris@0
|
551 $missing = $this->getMissingDependencies($config_name, $data, $enabled_extensions, $all_config);
|
Chris@0
|
552 return empty($missing);
|
Chris@0
|
553 }
|
Chris@0
|
554
|
Chris@0
|
555 /**
|
Chris@0
|
556 * Returns an array of missing dependencies for a config object.
|
Chris@0
|
557 *
|
Chris@0
|
558 * @param string $config_name
|
Chris@0
|
559 * The name of the configuration object that is being validated.
|
Chris@0
|
560 * @param array $data
|
Chris@0
|
561 * Configuration data.
|
Chris@0
|
562 * @param array $enabled_extensions
|
Chris@0
|
563 * A list of all the currently enabled modules and themes.
|
Chris@0
|
564 * @param array $all_config
|
Chris@0
|
565 * A list of all the active configuration names.
|
Chris@0
|
566 *
|
Chris@0
|
567 * @return array
|
Chris@0
|
568 * A list of missing config dependencies.
|
Chris@0
|
569 */
|
Chris@0
|
570 protected function getMissingDependencies($config_name, array $data, array $enabled_extensions, array $all_config) {
|
Chris@0
|
571 $missing = [];
|
Chris@0
|
572 if (isset($data['dependencies'])) {
|
Chris@0
|
573 list($provider) = explode('.', $config_name, 2);
|
Chris@0
|
574 $all_dependencies = $data['dependencies'];
|
Chris@0
|
575
|
Chris@0
|
576 // Ensure enforced dependencies are included.
|
Chris@0
|
577 if (isset($all_dependencies['enforced'])) {
|
Chris@0
|
578 $all_dependencies = array_merge($all_dependencies, $data['dependencies']['enforced']);
|
Chris@0
|
579 unset($all_dependencies['enforced']);
|
Chris@0
|
580 }
|
Chris@0
|
581 // Ensure the configuration entity type provider is in the list of
|
Chris@0
|
582 // dependencies.
|
Chris@0
|
583 if (!isset($all_dependencies['module']) || !in_array($provider, $all_dependencies['module'])) {
|
Chris@0
|
584 $all_dependencies['module'][] = $provider;
|
Chris@0
|
585 }
|
Chris@0
|
586
|
Chris@0
|
587 foreach ($all_dependencies as $type => $dependencies) {
|
Chris@0
|
588 $list_to_check = [];
|
Chris@0
|
589 switch ($type) {
|
Chris@0
|
590 case 'module':
|
Chris@0
|
591 case 'theme':
|
Chris@0
|
592 $list_to_check = $enabled_extensions;
|
Chris@0
|
593 break;
|
Chris@0
|
594 case 'config':
|
Chris@0
|
595 $list_to_check = $all_config;
|
Chris@0
|
596 break;
|
Chris@0
|
597 }
|
Chris@0
|
598 if (!empty($list_to_check)) {
|
Chris@0
|
599 $missing = array_merge($missing, array_diff($dependencies, $list_to_check));
|
Chris@0
|
600 }
|
Chris@0
|
601 }
|
Chris@0
|
602 }
|
Chris@0
|
603
|
Chris@0
|
604 return $missing;
|
Chris@0
|
605 }
|
Chris@0
|
606
|
Chris@0
|
607 /**
|
Chris@0
|
608 * Gets the list of enabled extensions including both modules and themes.
|
Chris@0
|
609 *
|
Chris@0
|
610 * @return array
|
Chris@0
|
611 * A list of enabled extensions which includes both modules and themes.
|
Chris@0
|
612 */
|
Chris@0
|
613 protected function getEnabledExtensions() {
|
Chris@0
|
614 // Read enabled extensions directly from configuration to avoid circular
|
Chris@0
|
615 // dependencies on ModuleHandler and ThemeHandler.
|
Chris@0
|
616 $extension_config = $this->configFactory->get('core.extension');
|
Chris@0
|
617 $enabled_extensions = (array) $extension_config->get('module');
|
Chris@0
|
618 $enabled_extensions += (array) $extension_config->get('theme');
|
Chris@0
|
619 // Core can provide configuration.
|
Chris@0
|
620 $enabled_extensions['core'] = 'core';
|
Chris@0
|
621 return array_keys($enabled_extensions);
|
Chris@0
|
622 }
|
Chris@0
|
623
|
Chris@0
|
624 /**
|
Chris@0
|
625 * Gets the profile storage to use to check for profile overrides.
|
Chris@0
|
626 *
|
Chris@0
|
627 * The install profile can override module configuration during a module
|
Chris@0
|
628 * install. Both the install and optional directories are checked for matching
|
Chris@0
|
629 * configuration. This allows profiles to override default configuration for
|
Chris@0
|
630 * modules they do not depend on.
|
Chris@0
|
631 *
|
Chris@0
|
632 * @param string $installing_name
|
Chris@0
|
633 * (optional) The name of the extension currently being installed.
|
Chris@0
|
634 *
|
Chris@0
|
635 * @return \Drupal\Core\Config\StorageInterface[]|null
|
Chris@0
|
636 * Storages to access configuration from the installation profile. If we're
|
Chris@0
|
637 * installing the profile itself, then it will return an empty array as the
|
Chris@0
|
638 * profile storage should not be used.
|
Chris@0
|
639 */
|
Chris@0
|
640 protected function getProfileStorages($installing_name = '') {
|
Chris@0
|
641 $profile = $this->drupalGetProfile();
|
Chris@0
|
642 $profile_storages = [];
|
Chris@0
|
643 if ($profile && $profile != $installing_name) {
|
Chris@0
|
644 $profile_path = $this->drupalGetPath('module', $profile);
|
Chris@0
|
645 foreach ([InstallStorage::CONFIG_INSTALL_DIRECTORY, InstallStorage::CONFIG_OPTIONAL_DIRECTORY] as $directory) {
|
Chris@0
|
646 if (is_dir($profile_path . '/' . $directory)) {
|
Chris@0
|
647 $profile_storages[] = new FileStorage($profile_path . '/' . $directory, StorageInterface::DEFAULT_COLLECTION);
|
Chris@0
|
648 }
|
Chris@0
|
649 }
|
Chris@0
|
650 }
|
Chris@0
|
651 return $profile_storages;
|
Chris@0
|
652 }
|
Chris@0
|
653
|
Chris@0
|
654 /**
|
Chris@0
|
655 * Gets an extension's default configuration directory.
|
Chris@0
|
656 *
|
Chris@0
|
657 * @param string $type
|
Chris@0
|
658 * Type of extension to install.
|
Chris@0
|
659 * @param string $name
|
Chris@0
|
660 * Name of extension to install.
|
Chris@0
|
661 *
|
Chris@0
|
662 * @return string
|
Chris@0
|
663 * The extension's default configuration directory.
|
Chris@0
|
664 */
|
Chris@0
|
665 protected function getDefaultConfigDirectory($type, $name) {
|
Chris@0
|
666 return $this->drupalGetPath($type, $name) . '/' . InstallStorage::CONFIG_INSTALL_DIRECTORY;
|
Chris@0
|
667 }
|
Chris@0
|
668
|
Chris@0
|
669 /**
|
Chris@0
|
670 * Wrapper for drupal_get_path().
|
Chris@0
|
671 *
|
Chris@0
|
672 * @param $type
|
Chris@0
|
673 * The type of the item; one of 'core', 'profile', 'module', 'theme', or
|
Chris@0
|
674 * 'theme_engine'.
|
Chris@0
|
675 * @param $name
|
Chris@0
|
676 * The name of the item for which the path is requested. Ignored for
|
Chris@0
|
677 * $type 'core'.
|
Chris@0
|
678 *
|
Chris@0
|
679 * @return string
|
Chris@0
|
680 * The path to the requested item or an empty string if the item is not
|
Chris@0
|
681 * found.
|
Chris@0
|
682 */
|
Chris@0
|
683 protected function drupalGetPath($type, $name) {
|
Chris@0
|
684 return drupal_get_path($type, $name);
|
Chris@0
|
685 }
|
Chris@0
|
686
|
Chris@0
|
687 /**
|
Chris@0
|
688 * Gets the install profile from settings.
|
Chris@0
|
689 *
|
Chris@0
|
690 * @return string|null
|
Chris@0
|
691 * The name of the installation profile or NULL if no installation profile
|
Chris@0
|
692 * is currently active. This is the case for example during the first steps
|
Chris@0
|
693 * of the installer or during unit tests.
|
Chris@0
|
694 */
|
Chris@0
|
695 protected function drupalGetProfile() {
|
Chris@0
|
696 return $this->installProfile;
|
Chris@0
|
697 }
|
Chris@0
|
698
|
Chris@0
|
699 /**
|
Chris@0
|
700 * Wrapper for drupal_installation_attempted().
|
Chris@0
|
701 *
|
Chris@0
|
702 * @return bool
|
Chris@0
|
703 * TRUE if a Drupal installation is currently being attempted.
|
Chris@0
|
704 */
|
Chris@0
|
705 protected function drupalInstallationAttempted() {
|
Chris@0
|
706 return drupal_installation_attempted();
|
Chris@0
|
707 }
|
Chris@0
|
708
|
Chris@0
|
709 }
|