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