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