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

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