PageRenderTime 23ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/web/core/lib/Drupal/Core/Theme/ThemeInitialization.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 365 lines | 188 code | 42 blank | 135 comment | 33 complexity | 9c78c3626f06aa6fda4ff4729cda4b6a MD5 | raw file
  1. <?php
  2. namespace Drupal\Core\Theme;
  3. use Drupal\Core\Cache\CacheBackendInterface;
  4. use Drupal\Core\Extension\Extension;
  5. use Drupal\Core\Extension\ModuleHandlerInterface;
  6. use Drupal\Core\Extension\ThemeHandlerInterface;
  7. /**
  8. * Provides the theme initialization logic.
  9. */
  10. class ThemeInitialization implements ThemeInitializationInterface {
  11. /**
  12. * The theme handler.
  13. *
  14. * @var \Drupal\Core\Extension\ThemeHandlerInterface
  15. */
  16. protected $themeHandler;
  17. /**
  18. * The cache backend to use for the active theme.
  19. *
  20. * @var \Drupal\Core\Cache\CacheBackendInterface
  21. */
  22. protected $cache;
  23. /**
  24. * The app root.
  25. *
  26. * @var string
  27. */
  28. protected $root;
  29. /**
  30. * The extensions that might be attaching assets.
  31. *
  32. * @var array
  33. */
  34. protected $extensions;
  35. /**
  36. * The module handler.
  37. *
  38. * @var \Drupal\Core\Extension\ModuleHandlerInterface
  39. */
  40. protected $moduleHandler;
  41. /**
  42. * Constructs a new ThemeInitialization object.
  43. *
  44. * @param string $root
  45. * The app root.
  46. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
  47. * The theme handler.
  48. * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  49. * The cache backend.
  50. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
  51. * The module handler to use to load modules.
  52. */
  53. public function __construct($root, ThemeHandlerInterface $theme_handler, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler) {
  54. $this->root = $root;
  55. $this->themeHandler = $theme_handler;
  56. $this->cache = $cache;
  57. $this->moduleHandler = $module_handler;
  58. }
  59. /**
  60. * {@inheritdoc}
  61. */
  62. public function initTheme($theme_name) {
  63. $active_theme = $this->getActiveThemeByName($theme_name);
  64. $this->loadActiveTheme($active_theme);
  65. return $active_theme;
  66. }
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function getActiveThemeByName($theme_name) {
  71. if ($cached = $this->cache->get('theme.active_theme.' . $theme_name)) {
  72. return $cached->data;
  73. }
  74. $themes = $this->themeHandler->listInfo();
  75. // If no theme could be negotiated, or if the negotiated theme is not within
  76. // the list of installed themes, fall back to the default theme output of
  77. // core and modules (like Stark, but without a theme extension at all). This
  78. // is possible, because loadActiveTheme() always loads the Twig theme
  79. // engine. This is desired, because missing or malformed theme configuration
  80. // should not leave the application in a broken state. By falling back to
  81. // default output, the user is able to reconfigure the theme through the UI.
  82. // Lastly, tests are expected to operate with no theme by default, so as to
  83. // only assert the original theme output of modules (unless a test manually
  84. // installs a specific theme).
  85. if (empty($themes) || !$theme_name || !isset($themes[$theme_name])) {
  86. $theme_name = 'core';
  87. // /core/core.info.yml does not actually exist, but is required because
  88. // Extension expects a pathname.
  89. $active_theme = $this->getActiveTheme(new Extension($this->root, 'theme', 'core/core.info.yml'));
  90. // Early-return and do not set state, because the initialized $theme_name
  91. // differs from the original $theme_name.
  92. return $active_theme;
  93. }
  94. // Find all our ancestor themes and put them in an array.
  95. $base_themes = [];
  96. $ancestor = $theme_name;
  97. while ($ancestor && isset($themes[$ancestor]->base_theme)) {
  98. $ancestor = $themes[$ancestor]->base_theme;
  99. if (!$this->themeHandler->themeExists($ancestor)) {
  100. if ($ancestor == 'stable') {
  101. // Themes that depend on Stable will be fixed by system_update_8014().
  102. // There is no harm in not adding it as an ancestor since at worst
  103. // some people might experience slight visual regressions on
  104. // update.php.
  105. continue;
  106. }
  107. throw new MissingThemeDependencyException(sprintf('Base theme %s has not been installed.', $ancestor), $ancestor);
  108. }
  109. $base_themes[] = $themes[$ancestor];
  110. }
  111. $active_theme = $this->getActiveTheme($themes[$theme_name], $base_themes);
  112. $this->cache->set('theme.active_theme.' . $theme_name, $active_theme);
  113. return $active_theme;
  114. }
  115. /**
  116. * {@inheritdoc}
  117. */
  118. public function loadActiveTheme(ActiveTheme $active_theme) {
  119. // Initialize the theme.
  120. if ($theme_engine = $active_theme->getEngine()) {
  121. // Include the engine.
  122. include_once $this->root . '/' . $active_theme->getOwner();
  123. if (function_exists($theme_engine . '_init')) {
  124. @trigger_error('THEME_ENGINE_init() is deprecated in drupal:9.3.0 and removed in drupal:10.0.0. There is no replacement. See https://www.drupal.org/node/3246978', E_USER_DEPRECATED);
  125. foreach ($active_theme->getBaseThemeExtensions() as $base) {
  126. call_user_func($theme_engine . '_init', $base);
  127. }
  128. call_user_func($theme_engine . '_init', $active_theme->getExtension());
  129. }
  130. else {
  131. foreach ($active_theme->getBaseThemeExtensions() as $base) {
  132. $base->load();
  133. }
  134. $active_theme->getExtension()->load();
  135. }
  136. }
  137. else {
  138. // include non-engine theme files
  139. foreach ($active_theme->getBaseThemeExtensions() as $base) {
  140. // Include the theme file or the engine.
  141. if ($base->owner) {
  142. include_once $this->root . '/' . $base->owner;
  143. }
  144. }
  145. // and our theme gets one too.
  146. if ($active_theme->getOwner()) {
  147. include_once $this->root . '/' . $active_theme->getOwner();
  148. }
  149. }
  150. // Always include Twig as the default theme engine.
  151. include_once $this->root . '/core/themes/engines/twig/twig.engine';
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function getActiveTheme(Extension $theme, array $base_themes = []) {
  157. $theme_path = $theme->getPath();
  158. $values['path'] = $theme_path;
  159. $values['name'] = $theme->getName();
  160. // Use the logo declared in this themes info file, otherwise use logo.svg
  161. // from the themes root.
  162. if (!empty($theme->info['logo'])) {
  163. $values['logo'] = $theme->getPath() . '/' . $theme->info['logo'];
  164. }
  165. else {
  166. $values['logo'] = $theme->getPath() . '/logo.svg';
  167. }
  168. // @todo Remove in Drupal 10.0.x.
  169. $values['stylesheets_remove'] = $this->prepareStylesheetsRemove($theme, $base_themes);
  170. // Prepare libraries overrides from this theme and ancestor themes. This
  171. // allows child themes to easily remove CSS files from base themes and
  172. // modules.
  173. $values['libraries_override'] = [];
  174. // Get libraries overrides declared by base themes.
  175. foreach ($base_themes as $base) {
  176. if (!empty($base->info['libraries-override'])) {
  177. foreach ($base->info['libraries-override'] as $library => $override) {
  178. $values['libraries_override'][$base->getPath()][$library] = $override;
  179. }
  180. }
  181. }
  182. // Add libraries overrides declared by this theme.
  183. if (!empty($theme->info['libraries-override'])) {
  184. foreach ($theme->info['libraries-override'] as $library => $override) {
  185. $values['libraries_override'][$theme->getPath()][$library] = $override;
  186. }
  187. }
  188. // Get libraries extensions declared by base themes.
  189. foreach ($base_themes as $base) {
  190. if (!empty($base->info['libraries-extend'])) {
  191. foreach ($base->info['libraries-extend'] as $library => $extend) {
  192. if (isset($values['libraries_extend'][$library])) {
  193. // Merge if libraries-extend has already been defined for this
  194. // library.
  195. $values['libraries_extend'][$library] = array_merge($values['libraries_extend'][$library], $extend);
  196. }
  197. else {
  198. $values['libraries_extend'][$library] = $extend;
  199. }
  200. }
  201. }
  202. }
  203. // Add libraries extensions declared by this theme.
  204. if (!empty($theme->info['libraries-extend'])) {
  205. foreach ($theme->info['libraries-extend'] as $library => $extend) {
  206. if (isset($values['libraries_extend'][$library])) {
  207. // Merge if libraries-extend has already been defined for this
  208. // library.
  209. $values['libraries_extend'][$library] = array_merge($values['libraries_extend'][$library], $extend);
  210. }
  211. else {
  212. $values['libraries_extend'][$library] = $extend;
  213. }
  214. }
  215. }
  216. // Do basically the same as the above for libraries
  217. $values['libraries'] = [];
  218. // Grab libraries from base theme
  219. foreach ($base_themes as $base) {
  220. if (!empty($base->libraries)) {
  221. foreach ($base->libraries as $library) {
  222. $values['libraries'][] = $library;
  223. }
  224. }
  225. }
  226. // Add libraries used by this theme.
  227. if (!empty($theme->libraries)) {
  228. foreach ($theme->libraries as $library) {
  229. $values['libraries'][] = $library;
  230. }
  231. }
  232. $values['engine'] = $theme->engine ?? NULL;
  233. $values['owner'] = $theme->owner ?? NULL;
  234. $values['extension'] = $theme;
  235. $base_active_themes = [];
  236. foreach ($base_themes as $base_theme) {
  237. $base_active_themes[$base_theme->getName()] = $base_theme;
  238. }
  239. $values['base_theme_extensions'] = $base_active_themes;
  240. if (!empty($theme->info['regions'])) {
  241. $values['regions'] = $theme->info['regions'];
  242. }
  243. return new ActiveTheme($values);
  244. }
  245. /**
  246. * Gets all extensions.
  247. *
  248. * @return array
  249. */
  250. protected function getExtensions() {
  251. if (!isset($this->extensions)) {
  252. $this->extensions = array_merge($this->moduleHandler->getModuleList(), $this->themeHandler->listInfo());
  253. }
  254. return $this->extensions;
  255. }
  256. /**
  257. * Gets CSS file where tokens have been resolved.
  258. *
  259. * @param string $css_file
  260. * CSS file which may contain tokens.
  261. *
  262. * @return string
  263. * CSS file where placeholders are replaced.
  264. *
  265. * @todo Remove in Drupal 9.0.x.
  266. */
  267. protected function resolveStyleSheetPlaceholders($css_file) {
  268. $token_candidate = explode('/', $css_file)[0];
  269. if (!preg_match('/@[A-z0-9_-]+/', $token_candidate)) {
  270. return $css_file;
  271. }
  272. $token = substr($token_candidate, 1);
  273. // Prime extensions.
  274. $extensions = $this->getExtensions();
  275. if (isset($extensions[$token])) {
  276. return str_replace($token_candidate, $extensions[$token]->getPath(), $css_file);
  277. }
  278. }
  279. /**
  280. * Prepares stylesheets-remove specified in the *.info.yml file.
  281. *
  282. * This method is used as a BC layer to access the contents of the deprecated
  283. * stylesheets-remove key in theme info.yml files. It will be removed once it
  284. * is no longer needed in Drupal 10.
  285. *
  286. * @param \Drupal\Core\Extension\Extension $theme
  287. * The theme extension object.
  288. * @param \Drupal\Core\Extension\Extension[] $base_themes
  289. * An array of base themes.
  290. *
  291. * @return string[]
  292. * The list of stylesheets-remove specified in the *.info.yml file.
  293. *
  294. * @todo Remove in Drupal 10.0.x.
  295. *
  296. * @internal
  297. */
  298. protected function prepareStylesheetsRemove(Extension $theme, $base_themes) {
  299. // Prepare stylesheets from this theme as well as all ancestor themes.
  300. // We work it this way so that we can have child themes remove CSS files
  301. // easily from parent.
  302. $stylesheets_remove = [];
  303. // Grab stylesheets from base theme.
  304. foreach ($base_themes as $base) {
  305. if (!empty($base->info['stylesheets-remove'])) {
  306. @trigger_error('The theme info key stylesheets-remove implemented by theme ' . $base->getName() . ' is deprecated in drupal:8.0.0 and is removed from drupal:10.0.0. See https://www.drupal.org/node/2497313', E_USER_DEPRECATED);
  307. foreach ($base->info['stylesheets-remove'] as $css_file) {
  308. $css_file = $this->resolveStyleSheetPlaceholders($css_file);
  309. $stylesheets_remove[$css_file] = $css_file;
  310. }
  311. }
  312. }
  313. // Add stylesheets used by this theme.
  314. if (!empty($theme->info['stylesheets-remove'])) {
  315. @trigger_error('The theme info key stylesheets-remove implemented by theme ' . $theme->getName() . ' is deprecated in drupal:8.0.0 and is removed from drupal:10.0.0. See https://www.drupal.org/node/2497313', E_USER_DEPRECATED);
  316. foreach ($theme->info['stylesheets-remove'] as $css_file) {
  317. $css_file = $this->resolveStyleSheetPlaceholders($css_file);
  318. $stylesheets_remove[$css_file] = $css_file;
  319. }
  320. }
  321. return $stylesheets_remove;
  322. }
  323. }