Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / EventSubscriber / ConfigImportSubscriber.php
1 <?php
2
3 namespace Drupal\Core\EventSubscriber;
4
5 use Drupal\Core\Config\Config;
6 use Drupal\Core\Config\ConfigImporter;
7 use Drupal\Core\Config\ConfigImporterEvent;
8 use Drupal\Core\Config\ConfigImportValidateEventSubscriberBase;
9 use Drupal\Core\Config\ConfigNameException;
10 use Drupal\Core\Extension\ThemeHandlerInterface;
11
12 /**
13  * Config import subscriber for config import events.
14  */
15 class ConfigImportSubscriber extends ConfigImportValidateEventSubscriberBase {
16
17   /**
18    * Theme data.
19    *
20    * @var \Drupal\Core\Extension\Extension[]
21    */
22   protected $themeData;
23
24   /**
25    * Module data.
26    *
27    * @var \Drupal\Core\Extension\Extension[]
28    */
29   protected $moduleData;
30
31   /**
32    * The theme handler.
33    *
34    * @var \Drupal\Core\Extension\ThemeHandlerInterface
35    */
36   protected $themeHandler;
37
38   /**
39    * Constructs the ConfigImportSubscriber.
40    *
41    * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
42    *   The theme handler.
43    */
44   public function __construct(ThemeHandlerInterface $theme_handler) {
45     $this->themeHandler = $theme_handler;
46   }
47
48   /**
49    * Validates the configuration to be imported.
50    *
51    * @param \Drupal\Core\Config\ConfigImporterEvent $event
52    *   The Event to process.
53    *
54    * @throws \Drupal\Core\Config\ConfigNameException
55    */
56   public function onConfigImporterValidate(ConfigImporterEvent $event) {
57     foreach (['delete', 'create', 'update'] as $op) {
58       foreach ($event->getConfigImporter()->getUnprocessedConfiguration($op) as $name) {
59         try {
60           Config::validateName($name);
61         }
62         catch (ConfigNameException $e) {
63           $message = $this->t('The config name @config_name is invalid.', ['@config_name' => $name]);
64           $event->getConfigImporter()->logError($message);
65         }
66       }
67     }
68     $config_importer = $event->getConfigImporter();
69     if ($config_importer->getStorageComparer()->getSourceStorage()->exists('core.extension')) {
70       $this->validateModules($config_importer);
71       $this->validateThemes($config_importer);
72       $this->validateDependencies($config_importer);
73     }
74     else {
75       $config_importer->logError($this->t('The core.extension configuration does not exist.'));
76     }
77   }
78
79   /**
80    * Validates module installations and uninstallations.
81    *
82    * @param \Drupal\Core\Config\ConfigImporter $config_importer
83    *   The configuration importer.
84    */
85   protected function validateModules(ConfigImporter $config_importer) {
86     $core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
87
88     // Get the install profile from the site's configuration.
89     $current_core_extension = $config_importer->getStorageComparer()->getTargetStorage()->read('core.extension');
90     $install_profile = isset($current_core_extension['profile']) ? $current_core_extension['profile'] : NULL;
91
92     // Ensure the profile is not changing.
93     if ($install_profile !== $core_extension['profile']) {
94       if (drupal_installation_attempted()) {
95         $config_importer->logError($this->t('The selected installation profile %install_profile does not match the profile stored in configuration %config_profile.', [
96           '%install_profile' => $install_profile,
97           '%config_profile' => $core_extension['profile'],
98         ]));
99         // If this error has occurred the other checks are irrelevant.
100         return;
101       }
102       else {
103         $config_importer->logError($this->t('Cannot change the install profile from %profile to %new_profile once Drupal is installed.', [
104           '%profile' => $install_profile,
105           '%new_profile' => $core_extension['profile'],
106         ]));
107       }
108     }
109
110     // Get a list of modules with dependency weights as values.
111     $module_data = $this->getModuleData();
112     $nonexistent_modules = array_keys(array_diff_key($core_extension['module'], $module_data));
113     foreach ($nonexistent_modules as $module) {
114       $config_importer->logError($this->t('Unable to install the %module module since it does not exist.', ['%module' => $module]));
115     }
116
117     // Ensure that all modules being installed have their dependencies met.
118     $installs = $config_importer->getExtensionChangelist('module', 'install');
119     foreach ($installs as $module) {
120       $missing_dependencies = [];
121       foreach (array_keys($module_data[$module]->requires) as $required_module) {
122         if (!isset($core_extension['module'][$required_module])) {
123           $missing_dependencies[] = $module_data[$required_module]->info['name'];
124         }
125       }
126       if (!empty($missing_dependencies)) {
127         $module_name = $module_data[$module]->info['name'];
128         $message = $this->formatPlural(count($missing_dependencies),
129           'Unable to install the %module module since it requires the %required_module module.',
130           'Unable to install the %module module since it requires the %required_module modules.',
131           ['%module' => $module_name, '%required_module' => implode(', ', $missing_dependencies)]
132         );
133         $config_importer->logError($message);
134       }
135     }
136
137     // Ensure that all modules being uninstalled are not required by modules
138     // that will be installed after the import.
139     $uninstalls = $config_importer->getExtensionChangelist('module', 'uninstall');
140     foreach ($uninstalls as $module) {
141       foreach (array_keys($module_data[$module]->required_by) as $dependent_module) {
142         if ($module_data[$dependent_module]->status && !in_array($dependent_module, $uninstalls, TRUE) && $dependent_module !== $install_profile) {
143           $module_name = $module_data[$module]->info['name'];
144           $dependent_module_name = $module_data[$dependent_module]->info['name'];
145           $config_importer->logError($this->t('Unable to uninstall the %module module since the %dependent_module module is installed.', ['%module' => $module_name, '%dependent_module' => $dependent_module_name]));
146         }
147       }
148     }
149
150     // Ensure that the install profile is not being uninstalled.
151     if (in_array($install_profile, $uninstalls, TRUE)) {
152       $profile_name = $module_data[$install_profile]->info['name'];
153       $config_importer->logError($this->t('Unable to uninstall the %profile profile since it is the install profile.', ['%profile' => $profile_name]));
154     }
155   }
156
157   /**
158    * Validates theme installations and uninstallations.
159    *
160    * @param \Drupal\Core\Config\ConfigImporter $config_importer
161    *   The configuration importer.
162    */
163   protected function validateThemes(ConfigImporter $config_importer) {
164     $core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
165     // Get all themes including those that are not installed.
166     $theme_data = $this->getThemeData();
167     $installs = $config_importer->getExtensionChangelist('theme', 'install');
168     foreach ($installs as $key => $theme) {
169       if (!isset($theme_data[$theme])) {
170         $config_importer->logError($this->t('Unable to install the %theme theme since it does not exist.', ['%theme' => $theme]));
171         // Remove non-existing installs from the list so we can validate theme
172         // dependencies later.
173         unset($installs[$key]);
174       }
175     }
176
177     // Ensure that all themes being installed have their dependencies met.
178     foreach ($installs as $theme) {
179       foreach (array_keys($theme_data[$theme]->requires) as $required_theme) {
180         if (!isset($core_extension['theme'][$required_theme])) {
181           $theme_name = $theme_data[$theme]->info['name'];
182           $required_theme_name = $theme_data[$required_theme]->info['name'];
183           $config_importer->logError($this->t('Unable to install the %theme theme since it requires the %required_theme theme.', ['%theme' => $theme_name, '%required_theme' => $required_theme_name]));
184         }
185       }
186     }
187
188     // Ensure that all themes being uninstalled are not required by themes that
189     // will be installed after the import.
190     $uninstalls = $config_importer->getExtensionChangelist('theme', 'uninstall');
191     foreach ($uninstalls as $theme) {
192       foreach (array_keys($theme_data[$theme]->required_by) as $dependent_theme) {
193         if ($theme_data[$dependent_theme]->status && !in_array($dependent_theme, $uninstalls, TRUE)) {
194           $theme_name = $theme_data[$theme]->info['name'];
195           $dependent_theme_name = $theme_data[$dependent_theme]->info['name'];
196           $config_importer->logError($this->t('Unable to uninstall the %theme theme since the %dependent_theme theme is installed.', ['%theme' => $theme_name, '%dependent_theme' => $dependent_theme_name]));
197         }
198       }
199     }
200   }
201
202   /**
203    * Validates configuration being imported does not have unmet dependencies.
204    *
205    * @param \Drupal\Core\Config\ConfigImporter $config_importer
206    *   The configuration importer.
207    */
208   protected function validateDependencies(ConfigImporter $config_importer) {
209     $core_extension = $config_importer->getStorageComparer()->getSourceStorage()->read('core.extension');
210     $existing_dependencies = [
211       'config' => $config_importer->getStorageComparer()->getSourceStorage()->listAll(),
212       'module' => array_keys($core_extension['module']),
213       'theme' => array_keys($core_extension['theme']),
214     ];
215
216     $theme_data = $this->getThemeData();
217     $module_data = $this->getModuleData();
218
219     // Validate the dependencies of all the configuration. We have to validate
220     // the entire tree because existing configuration might depend on
221     // configuration that is being deleted.
222     foreach ($config_importer->getStorageComparer()->getSourceStorage()->listAll() as $name) {
223       // Ensure that the config owner is installed. This checks all
224       // configuration including configuration entities.
225       list($owner,) = explode('.', $name, 2);
226       if ($owner !== 'core') {
227         $message = FALSE;
228         if (!isset($core_extension['module'][$owner]) && isset($module_data[$owner])) {
229           $message = $this->t('Configuration %name depends on the %owner module that will not be installed after import.', [
230             '%name' => $name,
231             '%owner' => $module_data[$owner]->info['name'],
232           ]);
233         }
234         elseif (!isset($core_extension['theme'][$owner]) && isset($theme_data[$owner])) {
235           $message = $this->t('Configuration %name depends on the %owner theme that will not be installed after import.', [
236             '%name' => $name,
237             '%owner' => $theme_data[$owner]->info['name'],
238           ]);
239         }
240         elseif (!isset($core_extension['module'][$owner]) && !isset($core_extension['theme'][$owner])) {
241           $message = $this->t('Configuration %name depends on the %owner extension that will not be installed after import.', [
242             '%name' => $name,
243             '%owner' => $owner,
244           ]);
245         }
246
247         if ($message) {
248           $config_importer->logError($message);
249           continue;
250         }
251       }
252
253       $data = $config_importer->getStorageComparer()->getSourceStorage()->read($name);
254       // Configuration entities have dependencies on modules, themes, and other
255       // configuration entities that we can validate. Their content dependencies
256       // are not validated since we assume that they are soft dependencies.
257       // Configuration entities can be identified by having 'dependencies' and
258       // 'uuid' keys.
259       if (isset($data['dependencies']) && isset($data['uuid'])) {
260         $dependencies_to_check = array_intersect_key($data['dependencies'], array_flip(['module', 'theme', 'config']));
261         foreach ($dependencies_to_check as $type => $dependencies) {
262           $diffs = array_diff($dependencies, $existing_dependencies[$type]);
263           if (!empty($diffs)) {
264             $message = FALSE;
265             switch ($type) {
266               case 'module':
267                 $message = $this->formatPlural(
268                   count($diffs),
269                   'Configuration %name depends on the %module module that will not be installed after import.',
270                   'Configuration %name depends on modules (%module) that will not be installed after import.',
271                   ['%name' => $name, '%module' => implode(', ', $this->getNames($diffs, $module_data))]
272                 );
273                 break;
274               case 'theme':
275                 $message = $this->formatPlural(
276                   count($diffs),
277                   'Configuration %name depends on the %theme theme that will not be installed after import.',
278                   'Configuration %name depends on themes (%theme) that will not be installed after import.',
279                   ['%name' => $name, '%theme' => implode(', ', $this->getNames($diffs, $theme_data))]
280                 );
281                 break;
282               case 'config':
283                 $message = $this->formatPlural(
284                   count($diffs),
285                   'Configuration %name depends on the %config configuration that will not exist after import.',
286                   'Configuration %name depends on configuration (%config) that will not exist after import.',
287                   ['%name' => $name, '%config' => implode(', ', $diffs)]
288                 );
289                 break;
290             }
291
292             if ($message) {
293               $config_importer->logError($message);
294             }
295           }
296         }
297       }
298     }
299   }
300
301   /**
302    * Gets theme data.
303    *
304    * @return \Drupal\Core\Extension\Extension[]
305    */
306   protected function getThemeData() {
307     if (!isset($this->themeData)) {
308       $this->themeData = $this->themeHandler->rebuildThemeData();
309     }
310     return $this->themeData;
311   }
312
313   /**
314    * Gets module data.
315    *
316    * @return \Drupal\Core\Extension\Extension[]
317    */
318   protected function getModuleData() {
319     if (!isset($this->moduleData)) {
320       $this->moduleData = system_rebuild_module_data();
321     }
322     return $this->moduleData;
323   }
324
325   /**
326    * Gets human readable extension names.
327    *
328    * @param array $names
329    *   A list of extension machine names.
330    * @param \Drupal\Core\Extension\Extension[] $extension_data
331    *   Extension data.
332    *
333    * @return array
334    *   A list of human-readable extension names, or machine names if
335    *   human-readable names are not available.
336    */
337   protected function getNames(array $names, array $extension_data) {
338     return array_map(function ($name) use ($extension_data) {
339       if (isset($extension_data[$name])) {
340         $name = $extension_data[$name]->info['name'];
341       }
342       return $name;
343     }, $names);
344   }
345
346 }