get(); } else { return $theme_registry->getRuntime(); } } /** * Returns an array of default theme features. * * @see \Drupal\Core\Extension\ThemeExtensionList::$defaults */ 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 = []; if (is_dir($path)) { $files = \Drupal::service('file_system')->scanDirectory($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'])) { if ($template === $info['template']) { $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')) { $logo = \Drupal::service('theme.initialization')->getActiveThemeByName($theme)->getLogo(); $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo))); } 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 \Drupal\Core\Config\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.' . mb_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) { /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ $date_formatter = \Drupal::service('date.formatter'); // 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'] = $date_formatter->format($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'] = $date_formatter->format($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']; // If the element title is a string, wrap it a render array so that markup // will not be escaped (but XSS-filtered). if (is_string($variables['title']) && $variables['title'] !== '') { $variables['title'] = ['#markup' => $variables['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 itself is an array, with * the following elements: * - title: The link text. * - url: (optional) The \Drupal\Core\Url object to link to. If the 'url' * element is supplied, the 'title' and 'url' are used to generate a link * through \Drupal::linkGenerator()->generate(). All data from the link * array other than 'title' and 'url' are added as #options on * the URL object. See \Drupal\Core\Url::fromUri() for details on the * options. If no 'url' is supplied, the 'title' is printed as plain text. * - attributes: (optional) Attributes for the anchor, or for the * tag used in its place if no 'url' is supplied. If element 'class' is * included, it must be an array of one or more class names. * - attributes: A keyed array of attributes for the