PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/web/core/lib/Drupal/Core/Extension/ThemeInstaller.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 322 lines | 157 code | 50 blank | 115 comment | 19 complexity | d8755fabc2b0846a27cea00664399f94 MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Extension;
  3. use Drupal\Component\Utility\Html;
  4. use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
  5. use Drupal\Core\Cache\Cache;
  6. use Drupal\Core\Config\ConfigFactoryInterface;
  7. use Drupal\Core\Config\ConfigInstallerInterface;
  8. use Drupal\Core\Config\ConfigManagerInterface;
  9. use Drupal\Core\Extension\Exception\UnknownExtensionException;
  10. use Drupal\Core\Routing\RouteBuilderInterface;
  11. use Drupal\Core\State\StateInterface;
  12. use Drupal\Core\StringTranslation\StringTranslationTrait;
  13. use Psr\Log\LoggerInterface;
  14. /**
  15. * Manages theme installation/uninstallation.
  16. */
  17. class ThemeInstaller implements ThemeInstallerInterface {
  18. use ModuleDependencyMessageTrait;
  19. use StringTranslationTrait;
  20. /**
  21. * @var \Drupal\Core\Extension\ThemeHandlerInterface
  22. */
  23. protected $themeHandler;
  24. /**
  25. * @var \Drupal\Core\Config\ConfigFactoryInterface
  26. */
  27. protected $configFactory;
  28. /**
  29. * @var \Drupal\Core\Config\ConfigInstallerInterface
  30. */
  31. protected $configInstaller;
  32. /**
  33. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  34. */
  35. protected $moduleHandler;
  36. /**
  37. * @var \Drupal\Core\State\StateInterface
  38. */
  39. protected $state;
  40. /**
  41. * @var \Drupal\Core\Config\ConfigManagerInterface
  42. */
  43. protected $configManager;
  44. /**
  45. * @var \Drupal\Core\Asset\AssetCollectionOptimizerInterface
  46. */
  47. protected $cssCollectionOptimizer;
  48. /**
  49. * @var \Drupal\Core\Routing\RouteBuilderInterface
  50. */
  51. protected $routeBuilder;
  52. /**
  53. * @var \Psr\Log\LoggerInterface
  54. */
  55. protected $logger;
  56. /**
  57. * The module extension list.
  58. *
  59. * @var \Drupal\Core\Extension\ModuleExtensionList
  60. */
  61. protected $moduleExtensionList;
  62. /**
  63. * Constructs a new ThemeInstaller.
  64. *
  65. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  66. * The theme handler.
  67. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  68. * The config factory to get the installed themes.
  69. * @param \Drupal\Core\Config\ConfigInstallerInterface $config_installer
  70. * (optional) The config installer to install configuration. This optional
  71. * to allow the theme handler to work before Drupal is installed and has a
  72. * database.
  73. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  74. * The module handler to fire themes_installed/themes_uninstalled hooks.
  75. * @param \Drupal\Core\Config\ConfigManagerInterface $config_manager
  76. * The config manager used to uninstall a theme.
  77. * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $css_collection_optimizer
  78. * The CSS asset collection optimizer service.
  79. * @param \Drupal\Core\Routing\RouteBuilderInterface $route_builder
  80. * (optional) The route builder service to rebuild the routes if a theme is
  81. * installed.
  82. * @param \Psr\Log\LoggerInterface $logger
  83. * A logger instance.
  84. * @param \Drupal\Core\State\StateInterface $state
  85. * The state store.
  86. * @param \Drupal\Core\Extension\ModuleExtensionList $module_extension_list
  87. * The module extension list.
  88. */
  89. public function __construct(ThemeHandlerInterface $theme_handler, ConfigFactoryInterface $config_factory, ConfigInstallerInterface $config_installer, ModuleHandlerInterface $module_handler, ConfigManagerInterface $config_manager, AssetCollectionOptimizerInterface $css_collection_optimizer, RouteBuilderInterface $route_builder, LoggerInterface $logger, StateInterface $state, ModuleExtensionList $module_extension_list = NULL) {
  90. $this->themeHandler = $theme_handler;
  91. $this->configFactory = $config_factory;
  92. $this->configInstaller = $config_installer;
  93. $this->moduleHandler = $module_handler;
  94. $this->configManager = $config_manager;
  95. $this->cssCollectionOptimizer = $css_collection_optimizer;
  96. $this->routeBuilder = $route_builder;
  97. $this->logger = $logger;
  98. $this->state = $state;
  99. if ($module_extension_list === NULL) {
  100. @trigger_error('The extension.list.module service must be passed to ' . __NAMESPACE__ . '\ThemeInstaller::__construct(). It was added in drupal:8.9.0 and will be required before drupal:10.0.0.', E_USER_DEPRECATED);
  101. $module_extension_list = \Drupal::service('extension.list.module');
  102. }
  103. $this->moduleExtensionList = $module_extension_list;
  104. }
  105. /**
  106. * {@inheritdoc}
  107. */
  108. public function install(array $theme_list, $install_dependencies = TRUE) {
  109. $extension_config = $this->configFactory->getEditable('core.extension');
  110. $theme_data = $this->themeHandler->rebuildThemeData();
  111. $installed_themes = $extension_config->get('theme') ?: [];
  112. $installed_modules = $extension_config->get('module') ?: [];
  113. if ($install_dependencies) {
  114. $theme_list = array_combine($theme_list, $theme_list);
  115. if ($missing = array_diff_key($theme_list, $theme_data)) {
  116. // One or more of the given themes doesn't exist.
  117. throw new UnknownExtensionException('Unknown themes: ' . implode(', ', $missing) . '.');
  118. }
  119. // Only process themes that are not installed currently.
  120. if (!$theme_list = array_diff_key($theme_list, $installed_themes)) {
  121. // Nothing to do. All themes already installed.
  122. return TRUE;
  123. }
  124. $module_list = $this->moduleExtensionList->getList();
  125. foreach ($theme_list as $theme => $value) {
  126. $module_dependencies = $theme_data[$theme]->module_dependencies;
  127. // $theme_data[$theme]->requires contains both theme and module
  128. // dependencies keyed by the extension machine names and
  129. // $theme_data[$theme]->module_dependencies contains only modules keyed
  130. // by the module extension machine name. Therefore we can find the theme
  131. // dependencies by finding array keys for 'requires' that are not in
  132. // $module_dependencies.
  133. $theme_dependencies = array_diff_key($theme_data[$theme]->requires, $module_dependencies);
  134. // We can find the unmet module dependencies by finding the module
  135. // machine names keys that are not in $installed_modules keys.
  136. $unmet_module_dependencies = array_diff_key($module_dependencies, $installed_modules);
  137. // Prevent themes with unmet module dependencies from being installed.
  138. if (!empty($unmet_module_dependencies)) {
  139. $unmet_module_dependencies_list = implode(', ', array_keys($unmet_module_dependencies));
  140. throw new MissingDependencyException("Unable to install theme: '$theme' due to unmet module dependencies: '$unmet_module_dependencies_list'.");
  141. }
  142. foreach ($module_dependencies as $dependency => $dependency_object) {
  143. if ($incompatible = $this->checkDependencyMessage($module_list, $dependency, $dependency_object)) {
  144. $sanitized_message = Html::decodeEntities(strip_tags($incompatible));
  145. throw new MissingDependencyException("Unable to install theme: $sanitized_message");
  146. }
  147. }
  148. // Add dependencies to the list of themes to install. The new themes
  149. // will be processed as the parent foreach loop continues.
  150. foreach (array_keys($theme_dependencies) as $dependency) {
  151. if (!isset($theme_data[$dependency])) {
  152. // The dependency does not exist.
  153. return FALSE;
  154. }
  155. // Skip already installed themes.
  156. if (!isset($theme_list[$dependency]) && !isset($installed_themes[$dependency])) {
  157. $theme_list[$dependency] = $dependency;
  158. }
  159. }
  160. }
  161. // Set the actual theme weights.
  162. $theme_list = array_map(function ($theme) use ($theme_data) {
  163. return $theme_data[$theme]->sort;
  164. }, $theme_list);
  165. // Sort the theme list by their weights (reverse).
  166. arsort($theme_list);
  167. $theme_list = array_keys($theme_list);
  168. }
  169. $themes_installed = [];
  170. foreach ($theme_list as $key) {
  171. // Only process themes that are not already installed.
  172. $installed = $extension_config->get("theme.$key") !== NULL;
  173. if ($installed) {
  174. continue;
  175. }
  176. // Throw an exception if the theme name is too long.
  177. if (strlen($key) > DRUPAL_EXTENSION_NAME_MAX_LENGTH) {
  178. throw new ExtensionNameLengthException("Theme name $key is over the maximum allowed length of " . DRUPAL_EXTENSION_NAME_MAX_LENGTH . ' characters.');
  179. }
  180. // Validate default configuration of the theme. If there is existing
  181. // configuration then stop installing.
  182. $this->configInstaller->checkConfigurationToInstall('theme', $key);
  183. // The value is not used; the weight is ignored for themes currently. Do
  184. // not check schema when saving the configuration.
  185. $extension_config
  186. ->set("theme.$key", 0)
  187. ->save(TRUE);
  188. // Reset theme settings.
  189. $theme_settings = &drupal_static('theme_get_setting');
  190. unset($theme_settings[$key]);
  191. // Reset theme listing.
  192. $this->themeHandler->reset();
  193. // Only install default configuration if this theme has not been installed
  194. // already.
  195. if (!isset($installed_themes[$key])) {
  196. // Install default configuration of the theme.
  197. $this->configInstaller->installDefaultConfig('theme', $key);
  198. }
  199. $themes_installed[] = $key;
  200. // Record the fact that it was installed.
  201. $this->logger->info('%theme theme installed.', ['%theme' => $key]);
  202. }
  203. $this->cssCollectionOptimizer->deleteAll();
  204. $this->resetSystem();
  205. // Invoke hook_themes_installed() after the themes have been installed.
  206. $this->moduleHandler->invokeAll('themes_installed', [$themes_installed]);
  207. return !empty($themes_installed);
  208. }
  209. /**
  210. * {@inheritdoc}
  211. */
  212. public function uninstall(array $theme_list) {
  213. $extension_config = $this->configFactory->getEditable('core.extension');
  214. $theme_config = $this->configFactory->getEditable('system.theme');
  215. $list = $this->themeHandler->listInfo();
  216. foreach ($theme_list as $key) {
  217. if (!isset($list[$key])) {
  218. throw new UnknownExtensionException("Unknown theme: $key.");
  219. }
  220. if ($key === $theme_config->get('default')) {
  221. throw new \InvalidArgumentException("The current default theme $key cannot be uninstalled.");
  222. }
  223. if ($key === $theme_config->get('admin')) {
  224. throw new \InvalidArgumentException("The current administration theme $key cannot be uninstalled.");
  225. }
  226. // Base themes cannot be uninstalled if sub themes are installed, and if
  227. // they are not uninstalled at the same time.
  228. if (!empty($list[$key]->sub_themes)) {
  229. foreach ($list[$key]->sub_themes as $sub_key => $sub_label) {
  230. if (isset($list[$sub_key]) && !in_array($sub_key, $theme_list, TRUE)) {
  231. throw new \InvalidArgumentException("The base theme $key cannot be uninstalled, because theme $sub_key depends on it.");
  232. }
  233. }
  234. }
  235. }
  236. $this->cssCollectionOptimizer->deleteAll();
  237. foreach ($theme_list as $key) {
  238. // The value is not used; the weight is ignored for themes currently.
  239. $extension_config->clear("theme.$key");
  240. // Reset theme settings.
  241. $theme_settings = &drupal_static('theme_get_setting');
  242. unset($theme_settings[$key]);
  243. // Remove all configuration belonging to the theme.
  244. $this->configManager->uninstall('theme', $key);
  245. }
  246. // Don't check schema when uninstalling a theme since we are only clearing
  247. // keys.
  248. $extension_config->save(TRUE);
  249. // Refresh theme info.
  250. $this->resetSystem();
  251. $this->themeHandler->reset();
  252. $this->moduleHandler->invokeAll('themes_uninstalled', [$theme_list]);
  253. }
  254. /**
  255. * Resets some other systems like rebuilding the route information or caches.
  256. */
  257. protected function resetSystem() {
  258. if ($this->routeBuilder) {
  259. $this->routeBuilder->setRebuildNeeded();
  260. }
  261. // @todo It feels wrong to have the requirement to clear the local tasks
  262. // cache here.
  263. Cache::invalidateTags(['local_task']);
  264. $this->themeRegistryRebuild();
  265. }
  266. /**
  267. * Wraps drupal_theme_rebuild().
  268. */
  269. protected function themeRegistryRebuild() {
  270. drupal_theme_rebuild();
  271. }
  272. }