PageRenderTime 25ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/themes/bootstrap/src/Plugin/Alter/ThemeSuggestions.php

https://gitlab.com/guillaumev/alkarama
PHP | 359 lines | 133 code | 47 blank | 179 comment | 20 complexity | 74b255438514d1973d4d8a2ab6a1416a MD5 | raw file
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\bootstrap\Plugin\Alter\ThemeSuggestions.
  5. */
  6. namespace Drupal\bootstrap\Plugin\Alter;
  7. use Drupal\bootstrap\Annotation\BootstrapAlter;
  8. use Drupal\bootstrap\Bootstrap;
  9. use Drupal\bootstrap\Plugin\PluginBase;
  10. use Drupal\bootstrap\Utility\Unicode;
  11. use Drupal\bootstrap\Utility\Variables;
  12. use Drupal\Core\Entity\EntityInterface;
  13. /**
  14. * Implements hook_theme_suggestions_alter().
  15. *
  16. * @ingroup plugins_alter
  17. *
  18. * @BootstrapAlter("theme_suggestions")
  19. */
  20. class ThemeSuggestions extends PluginBase implements AlterInterface {
  21. /**
  22. * @var array
  23. */
  24. protected $bootstrapPanelTypes = ['details', 'fieldset'];
  25. /**
  26. * An element object provided in the variables array, may not be set.
  27. *
  28. * @var \Drupal\bootstrap\Utility\Element|false
  29. */
  30. protected $element;
  31. /**
  32. * The theme hook invoked.
  33. *
  34. * @var string
  35. */
  36. protected $hook;
  37. /**
  38. * The theme hook suggestions, exploded by the "__" delimiter.
  39. *
  40. * @var array
  41. */
  42. protected $hookSuggestions;
  43. /**
  44. * The types of elements to ignore for the "input__form_control" suggestion.
  45. *
  46. * @var array
  47. */
  48. protected $ignoreFormControlTypes = ['checkbox', 'hidden', 'radio'];
  49. /**
  50. * The original "hook" value passed via hook_theme_suggestions_alter().
  51. *
  52. * @var string
  53. */
  54. protected $originalHook;
  55. /**
  56. * The array of suggestions to return.
  57. *
  58. * @var array
  59. */
  60. protected $suggestions;
  61. /**
  62. * The variables array object passed via hook_theme_suggestions_alter().
  63. *
  64. * @var \Drupal\bootstrap\Utility\Variables
  65. */
  66. protected $variables;
  67. /**
  68. * {@inheritdoc}
  69. */
  70. public function alter(&$suggestions, &$variables = [], &$hook = NULL) {
  71. // This is intentionally backwards. The "original" theme hook is actually
  72. // the hook being invoked. The provided $hook (to the alter) is the watered
  73. // down version of said original hook.
  74. $this->hook = !empty($variables['theme_hook_original']) ? $variables['theme_hook_original'] : $hook;
  75. $this->hookSuggestions = explode('__', $this->hook);
  76. $this->originalHook = $hook;
  77. $this->suggestions = $suggestions;
  78. $this->variables = Variables::create($variables);
  79. $this->element = $this->variables->element;
  80. // Processes the necessary theme hook suggestions.
  81. $this->processSuggestions();
  82. // Ensure the list of suggestions is unique.
  83. $suggestions = array_unique($this->suggestions);
  84. }
  85. /***************************************************************************
  86. * Dynamic alter methods.
  87. ***************************************************************************/
  88. /**
  89. * Dynamic alter method for "input".
  90. */
  91. protected function alterInput() {
  92. if ($this->element && $this->element->isButton()) {
  93. $hook = 'input__button';
  94. if ($this->element->getProperty('split')) {
  95. $hook .= '__split';
  96. }
  97. $this->addSuggestion($hook);
  98. }
  99. elseif ($this->element && !$this->element->isType($this->ignoreFormControlTypes)) {
  100. $this->addSuggestion('input__form_control');
  101. }
  102. }
  103. /**
  104. * Dynamic alter method for "links__dropbutton".
  105. */
  106. protected function alterLinksDropbutton() {
  107. // Remove the 'dropbutton' suggestion.
  108. array_shift($this->hookSuggestions);
  109. $this->addSuggestion('bootstrap_dropdown');
  110. }
  111. /**
  112. * Dynamic alter method for "user".
  113. *
  114. * @see https://www.drupal.org/node/2828634
  115. * @see https://www.drupal.org/node/2808481
  116. * @todo Remove/refactor once core issue is resolved.
  117. */
  118. protected function alterUser() {
  119. $this->addSuggestionsForEntity('user');
  120. }
  121. /***************************************************************************
  122. * Protected methods.
  123. ***************************************************************************/
  124. /**
  125. * Add a suggestion to the list of suggestions.
  126. *
  127. * @param string $hook
  128. * The theme hook suggestion to add.
  129. */
  130. protected function addSuggestion($hook) {
  131. $suggestions = $this->buildSuggestions($hook);
  132. foreach ($suggestions as $suggestion) {
  133. $this->suggestions[] = $suggestion;
  134. }
  135. }
  136. /**
  137. * Adds "bundle" and "view mode" suggestions for an entity.
  138. *
  139. * This is a helper method because core's implementation of theme hook
  140. * suggestions on entities is inconsistent.
  141. *
  142. * @see https://www.drupal.org/node/2808481
  143. *
  144. * @param string $entity_type
  145. * Optional. A specific type of entity to look for.
  146. * @param string $prefix
  147. * Optional. A prefix (like "entity") to use. It will automatically be
  148. * appended with the "__" separator.
  149. *
  150. * @todo Remove/refactor once core issue is resolved.
  151. */
  152. protected function addSuggestionsForEntity($entity_type = 'entity', $prefix = '') {
  153. // Immediately return if there is no element.
  154. if (!$this->element) {
  155. return;
  156. }
  157. // Extract the entity.
  158. if ($entity = $this->getEntityObject($entity_type)) {
  159. $entity_type_id = $entity->getEntityTypeId();
  160. // Only add the entity type identifier if there's a prefix.
  161. if (!empty($prefix)) {
  162. $prefix .= '__';
  163. $suggestions[] = $prefix . '__' . $entity_type_id;
  164. }
  165. // View mode.
  166. if ($view_mode = preg_replace('/[^A-Za-z0-9]+/', '_', $this->element->getProperty('view_mode'))) {
  167. $suggestions[] = $prefix . $entity_type_id . '__' . $view_mode;
  168. // Bundle.
  169. if ($entity->getEntityType()->hasKey('bundle')) {
  170. $suggestions[] = $prefix . $entity_type_id . '__' . $entity->bundle();
  171. $suggestions[] = $prefix . $entity_type_id . '__' . $entity->bundle() . '__' . $view_mode;
  172. }
  173. }
  174. }
  175. }
  176. /**
  177. * Builds a list of suggestions.
  178. *
  179. * @param string $hook
  180. * The theme hook suggestion to build.
  181. *
  182. * @return array
  183. */
  184. protected function buildSuggestions($hook) {
  185. $suggestions = [];
  186. $hook_suggestions = $this->hookSuggestions;
  187. // Replace the first hook suggestion with $hook.
  188. array_shift($hook_suggestions);
  189. array_unshift($suggestions, $hook);
  190. $suggestions = [];
  191. while ($hook_suggestions) {
  192. $suggestions[] = $hook . '__' . implode('__', $hook_suggestions);
  193. array_pop($hook_suggestions);
  194. }
  195. // Append the base hook.
  196. $suggestions[] = $hook;
  197. // Return the suggestions, reversed.
  198. return array_reverse($suggestions);
  199. }
  200. /**
  201. * Retrieves the methods to invoke to process the theme hook suggestion.
  202. *
  203. * @return array
  204. * An indexed array of methods to be invoked.
  205. */
  206. protected function getAlterMethods() {
  207. // Retrieve cached theme hook suggestion alter methods.
  208. $cache = $this->theme->getCache('theme_hook_suggestions');
  209. if ($cache->has($this->hook)) {
  210. return $cache->get($this->hook);
  211. }
  212. // Uppercase each theme hook suggestion to be used in the method name.
  213. $hook_suggestions = $this->hookSuggestions;
  214. foreach ($hook_suggestions as $key => $suggestion) {
  215. $hook_suggestions[$key] = Unicode::ucfirst($suggestion);
  216. }
  217. $methods = [];
  218. while ($hook_suggestions) {
  219. $method = 'alter' . implode('', $hook_suggestions);
  220. if (method_exists($this, $method)) {
  221. $methods[] = $method;
  222. }
  223. array_pop($hook_suggestions);
  224. }
  225. // Reverse the methods.
  226. $methods = array_reverse($methods);
  227. // Cache the methods.
  228. $cache->set($this->hook, $methods);
  229. return $methods;
  230. }
  231. /**
  232. * Extracts the entity from the element(s) passed in the Variables object.
  233. *
  234. * @param string $entity_type
  235. * Optional. The entity type to attempt to retrieve.
  236. *
  237. * @return \Drupal\Core\Entity\EntityInterface|null
  238. * The extracted entity, NULL if entity could not be found.
  239. */
  240. protected function getEntityObject($entity_type = 'entity') {
  241. // Immediately return if there is no element.
  242. if (!$this->element) {
  243. return NULL;
  244. }
  245. // Attempt to retrieve the provided element type.
  246. $entity = $this->element->getProperty($entity_type);
  247. // If the provided entity type doesn't exist, check to see if a generic
  248. // "entity" property was used instead.
  249. if ($entity_type !== 'entity' && (!$entity || !($entity instanceof EntityInterface))) {
  250. $entity = $this->element->getProperty('entity');
  251. }
  252. // Only return the entity if it's the proper object.
  253. return $entity instanceof EntityInterface ? $entity : NULL;
  254. }
  255. /**
  256. * Processes the necessary theme hook suggestions.
  257. */
  258. protected function processSuggestions() {
  259. // Add special hook suggestions for Bootstrap panels.
  260. if ((in_array($this->originalHook, $this->bootstrapPanelTypes)) && $this->element && $this->element->getProperty('bootstrap_panel', TRUE)) {
  261. $this->addSuggestion('bootstrap_panel');
  262. }
  263. // Retrieve any dynamic alter methods.
  264. $methods = $this->getAlterMethods();
  265. foreach ($methods as $method) {
  266. $this->$method();
  267. }
  268. }
  269. /***************************************************************************
  270. * Deprecated methods (DO NOT USE).
  271. ***************************************************************************/
  272. /**
  273. * Adds "bundle" and "view mode" suggestions for an entity.
  274. *
  275. * @param array $suggestions
  276. * The suggestions array, this is ignored.
  277. * @param \Drupal\bootstrap\Utility\Variables $variables
  278. * The variables object, this is ignored.
  279. * @param string $entity_type
  280. * Optional. A specific type of entity to look for.
  281. * @param string $prefix
  282. * Optional. A prefix (like "entity") to use. It will automatically be
  283. * appended with the "__" separator.
  284. *
  285. * @deprecated Since 8.x-3.2. Will be removed in a future release.
  286. *
  287. * @see \Drupal\bootstrap\Plugin\Alter\ThemeSuggestions::addSuggestionsForEntity
  288. */
  289. public function addEntitySuggestions(array &$suggestions, Variables $variables, $entity_type = 'entity', $prefix = '') {
  290. Bootstrap::deprecated();
  291. $this->addSuggestionsForEntity($entity_type, $prefix);
  292. }
  293. /**
  294. * Extracts the entity from the element(s) passed in the Variables object.
  295. *
  296. * @param \Drupal\bootstrap\Utility\Variables $variables
  297. * The Variables object, this is ignored.
  298. * @param string $entity_type
  299. * Optional. The entity type to attempt to retrieve.
  300. *
  301. * @return \Drupal\Core\Entity\EntityInterface|null
  302. * The extracted entity, NULL if entity could not be found.
  303. *
  304. * @deprecated Since 8.x-3.2. Will be removed in a future release.
  305. *
  306. * @see \Drupal\bootstrap\Plugin\Alter\ThemeSuggestions::getEntityObject
  307. */
  308. public function getEntity(Variables $variables, $entity_type = 'entity') {
  309. Bootstrap::deprecated();
  310. return $this->getEntityObject($entity_type);
  311. }
  312. }