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@18: * @see \Drupal\Core\Extension\ThemeExtensionList::$defaults 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@17: $logo = \Drupal::service('theme.initialization')->getActiveThemeByName($theme)->getLogo(); Chris@17: $cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo))); 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@12: * @param \Drupal\Core\Config\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@17: $config->set('features.' . mb_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@18: /** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */ Chris@18: $date_formatter = \Drupal::service('date.formatter'); 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@18: $variables['attributes']['datetime'] = $date_formatter->format($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@18: $variables['text'] = $date_formatter->format($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@17: // If the element title is a string, wrap it a render array so that markup Chris@17: // will not be escaped (but XSS-filtered). Chris@17: if (is_string($variables['title']) && $variables['title'] !== '') { Chris@17: $variables['title'] = ['#markup' => $variables['title']]; Chris@17: } 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