get(); } else { return $theme_registry->getRuntime(); } } /** * Returns an array of default theme features. * * @see \Drupal\Core\Extension\ThemeHandler::$defaultFeatures */ function _system_default_theme_features() { return [ 'favicon', 'logo', 'node_user_picture', 'comment_user_picture', 'comment_user_verification', ]; } /** * Forces the system to rebuild the theme registry. * * This function should be called when modules are added to the system, or when * a dynamic system needs to add more theme hooks. */ function drupal_theme_rebuild() { \Drupal::service('theme.registry')->reset(); } /** * Allows themes and/or theme engines to discover overridden theme functions. * * @param array $cache * The existing cache of theme hooks to test against. * @param array $prefixes * An array of prefixes to test, in reverse order of importance. * * @return array * The functions found, suitable for returning from hook_theme; */ function drupal_find_theme_functions($cache, $prefixes) { $implementations = []; $grouped_functions = \Drupal::service('theme.registry')->getPrefixGroupedUserFunctions($prefixes); foreach ($cache as $hook => $info) { foreach ($prefixes as $prefix) { // Find theme functions that implement possible "suggestion" variants of // registered theme hooks and add those as new registered theme hooks. // The 'pattern' key defines a common prefix that all suggestions must // start with. The default is the name of the hook followed by '__'. An // 'base hook' key is added to each entry made for a found suggestion, // so that common functionality can be implemented for all suggestions of // the same base hook. To keep things simple, deep hierarchy of // suggestions is not supported: each suggestion's 'base hook' key // refers to a base hook, not to another suggestion, and all suggestions // are found using the base hook's pattern, not a pattern from an // intermediary suggestion. $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); // Grep only the functions which are within the prefix group. list($first_prefix,) = explode('_', $prefix, 2); if (!isset($info['base hook']) && !empty($pattern) && isset($grouped_functions[$first_prefix])) { $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $grouped_functions[$first_prefix]); if ($matches) { foreach ($matches as $match) { $new_hook = substr($match, strlen($prefix) + 1); $arg_name = isset($info['variables']) ? 'variables' : 'render element'; $implementations[$new_hook] = [ 'function' => $match, $arg_name => $info[$arg_name], 'base hook' => $hook, ]; } } } // Find theme functions that implement registered theme hooks and include // that in what is returned so that the registry knows that the theme has // this implementation. if (function_exists($prefix . '_' . $hook)) { $implementations[$hook] = [ 'function' => $prefix . '_' . $hook, ]; } } } return $implementations; } /** * Allows themes and/or theme engines to easily discover overridden templates. * * @param $cache * The existing cache of theme hooks to test against. * @param $extension * The extension that these templates will have. * @param $path * The path to search. */ function drupal_find_theme_templates($cache, $extension, $path) { $implementations = []; // Collect paths to all sub-themes grouped by base themes. These will be // used for filtering. This allows base themes to have sub-themes in its // folder hierarchy without affecting the base themes template discovery. $theme_paths = []; foreach (\Drupal::service('theme_handler')->listInfo() as $theme_info) { if (!empty($theme_info->base_theme)) { $theme_paths[$theme_info->base_theme][$theme_info->getName()] = $theme_info->getPath(); } } foreach ($theme_paths as $basetheme => $subthemes) { foreach ($subthemes as $subtheme => $subtheme_path) { if (isset($theme_paths[$subtheme])) { $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]); } } } $theme = \Drupal::theme()->getActiveTheme()->getName(); $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : []; // Escape the periods in the extension. $regex = '/' . str_replace('.', '\.', $extension) . '$/'; // Get a listing of all template files in the path to search. $files = file_scan_directory($path, $regex, ['key' => 'filename']); // Find templates that implement registered theme hooks and include that in // what is returned so that the registry knows that the theme has this // implementation. foreach ($files as $template => $file) { // Ignore sub-theme templates for the current theme. if (strpos($file->uri, str_replace($subtheme_paths, '', $file->uri)) !== 0) { continue; } // Remove the extension from the filename. $template = str_replace($extension, '', $template); // Transform - in filenames to _ to match function naming scheme // for the purposes of searching. $hook = strtr($template, '-', '_'); if (isset($cache[$hook])) { $implementations[$hook] = [ 'template' => $template, 'path' => dirname($file->uri), ]; } // Match templates based on the 'template' filename. foreach ($cache as $hook => $info) { if (isset($info['template'])) { $template_candidates = [$info['template'], str_replace($info['theme path'] . '/templates/', '', $info['template'])]; if (in_array($template, $template_candidates)) { $implementations[$hook] = [ 'template' => $template, 'path' => dirname($file->uri), ]; } } } } // Find templates that implement possible "suggestion" variants of registered // theme hooks and add those as new registered theme hooks. See // drupal_find_theme_functions() for more information about suggestions and // the use of 'pattern' and 'base hook'. $patterns = array_keys($files); foreach ($cache as $hook => $info) { $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); if (!isset($info['base hook']) && !empty($pattern)) { // Transform _ in pattern to - to match file naming scheme // for the purposes of searching. $pattern = strtr($pattern, '_', '-'); $matches = preg_grep('/^' . $pattern . '/', $patterns); if ($matches) { foreach ($matches as $match) { $file = $match; // Remove the extension from the filename. $file = str_replace($extension, '', $file); // Put the underscores back in for the hook name and register this // pattern. $arg_name = isset($info['variables']) ? 'variables' : 'render element'; $implementations[strtr($file, '-', '_')] = [ 'template' => $file, 'path' => dirname($files[$match]->uri), $arg_name => $info[$arg_name], 'base hook' => $hook, ]; } } } } return $implementations; } /** * Retrieves a setting for the current theme or for a given theme. * * The final setting is obtained from the last value found in the following * sources: * - the saved values from the global theme settings form * - the saved values from the theme's settings form * To only retrieve the default global theme setting, an empty string should be * given for $theme. * * @param $setting_name * The name of the setting to be retrieved. * @param $theme * The name of a given theme; defaults to the current theme. * * @return * The value of the requested setting, NULL if the setting does not exist. */ function theme_get_setting($setting_name, $theme = NULL) { /** @var \Drupal\Core\Theme\ThemeSettings[] $cache */ $cache = &drupal_static(__FUNCTION__, []); // If no key is given, use the current theme if we can determine it. if (!isset($theme)) { $theme = \Drupal::theme()->getActiveTheme()->getName(); } if (empty($cache[$theme])) { // Create a theme settings object. $cache[$theme] = new ThemeSettings($theme); // Get the global settings from configuration. $cache[$theme]->setData(\Drupal::config('system.theme.global')->get()); // Get the values for the theme-specific settings from the .info.yml files // of the theme and all its base themes. $themes = \Drupal::service('theme_handler')->listInfo(); if (isset($themes[$theme])) { $theme_object = $themes[$theme]; // Retrieve configured theme-specific settings, if any. try { if ($theme_settings = \Drupal::config($theme . '.settings')->get()) { $cache[$theme]->merge($theme_settings); } } catch (StorageException $e) { } // If the theme does not support a particular feature, override the global // setting and set the value to NULL. if (!empty($theme_object->info['features'])) { foreach (_system_default_theme_features() as $feature) { if (!in_array($feature, $theme_object->info['features'])) { $cache[$theme]->set('features.' . $feature, NULL); } } } // Generate the path to the logo image. if ($cache[$theme]->get('logo.use_default')) { $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($theme_object->getPath() . '/logo.svg'))); } elseif ($logo_path = $cache[$theme]->get('logo.path')) { $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo_path))); } // Generate the path to the favicon. if ($cache[$theme]->get('features.favicon')) { $favicon_path = $cache[$theme]->get('favicon.path'); if ($cache[$theme]->get('favicon.use_default')) { if (file_exists($favicon = $theme_object->getPath() . '/favicon.ico')) { $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon))); } else { $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url('core/misc/favicon.ico'))); } } elseif ($favicon_path) { $cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon_path))); } else { $cache[$theme]->set('features.favicon', FALSE); } } } } return $cache[$theme]->get($setting_name); } /** * Escapes and renders variables for theme functions. * * This method is used in theme functions to ensure that the result is safe for * output inside HTML fragments. This mimics the behavior of the auto-escape * functionality in Twig. * * Note: This function should be kept in sync with * \Drupal\Core\Template\TwigExtension::escapeFilter(). * * @param mixed $arg * The string, object, or render array to escape if needed. * * @return string * The rendered string, safe for use in HTML. The string is not safe when used * as any part of an HTML attribute name or value. * * @throws \Exception * Thrown when an object is passed in which cannot be printed. * * @see \Drupal\Core\Template\TwigExtension::escapeFilter() * * @todo Discuss deprecating this in https://www.drupal.org/node/2575081. * @todo Refactor this to keep it in sync with Twig filtering in * https://www.drupal.org/node/2575065 */ function theme_render_and_autoescape($arg) { // If it's a renderable, then it'll be up to the generated render array it // returns to contain the necessary cacheability & attachment metadata. If // it doesn't implement CacheableDependencyInterface or AttachmentsInterface // then there is nothing to do here. if (!($arg instanceof RenderableInterface) && ($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) { $arg_bubbleable = []; BubbleableMetadata::createFromObject($arg) ->applyTo($arg_bubbleable); \Drupal::service('renderer')->render($arg_bubbleable); } if ($arg instanceof MarkupInterface) { return (string) $arg; } $return = NULL; if (is_scalar($arg)) { $return = (string) $arg; } elseif (is_object($arg)) { if ($arg instanceof RenderableInterface) { $arg = $arg->toRenderable(); } elseif (method_exists($arg, '__toString')) { $return = (string) $arg; } // You can't throw exceptions in the magic PHP __toString methods, see // http://php.net/manual/language.oop5.magic.php#object.tostring so // we also support a toString method. elseif (method_exists($arg, 'toString')) { $return = $arg->toString(); } else { throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.'); } } // We have a string or an object converted to a string: Escape it! if (isset($return)) { return $return instanceof MarkupInterface ? $return : Html::escape($return); } // This is a normal render array, which is safe by definition, with special // simple cases already handled. // Early return if this element was pre-rendered (no need to re-render). if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) { return (string) $arg['#markup']; } $arg['#printed'] = FALSE; return (string) \Drupal::service('renderer')->render($arg); } /** * Converts theme settings to configuration. * * @see system_theme_settings_submit() * * @param array $theme_settings * An array of theme settings from system setting form or a Drupal 7 variable. * @param Config $config * The configuration object to update. * * @return * The Config object with updated data. */ function theme_settings_convert_to_config(array $theme_settings, Config $config) { foreach ($theme_settings as $key => $value) { if ($key == 'default_logo') { $config->set('logo.use_default', $value); } elseif ($key == 'logo_path') { $config->set('logo.path', $value); } elseif ($key == 'default_favicon') { $config->set('favicon.use_default', $value); } elseif ($key == 'favicon_path') { $config->set('favicon.path', $value); } elseif ($key == 'favicon_mimetype') { $config->set('favicon.mimetype', $value); } elseif (substr($key, 0, 7) == 'toggle_') { $config->set('features.' . Unicode::substr($key, 7), $value); } elseif (!in_array($key, ['theme', 'logo_upload'])) { $config->set($key, $value); } } return $config; } /** * Prepares variables for time templates. * * Default template: time.html.twig. * * @param array $variables * An associative array possibly containing: * - attributes['timestamp']: * - timestamp: * - text: */ function template_preprocess_time(&$variables) { // Format the 'datetime' attribute based on the timestamp. // @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime if (!isset($variables['attributes']['datetime']) && isset($variables['timestamp'])) { $variables['attributes']['datetime'] = format_date($variables['timestamp'], 'html_datetime', '', 'UTC'); } // If no text was provided, try to auto-generate it. if (!isset($variables['text'])) { // Format and use a human-readable version of the timestamp, if any. if (isset($variables['timestamp'])) { $variables['text'] = format_date($variables['timestamp']); } // Otherwise, use the literal datetime attribute. elseif (isset($variables['attributes']['datetime'])) { $variables['text'] = $variables['attributes']['datetime']; } } } /** * Prepares variables for datetime form element templates. * * The datetime form element serves as a wrapper around the date element type, * which creates a date and a time component for a date. * * Default template: datetime-form.html.twig. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #value, #options, #description, #required, * #attributes. * * @see form_process_datetime() */ function template_preprocess_datetime_form(&$variables) { $element = $variables['element']; $variables['attributes'] = []; if (isset($element['#id'])) { $variables['attributes']['id'] = $element['#id']; } if (!empty($element['#attributes']['class'])) { $variables['attributes']['class'] = (array) $element['#attributes']['class']; } $variables['content'] = $element; } /** * Prepares variables for datetime form wrapper templates. * * Default template: datetime-wrapper.html.twig. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #children, #required, #attributes. */ function template_preprocess_datetime_wrapper(&$variables) { $element = $variables['element']; if (!empty($element['#title'])) { $variables['title'] = $element['#title']; } // Suppress error messages. $variables['errors'] = NULL; $variables['description'] = NULL; if (!empty($element['#description'])) { $description_attributes = []; if (!empty($element['#id'])) { $description_attributes['id'] = $element['#id'] . '--description'; } $variables['description'] = $element['#description']; $variables['description_attributes'] = new Attribute($description_attributes); } $variables['required'] = FALSE; // For required datetime fields 'form-required' & 'js-form-required' classes // are appended to the label attributes. if (!empty($element['#required'])) { $variables['required'] = TRUE; } $variables['content'] = $element['#children']; } /** * Prepares variables for links templates. * * Default template: links.html.twig. * * Unfortunately links templates duplicate the "active" class handling of l() * and LinkGenerator::generate() because it needs to be able to set the "active" * class not on the links themselves ( tags), but on the list items (
  • * tags) that contain the links. This is necessary for CSS to be able to style * list items differently when the link is active, since CSS does not yet allow * one to style list items only if it contains a certain element with a certain * class. I.e. we cannot yet convert this jQuery selector to a CSS selector: * jQuery('li:has("a.is-active")') * * @param array $variables * An associative array containing: * - links: An array of links to be themed. Each link should be itself an * array, with the following elements: * - title: The link text. * - url: (optional) The \Drupal\Core\Url object to link to. If omitted, no * anchor tag is printed out. * - attributes: (optional) Attributes for the anchor, or for the * tag used in its place if no 'href' is supplied. If element 'class' is * included, it must be an array of one or more class names. * If the 'href' element is supplied, the entire link array is passed to * l() as its $options parameter. * - attributes: A keyed array of attributes for the
      containing the list * of links. * - set_active_class: (optional) Whether each link should compare the * route_name + route_parameters or href (path), language and query options * to the current URL, to determine whether the link is "active". If so, an * "active" class will be applied to the list item containing the link, as * well as the link itself. It is important to use this sparingly since it * is usually unnecessary and requires extra processing. * For anonymous users, the "active" class will be calculated on the server, * because most sites serve each anonymous user the same cached page anyway. * For authenticated users, the "active" class will be calculated on the * client (through JavaScript), only data- attributes are added to list * items and contained links, to prevent breaking the render cache. The * JavaScript is added in system_page_attachments(). * - heading: (optional) A heading to precede the links. May be an * associative array or a string. If it's an array, it can have the * following elements: * - text: The heading text. * - level: The heading level (e.g. 'h2', 'h3'). * - attributes: (optional) An array of the CSS attributes for the heading. * When using a string it will be used as the text of the heading and the * level will default to 'h2'. Headings should be used on navigation menus * and any list of links that consistently appears on multiple pages. To * make the heading invisible use the 'visually-hidden' CSS class. Do not * use 'display:none', which removes it from screen readers and assistive * technology. Headings allow screen reader and keyboard only users to * navigate to or skip the links. See * http://juicystudio.com/article/screen-readers-display-none.php and * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. * * @see \Drupal\Core\Utility\LinkGenerator * @see \Drupal\Core\Utility\LinkGenerator::generate() * @see system_page_attachments() */ function template_preprocess_links(&$variables) { $links = $variables['links']; $heading = &$variables['heading']; if (!empty($links)) { // Prepend the heading to the list, if any. if (!empty($heading)) { // Convert a string heading into an array, using a

      tag by default. if (is_string($heading)) { $heading = ['text' => $heading]; } // Merge in default array properties into $heading. $heading += [ 'level' => 'h2', 'attributes' => [], ]; // Convert the attributes array into an Attribute object. $heading['attributes'] = new Attribute($heading['attributes']); } $variables['links'] = []; foreach ($links as $key => $link) { $item = []; $link += [ 'ajax' => NULL, 'url' => NULL, ]; $li_attributes = []; $keys = ['title', 'url']; $link_element = [ '#type' => 'link', '#title' => $link['title'], '#options' => array_diff_key($link, array_combine($keys, $keys)), '#url' => $link['url'], '#ajax' => $link['ajax'], ]; // Handle links and ensure that the active class is added on the LIs, but // only if the 'set_active_class' option is not empty. if (isset($link['url'])) { if (!empty($variables['set_active_class'])) { // Also enable set_active_class for the contained link. $link_element['#options']['set_active_class'] = TRUE; if (!empty($link['language'])) { $li_attributes['hreflang'] = $link['language']->getId(); } // Add a "data-drupal-link-query" attribute to let the // drupal.active-link library know the query in a standardized manner. if (!empty($link['query'])) { $query = $link['query']; ksort($query); $li_attributes['data-drupal-link-query'] = Json::encode($query); } /** @var \Drupal\Core\Url $url */ $url = $link['url']; if ($url->isRouted()) { // Add a "data-drupal-link-system-path" attribute to let the // drupal.active-link library know the path in a standardized manner. $system_path = $url->getInternalPath(); // @todo System path is deprecated - use the route name and parameters. // Special case for the front page. $li_attributes['data-drupal-link-system-path'] = $system_path == '' ? '' : $system_path; } } $item['link'] = $link_element; } // Handle title-only text items. $item['text'] = $link['title']; if (isset($link['attributes'])) { $item['text_attributes'] = new Attribute($link['attributes']); } // Handle list item attributes. $item['attributes'] = new Attribute($li_attributes); // Add the item to the list of links. $variables['links'][$key] = $item; } } } /** * Prepares variables for image templates. * * Default template: image.html.twig. * * @param array $variables * An associative array containing: * - uri: Either the path of the image file (relative to base_path()) or a * full URL. * - width: The width of the image (if known). * - height: The height of the image (if known). * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0 * always require an alt attribute. The HTML 5 draft allows the alt * attribute to be omitted in some cases. Therefore, this variable defaults * to an empty string, but can be set to NULL for the attribute to be * omitted. Usually, neither omission nor an empty string satisfies * accessibility requirements, so it is strongly encouraged for code * building variables for image.html.twig templates to pass a meaningful * value for this variable. * - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 * - http://www.w3.org/TR/xhtml1/dtds.html * - http://dev.w3.org/html5/spec/Overview.html#alt * - title: The title text is displayed when the image is hovered in some * popular browsers. * - attributes: Associative array of attributes to be placed in the img tag. * - srcset: Array of multiple URIs and sizes/multipliers. * - sizes: The sizes attribute for viewport-based selection of images. * - http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2 */ function template_preprocess_image(&$variables) { if (!empty($variables['uri'])) { $variables['attributes']['src'] = file_url_transform_relative(file_create_url($variables['uri'])); } // Generate a srcset attribute conforming to the spec at // http://www.w3.org/html/wg/drafts/html/master/embedded-content.html#attr-img-srcset if (!empty($variables['srcset'])) { $srcset = []; foreach ($variables['srcset'] as $src) { // URI is mandatory. $source = file_url_transform_relative(file_create_url($src['uri'])); if (isset($src['width']) && !empty($src['width'])) { $source .= ' ' . $src['width']; } elseif (isset($src['multiplier']) && !empty($src['multiplier'])) { $source .= ' ' . $src['multiplier']; } $srcset[] = $source; } $variables['attributes']['srcset'] = implode(', ', $srcset); } foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) { if (isset($variables[$key])) { // If the property has already been defined in the attributes, // do not override, including NULL. if (array_key_exists($key, $variables['attributes'])) { continue; } $variables['attributes'][$key] = $variables[$key]; } } } /** * Prepares variables for table templates. * * Default template: table.html.twig. * * @param array $variables * An associative array containing: * - header: An array containing the table headers. Each element of the array * can be either a localized string or an associative array with the * following keys: * - data: The localized title of the table column, as a string or render * array. * - field: The database field represented in the table column (required * if user is to be able to sort on this column). * - sort: A default sort order for this column ("asc" or "desc"). Only * one column should be given a default sort order because table sorting * only applies to one column at a time. * - class: An array of values for the 'class' attribute. In particular, * the least important columns that can be hidden on narrow and medium * width screens should have a 'priority-low' class, referenced with the * RESPONSIVE_PRIORITY_LOW constant. Columns that should be shown on * medium+ wide screens should be marked up with a class of * 'priority-medium', referenced by with the RESPONSIVE_PRIORITY_MEDIUM * constant. Themes may hide columns with one of these two classes on * narrow viewports to save horizontal space. * - Any HTML attributes, such as "colspan", to apply to the column header * cell. * - rows: An array of table rows. Every row is an array of cells, or an * associative array with the following keys: * - data: An array of cells. * - Any HTML attributes, such as "class", to apply to the table row. * - no_striping: A Boolean indicating that the row should receive no * 'even / odd' styling. Defaults to FALSE. * Each cell can be either a string or an associative array with the * following keys: * - data: The string or render array to display in the table cell. * - header: Indicates this cell is a header. * - Any HTML attributes, such as "colspan", to apply to the table cell. * Here's an example for $rows: * @code * $rows = array( * // Simple row * array( * 'Cell 1', 'Cell 2', 'Cell 3' * ), * // Row with attributes on the row and some of its cells. * array( * 'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => array('funky') * ), * ); * @endcode * - footer: An array of table rows which will be printed within a * tag, in the same format as the rows element (see above). * - attributes: An array of HTML attributes to apply to the table tag. * - caption: A localized string to use for the tag. * - colgroups: An array of column groups. Each element of the array can be * either: * - An array of columns, each of which is an associative array of HTML * attributes applied to the element. * - An array of attributes applied to the element, which must * include a "data" attribute. To add attributes to elements, * set the "data" attribute with an array of columns, each of which is an * associative array of HTML attributes. * Here's an example for $colgroup: * @code * $colgroup = array( * // with one element. * array( * array( * 'class' => array('funky'), // Attribute for the element. * ), * ), * // with attributes and inner elements. * array( * 'data' => array( * array( * 'class' => array('funky'), // Attribute for the element. * ), * ), * 'class' => array('jazzy'), // Attribute for the element. * ), * ); * @endcode * These optional tags are used to group and set properties on columns * within a table. For example, one may easily group three columns and * apply same background style to all. * - sticky: Use a "sticky" table header. * - empty: The message to display in an extra row if table does not have any * rows. */ function template_preprocess_table(&$variables) { // Format the table columns: if (!empty($variables['colgroups'])) { foreach ($variables['colgroups'] as &$colgroup) { // Check if we're dealing with a simple or complex column if (isset($colgroup['data'])) { $cols = $colgroup['data']; unset($colgroup['data']); $colgroup_attributes = $colgroup; } else { $cols = $colgroup; $colgroup_attributes = []; } $colgroup = []; $colgroup['attributes'] = new Attribute($colgroup_attributes); $colgroup['cols'] = []; // Build columns. if (is_array($cols) && !empty($cols)) { foreach ($cols as $col_key => $col) { $colgroup['cols'][$col_key]['attributes'] = new Attribute($col); } } } } // Build an associative array of responsive classes keyed by column. $responsive_classes = []; // Format the table header: $ts = []; $header_columns = 0; if (!empty($variables['header'])) { $ts = tablesort_init($variables['header']); // Use a separate index with responsive classes as headers // may be associative. $responsive_index = -1; foreach ($variables['header'] as $col_key => $cell) { // Increase the responsive index. $responsive_index++; if (!is_array($cell)) { $header_columns++; $cell_content = $cell; $cell_attributes = new Attribute(); $is_header = TRUE; } else { if (isset($cell['colspan'])) { $header_columns += $cell['colspan']; } else { $header_columns++; } $cell_content = ''; if (isset($cell['data'])) { $cell_content = $cell['data']; unset($cell['data']); } // Flag the cell as a header or not and remove the flag. $is_header = isset($cell['header']) ? $cell['header'] : TRUE; unset($cell['header']); // Track responsive classes for each column as needed. Only the header // cells for a column are marked up with the responsive classes by a // module developer or themer. The responsive classes on the header cells // must be transferred to the content cells. if (!empty($cell['class']) && is_array($cell['class'])) { if (in_array(RESPONSIVE_PRIORITY_MEDIUM, $cell['class'])) { $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_MEDIUM; } elseif (in_array(RESPONSIVE_PRIORITY_LOW, $cell['class'])) { $responsive_classes[$responsive_index] = RESPONSIVE_PRIORITY_LOW; } } tablesort_header($cell_content, $cell, $variables['header'], $ts); // tablesort_header() removes the 'sort' and 'field' keys. $cell_attributes = new Attribute($cell); } $variables['header'][$col_key] = []; $variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td'; $variables['header'][$col_key]['attributes'] = $cell_attributes; $variables['header'][$col_key]['content'] = $cell_content; } } $variables['header_columns'] = $header_columns; // Rows and footer have the same structure. $sections = ['rows' , 'footer']; foreach ($sections as $section) { if (!empty($variables[$section])) { foreach ($variables[$section] as $row_key => $row) { $cells = $row; $row_attributes = []; // Check if we're dealing with a simple or complex row if (isset($row['data'])) { $cells = $row['data']; $variables['no_striping'] = isset($row['no_striping']) ? $row['no_striping'] : FALSE; // Set the attributes array and exclude 'data' and 'no_striping'. $row_attributes = $row; unset($row_attributes['data']); unset($row_attributes['no_striping']); } // Build row. $variables[$section][$row_key] = []; $variables[$section][$row_key]['attributes'] = new Attribute($row_attributes); $variables[$section][$row_key]['cells'] = []; if (!empty($cells)) { // Reset the responsive index. $responsive_index = -1; foreach ($cells as $col_key => $cell) { // Increase the responsive index. $responsive_index++; if (!is_array($cell)) { $cell_content = $cell; $cell_attributes = []; $is_header = FALSE; } else { $cell_content = ''; if (isset($cell['data'])) { $cell_content = $cell['data']; unset($cell['data']); } // Flag the cell as a header or not and remove the flag. $is_header = !empty($cell['header']); unset($cell['header']); $cell_attributes = $cell; } // Active table sort information. if (isset($variables['header'][$col_key]['data']) && $variables['header'][$col_key]['data'] == $ts['name'] && !empty($variables['header'][$col_key]['field'])) { $variables[$section][$row_key]['cells'][$col_key]['active_table_sort'] = TRUE; } // Copy RESPONSIVE_PRIORITY_LOW/RESPONSIVE_PRIORITY_MEDIUM // class from header to cell as needed. if (isset($responsive_classes[$responsive_index])) { $cell_attributes['class'][] = $responsive_classes[$responsive_index]; } $variables[$section][$row_key]['cells'][$col_key]['tag'] = $is_header ? 'th' : 'td'; $variables[$section][$row_key]['cells'][$col_key]['attributes'] = new Attribute($cell_attributes); $variables[$section][$row_key]['cells'][$col_key]['content'] = $cell_content; } } } } } if (empty($variables['no_striping'])) { $variables['attributes']['data-striping'] = 1; } } /** * Prepares variables for item list templates. * * Default template: item-list.html.twig. * * @param array $variables * An associative array containing: * - items: An array of items to be displayed in the list. Each item can be * either a string or a render array. If #type, #theme, or #markup * properties are not specified for child render arrays, they will be * inherited from the parent list, allowing callers to specify larger * nested lists without having to explicitly specify and repeat the * render properties for all nested child lists. * - title: A title to be prepended to the list. * - list_type: The type of list to return (e.g. "ul", "ol"). * - wrapper_attributes: HTML attributes to be applied to the list wrapper. * * @see https://www.drupal.org/node/1842756 */ function template_preprocess_item_list(&$variables) { $variables['wrapper_attributes'] = new Attribute($variables['wrapper_attributes']); foreach ($variables['items'] as &$item) { $attributes = []; // If the item value is an array, then it is a render array. if (is_array($item)) { // List items support attributes via the '#wrapper_attributes' property. if (isset($item['#wrapper_attributes'])) { $attributes = $item['#wrapper_attributes']; } // Determine whether there are any child elements in the item that are not // fully-specified render arrays. If there are any, then the child // elements present nested lists and we automatically inherit the render // array properties of the current list to them. foreach (Element::children($item) as $key) { $child = &$item[$key]; // If this child element does not specify how it can be rendered, then // we need to inherit the render properties of the current list. if (!isset($child['#type']) && !isset($child['#theme']) && !isset($child['#markup'])) { // Since item-list.html.twig supports both strings and render arrays // as items, the items of the nested list may have been specified as // the child elements of the nested list, instead of #items. For // convenience, we automatically move them into #items. if (!isset($child['#items'])) { // This is the same condition as in // \Drupal\Core\Render\Element::children(), which cannot be used // here, since it triggers an error on string values. foreach ($child as $child_key => $child_value) { if ($child_key[0] !== '#') { $child['#items'][$child_key] = $child_value; unset($child[$child_key]); } } } // Lastly, inherit the original theme variables of the current list. $child['#theme'] = $variables['theme_hook_original']; $child['#list_type'] = $variables['list_type']; } } } // Set the item's value and attributes for the template. $item = [ 'value' => $item, 'attributes' => new Attribute($attributes), ]; } } /** * Prepares variables for container templates. * * Default template: container.html.twig. * * @param array $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #id, #attributes, #children. */ function template_preprocess_container(&$variables) { $variables['has_parent'] = FALSE; $element = $variables['element']; // Ensure #attributes is set. $element += ['#attributes' => []]; // Special handling for form elements. if (isset($element['#array_parents'])) { // Assign an html ID. if (!isset($element['#attributes']['id'])) { $element['#attributes']['id'] = $element['#id']; } $variables['has_parent'] = TRUE; } $variables['children'] = $element['#children']; $variables['attributes'] = $element['#attributes']; } /** * Prepares variables for maintenance task list templates. * * Default template: maintenance-task-list.html.twig. * * @param array $variables * An associative array containing: * - items: An associative array of maintenance tasks. * It's the caller's responsibility to ensure this array's items contain no * dangerous HTML such as