file ThemeSettings.php

Namespace

Drupal\bootstrap
  1. <?php
  2. namespace Drupal\bootstrap;
  3. use Drupal\bootstrap\Plugin\Setting\DeprecatedSettingInterface;
  4. use Drupal\bootstrap\Plugin\Setting\SettingInterface;
  5. use Drupal\Core\Theme\ThemeSettings as CoreThemeSettings;
  6. use Drupal\Component\Utility\DiffArray;
  7. use Drupal\Component\Utility\NestedArray;
  8. use Drupal\Core\Config\Config;
  9. use Drupal\Core\Config\StorageException;
  10. /**
  11. * Provides a configuration API wrapper for runtime merged theme settings.
  12. *
  13. * This is a wrapper around theme_get_setting() since it does not inherit
  14. * base theme config nor handle default/overridden values very well.
  15. *
  16. * @ingroup utility
  17. */
  18. class ThemeSettings extends Config {
  19. /**
  20. * The default settings.
  21. *
  22. * @var array
  23. */
  24. protected $defaults;
  25. /**
  26. * A list of deprecated settings, keyed by the newer setting name.
  27. *
  28. * @var array
  29. */
  30. protected $deprecated;
  31. /**
  32. * The current theme object.
  33. *
  34. * @var \Drupal\bootstrap\Theme
  35. */
  36. protected $theme;
  37. /**
  38. * A list of available Setting plugins.
  39. *
  40. * @var \Drupal\bootstrap\Plugin\Setting\SettingInterface[]
  41. */
  42. protected $settings;
  43. /**
  44. * {@inheritdoc}
  45. */
  46. public function __construct(Theme $theme) {
  47. parent::__construct($theme->getName() . '.settings', \Drupal::service('config.storage'), \Drupal::service('event_dispatcher'), \Drupal::service('config.typed'));
  48. $this->theme = $theme;
  49. // Retrieve the available settings.
  50. $this->settings = $theme->getSettingPlugin();
  51. // Filter out the deprecated settings.
  52. /** @var \Drupal\bootstrap\Plugin\Setting\DeprecatedSettingInterface[] $deprecated */
  53. $deprecated = array_filter($this->settings, function ($setting) {
  54. return $setting instanceof DeprecatedSettingInterface;
  55. });
  56. $this->deprecated = [];
  57. foreach ($deprecated as $deprecatedName => $deprecatedSetting) {
  58. if (!is_null($deprecatedSetting)) {
  59. $key = $deprecatedSetting->getDeprecatedReplacementSetting()->getPluginId();
  60. if (!isset($this->deprecated[$key])) {
  61. $this->deprecated[$key] = [];
  62. }
  63. $this->deprecated[$key][$deprecatedName] = $deprecatedSetting;
  64. }
  65. }
  66. // Retrieve cache.
  67. $cache = $theme->getCache('settings');
  68. // Use cached settings.
  69. if ($defaults = $cache->get('defaults')) {
  70. $this->defaults = $defaults;
  71. $this->initWithData($cache->get('data', []));
  72. return;
  73. }
  74. // Retrieve the global settings from configuration.
  75. $this->defaults = \Drupal::config('system.theme.global')->get();
  76. // Retrieve the theme setting plugin discovery defaults (code).
  77. foreach ($this->settings as $name => $deprecatedSetting) {
  78. // Deprecated settings shouldn't provide default values, only set values.
  79. if ($deprecatedSetting instanceof DeprecatedSettingInterface) {
  80. continue;
  81. }
  82. $this->defaults[$name] = $deprecatedSetting->getDefaultValue();
  83. }
  84. // Retrieve the theme ancestry.
  85. $ancestry = $theme->getAncestry();
  86. // Remove the active theme from the ancestry.
  87. $active_theme = array_pop($ancestry);
  88. // Iterate and merge all ancestor theme config into the defaults.
  89. foreach ($ancestry as $ancestor) {
  90. $this->defaults = NestedArray::mergeDeepArray([$this->defaults, $this->getThemeConfig($ancestor)], TRUE);
  91. }
  92. // Merge the active theme config.
  93. $this->initWithData($this->getThemeConfig($active_theme, TRUE));
  94. // Cache the data and defaults.
  95. $cache->set('data', $this->data);
  96. $cache->set('defaults', $this->defaults);
  97. }
  98. /**
  99. * {@inheritdoc}
  100. */
  101. public function getCacheTags() {
  102. return ['rendered'];
  103. }
  104. /**
  105. * {@inheritdoc}
  106. */
  107. public function get($key = '') {
  108. if (empty($key)) {
  109. return NestedArray::mergeDeepArray([$this->defaults, $this->data], TRUE);
  110. }
  111. else {
  112. $value = parent::get($key);
  113. if (!isset($value)) {
  114. $value = $this->getDeprecatedValue($key);
  115. }
  116. if (!isset($value)) {
  117. $value = $this->getOriginal($key);
  118. }
  119. }
  120. return $value;
  121. }
  122. /**
  123. * Retrieves a deprecated value from other setting(s).
  124. *
  125. * @param string $key
  126. * The name of the setting to retrieve a deprecated value for.
  127. *
  128. * @return mixed
  129. * A value from deprecated setting(s) or NULL if there is no deprecated
  130. * value currently in use.
  131. */
  132. protected function getDeprecatedValue($key) {
  133. $value = NULL;
  134. // Immediately return if no deprecated settings to extract a values from.
  135. if (!isset($this->settings[$key]) || empty($this->deprecated[$key])) {
  136. return $value;
  137. }
  138. $setting = $this->settings[$key];
  139. $deprecatedSettings = $this->deprecated[$key];
  140. $values = [];
  141. foreach (array_keys($deprecatedSettings) as $deprecatedName) {
  142. $deprecatedValue = $this->get($deprecatedName);
  143. if (isset($deprecatedValue)) {
  144. $values[$deprecatedName] = $deprecatedValue;
  145. }
  146. }
  147. // Immediately return if there are no deprecated values to process.
  148. if (!$values) {
  149. return $value;
  150. }
  151. // Let the new setting handle how it should process the deprecated values.
  152. $value = $setting->processDeprecatedValues($values, $deprecatedSettings);
  153. // Deprecated value(s) found, show a message and then migrate them.
  154. if (isset($value)) {
  155. if (count($values) > 1) {
  156. Bootstrap::deprecated(NULL, NULL, t('The following theme settings have been deprecated and should no longer be used: "%deprecated". The values have been converted automatically for use with the supported theme setting "%name" instead. The configuration for the site should be exported to accommodate this change.', [
  157. '%deprecated' => implode('", "', array_keys($deprecatedSettings)),
  158. '%name' => $key,
  159. ]));
  160. }
  161. else {
  162. Bootstrap::deprecated(NULL, NULL, t('The following theme setting has been deprecated and should no longer be used: "%deprecated". The value has been converted automatically for use with the supported theme setting "%name" instead. The configuration for the site should be exported to accommodate this change.', [
  163. '%deprecated' => array_keys($deprecatedSettings)[0],
  164. '%name' => $key,
  165. ]));
  166. }
  167. $this->set($key, $value);
  168. foreach (array_keys($values) as $setting) {
  169. $this->clear($setting);
  170. }
  171. $this->save();
  172. }
  173. return $value;
  174. }
  175. /**
  176. * {@inheritdoc}
  177. */
  178. public function getOriginal($key = '', $apply_overrides = TRUE) {
  179. $original_data = $this->defaults;
  180. if ($apply_overrides) {
  181. // Apply overrides.
  182. if (isset($this->moduleOverrides) && is_array($this->moduleOverrides)) {
  183. $original_data = NestedArray::mergeDeepArray([$original_data, $this->moduleOverrides], TRUE);
  184. }
  185. if (isset($this->settingsOverrides) && is_array($this->settingsOverrides)) {
  186. $original_data = NestedArray::mergeDeepArray([$original_data, $this->settingsOverrides], TRUE);
  187. }
  188. }
  189. if (empty($key)) {
  190. return $original_data;
  191. }
  192. else {
  193. $parts = explode('.', $key);
  194. if (count($parts) == 1) {
  195. return isset($original_data[$key]) ? $original_data[$key] : NULL;
  196. }
  197. else {
  198. $value = NestedArray::getValue($original_data, $parts, $key_exists);
  199. return $key_exists ? $value : NULL;
  200. }
  201. }
  202. }
  203. /**
  204. * Retrieves a specific theme's stored config settings.
  205. *
  206. * @param \Drupal\bootstrap\Theme $theme
  207. * A theme object.
  208. * @param bool $active_theme
  209. * Flag indicating whether or not $theme is the active theme.
  210. *
  211. * @return array
  212. * A array diff of overridden config theme settings.
  213. */
  214. public function getThemeConfig(Theme $theme, $active_theme = FALSE) {
  215. $config = new CoreThemeSettings($theme->getName());
  216. // Retrieve configured theme-specific settings, if any.
  217. try {
  218. if ($theme_settings = \Drupal::config($theme->getName() . '.settings')->get()) {
  219. // Remove schemas if not the active theme.
  220. if (!$active_theme) {
  221. unset($theme_settings['schemas']);
  222. }
  223. $config->merge($theme_settings);
  224. }
  225. }
  226. catch (StorageException $e) {
  227. }
  228. // If the theme does not support a particular feature, override the
  229. // global setting and set the value to NULL.
  230. $info = $theme->getInfo();
  231. if (!empty($info['features'])) {
  232. foreach (_system_default_theme_features() as $feature) {
  233. if (!in_array($feature, $info['features'])) {
  234. $config->set('features.' . $feature, NULL);
  235. }
  236. }
  237. }
  238. // Generate the path to the logo image.
  239. if ($config->get('features.logo')) {
  240. $logo_url = FALSE;
  241. foreach (['svg', 'png', 'jpg'] as $type) {
  242. if (file_exists($theme->getPath() . "/logo.$type")) {
  243. $logo_url = file_create_url($theme->getPath() . "/logo.$type");
  244. break;
  245. }
  246. }
  247. if ($config->get('logo.use_default') && $logo_url) {
  248. $config->set('logo.url', $logo_url);
  249. }
  250. elseif (($logo_path = $config->get('logo.path')) && file_exists($logo_path)) {
  251. $config->set('logo.url', file_create_url($logo_path));
  252. }
  253. }
  254. // Generate the path to the favicon.
  255. if ($config->get('features.favicon')) {
  256. $favicon_url = $theme->getPath() . '/favicon.ico';
  257. if ($config->get('favicon.use_default') && file_exists($favicon_url)) {
  258. $config->set('favicon.url', file_create_url($favicon_url));
  259. }
  260. elseif ($favicon_path = $config->get('favicon.path')) {
  261. $config->set('favicon.url', file_create_url($favicon_path));
  262. }
  263. }
  264. // Retrieve the config data.
  265. $data = $config->get();
  266. // Retrieve a diff of settings that override the defaults.
  267. $diff = DiffArray::diffAssocRecursive($data, $this->defaults);
  268. // Ensure core features are always present in the diff. The theme settings
  269. // form will not work properly otherwise.
  270. // @todo Just rebuild the features section of the form?
  271. foreach (['favicon', 'features', 'logo'] as $key) {
  272. $arrays = [];
  273. $arrays[] = isset($this->defaults[$key]) ? $this->defaults[$key] : [];
  274. $arrays[] = isset($data[$key]) ? $data[$key] : [];
  275. $diff[$key] = NestedArray::mergeDeepArray($arrays, TRUE);
  276. }
  277. return $diff;
  278. }
  279. /**
  280. * Determines if a setting overrides the default value.
  281. *
  282. * @param string $name
  283. * The name of the setting to check.
  284. * @param mixed $value
  285. * The new value to check.
  286. *
  287. * @return bool
  288. * TRUE or FALSE
  289. */
  290. public function overridesValue($name, $value) {
  291. // Retrieve the currently stored value for comparison purposes.
  292. $current_value = $this->get($name);
  293. // Due to the nature of DiffArray::diffAssocRecursive, if the provided
  294. // value is an empty array, it cannot be iterated over to determine if
  295. // the values are different. Instead, it must be checked explicitly.
  296. // @see https://www.drupal.org/node/2771121
  297. if ($value === [] && $current_value !== []) {
  298. return TRUE;
  299. }
  300. // Otherwise, determine if value is overridden by any array differences.
  301. return !!DiffArray::diffAssocRecursive([$name => $value], [$name => $current_value]);
  302. }
  303. /**
  304. * {@inheritdoc}
  305. */
  306. public function save($has_trusted_data = FALSE) {
  307. parent::save($has_trusted_data);
  308. $this->theme->getCache('settings')->deleteAll();
  309. return $this;
  310. }
  311. }

Classes

Name Description
ThemeSettings Provides a configuration API wrapper for runtime merged theme settings.