Backup of db before drupal security update
[yaffs-website] / web / core / includes / theme.inc
1 <?php
2
3 /**
4  * @file
5  * The theme system, which controls the output of Drupal.
6  *
7  * The theme system allows for nearly all output of the Drupal system to be
8  * customized by user themes.
9  */
10
11 use Drupal\Component\Serialization\Json;
12 use Drupal\Component\Utility\Crypt;
13 use Drupal\Component\Utility\Html;
14 use Drupal\Component\Render\MarkupInterface;
15 use Drupal\Component\Utility\Unicode;
16 use Drupal\Core\Cache\CacheableDependencyInterface;
17 use Drupal\Core\Config\Config;
18 use Drupal\Core\Config\StorageException;
19 use Drupal\Core\Render\AttachmentsInterface;
20 use Drupal\Core\Render\BubbleableMetadata;
21 use Drupal\Core\Render\RenderableInterface;
22 use Drupal\Core\Template\Attribute;
23 use Drupal\Core\Theme\ThemeSettings;
24 use Drupal\Component\Utility\NestedArray;
25 use Drupal\Core\Render\Element;
26 use Drupal\Core\Render\Markup;
27
28 /**
29  * @defgroup content_flags Content markers
30  * @{
31  * Markers used by mark.html.twig and node_mark() to designate content.
32  *
33  * @see mark.html.twig
34  * @see node_mark()
35  */
36
37 /**
38  * Mark content as read.
39  */
40 const MARK_READ = 0;
41
42 /**
43  * Mark content as being new.
44  */
45 const MARK_NEW = 1;
46
47 /**
48  * Mark content as being updated.
49  */
50 const MARK_UPDATED = 2;
51
52 /**
53  * A responsive table class; hide table cell on narrow devices.
54  *
55  * Indicates that a column has medium priority and thus can be hidden on narrow
56  * width devices and shown on medium+ width devices (i.e. tablets and desktops).
57  */
58 const RESPONSIVE_PRIORITY_MEDIUM = 'priority-medium';
59
60 /**
61  * A responsive table class; only show table cell on wide devices.
62  *
63  * Indicates that a column has low priority and thus can be hidden on narrow
64  * and medium viewports and shown on wide devices (i.e. desktops).
65  */
66 const RESPONSIVE_PRIORITY_LOW = 'priority-low';
67
68 /**
69  * @} End of "defgroup content_flags".
70  */
71
72 /**
73  * Gets the theme registry.
74  *
75  * @param bool $complete
76  *   Optional boolean to indicate whether to return the complete theme registry
77  *   array or an instance of the Drupal\Core\Utility\ThemeRegistry class.
78  *   If TRUE, the complete theme registry array will be returned. This is useful
79  *   if you want to foreach over the whole registry, use array_* functions or
80  *   inspect it in a debugger. If FALSE, an instance of the
81  *   Drupal\Core\Utility\ThemeRegistry class will be returned, this provides an
82  *   ArrayObject which allows it to be accessed with array syntax and isset(),
83  *   and should be more lightweight than the full registry. Defaults to TRUE.
84  *
85  * @return
86  *   The complete theme registry array, or an instance of the
87  *   Drupal\Core\Utility\ThemeRegistry class.
88  */
89 function theme_get_registry($complete = TRUE) {
90   $theme_registry = \Drupal::service('theme.registry');
91   if ($complete) {
92     return $theme_registry->get();
93   }
94   else {
95     return $theme_registry->getRuntime();
96   }
97 }
98
99 /**
100  * Returns an array of default theme features.
101  *
102  * @see \Drupal\Core\Extension\ThemeHandler::$defaultFeatures
103  */
104 function _system_default_theme_features() {
105   return [
106     'favicon',
107     'logo',
108     'node_user_picture',
109     'comment_user_picture',
110     'comment_user_verification',
111   ];
112 }
113
114 /**
115  * Forces the system to rebuild the theme registry.
116  *
117  * This function should be called when modules are added to the system, or when
118  * a dynamic system needs to add more theme hooks.
119  */
120 function drupal_theme_rebuild() {
121   \Drupal::service('theme.registry')->reset();
122 }
123
124 /**
125  * Allows themes and/or theme engines to discover overridden theme functions.
126  *
127  * @param array $cache
128  *   The existing cache of theme hooks to test against.
129  * @param array $prefixes
130  *   An array of prefixes to test, in reverse order of importance.
131  *
132  * @return array
133  *   The functions found, suitable for returning from hook_theme;
134  */
135 function drupal_find_theme_functions($cache, $prefixes) {
136   $implementations = [];
137   $grouped_functions = \Drupal::service('theme.registry')->getPrefixGroupedUserFunctions($prefixes);
138
139   foreach ($cache as $hook => $info) {
140     foreach ($prefixes as $prefix) {
141       // Find theme functions that implement possible "suggestion" variants of
142       // registered theme hooks and add those as new registered theme hooks.
143       // The 'pattern' key defines a common prefix that all suggestions must
144       // start with. The default is the name of the hook followed by '__'. An
145       // 'base hook' key is added to each entry made for a found suggestion,
146       // so that common functionality can be implemented for all suggestions of
147       // the same base hook. To keep things simple, deep hierarchy of
148       // suggestions is not supported: each suggestion's 'base hook' key
149       // refers to a base hook, not to another suggestion, and all suggestions
150       // are found using the base hook's pattern, not a pattern from an
151       // intermediary suggestion.
152       $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
153       // Grep only the functions which are within the prefix group.
154       list($first_prefix,) = explode('_', $prefix, 2);
155       if (!isset($info['base hook']) && !empty($pattern) && isset($grouped_functions[$first_prefix])) {
156         $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $grouped_functions[$first_prefix]);
157         if ($matches) {
158           foreach ($matches as $match) {
159             $new_hook = substr($match, strlen($prefix) + 1);
160             $arg_name = isset($info['variables']) ? 'variables' : 'render element';
161             $implementations[$new_hook] = [
162               'function' => $match,
163               $arg_name => $info[$arg_name],
164               'base hook' => $hook,
165             ];
166           }
167         }
168       }
169       // Find theme functions that implement registered theme hooks and include
170       // that in what is returned so that the registry knows that the theme has
171       // this implementation.
172       if (function_exists($prefix . '_' . $hook)) {
173         $implementations[$hook] = [
174           'function' => $prefix . '_' . $hook,
175         ];
176       }
177     }
178   }
179
180   return $implementations;
181 }
182
183 /**
184  * Allows themes and/or theme engines to easily discover overridden templates.
185  *
186  * @param $cache
187  *   The existing cache of theme hooks to test against.
188  * @param $extension
189  *   The extension that these templates will have.
190  * @param $path
191  *   The path to search.
192  */
193 function drupal_find_theme_templates($cache, $extension, $path) {
194   $implementations = [];
195
196   // Collect paths to all sub-themes grouped by base themes. These will be
197   // used for filtering. This allows base themes to have sub-themes in its
198   // folder hierarchy without affecting the base themes template discovery.
199   $theme_paths = [];
200   foreach (\Drupal::service('theme_handler')->listInfo() as $theme_info) {
201     if (!empty($theme_info->base_theme)) {
202       $theme_paths[$theme_info->base_theme][$theme_info->getName()] = $theme_info->getPath();
203     }
204   }
205   foreach ($theme_paths as $basetheme => $subthemes) {
206     foreach ($subthemes as $subtheme => $subtheme_path) {
207       if (isset($theme_paths[$subtheme])) {
208         $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]);
209       }
210     }
211   }
212   $theme = \Drupal::theme()->getActiveTheme()->getName();
213   $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : [];
214
215   // Escape the periods in the extension.
216   $regex = '/' . str_replace('.', '\.', $extension) . '$/';
217   // Get a listing of all template files in the path to search.
218   $files = file_scan_directory($path, $regex, ['key' => 'filename']);
219
220   // Find templates that implement registered theme hooks and include that in
221   // what is returned so that the registry knows that the theme has this
222   // implementation.
223   foreach ($files as $template => $file) {
224     // Ignore sub-theme templates for the current theme.
225     if (strpos($file->uri, str_replace($subtheme_paths, '', $file->uri)) !== 0) {
226       continue;
227     }
228     // Remove the extension from the filename.
229     $template = str_replace($extension, '', $template);
230     // Transform - in filenames to _ to match function naming scheme
231     // for the purposes of searching.
232     $hook = strtr($template, '-', '_');
233     if (isset($cache[$hook])) {
234       $implementations[$hook] = [
235         'template' => $template,
236         'path' => dirname($file->uri),
237       ];
238     }
239
240     // Match templates based on the 'template' filename.
241     foreach ($cache as $hook => $info) {
242       if (isset($info['template'])) {
243         $template_candidates = [$info['template'], str_replace($info['theme path'] . '/templates/', '', $info['template'])];
244         if (in_array($template, $template_candidates)) {
245           $implementations[$hook] = [
246             'template' => $template,
247             'path' => dirname($file->uri),
248           ];
249         }
250       }
251     }
252   }
253
254   // Find templates that implement possible "suggestion" variants of registered
255   // theme hooks and add those as new registered theme hooks. See
256   // drupal_find_theme_functions() for more information about suggestions and
257   // the use of 'pattern' and 'base hook'.
258   $patterns = array_keys($files);
259   foreach ($cache as $hook => $info) {
260     $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
261     if (!isset($info['base hook']) && !empty($pattern)) {
262       // Transform _ in pattern to - to match file naming scheme
263       // for the purposes of searching.
264       $pattern = strtr($pattern, '_', '-');
265
266       $matches = preg_grep('/^' . $pattern . '/', $patterns);
267       if ($matches) {
268         foreach ($matches as $match) {
269           $file = $match;
270           // Remove the extension from the filename.
271           $file = str_replace($extension, '', $file);
272           // Put the underscores back in for the hook name and register this
273           // pattern.
274           $arg_name = isset($info['variables']) ? 'variables' : 'render element';
275           $implementations[strtr($file, '-', '_')] = [
276             'template' => $file,
277             'path' => dirname($files[$match]->uri),
278             $arg_name => $info[$arg_name],
279             'base hook' => $hook,
280           ];
281         }
282       }
283     }
284   }
285   return $implementations;
286 }
287
288 /**
289  * Retrieves a setting for the current theme or for a given theme.
290  *
291  * The final setting is obtained from the last value found in the following
292  * sources:
293  * - the saved values from the global theme settings form
294  * - the saved values from the theme's settings form
295  * To only retrieve the default global theme setting, an empty string should be
296  * given for $theme.
297  *
298  * @param $setting_name
299  *   The name of the setting to be retrieved.
300  * @param $theme
301  *   The name of a given theme; defaults to the current theme.
302  *
303  * @return
304  *   The value of the requested setting, NULL if the setting does not exist.
305  */
306 function theme_get_setting($setting_name, $theme = NULL) {
307   /** @var \Drupal\Core\Theme\ThemeSettings[] $cache */
308   $cache = &drupal_static(__FUNCTION__, []);
309
310   // If no key is given, use the current theme if we can determine it.
311   if (!isset($theme)) {
312     $theme = \Drupal::theme()->getActiveTheme()->getName();
313   }
314
315   if (empty($cache[$theme])) {
316     // Create a theme settings object.
317     $cache[$theme] = new ThemeSettings($theme);
318     // Get the global settings from configuration.
319     $cache[$theme]->setData(\Drupal::config('system.theme.global')->get());
320
321     // Get the values for the theme-specific settings from the .info.yml files
322     // of the theme and all its base themes.
323     $themes = \Drupal::service('theme_handler')->listInfo();
324     if (isset($themes[$theme])) {
325       $theme_object = $themes[$theme];
326
327       // Retrieve configured theme-specific settings, if any.
328       try {
329         if ($theme_settings = \Drupal::config($theme . '.settings')->get()) {
330           $cache[$theme]->merge($theme_settings);
331         }
332       }
333       catch (StorageException $e) {
334       }
335
336       // If the theme does not support a particular feature, override the global
337       // setting and set the value to NULL.
338       if (!empty($theme_object->info['features'])) {
339         foreach (_system_default_theme_features() as $feature) {
340           if (!in_array($feature, $theme_object->info['features'])) {
341             $cache[$theme]->set('features.' . $feature, NULL);
342           }
343         }
344       }
345
346       // Generate the path to the logo image.
347       if ($cache[$theme]->get('logo.use_default')) {
348         $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($theme_object->getPath() . '/logo.svg')));
349       }
350       elseif ($logo_path = $cache[$theme]->get('logo.path')) {
351         $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo_path)));
352       }
353
354       // Generate the path to the favicon.
355       if ($cache[$theme]->get('features.favicon')) {
356         $favicon_path = $cache[$theme]->get('favicon.path');
357         if ($cache[$theme]->get('favicon.use_default')) {
358           if (file_exists($favicon = $theme_object->getPath() . '/favicon.ico')) {
359             $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon)));
360           }
361           else {
362             $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url('core/misc/favicon.ico')));
363           }
364         }
365         elseif ($favicon_path) {
366           $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon_path)));
367         }
368         else {
369           $cache[$theme]->set('features.favicon', FALSE);
370         }
371       }
372     }
373   }
374
375   return $cache[$theme]->get($setting_name);
376 }
377
378 /**
379  * Escapes and renders variables for theme functions.
380  *
381  * This method is used in theme functions to ensure that the result is safe for
382  * output inside HTML fragments. This mimics the behavior of the auto-escape
383  * functionality in Twig.
384  *
385  * Note: This function should be kept in sync with
386  * \Drupal\Core\Template\TwigExtension::escapeFilter().
387  *
388  * @param mixed $arg
389  *   The string, object, or render array to escape if needed.
390  *
391  * @return string
392  *   The rendered string, safe for use in HTML. The string is not safe when used
393  *   as any part of an HTML attribute name or value.
394  *
395  * @throws \Exception
396  *   Thrown when an object is passed in which cannot be printed.
397  *
398  * @see \Drupal\Core\Template\TwigExtension::escapeFilter()
399  *
400  * @todo Discuss deprecating this in https://www.drupal.org/node/2575081.
401  * @todo Refactor this to keep it in sync with Twig filtering in
402  *   https://www.drupal.org/node/2575065
403  */
404 function theme_render_and_autoescape($arg) {
405   // If it's a renderable, then it'll be up to the generated render array it
406   // returns to contain the necessary cacheability & attachment metadata. If
407   // it doesn't implement CacheableDependencyInterface or AttachmentsInterface
408   // then there is nothing to do here.
409   if (!($arg instanceof RenderableInterface) && ($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) {
410     $arg_bubbleable = [];
411     BubbleableMetadata::createFromObject($arg)
412       ->applyTo($arg_bubbleable);
413     \Drupal::service('renderer')->render($arg_bubbleable);
414   }
415
416   if ($arg instanceof MarkupInterface) {
417     return (string) $arg;
418   }
419   $return = NULL;
420
421   if (is_scalar($arg)) {
422     $return = (string) $arg;
423   }
424   elseif (is_object($arg)) {
425     if ($arg instanceof RenderableInterface) {
426       $arg = $arg->toRenderable();
427     }
428     elseif (method_exists($arg, '__toString')) {
429       $return = (string) $arg;
430     }
431     // You can't throw exceptions in the magic PHP __toString methods, see
432     // http://php.net/manual/language.oop5.magic.php#object.tostring so
433     // we also support a toString method.
434     elseif (method_exists($arg, 'toString')) {
435       $return = $arg->toString();
436     }
437     else {
438       throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.');
439     }
440   }
441
442   // We have a string or an object converted to a string: Escape it!
443   if (isset($return)) {
444     return $return instanceof MarkupInterface ? $return : Html::escape($return);
445   }
446
447   // This is a normal render array, which is safe by definition, with special
448   // simple cases already handled.
449
450   // Early return if this element was pre-rendered (no need to re-render).
451   if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
452     return (string) $arg['#markup'];
453   }
454   $arg['#printed'] = FALSE;
455   return (string) \Drupal::service('renderer')->render($arg);
456 }
457
458 /**
459  * Converts theme settings to configuration.
460  *
461  * @see system_theme_settings_submit()
462  *
463  * @param array $theme_settings
464  *   An array of theme settings from system setting form or a Drupal 7 variable.
465  * @param Config $config
466  *   The configuration object to update.
467  *
468  * @return
469  *   The Config object with updated data.
470  */
471 function theme_settings_convert_to_config(array $theme_settings, Config $config) {
472   foreach ($theme_settings as $key => $value) {
473     if ($key == 'default_logo') {
474       $config->set('logo.use_default', $value);
475     }
476     elseif ($key == 'logo_path') {
477       $config->set('logo.path', $value);
478     }
479     elseif ($key == 'default_favicon') {
480       $config->set('favicon.use_default', $value);
481     }
482     elseif ($key == 'favicon_path') {
483       $config->set('favicon.path', $value);
484     }
485     elseif ($key == 'favicon_mimetype') {
486       $config->set('favicon.mimetype', $value);
487     }
488     elseif (substr($key, 0, 7) == 'toggle_') {
489       $config->set('features.' . Unicode::substr($key, 7), $value);
490     }
491     elseif (!in_array($key, ['theme', 'logo_upload'])) {
492       $config->set($key, $value);
493     }
494   }
495   return $config;
496 }
497
498 /**
499  * Prepares variables for time templates.
500  *
501  * Default template: time.html.twig.
502  *
503  * @param array $variables
504  *   An associative array possibly containing:
505  *   - attributes['timestamp']:
506  *   - timestamp:
507  *   - text:
508  */
509 function template_preprocess_time(&$variables) {
510   // Format the 'datetime' attribute based on the timestamp.
511   // @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
512   if (!isset($variables['attributes']['datetime']) && isset($variables['timestamp'])) {
513     $variables['attributes']['datetime'] = format_date($variables['timestamp'], 'html_datetime', '', 'UTC');
514   }
515
516   // If no text was provided, try to auto-generate it.
517   if (!isset($variables['text'])) {
518     // Format and use a human-readable version of the timestamp, if any.
519     if (isset($variables['timestamp'])) {
520       $variables['text'] = format_date($variables['timestamp']);
521     }
522     // Otherwise, use the literal datetime attribute.
523     elseif (isset($variables['attributes']['datetime'])) {
524       $variables['text'] = $variables['attributes']['datetime'];
525     }
526   }
527 }
528
529 /**
530  * Prepares variables for datetime form element templates.
531  *
532  * The datetime form element serves as a wrapper around the date element type,
533  * which creates a date and a time component for a date.
534  *
535  * Default template: datetime-form.html.twig.
536  *
537  * @param array $variables
538  *   An associative array containing:
539  *   - element: An associative array containing the properties of the element.
540  *     Properties used: #title, #value, #options, #description, #required,
541  *     #attributes.
542  *
543  * @see form_process_datetime()
544  */
545 function template_preprocess_datetime_form(&$variables) {
546   $element = $variables['element'];
547
548   $variables['attributes'] = [];
549   if (isset($element['#id'])) {
550     $variables['attributes']['id'] = $element['#id'];
551   }
552   if (!empty($element['#attributes']['class'])) {
553     $variables['attributes']['class'] = (array) $element['#attributes']['class'];
554   }
555
556   $variables['content'] = $element;
557 }
558
559 /**
560  * Prepares variables for datetime form wrapper templates.
561  *
562  * Default template: datetime-wrapper.html.twig.
563  *
564  * @param array $variables
565  *   An associative array containing:
566  *   - element: An associative array containing the properties of the element.
567  *     Properties used: #title, #children, #required, #attributes.
568  */
569 function template_preprocess_datetime_wrapper(&$variables) {
570   $element = $variables['element'];
571
572   if (!empty($element['#title'])) {
573     $variables['title'] = $element['#title'];
574   }
575
576   // Suppress error messages.
577   $variables['errors'] = NULL;
578
579   $variables['description'] = NULL;
580   if (!empty($element['#description'])) {
581     $description_attributes = [];
582     if (!empty($element['#id'])) {
583       $description_attributes['id'] = $element['#id'] . '--description';
584     }
585     $variables['description'] = $element['#description'];
586     $variables['description_attributes'] = new Attribute($description_attributes);
587   }
588
589   $variables['required'] = FALSE;
590   // For required datetime fields 'form-required' & 'js-form-required' classes
591   // are appended to the label attributes.
592   if (!empty($element['#required'])) {
593     $variables['required'] = TRUE;
594   }
595   $variables['content'] = $element['#children'];
596 }
597
598 /**
599  * Prepares variables for links templates.
600  *
601  * Default template: links.html.twig.
602  *
603  * Unfortunately links templates duplicate the "active" class handling of l()
604  * and LinkGenerator::generate() because it needs to be able to set the "active"
605  * class not on the links themselves (<a> tags), but on the list items (<li>
606  * tags) that contain the links. This is necessary for CSS to be able to style
607  * list items differently when the link is active, since CSS does not yet allow
608  * one to style list items only if it contains a certain element with a certain
609  * class. I.e. we cannot yet convert this jQuery selector to a CSS selector:
610  * jQuery('li:has("a.is-active")')
611  *
612  * @param array $variables
613  *   An associative array containing:
614  *   - links: An array of links to be themed. Each link should be itself an
615  *     array, with the following elements:
616  *     - title: The link text.
617  *     - url: (optional) The \Drupal\Core\Url object to link to. If omitted, no
618  *       anchor tag is printed out.
619  *     - attributes: (optional) Attributes for the anchor, or for the <span>
620  *       tag used in its place if no 'href' is supplied. If element 'class' is
621  *       included, it must be an array of one or more class names.
622  *     If the 'href' element is supplied, the entire link array is passed to
623  *     l() as its $options parameter.
624  *   - attributes: A keyed array of attributes for the <ul> containing the list
625  *     of links.
626  *   - set_active_class: (optional) Whether each link should compare the
627  *     route_name + route_parameters or href (path), language and query options
628  *     to the current URL, to determine whether the link is "active". If so, an
629  *     "active" class will be applied to the list item containing the link, as
630  *     well as the link itself. It is important to use this sparingly since it
631  *     is usually unnecessary and requires extra processing.
632  *     For anonymous users, the "active" class will be calculated on the server,
633  *     because most sites serve each anonymous user the same cached page anyway.
634  *     For authenticated users, the "active" class will be calculated on the
635  *     client (through JavaScript), only data- attributes are added to list
636  *     items and contained links, to prevent breaking the render cache. The
637  *     JavaScript is added in system_page_attachments().
638  *   - heading: (optional) A heading to precede the links. May be an
639  *     associative array or a string. If it's an array, it can have the
640  *     following elements:
641  *     - text: The heading text.
642  *     - level: The heading level (e.g. 'h2', 'h3').
643  *     - attributes: (optional) An array of the CSS attributes for the heading.
644  *     When using a string it will be used as the text of the heading and the
645  *     level will default to 'h2'. Headings should be used on navigation menus
646  *     and any list of links that consistently appears on multiple pages. To
647  *     make the heading invisible use the 'visually-hidden' CSS class. Do not
648  *     use 'display:none', which removes it from screen readers and assistive
649  *     technology. Headings allow screen reader and keyboard only users to
650  *     navigate to or skip the links. See
651  *     http://juicystudio.com/article/screen-readers-display-none.php and
652  *     http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
653  *
654  * @see \Drupal\Core\Utility\LinkGenerator
655  * @see \Drupal\Core\Utility\LinkGenerator::generate()
656  * @see system_page_attachments()
657  */
658 function template_preprocess_links(&$variables) {
659   $links = $variables['links'];
660   $heading = &$variables['heading'];
661
662   if (!empty($links)) {
663     // Prepend the heading to the list, if any.
664     if (!empty($heading)) {
665       // Convert a string heading into an array, using a <h2> tag by default.
666       if (is_string($heading)) {
667         $heading = ['text' => $heading];
668       }
669       // Merge in default array properties into $heading.
670       $heading += [
671         'level' => 'h2',
672         'attributes' => [],
673       ];
674       // Convert the attributes array into an Attribute object.
675       $heading['attributes'] = new Attribute($heading['attributes']);
676     }
677
678     $variables['links'] = [];
679     foreach ($links as $key => $link) {
680       $item = [];
681       $link += [
682         'ajax' => NULL,
683         'url' => NULL,
684       ];
685
686       $li_attributes = [];
687       $keys = ['title', 'url'];
688       $link_element = [
689         '#type' => 'link',
690         '#title' => $link['title'],
691         '#options' => array_diff_key($link, array_combine($keys, $keys)),
692         '#url' => $link['url'],
693         '#ajax' => $link['ajax'],
694       ];
695
696       // Handle links and ensure that the active class is added on the LIs, but
697       // only if the 'set_active_class' option is not empty.
698       if (isset($link['url'])) {
699         if (!empty($variables['set_active_class'])) {
700
701           // Also enable set_active_class for the contained link.
702           $link_element['#options']['set_active_class'] = TRUE;
703
704           if (!empty($link['language'])) {
705             $li_attributes['hreflang'] = $link['language']->getId();
706           }
707
708           // Add a "data-drupal-link-query" attribute to let the
709           // drupal.active-link library know the query in a standardized manner.
710           if (!empty($link['query'])) {
711             $query = $link['query'];
712             ksort($query);
713             $li_attributes['data-drupal-link-query'] = Json::encode($query);
714           }
715
716           /** @var \Drupal\Core\Url $url */
717           $url = $link['url'];
718           if ($url->isRouted()) {
719             // Add a "data-drupal-link-system-path" attribute to let the
720             // drupal.active-link library know the path in a standardized manner.
721             $system_path = $url->getInternalPath();
722             // @todo System path is deprecated - use the route name and parameters.
723             // Special case for the front page.
724             $li_attributes['data-drupal-link-system-path'] = $system_path == '' ? '<front>' : $system_path;
725           }
726         }
727
728         $item['link'] = $link_element;
729       }
730
731       // Handle title-only text items.
732       $item['text'] = $link['title'];
733       if (isset($link['attributes'])) {
734         $item['text_attributes'] = new Attribute($link['attributes']);
735       }
736
737       // Handle list item attributes.
738       $item['attributes'] = new Attribute($li_attributes);
739
740       // Add the item to the list of links.
741       $variables['links'][$key] = $item;
742     }
743   }
744 }
745
746 /**
747  * Prepares variables for image templates.
748  *
749  * Default template: image.html.twig.
750  *
751  * @param array $variables
752  *   An associative array containing:
753  *   - uri: Either the path of the image file (relative to base_path()) or a
754  *     full URL.
755  *   - width: The width of the image (if known).
756  *   - height: The height of the image (if known).
757  *   - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0
758  *     always require an alt attribute. The HTML 5 draft allows the alt
759  *     attribute to be omitted in some cases. Therefore, this variable defaults
760  *     to an empty string, but can be set to NULL for the attribute to be
761  *     omitted. Usually, neither omission nor an empty string satisfies
762  *     accessibility requirements, so it is strongly encouraged for code
763  *     building variables for image.html.twig templates to pass a meaningful
764  *     value for this variable.
765  *     - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
766  *     - http://www.w3.org/TR/xhtml1/dtds.html
767  *     - http://dev.w3.org/html5/spec/Overview.html#alt
768  *   - title: The title text is displayed when the image is hovered in some
769  *     popular browsers.
770  *   - attributes: Associative array of attributes to be placed in the img tag.
771  *   - srcset: Array of multiple URIs and sizes/multipliers.
772  *   - sizes: The sizes attribute for viewport-based selection of images.
773  *     - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2
774  */
775 function template_preprocess_image(&$variables) {
776   if (!empty($variables['uri'])) {
777     $variables['attributes']['src'] = file_url_transform_relative(file_create_url($variables['uri']));
778   }
779   // Generate a srcset attribute conforming to the spec at
780   // http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset
781   if (!empty($variables['srcset'])) {
782     $srcset = [];
783     foreach ($variables['srcset'] as $src) {
784       // URI is mandatory.
785       $source = file_url_transform_relative(file_create_url($src['uri']));
786       if (isset($src['width']) && !empty($src['width'])) {
787         $source .= ' ' . $src['width'];
788       }
789       elseif (isset($src['multiplier']) && !empty($src['multiplier'])) {
790         $source .= ' ' . $src['multiplier'];
791       }
792       $srcset[] = $source;
793     }
794     $variables['attributes']['srcset'] = implode(', ', $srcset);
795   }
796
797   foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) {
798     if (isset($variables[$key])) {
799       // If the property has already been defined in the attributes,
800       // do not override, including NULL.
801       if (array_key_exists($key, $variables['attributes'])) {
802         continue;
803       }
804       $variables['attributes'][$key] = $variables[$key];
805     }
806   }
807 }
808
809 /**
810  * Prepares variables for table templates.
811  *
812  * Default template: table.html.twig.
813  *
814  * @param array $variables
815  *   An associative array containing:
816  *   - header: An array containing the table headers. Each element of the array
817  *     can be either a localized string or an associative array with the
818  *     following keys:
819  *     - data: The localized title of the table column, as a string or render
820  *       array.
821  *     - field: The database field represented in the table column (required
822  *       if user is to be able to sort on this column).
823  *     - sort: A default sort order for this column ("asc" or "desc"). Only
824  *       one column should be given a default sort order because table sorting
825  *       only applies to one column at a time.
826  *     - class: An array of values for the 'class' attribute. In particular,
827  *       the least important columns that can be hidden on narrow and medium
828  *       width screens should have a 'priority-low' class, referenced with the
829  *       RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on
830  *       medium+ wide screens should be marked up with a class of
831  *       'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM
832  *       constant. Themes may hide columns with one of these two classes on
833  *       narrow viewports to save horizontal space.
834  *     - Any HTML attributes, such as "colspan", to apply to the column header
835  *       cell.
836  *   - rows: An array of table rows. Every row is an array of cells, or an
837  *     associative array with the following keys:
838  *     - data: An array of cells.
839  *     - Any HTML attributes, such as "class", to apply to the table row.
840  *     - no_striping: A Boolean indicating that the row should receive no
841  *       'even / odd' styling. Defaults to FALSE.
842  *     Each cell can be either a string or an associative array with the
843  *     following keys:
844  *     - data: The string or render array to display in the table cell.
845  *     - header: Indicates this cell is a header.
846  *     - Any HTML attributes, such as "colspan", to apply to the table cell.
847  *     Here's an example for $rows:
848  *     @code
849  *     $rows = array(
850  *       // Simple row
851  *       array(
852  *         'Cell 1', 'Cell 2', 'Cell 3'
853  *       ),
854  *       // Row with attributes on the row and some of its cells.
855  *       array(
856  *         'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => array('funky')
857  *       ),
858  *     );
859  *     @endcode
860  *   - footer: An array of table rows which will be printed within a <tfoot>
861  *     tag, in the same format as the rows element (see above).
862  *   - attributes: An array of HTML attributes to apply to the table tag.
863  *   - caption: A localized string to use for the <caption> tag.
864  *   - colgroups: An array of column groups. Each element of the array can be
865  *     either:
866  *     - An array of columns, each of which is an associative array of HTML
867  *       attributes applied to the <col> element.
868  *     - An array of attributes applied to the <colgroup> element, which must
869  *       include a "data" attribute. To add attributes to <col> elements,
870  *       set the "data" attribute with an array of columns, each of which is an
871  *       associative array of HTML attributes.
872  *     Here's an example for $colgroup:
873  *     @code
874  *     $colgroup = array(
875  *       // <colgroup> with one <col> element.
876  *       array(
877  *         array(
878  *           'class' => array('funky'), // Attribute for the <col> element.
879  *         ),
880  *       ),
881  *       // <colgroup> with attributes and inner <col> elements.
882  *       array(
883  *         'data' => array(
884  *           array(
885  *             'class' => array('funky'), // Attribute for the <col> element.
886  *           ),
887  *         ),
888  *         'class' => array('jazzy'), // Attribute for the <colgroup> element.
889  *       ),
890  *     );
891  *     @endcode
892  *     These optional tags are used to group and set properties on columns
893  *     within a table. For example, one may easily group three columns and
894  *     apply same background style to all.
895  *   - sticky: Use a "sticky" table header.
896  *   - empty: The message to display in an extra row if table does not have any
897  *     rows.
898  */
899 function template_preprocess_table(&$variables) {
900   // Format the table columns:
901   if (!empty($variables['colgroups'])) {
902     foreach ($variables['colgroups'] as &$colgroup) {
903       // Check if we're dealing with a simple or complex column
904       if (isset($colgroup['data'])) {
905         $cols = $colgroup['data'];
906         unset($colgroup['data']);
907         $colgroup_attributes = $colgroup;
908       }
909       else {
910         $cols = $colgroup;
911         $colgroup_attributes = [];
912       }
913       $colgroup = [];
914       $colgroup['attributes'] = new Attribute($colgroup_attributes);
915       $colgroup['cols'] = [];
916
917       // Build columns.
918       if (is_array($cols) && !empty($cols)) {
919         foreach ($cols as $col_key => $col) {
920           $colgroup['cols'][$col_key]['attributes'] = new Attribute($col);
921         }
922       }
923     }
924   }
925
926   // Build an associative array of responsive classes keyed by column.
927   $responsive_classes = [];
928
929   // Format the table header:
930   $ts = [];
931   $header_columns = 0;
932   if (!empty($variables['header'])) {
933     $ts = tablesort_init($variables['header']);
934
935     // Use a separate index with responsive classes as headers
936     // may be associative.
937     $responsive_index = -1;
938     foreach ($variables['header'] as $col_key => $cell) {
939       // Increase the responsive index.
940       $responsive_index++;
941
942       if (!is_array($cell)) {
943         $header_columns++;
944         $cell_content = $cell;
945         $cell_attributes = new Attribute();
946         $is_header = TRUE;
947       }
948       else {
949         if (isset($cell['colspan'])) {
950           $header_columns += $cell['colspan'];
951         }
952         else {
953           $header_columns++;
954         }
955         $cell_content = '';
956         if (isset($cell['data'])) {
957           $cell_content = $cell['data'];
958           unset($cell['data']);
959         }
960         // Flag the cell as a header or not and remove the flag.
961         $is_header = isset($cell['header']) ? $cell['header'] : TRUE;
962         unset($cell['header']);
963
964         // Track responsive classes for each column as needed. Only the header
965         // cells for a column are marked up with the responsive classes by a
966         // module developer or themer. The responsive classes on the header cells
967         // must be transferred to the content cells.
968         if (!empty($cell['class']) && is_array($cell['class'])) {
969           if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) {
970             $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM;
971           }
972           elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) {
973             $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW;
974           }
975         }
976
977         tablesort_header($cell_content, $cell, $variables['header'], $ts);
978
979         // tablesort_header() removes the 'sort' and 'field' keys.
980         $cell_attributes = new Attribute($cell);
981       }
982       $variables['header'][$col_key] = [];
983       $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td';
984       $variables['header'][$col_key]['attributes'] = $cell_attributes;
985       $variables['header'][$col_key]['content'] = $cell_content;
986     }
987   }
988   $variables['header_columns'] = $header_columns;
989
990   // Rows and footer have the same structure.
991   $sections = ['rows' , 'footer'];
992   foreach ($sections as $section) {
993     if (!empty($variables[$section])) {
994       foreach ($variables[$section] as $row_key => $row) {
995         $cells = $row;
996         $row_attributes = [];
997
998         // Check if we're dealing with a simple or complex row
999         if (isset($row['data'])) {
1000           $cells = $row['data'];
1001           $variables['no_striping'] = isset($row['no_striping']) ? $row['no_striping'] : FALSE;
1002
1003           // Set the attributes array and exclude 'data' and 'no_striping'.
1004           $row_attributes = $row;
1005           unset($row_attributes['data']);
1006           unset($row_attributes['no_striping']);
1007         }
1008
1009         // Build row.
1010         $variables[$section][$row_key] = [];
1011         $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes);
1012         $variables[$section][$row_key]['cells'] = [];
1013         if (!empty($cells)) {
1014           // Reset the responsive index.
1015           $responsive_index = -1;
1016           foreach ($cells as $col_key => $cell) {
1017             // Increase the responsive index.
1018             $responsive_index++;
1019
1020             if (!is_array($cell)) {
1021               $cell_content = $cell;
1022               $cell_attributes = [];
1023               $is_header = FALSE;
1024             }
1025             else {
1026               $cell_content = '';
1027               if (isset($cell['data'])) {
1028                 $cell_content = $cell['data'];
1029                 unset($cell['data']);
1030               }
1031
1032               // Flag the cell as a header or not and remove the flag.
1033               $is_header = !empty($cell['header']);
1034               unset($cell['header']);
1035
1036               $cell_attributes = $cell;
1037             }
1038             // Active table sort information.
1039             if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) {
1040               $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE;
1041             }
1042             // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM
1043             // class from header to cell as needed.
1044             if (isset($responsive_classes[$responsive_index])) {
1045               $cell_attributes['class'][] = $responsive_classes[$responsive_index];
1046             }
1047             $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td';
1048             $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes);
1049             $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content;
1050           }
1051         }
1052       }
1053     }
1054   }
1055   if (empty($variables['no_striping'])) {
1056     $variables['attributes']['data-striping'] = 1;
1057   }
1058 }
1059
1060 /**
1061  * Prepares variables for item list templates.
1062  *
1063  * Default template: item-list.html.twig.
1064  *
1065  * @param array $variables
1066  *   An associative array containing:
1067  *   - items: An array of items to be displayed in the list. Each item can be
1068  *     either a string or a render array. If #type, #theme, or #markup
1069  *     properties are not specified for child render arrays, they will be
1070  *     inherited from the parent list, allowing callers to specify larger
1071  *     nested lists without having to explicitly specify and repeat the
1072  *     render properties for all nested child lists.
1073  *   - title: A title to be prepended to the list.
1074  *   - list_type: The type of list to return (e.g. "ul", "ol").
1075  *   - wrapper_attributes: HTML attributes to be applied to the list wrapper.
1076  *
1077  * @see https://www.drupal.org/node/1842756
1078  */
1079 function template_preprocess_item_list(&$variables) {
1080   $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']);
1081   foreach ($variables['items'] as &$item) {
1082     $attributes = [];
1083     // If the item value is an array, then it is a render array.
1084     if (is_array($item)) {
1085       // List items support attributes via the '#wrapper_attributes' property.
1086       if (isset($item['#wrapper_attributes'])) {
1087         $attributes = $item['#wrapper_attributes'];
1088       }
1089       // Determine whether there are any child elements in the item that are not
1090       // fully-specified render arrays. If there are any, then the child
1091       // elements present nested lists and we automatically inherit the render
1092       // array properties of the current list to them.
1093       foreach (Element::children($item) as $key) {
1094         $child = &$item[$key];
1095         // If this child element does not specify how it can be rendered, then
1096         // we need to inherit the render properties of the current list.
1097         if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) {
1098           // Since item-list.html.twig supports both strings and render arrays
1099           // as items, the items of the nested list may have been specified as
1100           // the child elements of the nested list, instead of #items. For
1101           // convenience, we automatically move them into #items.
1102           if (!isset($child['#items'])) {
1103             // This is the same condition as in
1104             // \Drupal\Core\Render\Element::children(), which cannot be used
1105             // here, since it triggers an error on string values.
1106             foreach ($child as $child_key => $child_value) {
1107               if ($child_key[0] !== '#') {
1108                 $child['#items'][$child_key] = $child_value;
1109                 unset($child[$child_key]);
1110               }
1111             }
1112           }
1113           // Lastly, inherit the original theme variables of the current list.
1114           $child['#theme'] = $variables['theme_hook_original'];
1115           $child['#list_type'] = $variables['list_type'];
1116         }
1117       }
1118     }
1119
1120     // Set the item's value and attributes for the template.
1121     $item = [
1122       'value' => $item,
1123       'attributes' => new Attribute($attributes),
1124     ];
1125   }
1126 }
1127
1128 /**
1129  * Prepares variables for container templates.
1130  *
1131  * Default template: container.html.twig.
1132  *
1133  * @param array $variables
1134  *   An associative array containing:
1135  *   - element: An associative array containing the properties of the element.
1136  *     Properties used: #id, #attributes, #children.
1137  */
1138 function template_preprocess_container(&$variables) {
1139   $variables['has_parent'] = FALSE;
1140   $element = $variables['element'];
1141   // Ensure #attributes is set.
1142   $element += ['#attributes' => []];
1143
1144   // Special handling for form elements.
1145   if (isset($element['#array_parents'])) {
1146     // Assign an html ID.
1147     if (!isset($element['#attributes']['id'])) {
1148       $element['#attributes']['id'] = $element['#id'];
1149     }
1150     $variables['has_parent'] = TRUE;
1151   }
1152
1153   $variables['children'] = $element['#children'];
1154   $variables['attributes'] = $element['#attributes'];
1155 }
1156
1157 /**
1158  * Prepares variables for maintenance task list templates.
1159  *
1160  * Default template: maintenance-task-list.html.twig.
1161  *
1162  * @param array $variables
1163  *   An associative array containing:
1164  *   - items: An associative array of maintenance tasks.
1165  *     It's the caller's responsibility to ensure this array's items contain no
1166  *     dangerous HTML such as <script> tags.
1167  *   - active: The key for the currently active maintenance task.
1168  */
1169 function template_preprocess_maintenance_task_list(&$variables) {
1170   $items = $variables['items'];
1171   $active = $variables['active'];
1172
1173   $done = isset($items[$active]) || $active == NULL;
1174   foreach ($items as $k => $item) {
1175     $variables['tasks'][$k]['item'] = $item;
1176     $variables['tasks'][$k]['attributes'] = new Attribute();
1177     if ($active == $k) {
1178       $variables['tasks'][$k]['attributes']->addClass('is-active');
1179       $variables['tasks'][$k]['status'] = t('active');
1180       $done = FALSE;
1181     }
1182     else {
1183       if ($done) {
1184         $variables['tasks'][$k]['attributes']->addClass('done');
1185         $variables['tasks'][$k]['status'] = t('done');
1186       }
1187     }
1188   }
1189 }
1190
1191 /**
1192  * Adds a default set of helper variables for preprocessors and templates.
1193  *
1194  * This function is called for theme hooks implemented as templates only, not
1195  * for theme hooks implemented as functions. This preprocess function is the
1196  * first in the sequence of preprocessing functions that are called when
1197  * preparing variables for a template.
1198  *
1199  * See the @link themeable Default theme implementations topic @endlink for
1200  * details.
1201  */
1202 function template_preprocess(&$variables, $hook, $info) {
1203   // Merge in variables that don't depend on hook and don't change during a
1204   // single page request.
1205   // Use the advanced drupal_static() pattern, since this is called very often.
1206   static $drupal_static_fast;
1207   if (!isset($drupal_static_fast)) {
1208     $drupal_static_fast['default_variables'] = &drupal_static(__FUNCTION__);
1209   }
1210   $default_variables = &$drupal_static_fast['default_variables'];
1211   if (!isset($default_variables)) {
1212     $default_variables = _template_preprocess_default_variables();
1213   }
1214   $variables += $default_variables;
1215
1216   // When theming a render element, merge its #attributes into
1217   // $variables['attributes'].
1218   if (isset($info['render element'])) {
1219     $key = $info['render element'];
1220     if (isset($variables[$key]['#attributes'])) {
1221       $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], $variables[$key]['#attributes']);
1222     }
1223   }
1224 }
1225
1226 /**
1227  * Returns hook-independent variables to template_preprocess().
1228  */
1229 function _template_preprocess_default_variables() {
1230   // Variables that don't depend on a database connection.
1231   $variables = [
1232     'attributes' => [],
1233     'title_attributes' => [],
1234     'content_attributes' => [],
1235     'title_prefix' => [],
1236     'title_suffix' => [],
1237     'db_is_active' => !defined('MAINTENANCE_MODE'),
1238     'is_admin' => FALSE,
1239     'logged_in' => FALSE,
1240   ];
1241
1242   // Give modules a chance to alter the default template variables.
1243   \Drupal::moduleHandler()->alter('template_preprocess_default_variables', $variables);
1244
1245   // Tell all templates where they are located.
1246   $variables['directory'] = \Drupal::theme()->getActiveTheme()->getPath();
1247
1248   return $variables;
1249 }
1250
1251 /**
1252  * Prepares variables for HTML document templates.
1253  *
1254  * Default template: html.html.twig.
1255  *
1256  * @param array $variables
1257  *   An associative array containing:
1258  *   - page: A render element representing the page.
1259  */
1260 function template_preprocess_html(&$variables) {
1261   $variables['page'] = $variables['html']['page'];
1262   unset($variables['html']['page']);
1263   $variables['page_top'] = NULL;
1264   if (isset($variables['html']['page_top'])) {
1265     $variables['page_top'] = $variables['html']['page_top'];
1266     unset($variables['html']['page_top']);
1267   }
1268   $variables['page_bottom'] = NULL;
1269   if (isset($variables['html']['page_bottom'])) {
1270     $variables['page_bottom'] = $variables['html']['page_bottom'];
1271     unset($variables['html']['page_bottom']);
1272   }
1273
1274   $variables['html_attributes'] = new Attribute();
1275
1276   // <html> element attributes.
1277   $language_interface = \Drupal::languageManager()->getCurrentLanguage();
1278   $variables['html_attributes']['lang'] = $language_interface->getId();
1279   $variables['html_attributes']['dir'] = $language_interface->getDirection();
1280
1281   if (isset($variables['db_is_active']) && !$variables['db_is_active']) {
1282     $variables['db_offline'] = TRUE;
1283   }
1284
1285   // Add a variable for the root path. This can be used to create a class and
1286   // theme the page depending on the current path (e.g. node, admin, user) as
1287   // well as more specific data like path-frontpage.
1288   $is_front_page = \Drupal::service('path.matcher')->isFrontPage();
1289
1290   if ($is_front_page) {
1291     $variables['root_path'] = FALSE;
1292   }
1293   else {
1294     $system_path = \Drupal::service('path.current')->getPath();
1295     $variables['root_path'] = explode('/', $system_path)[1];
1296   }
1297
1298   $site_config = \Drupal::config('system.site');
1299   // Construct page title.
1300   if (isset($variables['page']['#title']) && is_array($variables['page']['#title'])) {
1301     // Do an early render if the title is a render array.
1302     $variables['page']['#title'] = (string) \Drupal::service('renderer')->render($variables['page']['#title']);
1303   }
1304   if (!empty($variables['page']['#title'])) {
1305     $head_title = [
1306       // Marking the title as safe since it has had the tags stripped.
1307       'title' => Markup::create(trim(strip_tags($variables['page']['#title']))),
1308       'name' => $site_config->get('name'),
1309     ];
1310   }
1311   // @todo Remove once views is not bypassing the view subscriber anymore.
1312   //   @see https://www.drupal.org/node/2068471
1313   elseif ($is_front_page) {
1314     $head_title = [
1315       'title' => t('Home'),
1316       'name' => $site_config->get('name'),
1317     ];
1318   }
1319   else {
1320     $head_title = ['name' => $site_config->get('name')];
1321     if ($site_config->get('slogan')) {
1322       $head_title['slogan'] = strip_tags($site_config->get('slogan'));
1323     }
1324   }
1325
1326   $variables['head_title'] = $head_title;
1327   // @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
1328   $variables['head_title_array'] = $head_title;
1329
1330   // Create placeholder strings for these keys.
1331   // @see \Drupal\Core\Render\HtmlResponseSubscriber
1332   $types = [
1333     'styles' => 'css',
1334     'scripts' => 'js',
1335     'scripts_bottom' => 'js-bottom',
1336     'head' => 'head',
1337   ];
1338   $variables['placeholder_token'] = Crypt::randomBytesBase64(55);
1339   foreach ($types as $type => $placeholder_name) {
1340     $placeholder = '<' . $placeholder_name . '-placeholder token="' . $variables['placeholder_token'] . '">';
1341     $variables['#attached']['html_response_attachment_placeholders'][$type] = $placeholder;
1342   }
1343 }
1344
1345 /**
1346  * Prepares variables for the page template.
1347  *
1348  * Default template: page.html.twig.
1349  *
1350  * See the page.html.twig template for the list of variables.
1351  */
1352 function template_preprocess_page(&$variables) {
1353   $language_interface = \Drupal::languageManager()->getCurrentLanguage();
1354
1355   foreach (\Drupal::theme()->getActiveTheme()->getRegions() as $region) {
1356     if (!isset($variables['page'][$region])) {
1357       $variables['page'][$region] = [];
1358     }
1359   }
1360
1361   $variables['base_path']         = base_path();
1362   $variables['front_page']        = \Drupal::url('<front>');
1363   $variables['language']          = $language_interface;
1364
1365   // An exception might be thrown.
1366   try {
1367     $variables['is_front'] = \Drupal::service('path.matcher')->isFrontPage();
1368   }
1369   catch (Exception $e) {
1370     // If the database is not yet available, set default values for these
1371     // variables.
1372     $variables['is_front'] = FALSE;
1373     $variables['db_is_active'] = FALSE;
1374   }
1375
1376   if ($node = \Drupal::routeMatch()->getParameter('node')) {
1377     $variables['node'] = $node;
1378   }
1379 }
1380
1381 /**
1382  * Generate an array of suggestions from path arguments.
1383  *
1384  * This is typically called for adding to the suggestions in
1385  * hook_theme_suggestions_HOOK_alter() or adding to 'attributes' class key
1386  * variables from within preprocess functions, when wanting to base the
1387  * additional suggestions or classes on the path of the current page.
1388  *
1389  * @param $args
1390  *   An array of path arguments.
1391  * @param $base
1392  *   A string identifying the base 'thing' from which more specific suggestions
1393  *   are derived. For example, 'page' or 'html'.
1394  * @param $delimiter
1395  *   The string used to delimit increasingly specific information. The default
1396  *   of '__' is appropriate for theme hook suggestions. '-' is appropriate for
1397  *   extra classes.
1398  *
1399  * @return
1400  *   An array of suggestions, suitable for adding to
1401  *   hook_theme_suggestions_HOOK_alter() or to $variables['attributes']['class']
1402  *   if the suggestions represent extra CSS classes.
1403  */
1404 function theme_get_suggestions($args, $base, $delimiter = '__') {
1405
1406   // Build a list of suggested theme hooks in order of
1407   // specificity. One suggestion is made for every element of the current path,
1408   // though numeric elements are not carried to subsequent suggestions. For
1409   // example, for $base='page', http://www.example.com/node/1/edit would result
1410   // in the following suggestions:
1411   //
1412   // page__node
1413   // page__node__%
1414   // page__node__1
1415   // page__node__edit
1416
1417   $suggestions = [];
1418   $prefix = $base;
1419   foreach ($args as $arg) {
1420     // Remove slashes or null per SA-CORE-2009-003 and change - (hyphen) to _
1421     // (underscore).
1422     //
1423     // When we discover templates in @see drupal_find_theme_templates,
1424     // hyphens (-) are converted to underscores (_) before the theme hook
1425     // is registered. We do this because the hyphens used for delimiters
1426     // in hook suggestions cannot be used in the function names of the
1427     // associated preprocess functions. Any page templates designed to be used
1428     // on paths that contain a hyphen are also registered with these hyphens
1429     // converted to underscores so here we must convert any hyphens in path
1430     // arguments to underscores here before fetching theme hook suggestions
1431     // to ensure the templates are appropriately recognized.
1432     $arg = str_replace(["/", "\\", "\0", '-'], ['', '', '', '_'], $arg);
1433     // The percent acts as a wildcard for numeric arguments since
1434     // asterisks are not valid filename characters on many filesystems.
1435     if (is_numeric($arg)) {
1436       $suggestions[] = $prefix . $delimiter . '%';
1437     }
1438     $suggestions[] = $prefix . $delimiter . $arg;
1439     if (!is_numeric($arg)) {
1440       $prefix .= $delimiter . $arg;
1441     }
1442   }
1443   if (\Drupal::service('path.matcher')->isFrontPage()) {
1444     // Front templates should be based on root only, not prefixed arguments.
1445     $suggestions[] = $base . $delimiter . 'front';
1446   }
1447
1448   return $suggestions;
1449 }
1450
1451 /**
1452  * Prepares variables for maintenance page templates.
1453  *
1454  * Default template: maintenance-page.html.twig.
1455  *
1456  * @param array $variables
1457  *   An associative array containing:
1458  *   - content - An array of page content.
1459  *
1460  * @see system_page_attachments()
1461  */
1462 function template_preprocess_maintenance_page(&$variables) {
1463   // @todo Rename the templates to page--maintenance + page--install.
1464   template_preprocess_page($variables);
1465
1466   // @see system_page_attachments()
1467   $variables['#attached']['library'][] = 'system/maintenance';
1468
1469   // Maintenance page and install page need branding info in variables because
1470   // there is no blocks.
1471   $site_config = \Drupal::config('system.site');
1472   $variables['logo'] = theme_get_setting('logo.url');
1473   $variables['site_name'] = $site_config->get('name');
1474   $variables['site_slogan'] = $site_config->get('slogan');
1475
1476   // Maintenance page and install page need page title in variable because there
1477   // are no blocks.
1478   $variables['title'] = $variables['page']['#title'];
1479 }
1480
1481 /**
1482  * Prepares variables for install page templates.
1483  *
1484  * Default template: install-page.html.twig.
1485  *
1486  * @param array $variables
1487  *   An associative array containing:
1488  *   - content - An array of page content.
1489  *
1490  * @see template_preprocess_maintenance_page()
1491  */
1492 function template_preprocess_install_page(&$variables) {
1493   template_preprocess_maintenance_page($variables);
1494
1495   // Override the site name that is displayed on the page, since Drupal is
1496   // still in the process of being installed.
1497   $distribution_name = drupal_install_profile_distribution_name();
1498   $variables['site_name'] = $distribution_name;
1499   $variables['site_version'] = drupal_install_profile_distribution_version();
1500 }
1501
1502 /**
1503  * Prepares variables for region templates.
1504  *
1505  * Default template: region.html.twig.
1506  *
1507  * Prepares the values passed to the theme_region function to be passed into a
1508  * pluggable template engine. Uses the region name to generate a template file
1509  * suggestions.
1510  *
1511  * @param array $variables
1512  *   An associative array containing:
1513  *   - elements: An associative array containing properties of the region.
1514  */
1515 function template_preprocess_region(&$variables) {
1516   // Create the $content variable that templates expect.
1517   $variables['content'] = $variables['elements']['#children'];
1518   $variables['region'] = $variables['elements']['#region'];
1519 }
1520
1521 /**
1522  * Prepares variables for field templates.
1523  *
1524  * Default template: field.html.twig.
1525  *
1526  * @param array $variables
1527  *   An associative array containing:
1528  *   - element: A render element representing the field.
1529  *   - attributes: A string containing the attributes for the wrapping div.
1530  *   - title_attributes: A string containing the attributes for the title.
1531  */
1532 function template_preprocess_field(&$variables, $hook) {
1533   $element = $variables['element'];
1534
1535   // Creating variables for the template.
1536   $variables['entity_type'] = $element['#entity_type'];
1537   $variables['field_name'] = $element['#field_name'];
1538   $variables['field_type'] = $element['#field_type'];
1539   $variables['label_display'] = $element['#label_display'];
1540
1541   $variables['label_hidden'] = ($element['#label_display'] == 'hidden');
1542   // Always set the field label - allow themes to decide whether to display it.
1543   // In addition the label should be rendered but hidden to support screen
1544   // readers.
1545   $variables['label'] = $element['#title'];
1546
1547   $variables['multiple'] = $element['#is_multiple'];
1548
1549   static $default_attributes;
1550   if (!isset($default_attributes)) {
1551     $default_attributes = new Attribute();
1552   }
1553
1554   // Merge attributes when a single-value field has a hidden label.
1555   if ($element['#label_display'] == 'hidden' && !$variables['multiple'] && !empty($element['#items'][0]->_attributes)) {
1556     $variables['attributes'] = NestedArray::mergeDeep($variables['attributes'], (array) $element['#items'][0]->_attributes);
1557   }
1558
1559   // We want other preprocess functions and the theme implementation to have
1560   // fast access to the field item render arrays. The item render array keys
1561   // (deltas) should always be numerically indexed starting from 0, and looping
1562   // on those keys is faster than calling Element::children() or looping on all
1563   // keys within $element, since that requires traversal of all element
1564   // properties.
1565   $variables['items'] = [];
1566   $delta = 0;
1567   while (!empty($element[$delta])) {
1568     $variables['items'][$delta]['content'] = $element[$delta];
1569
1570     // Modules (e.g., rdf.module) can add field item attributes (to
1571     // $item->_attributes) within hook_entity_prepare_view(). Some field
1572     // formatters move those attributes into some nested formatter-specific
1573     // element in order have them rendered on the desired HTML element (e.g., on
1574     // the <a> element of a field item being rendered as a link). Other field
1575     // formatters leave them within $element['#items'][$delta]['_attributes'] to
1576     // be rendered on the item wrappers provided by field.html.twig.
1577     $variables['items'][$delta]['attributes'] = !empty($element['#items'][$delta]->_attributes) ? new Attribute($element['#items'][$delta]->_attributes) : clone($default_attributes);
1578     $delta++;
1579   }
1580 }
1581
1582 /**
1583  * Prepares variables for individual form element templates.
1584  *
1585  * Default template: field-multiple-value-form.html.twig.
1586  *
1587  * Combines multiple values into a table with drag-n-drop reordering.
1588  *
1589  * @param array $variables
1590  *   An associative array containing:
1591  *   - element: A render element representing the form element.
1592  */
1593 function template_preprocess_field_multiple_value_form(&$variables) {
1594   $element = $variables['element'];
1595   $variables['multiple'] = $element['#cardinality_multiple'];
1596
1597   if ($variables['multiple']) {
1598     $table_id = Html::getUniqueId($element['#field_name'] . '_values');
1599     $order_class = $element['#field_name'] . '-delta-order';
1600     $header_attributes = new Attribute(['class' => ['label']]);
1601     if (!empty($element['#required'])) {
1602       $header_attributes['class'][] = 'js-form-required';
1603       $header_attributes['class'][] = 'form-required';
1604     }
1605     $header = [
1606       [
1607         'data' => [
1608           '#prefix' => '<h4' . $header_attributes . '>',
1609           '#markup' => $element['#title'],
1610           '#suffix' => '</h4>',
1611         ],
1612         'colspan' => 2,
1613         'class' => ['field-label'],
1614       ],
1615       t('Order', [], ['context' => 'Sort order']),
1616     ];
1617     $rows = [];
1618
1619     // Sort items according to '_weight' (needed when the form comes back after
1620     // preview or failed validation).
1621     $items = [];
1622     $variables['button'] = [];
1623     foreach (Element::children($element) as $key) {
1624       if ($key === 'add_more') {
1625         $variables['button'] = &$element[$key];
1626       }
1627       else {
1628         $items[] = &$element[$key];
1629       }
1630     }
1631     usort($items, '_field_multiple_value_form_sort_helper');
1632
1633     // Add the items as table rows.
1634     foreach ($items as $item) {
1635       $item['_weight']['#attributes']['class'] = [$order_class];
1636
1637       // Remove weight form element from item render array so it can be rendered
1638       // in a separate table column.
1639       $delta_element = $item['_weight'];
1640       unset($item['_weight']);
1641
1642       $cells = [
1643         ['data' => '', 'class' => ['field-multiple-drag']],
1644         ['data' => $item],
1645         ['data' => $delta_element, 'class' => ['delta-order']],
1646       ];
1647       $rows[] = [
1648         'data' => $cells,
1649         'class' => ['draggable'],
1650       ];
1651     }
1652
1653     $variables['table'] = [
1654       '#type' => 'table',
1655       '#header' => $header,
1656       '#rows' => $rows,
1657       '#attributes' => [
1658         'id' => $table_id,
1659         'class' => ['field-multiple-table'],
1660       ],
1661       '#tabledrag' => [
1662         [
1663           'action' => 'order',
1664           'relationship' => 'sibling',
1665           'group' => $order_class,
1666         ],
1667       ],
1668     ];
1669
1670     if (!empty($element['#description'])) {
1671       $description_id = $element['#attributes']['aria-describedby'];
1672       $description_attributes['id'] = $description_id;
1673       $variables['description']['attributes'] = new Attribute($description_attributes);
1674       $variables['description']['content'] = $element['#description'];
1675
1676       // Add the description's id to the table aria attributes.
1677       $variables['table']['#attributes']['aria-describedby'] = $element['#attributes']['aria-describedby'];
1678     }
1679   }
1680   else {
1681     $variables['elements'] = [];
1682     foreach (Element::children($element) as $key) {
1683       $variables['elements'][] = $element[$key];
1684     }
1685   }
1686 }
1687
1688 /**
1689  * Prepares variables for breadcrumb templates.
1690  *
1691  * Default template: breadcrumb.html.twig.
1692  *
1693  * @param array $variables
1694  *   An associative array containing:
1695  *   - links: A list of \Drupal\Core\Link objects which should be rendered.
1696  */
1697 function template_preprocess_breadcrumb(&$variables) {
1698   $variables['breadcrumb'] = [];
1699   /** @var \Drupal\Core\Link $link */
1700   foreach ($variables['links'] as $key => $link) {
1701     $variables['breadcrumb'][$key] = ['text' => $link->getText(), 'url' => $link->getUrl()->toString()];
1702   }
1703 }
1704
1705 /**
1706  * Callback for usort() within template_preprocess_field_multiple_value_form().
1707  *
1708  * Sorts using ['_weight']['#value']
1709  */
1710 function _field_multiple_value_form_sort_helper($a, $b) {
1711   $a_weight = (is_array($a) && isset($a['_weight']['#value']) ? $a['_weight']['#value'] : 0);
1712   $b_weight = (is_array($b) && isset($b['_weight']['#value']) ? $b['_weight']['#value'] : 0);
1713   return $a_weight - $b_weight;
1714 }
1715
1716 /**
1717  * Provides theme registration for themes across .inc files.
1718  */
1719 function drupal_common_theme() {
1720   return [
1721     // From theme.inc.
1722     'html' => [
1723       'render element' => 'html',
1724     ],
1725     'page' => [
1726       'render element' => 'page',
1727     ],
1728     'page_title' => [
1729       'variables' => ['title' => NULL],
1730     ],
1731     'region' => [
1732       'render element' => 'elements',
1733     ],
1734     'time' => [
1735       'variables' => ['timestamp' => NULL, 'text' => NULL, 'attributes' => []],
1736     ],
1737     'datetime_form' => [
1738       'render element' => 'element',
1739     ],
1740     'datetime_wrapper' => [
1741       'render element' => 'element',
1742     ],
1743     'status_messages' => [
1744       'variables' => ['status_headings' => [], 'message_list' => NULL],
1745     ],
1746     'links' => [
1747       'variables' => ['links' => [], 'attributes' => ['class' => ['links']], 'heading' => [], 'set_active_class' => FALSE],
1748     ],
1749     'dropbutton_wrapper' => [
1750       'variables' => ['children' => NULL],
1751     ],
1752     'image' => [
1753       // HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft
1754       // allows the alt attribute to be omitted in some cases. Therefore,
1755       // default the alt attribute to an empty string, but allow code providing
1756       // variables to image.html.twig templates to pass explicit NULL for it to
1757       // be omitted. Usually, neither omission nor an empty string satisfies
1758       // accessibility requirements, so it is strongly encouraged for code
1759       // building variables for image.html.twig templates to pass a meaningful
1760       // value for the alt variable.
1761       // - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
1762       // - http://www.w3.org/TR/xhtml1/dtds.html
1763       // - http://dev.w3.org/html5/spec/Overview.html#alt
1764       // The title attribute is optional in all cases, so it is omitted by
1765       // default.
1766       'variables' => ['uri' => NULL, 'width' => NULL, 'height' => NULL, 'alt' => '', 'title' => NULL, 'attributes' => [], 'sizes' => NULL, 'srcset' => [], 'style_name' => NULL],
1767     ],
1768     'breadcrumb' => [
1769       'variables' => ['links' => []],
1770     ],
1771     'table' => [
1772       'variables' => ['header' => NULL, 'rows' => NULL, 'footer' => NULL, 'attributes' => [], 'caption' => NULL, 'colgroups' => [], 'sticky' => FALSE, 'responsive' => TRUE, 'empty' => ''],
1773     ],
1774     'tablesort_indicator' => [
1775       'variables' => ['style' => NULL],
1776     ],
1777     'mark' => [
1778       'variables' => ['status' => MARK_NEW],
1779     ],
1780     'item_list' => [
1781       'variables' => ['items' => [], 'title' => '', 'list_type' => 'ul', 'wrapper_attributes' => [], 'attributes' => [], 'empty' => NULL, 'context' => []],
1782     ],
1783     'feed_icon' => [
1784       'variables' => ['url' => NULL, 'title' => NULL],
1785     ],
1786     'progress_bar' => [
1787       'variables' => ['label' => NULL, 'percent' => NULL, 'message' => NULL],
1788     ],
1789     'indentation' => [
1790       'variables' => ['size' => 1],
1791     ],
1792     // From theme.maintenance.inc.
1793     'maintenance_page' => [
1794       'render element' => 'page',
1795     ],
1796     'install_page' => [
1797       'render element' => 'page',
1798     ],
1799     'maintenance_task_list' => [
1800       'variables' => ['items' => NULL, 'active' => NULL, 'variant' => NULL],
1801     ],
1802     'authorize_report' => [
1803       'variables' => ['messages' => [], 'attributes' => []],
1804       'includes' => ['core/includes/theme.maintenance.inc'],
1805       'template' => 'authorize-report',
1806     ],
1807     // From pager.inc.
1808     'pager' => [
1809       'render element' => 'pager',
1810     ],
1811     // From menu.inc.
1812     'menu' => [
1813       'variables' => ['menu_name' => NULL, 'items' => [], 'attributes' => []],
1814     ],
1815     'menu_local_task' => [
1816       'render element' => 'element',
1817     ],
1818     'menu_local_action' => [
1819       'render element' => 'element',
1820     ],
1821     'menu_local_tasks' => [
1822       'variables' => ['primary' => [], 'secondary' => []],
1823     ],
1824     // From form.inc.
1825     'input' => [
1826       'render element' => 'element',
1827     ],
1828     'select' => [
1829       'render element' => 'element',
1830     ],
1831     'fieldset' => [
1832       'render element' => 'element',
1833     ],
1834     'details' => [
1835       'render element' => 'element',
1836     ],
1837     'radios' => [
1838       'render element' => 'element',
1839     ],
1840     'checkboxes' => [
1841       'render element' => 'element',
1842     ],
1843     'form' => [
1844       'render element' => 'element',
1845     ],
1846     'textarea' => [
1847       'render element' => 'element',
1848     ],
1849     'form_element' => [
1850       'render element' => 'element',
1851     ],
1852     'form_element_label' => [
1853       'render element' => 'element',
1854     ],
1855     'vertical_tabs' => [
1856       'render element' => 'element',
1857     ],
1858     'container' => [
1859       'render element' => 'element',
1860     ],
1861     // From field system.
1862     'field' => [
1863       'render element' => 'element',
1864     ],
1865     'field_multiple_value_form' => [
1866       'render element' => 'element',
1867     ],
1868   ];
1869 }