PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

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

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