Newer
Older
Angie Byron
committed
* The theme system, which controls the output of Drupal.
*
* The theme system allows for nearly all output of the Drupal system to be
* customized by user themes.
*/
Alex Pott
committed
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\Html;
Alex Bronstein
committed
use Drupal\Component\Render\MarkupInterface;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Config\Config;
Alex Pott
committed
use Drupal\Core\Config\StorageException;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\BubbleableMetadata;
Alex Pott
committed
use Drupal\Core\Render\RenderableInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\ThemeSettings;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Render\Element;
Alex Bronstein
committed
use Drupal\Core\Render\Markup;
Angie Byron
committed
Dries Buytaert
committed
/**
* @defgroup content_flags Content markers
Dries Buytaert
committed
* @{
* Markers used by mark.html.twig and node_mark() to designate content.
*
* @see mark.html.twig
* @see node_mark()
Dries Buytaert
committed
*/
/**
* Mark content as read.
*/
const MARK_READ = 0;
/**
* Mark content as being new.
*/
const MARK_NEW = 1;
/**
* Mark content as being updated.
*/
const MARK_UPDATED = 2;
/**
* A responsive table class; hide table cell on narrow devices.
*
* Indicates that a column has medium priority and thus can be hidden on narrow
* width devices and shown on medium+ width devices (i.e. tablets and desktops).
*/
const RESPONSIVE_PRIORITY_MEDIUM = 'priority-medium';
/**
* A responsive table class; only show table cell on wide devices.
*
* Indicates that a column has low priority and thus can be hidden on narrow
* and medium viewports and shown on wide devices (i.e. desktops).
*/
const RESPONSIVE_PRIORITY_LOW = 'priority-low';
Dries Buytaert
committed
/**
* @} End of "defgroup content_flags".
Dries Buytaert
committed
*/
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* Gets the theme registry.
Dries Buytaert
committed
*
* @param bool $complete
* Optional boolean to indicate whether to return the complete theme registry
Angie Byron
committed
* array or an instance of the Drupal\Core\Utility\ThemeRegistry class.
* If TRUE, the complete theme registry array will be returned. This is useful
* if you want to foreach over the whole registry, use array_* functions or
* inspect it in a debugger. If FALSE, an instance of the
* Drupal\Core\Utility\ThemeRegistry class will be returned, this provides an
* ArrayObject which allows it to be accessed with array syntax and isset(),
* and should be more lightweight than the full registry. Defaults to TRUE.
*
Angie Byron
committed
* The complete theme registry array, or an instance of the
* Drupal\Core\Utility\ThemeRegistry class.
Dries Buytaert
committed
*/
function theme_get_registry($complete = TRUE) {
$theme_registry = \Drupal::service('theme.registry');
if ($complete) {
return $theme_registry->get();
Dries Buytaert
committed
}
else {
return $theme_registry->getRuntime();
Dries Buytaert
committed
}
}
catch
committed
/**
* Returns an array of default theme features.
*
* @see \Drupal\Core\Extension\ThemeHandler::$defaultFeatures
*/
function _system_default_theme_features() {
return [
catch
committed
'favicon',
'logo',
'node_user_picture',
'comment_user_picture',
'comment_user_verification',
];
catch
committed
}
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* 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.
Dries Buytaert
committed
*/
function drupal_theme_rebuild() {
\Drupal::service('theme.registry')->reset();
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* Allows themes and/or theme engines to discover overridden theme functions.
Dries Buytaert
committed
*
* @param array $cache
Dries Buytaert
committed
* The existing cache of theme hooks to test against.
* @param array $prefixes
Dries Buytaert
committed
* An array of prefixes to test, in reverse order of importance.
*
* @return array
Dries Buytaert
committed
* The functions found, suitable for returning from hook_theme;
*/
function drupal_find_theme_functions($cache, $prefixes) {
$implementations = [];
catch
committed
$grouped_functions = \Drupal::service('theme.registry')->getPrefixGroupedUserFunctions($prefixes);
Dries Buytaert
committed
foreach ($cache as $hook => $info) {
foreach ($prefixes as $prefix) {
Angie Byron
committed
// 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
Dries Buytaert
committed
// the same base hook. To keep things simple, deep hierarchy of
Angie Byron
committed
// 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.
Angie Byron
committed
$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]);
Dries Buytaert
committed
if ($matches) {
foreach ($matches as $match) {
Dries Buytaert
committed
$new_hook = substr($match, strlen($prefix) + 1);
$arg_name = isset($info['variables']) ? 'variables' : 'render element';
$implementations[$new_hook] = [
Dries Buytaert
committed
'function' => $match,
$arg_name => $info[$arg_name],
Angie Byron
committed
'base hook' => $hook,
];
Dries Buytaert
committed
}
}
}
Angie Byron
committed
// 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,
];
Dries Buytaert
committed
}
}
}
Angie Byron
committed
return $implementations;
Dries Buytaert
committed
}
/**
Jennifer Hodgdon
committed
* Allows themes and/or theme engines to easily discover overridden templates.
Dries Buytaert
committed
*
* @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 = [];
Dries Buytaert
committed
// 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]);
}
Gábor Hojtsy
committed
}
}
$theme = \Drupal::theme()->getActiveTheme()->getName();
$subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : [];
Gábor Hojtsy
committed
// Escape the periods in the extension.
$regex = '/' . str_replace('.', '\.', $extension) . '$/';
Angie Byron
committed
// Get a listing of all template files in the path to search.
$files = file_scan_directory($path, $regex, ['key' => 'filename']);
Angie Byron
committed
// 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.
Dries Buytaert
committed
foreach ($files as $template => $file) {
// Ignore sub-theme templates for the current theme.
if (strpos($file->uri, str_replace($subtheme_paths, '', $file->uri)) !== 0) {
Gábor Hojtsy
committed
continue;
}
Angie Byron
committed
// 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),
];
Dries Buytaert
committed
}
// Match templates based on the 'template' filename.
foreach ($cache as $hook => $info) {
if (isset($info['template'])) {
Alex Pott
committed
if ($template === $info['template']) {
$implementations[$hook] = [
'template' => $template,
'path' => dirname($file->uri),
];
}
}
}
Dries Buytaert
committed
}
Angie Byron
committed
// Find templates that implement possible "suggestion" variants of registered
Dries Buytaert
committed
// theme hooks and add those as new registered theme hooks. See
Angie Byron
committed
// drupal_find_theme_functions() for more information about suggestions and
// the use of 'pattern' and 'base hook'.
Dries Buytaert
committed
$patterns = array_keys($files);
foreach ($cache as $hook => $info) {
Angie Byron
committed
$pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__');
Angie Byron
committed
if (!isset($info['base hook']) && !empty($pattern)) {
// Transform _ in pattern to - to match file naming scheme
// for the purposes of searching.
Angie Byron
committed
$pattern = strtr($pattern, '_', '-');
$matches = preg_grep('/^' . $pattern . '/', $patterns);
Dries Buytaert
committed
if ($matches) {
foreach ($matches as $match) {
Dries Buytaert
committed
$file = $match;
Angie Byron
committed
// Remove the extension from the filename.
$file = str_replace($extension, '', $file);
Jennifer Hodgdon
committed
// 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],
Angie Byron
committed
'base hook' => $hook,
];
Dries Buytaert
committed
}
}
}
}
Angie Byron
committed
return $implementations;
Dries Buytaert
committed
}
Jennifer Hodgdon
committed
* Retrieves a setting for the current theme or for a given theme.
Dries Buytaert
committed
* 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.
Dries Buytaert
committed
* The name of the setting to be retrieved.
Dries Buytaert
committed
* @param $theme
Dries Buytaert
committed
* 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.
*/
Dries Buytaert
committed
function theme_get_setting($setting_name, $theme = NULL) {
Dries Buytaert
committed
/** @var \Drupal\Core\Theme\ThemeSettings[] $cache */
$cache = &drupal_static(__FUNCTION__, []);
Dries Buytaert
committed
// If no key is given, use the current theme if we can determine it.
if (!isset($theme)) {
$theme = \Drupal::theme()->getActiveTheme()->getName();
Dries Buytaert
committed
}
Dries Buytaert
committed
if (empty($cache[$theme])) {
// Create a theme settings object.
$cache[$theme] = new ThemeSettings($theme);
Dries Buytaert
committed
// Get the global settings from configuration.
$cache[$theme]->setData(\Drupal::config('system.theme.global')->get());
Dries Buytaert
committed
// Get the values for the theme-specific settings from the .info.yml files
// of the theme and all its base themes.
Dries Buytaert
committed
$themes = \Drupal::service('theme_handler')->listInfo();
if (isset($themes[$theme])) {
Dries Buytaert
committed
$theme_object = $themes[$theme];
Alex Pott
committed
// Retrieve configured theme-specific settings, if any.
try {
if ($theme_settings = \Drupal::config($theme . '.settings')->get()) {
$cache[$theme]->merge($theme_settings);
}
}
catch (StorageException $e) {
}
Dries Buytaert
committed
// 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);
}
}
}
Dries Buytaert
committed
// Generate the path to the logo image.
Angie Byron
committed
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)));
Angie Byron
committed
}
elseif ($logo_path = $cache[$theme]->get('logo.path')) {
$cache[$theme]->set('logo.url', file_url_transform_relative(file_create_url($logo_path)));
Dries Buytaert
committed
// 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)));
Dries Buytaert
committed
}
else {
$cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url('core/misc/favicon.ico')));
Dries Buytaert
committed
}
}
elseif ($favicon_path) {
$cache[$theme]->set('favicon.url', file_url_transform_relative(file_create_url($favicon_path)));
Dries Buytaert
committed
}
else {
$cache[$theme]->set('features.favicon', FALSE);
Dries Buytaert
committed
}
Dries Buytaert
committed
}
return $cache[$theme]->get($setting_name);
}
Alex Pott
committed
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/**
* 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) {
Alex Pott
committed
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
Alex Pott
committed
// 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.');
Alex Pott
committed
}
}
// We have a string or an object converted to a string: Escape it!
if (isset($return)) {
Scott Reeves
committed
return $return instanceof MarkupInterface ? $return : Html::escape($return);
Alex Pott
committed
}
// 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.
catch
committed
* @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.
Dries Buytaert
committed
*
* @param array $variables
Dries Buytaert
committed
* An associative array possibly containing:
Dries Buytaert
committed
* - 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'];
}
}
}
Angie Byron
committed
/**
* 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'] = [];
Angie Byron
committed
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']];
}
Angie Byron
committed
}
// Suppress error messages.
$variables['errors'] = NULL;
$variables['description'] = NULL;
Angie Byron
committed
if (!empty($element['#description'])) {
$description_attributes = [];
if (!empty($element['#id'])) {
$description_attributes['id'] = $element['#id'] . '--description';
}
Angie Byron
committed
$variables['description'] = $element['#description'];
$variables['description_attributes'] = new Attribute($description_attributes);
Angie Byron
committed
}
Alex Pott
committed
$variables['required'] = FALSE;
// For required datetime fields 'form-required' & 'js-form-required' classes
// are appended to the label attributes.
Angie Byron
committed
if (!empty($element['#required'])) {
Alex Pott
committed
$variables['required'] = TRUE;
Angie Byron
committed
}
$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 (<a> tags), but on the list items (<li>
* 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
Dries Buytaert
committed
* 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.
Jennifer Hodgdon
committed
* - attributes: (optional) Attributes for the anchor, or for the <span>
* 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.
Jennifer Hodgdon
committed
* 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 <ul> 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,
* attributes will be added to the HTML elements for both the link and the
* list item that contains it, which will result in an "is-active" class
* being added to both. The class is added via JavaScript for authenticated
* users (in the active-link library), and via PHP for anonymous users (in
* the \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter class).
Jennifer Hodgdon
committed
* - 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').
Alex Pott
committed
* - attributes: (optional) An array of the CSS attributes for the heading.
Dries Buytaert
committed
* 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) {
Dries Buytaert
committed
$links = $variables['links'];
$heading = &$variables['heading'];
Dries Buytaert
committed
catch
committed
if (!empty($links)) {
// Prepend the heading to the list, if any.
if (!empty($heading)) {
// Convert a string heading into an array, using a <h2> tag by default.
if (is_string($heading)) {
$heading = ['text' => $heading];
}
catch
committed
// Merge in default array properties into $heading.
$heading += [
catch
committed
'level' => 'h2',
'attributes' => [],
];
// Convert the attributes array into an Attribute object.
$heading['attributes'] = new Attribute($heading['attributes']);
}
$variables['links'] = [];
Dries Buytaert
committed
foreach ($links as $key => $link) {
$item = [];
$link += [
Alex Pott
committed
'ajax' => NULL,
'url' => NULL,
];
Alex Pott
committed
$li_attributes = [];
$keys = ['title', 'url'];
$link_element = [
Alex Pott
committed
'#type' => 'link',
Alex Pott
committed
'#title' => $link['title'],
catch
committed
'#options' => array_diff_key($link, array_combine($keys, $keys)),
'#url' => $link['url'],
Alex Pott
committed
'#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. Links templates
// duplicate the "is-active" class handling of l() and
// LinkGenerator::generate() because they need to be able to set the
// "is-active" class not on the links themselves (<a> tags), but on the
// list items (<li> 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 they
// contain a certain element with a certain class. That is, we cannot yet
// convert this jQuery selector to a CSS selector:
// jQuery('li:has("a.is-active")')
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.
// Only add the data- attribute. The "is-active" class will be
// calculated using JavaScript, to prevent breaking the render cache.
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. Only add the data- attribute. The "is-active" class will
// be calculated using JavaScript, to prevent breaking the render
// cache.
$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 == '' ? '<front>' : $system_path;
$item['link'] = $link_element;
catch
committed
// Handle title-only text items.
$item['text'] = $link['title'];
if (isset($link['attributes'])) {
$item['text_attributes'] = new Attribute($link['attributes']);
Dries Buytaert
committed
}
// 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
Dries Buytaert
committed
* An associative array containing:
* - uri: Either the path of the image file (relative to base_path()) or a
Dries Buytaert
committed
* 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
Jennifer Hodgdon
committed
* 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
Dries Buytaert
committed
* - 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);
}
Dries Buytaert
committed
foreach (['width', 'height', 'alt', 'title', 'sizes'] as $key) {
if (isset($variables[$key])) {
Alex Pott
committed
// 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];
Dries Buytaert
committed
}
Angie Byron
committed
* Prepares variables for table templates.
Angie Byron
committed
* Default template: table.html.twig.
*
* @param array $variables
Dries Buytaert
committed
* 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
Dries Buytaert
committed
* 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.
Dries Buytaert
committed
* - 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.
Dries Buytaert
committed
* - 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.
Dries Buytaert
committed
* 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.
Dries Buytaert
committed
* - Any HTML attributes, such as "colspan", to apply to the table cell.
* Here's an example for $rows:
Dries Buytaert
committed
* $rows = array(
* // Simple row
Dries Buytaert
committed
* array(
Dries Buytaert
committed
* 'Cell 1', 'Cell 2', 'Cell 3'
Dries Buytaert
committed
* ),
Dries Buytaert
committed
* // 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')
* ),
Dries Buytaert
committed
* );
* - footer: An array of table rows which will be printed within a <tfoot>
* tag, in the same format as the rows element (see above).
Dries Buytaert
committed
* - attributes: An array of HTML attributes to apply to the table tag.
* - caption: A localized string to use for the <caption> 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 <col> element.
* - An array of attributes applied to the <colgroup> element, which must
* include a "data" attribute. To add attributes to <col> elements,
* set the "data" attribute with an array of columns, each of which is an
Dries Buytaert
committed
* associative array of HTML attributes.
* Here's an example for $colgroup:
Dries Buytaert
committed
* $colgroup = array(
* // <colgroup> with one <col> element.
Dries Buytaert
committed
* array(
Dries Buytaert
committed
* array(
* 'class' => array('funky'), // Attribute for the <col> element.
Dries Buytaert
committed
* ),
* ),
* // <colgroup> with attributes and inner <col> elements.
Dries Buytaert
committed
* array(
* 'data' => array(
* array(
* 'class' => array('funky'), // Attribute for the <col> element.
Dries Buytaert
committed
* ),
* ),
* 'class' => array('jazzy'), // Attribute for the <colgroup> element.
Dries Buytaert
committed
* ),
* );
Dries Buytaert
committed
* 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.
Dries Buytaert
committed
* - empty: The message to display in an extra row if table does not have any
* rows.
Angie Byron
committed
function template_preprocess_table(&$variables) {
Dries Buytaert
committed
// Format the table columns:
Angie Byron
committed
if (!empty($variables['colgroups'])) {
foreach ($variables['colgroups'] as &$colgroup) {
Dries Buytaert
committed
// Check if we're dealing with a simple or complex column
if (isset($colgroup['data'])) {
Angie Byron
committed
$cols = $colgroup['data'];
unset($colgroup['data']);
$colgroup_attributes = $colgroup;
Dries Buytaert
committed
}
else {
$cols = $colgroup;
$colgroup_attributes = [];
Dries Buytaert
committed
}
$colgroup = [];
Angie Byron
committed
$colgroup['attributes'] = new Attribute($colgroup_attributes);
$colgroup['cols'] = [];
Angie Byron
committed
// Build columns.
if (is_array($cols) && !empty($cols)) {
foreach ($cols as $col_key => $col) {
$colgroup['cols'][$col_key]['attributes'] = new Attribute($col);
Dries Buytaert
committed
}
}
}
}
Angie Byron
committed
// Build an associative array of responsive classes keyed by column.
$responsive_classes = [];
Angie Byron
committed
// Format the table header:
$ts = [];
$header_columns = 0;
Angie Byron
committed
if (!empty($variables['header'])) {
$ts = tablesort_init($variables['header']);
// Use a separate index with responsive classes as headers
// may be associative.
$responsive_index = -1;
Angie Byron
committed
foreach ($variables['header'] as $col_key => $cell) {
// Increase the responsive index.
$responsive_index++;
if (!is_array($cell)) {
$header_columns++;
$cell_content = $cell;
Angie Byron
committed
$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;
}
Angie Byron
committed
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] = [];
Angie Byron
committed
$variables['header'][$col_key]['tag'] = $is_header ? 'th' : 'td';
$variables['header'][$col_key]['attributes'] = $cell_attributes;
$variables['header'][$col_key]['content'] = $cell_content;