'html_tag',
'#tag' => 'meta',
'#attributes' => array(
'charset' => 'utf-8',
),
// Security: This always has to be output first.
'#weight' => -1000,
);
// Show Drupal and the major version number in the META GENERATOR tag.
// Get the major version.
list($version, ) = explode('.', \Drupal::VERSION);
$elements['system_meta_generator'] = array(
'#type' => 'html_tag',
'#tag' => 'meta',
'#attributes' => array(
'name' => 'Generator',
'content' => 'Drupal ' . $version . ' (http://drupal.org)',
),
);
// Also send the generator in the HTTP header.
$elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
return $elements;
}
/**
* Retrieves output to be displayed in the HEAD tag of the HTML page.
*/
function drupal_get_html_head() {
$elements = drupal_add_html_head();
\Drupal::moduleHandler()->alter('html_head', $elements);
return drupal_render($elements);
}
/**
* Adds a feed URL for the current page.
*
* This function can be called as long the HTML header hasn't been sent.
*
* @param $url
* An internal system path or a fully qualified external URL of the feed.
* @param $title
* The title of the feed.
*/
function drupal_add_feed($url = NULL, $title = '') {
$stored_feed_links = &drupal_static(__FUNCTION__, array());
if (isset($url)) {
$feed_icon = array(
'#theme' => 'feed_icon',
'#url' => $url,
'#title' => $title,
);
$feed_icon['#attached']['drupal_add_html_head_link'][][] = array(
'rel' => 'alternate',
'type' => 'application/rss+xml',
'title' => $title,
// Force the URL to be absolute, for consistency with other tags
// output by Drupal.
'href' => url($url, array('absolute' => TRUE)),
);
$stored_feed_links[$url] = drupal_render($feed_icon);
}
return $stored_feed_links;
}
/**
* Gets the feed URLs for the current page.
*
* @param $delimiter
* A delimiter to split feeds by.
*/
function drupal_get_feeds($delimiter = "\n") {
$feeds = drupal_add_feed();
return implode($feeds, $delimiter);
}
/**
* @defgroup http_handling HTTP handling
* @{
* Functions to properly handle HTTP responses.
*/
/**
* Processes a URL query parameter array to remove unwanted elements.
*
* @param $query
* (optional) An array to be processed. Defaults to \Drupal::request()->query
* parameters.
* @param $exclude
* (optional) A list of $query array keys to remove. Use "parent[child]" to
* exclude nested items.
* @param $parent
* Internal use only. Used to build the $query array key for nested items.
*
* @return
* An array containing query parameters, which can be used for url().
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal\Component\Utility\UrlHelper::filterQueryParameters().
*/
function drupal_get_query_parameters(array $query = NULL, array $exclude = array(), $parent = '') {
if (!isset($query)) {
$query = \Drupal::request()->query->all();
}
return UrlHelper::filterQueryParameters($query, $exclude, $parent);
}
/**
* Parses an array into a valid, rawurlencoded query string.
*
* @see drupal_get_query_parameters()
* @ingroup php_wrappers
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal\Component\Utility\UrlHelper::buildQuery().
*/
function drupal_http_build_query(array $query, $parent = '') {
return UrlHelper::buildQuery($query, $parent);
}
/**
* Prepares a 'destination' URL query parameter for use with url().
*
* Used to direct the user back to the referring page after completing a form.
* By default the current URL is returned. If a destination exists in the
* previous request, that destination is returned. As such, a destination can
* persist across multiple pages.
*
* @return
* An associative array containing the key:
* - destination: The path provided via the destination query string or, if
* not available, the current path.
*
* @see current_path()
*/
function drupal_get_destination() {
$destination = &drupal_static(__FUNCTION__);
if (isset($destination)) {
return $destination;
}
$query = \Drupal::request()->query;
if ($query->has('destination')) {
$destination = array('destination' => $query->get('destination'));
}
else {
$path = current_path();
$query = UrlHelper::buildQuery(UrlHelper::filterQueryParameters($query->all()));
if ($query != '') {
$path .= '?' . $query;
}
$destination = array('destination' => $path);
}
return $destination;
}
/**
* Parses a system URL string into an associative array suitable for url().
*
* This function should only be used for URLs that have been generated by the
* system, such as via url(). It should not be used for URLs that come from
* external sources, or URLs that link to external resources.
*
* The returned array contains a 'path' that may be passed separately to url().
* For example:
* @code
* $options = drupal_parse_url(\Drupal::request()->query->get('destination'));
* $my_url = url($options['path'], $options);
* $my_link = l('Example link', $options['path'], $options);
* @endcode
*
* This is required, because url() does not support relative URLs containing a
* query string or fragment in its $path argument. Instead, any query string
* needs to be parsed into an associative query parameter array in
* $options['query'] and the fragment into $options['fragment'].
*
* @param $url
* The URL string to parse.
*
* @return
* An associative array containing the keys:
* - 'path': The path of the URL. If the given $url is external, this includes
* the scheme and host.
* - 'query': An array of query parameters of $url, if existent.
* - 'fragment': The fragment of $url, if existent.
*
* @see url()
* @ingroup php_wrappers
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal\Component\Utility\UrlHelper::parse().
*/
function drupal_parse_url($url) {
return UrlHelper::parse($url);
}
/**
* Encodes a Drupal path for use in a URL.
*
* For aesthetic reasons slashes are not escaped.
*
* Note that url() takes care of calling this function, so a path passed to that
* function should not be encoded in advance.
*
* @param $path
* The Drupal path to encode.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal\Component\Utility\UrlHelper::encodePath().
*/
function drupal_encode_path($path) {
return UrlHelper::encodePath($path);
}
/**
* Determines if an external URL points to this Drupal installation.
*
* @param $url
* A string containing an external URL, such as "http://example.com/foo".
*
* @return
* TRUE if the URL has the same domain and base path.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal\Component\Utility\UrlHelper::externalIsLocal().
*/
function _external_url_is_local($url) {
return UrlHelper::externalIsLocal($url, base_path());
}
/**
* Helper function for determining hosts excluded from needing a proxy.
*
* @return
* TRUE if a proxy should be used for this host.
*/
function _drupal_http_use_proxy($host) {
$proxy_exceptions = Settings::get('proxy_exceptions', array('localhost', '127.0.0.1'));
return !in_array(strtolower($host), $proxy_exceptions, TRUE);
}
/**
* @} End of "defgroup http_handling".
*/
/**
* @defgroup validation Input validation
* @{
* Functions to validate user input.
*/
/**
* Verifies the syntax of the given e-mail address.
*
* This uses the
* @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
*
* @param $mail
* A string containing an e-mail address.
*
* @return
* TRUE if the address is in a valid format.
*/
function valid_email_address($mail) {
return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
}
/**
* Verifies the syntax of the given URL.
*
* This function should only be used on actual URLs. It should not be used for
* Drupal menu paths, which can contain arbitrary characters.
* Valid values per RFC 3986.
* @param $url
* The URL to verify.
* @param $absolute
* Whether the URL is absolute (beginning with a scheme such as "http:").
*
* @return
* TRUE if the URL is in a valid format.
*
* @see \Drupal\Component\Utility\UrlHelper::isValid()
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal\Component\Utility\UrlHelper::isValid().
*/
function valid_url($url, $absolute = FALSE) {
return UrlHelper::isValid($url, $absolute);
}
/**
* @} End of "defgroup validation".
*/
/**
* @defgroup sanitization Sanitization functions
* @{
* Functions to sanitize values.
*
* See http://drupal.org/writing-secure-code for information
* on writing secure code.
*/
/**
* Strips dangerous protocols from a URI and encodes it for output to HTML.
*
* @param $uri
* A plain-text URI that might contain dangerous protocols.
*
* @return
* A URI stripped of dangerous protocols and encoded for output to an HTML
* attribute value. Because it is already encoded, it should not be set as a
* value within a $attributes array passed to Drupal\Core\Template\Attribute,
* because Drupal\Core\Template\Attribute expects those values to be
* plain-text strings. To pass a filtered URI to
* Drupal\Core\Template\Attribute, call
* \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() instead.
*
* @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
* @see \Drupal\Component\Utility\String::checkPlain()
*/
function check_url($uri) {
return String::checkPlain(UrlHelper::stripDangerousProtocols($uri));
}
/**
* Applies a very permissive XSS/HTML filter for admin-only use.
*
* Use only for fields where it is impractical to use the
* whole filter system, but where some (mainly inline) mark-up
* is desired (so \Drupal\Component\Utility\String::checkPlain() is not
* acceptable).
*
* Allows all tags that can be used inside an HTML body, save
* for scripts and styles.
*
* @param string $string
* The string to apply the filter to.
*
* @return string
* The filtered string.
*
* @see \Drupal\Component\Utility\Xss::filterAdmin()
*/
function filter_xss_admin($string) {
return Xss::filterAdmin($string);
}
/**
* Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
*
* Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
* For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
*
* This code does four things:
* - Removes characters and constructs that can trick browsers.
* - Makes sure all HTML entities are well-formed.
* - Makes sure all HTML tags and attributes are well-formed.
* - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
* javascript:).
*
* @param $string
* The string with raw HTML in it. It will be stripped of everything that can
* cause an XSS attack.
* @param $allowed_tags
* An array of allowed tags.
*
* @return
* An XSS safe version of $string, or an empty string if $string is not
* valid UTF-8.
*
* @see \Drupal\Component\Utility\Xss::filter()
*/
function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
return Xss::filter($string, $allowed_tags);
}
/**
* Processes an HTML attribute value and strips dangerous protocols from URLs.
*
* @param string $string
* The string with the attribute value.
*
* @return string
* Cleaned up and HTML-escaped version of $string.
*
* @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
*/
function filter_xss_bad_protocol($string) {
return UrlHelper::filterBadProtocol($string);
}
/**
* @} End of "defgroup sanitization".
*/
/**
* @defgroup format Formatting
* @{
* Functions to format numbers, strings, dates, etc.
*/
/**
* Formats an RSS channel.
*
* Arbitrary elements may be added using the $args associative array.
*/
function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
$langcode = $langcode ? $langcode : \Drupal::languageManager()->getCurrentLanguage(Language::TYPE_CONTENT)->id;
$output = "\n";
$output .= ' ' . String::checkPlain($title) . "\n";
$output .= ' ' . check_url($link) . "\n";
// The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
// We strip all HTML tags, but need to prevent double encoding from properly
// escaped source data (such as & becoming &).
$output .= ' ' . String::checkPlain(decode_entities(strip_tags($description))) . "\n";
$output .= ' ' . String::checkPlain($langcode) . "\n";
$output .= format_xml_elements($args);
$output .= $items;
$output .= "\n";
return $output;
}
/**
* Formats a single RSS item.
*
* Arbitrary elements may be added using the $args associative array.
*/
function format_rss_item($title, $link, $description, $args = array()) {
$output = "\n";
$output .= ' ' . String::checkPlain($title) . "\n";
$output .= ' ' . check_url($link) . "\n";
$output .= ' ' . String::checkPlain($description) . "\n";
$output .= format_xml_elements($args);
$output .= "\n";
return $output;
}
/**
* Formats XML elements.
*
* @param $array
* An array where each item represents an element and is either a:
* - (key => value) pair (value)
* - Associative array with fields:
* - 'key': element name
* - 'value': element contents
* - 'attributes': associative array of element attributes
*
* In both cases, 'value' can be a simple string, or it can be another array
* with the same format as $array itself for nesting.
*/
function format_xml_elements($array) {
$output = '';
foreach ($array as $key => $value) {
if (is_numeric($key)) {
if ($value['key']) {
$output .= ' <' . $value['key'];
if (isset($value['attributes']) && is_array($value['attributes'])) {
$output .= new Attribute($value['attributes']);
}
if (isset($value['value']) && $value['value'] != '') {
$output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : String::checkPlain($value['value'])) . '' . $value['key'] . ">\n";
}
else {
$output .= " />\n";
}
}
}
else {
$output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : String::checkPlain($value)) . "$key>\n";
}
}
return $output;
}
/**
* Formats a string containing a count of items.
*
* This function ensures that the string is pluralized correctly. Since t() is
* called by this function, make sure not to pass already-localized strings to
* it.
*
* For example:
* @code
* $output = format_plural($node->comment_count, '1 comment', '@count comments');
* @endcode
*
* Example with additional replacements:
* @code
* $output = format_plural($update_count,
* 'Changed the content type of 1 post from %old-type to %new-type.',
* 'Changed the content type of @count posts from %old-type to %new-type.',
* array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
* @endcode
*
* @param $count
* The item count to display.
* @param $singular
* The string for the singular case. Make sure it is clear this is singular,
* to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
* use @count in the singular string.
* @param $plural
* The string for the plural case. Make sure it is clear this is plural, to
* ease translation. Use @count in place of the item count, as in
* "@count new comments".
* @param $args
* An associative array of replacements to make after translation. Instances
* of any key in this array are replaced with the corresponding value.
* Based on the first character of the key, the value is escaped and/or
* themed. See format_string(). Note that you do not need to include @count
* in this array; this replacement is done automatically for the plural case.
* @param $options
* An associative array of additional options. See t() for allowed keys.
*
* @return
* A translated string.
*
* @see t()
* @see format_string()
* @see \Drupal\Core\StringTranslation\TranslationManager->formatPlural()
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::translation()->formatPlural().
*/
function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
return \Drupal::translation()->formatPlural($count, $singular, $plural, $args, $options);
}
/**
* Parses a given byte count.
*
* @param $size
* A size expressed as a number of bytes with optional SI or IEC binary unit
* prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
*
* @return
* An integer representation of the size in bytes.
*/
function parse_size($size) {
$unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
$size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
if ($unit) {
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
return round($size * pow(DRUPAL_KILOBYTE, stripos('bkmgtpezy', $unit[0])));
}
else {
return round($size);
}
}
/**
* Generates a string representation for the given byte count.
*
* @param $size
* A size in bytes.
* @param $langcode
* Optional language code to translate to a language other than what is used
* to display the page.
*
* @return
* A translated string representation of the size.
*/
function format_size($size, $langcode = NULL) {
if ($size < DRUPAL_KILOBYTE) {
return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
}
else {
$size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
$units = array(
t('@size KB', array(), array('langcode' => $langcode)),
t('@size MB', array(), array('langcode' => $langcode)),
t('@size GB', array(), array('langcode' => $langcode)),
t('@size TB', array(), array('langcode' => $langcode)),
t('@size PB', array(), array('langcode' => $langcode)),
t('@size EB', array(), array('langcode' => $langcode)),
t('@size ZB', array(), array('langcode' => $langcode)),
t('@size YB', array(), array('langcode' => $langcode)),
);
foreach ($units as $unit) {
if (round($size, 2) >= DRUPAL_KILOBYTE) {
$size = $size / DRUPAL_KILOBYTE;
}
else {
break;
}
}
return str_replace('@size', round($size, 2), $unit);
}
}
/**
* Formats a time interval with the requested granularity.
*
* @param $interval
* The length of the interval in seconds.
* @param $granularity
* How many different units to display in the string.
* @param $langcode
* Optional language code to translate to a language other than
* what is used to display the page.
*
* @return
* A translated string representation of the interval.
*
* @see \Drupal\Core\Datetime\Date::formatInterval()
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* Use \Drupal::service('date')->formatInterval().
*/
function format_interval($interval, $granularity = 2, $langcode = NULL) {
return \Drupal::service('date')->formatInterval($interval, $granularity, $langcode);
}
/**
* Formats a date, using a date type or a custom date format string.
*
* @param $timestamp
* A UNIX timestamp to format.
* @param $type
* (optional) The format to use, one of:
* - One of the built-in formats: 'short', 'medium',
* 'long', 'html_datetime', 'html_date', 'html_time',
* 'html_yearless_date', 'html_week', 'html_month', 'html_year'.
* - The name of a date type defined by a module in
* hook_date_format_types(), if it's been assigned a format.
* - The machine name of an administrator-defined date format.
* - 'custom', to use $format.
* Defaults to 'medium'.
* @param $format
* (optional) If $type is 'custom', a PHP date format string suitable for
* input to date(). Use a backslash to escape ordinary text, so it does not
* get interpreted as date format characters.
* @param $timezone
* (optional) Time zone identifier, as described at
* http://php.net/manual/timezones.php Defaults to the time zone used to
* display the page.
* @param $langcode
* (optional) Language code to translate to. Defaults to the language used to
* display the page.
*
* @return
* A translated date string in the requested format.
*
* @see \Drupal\Component\Datetime\Date::format()
*/
function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
return \Drupal::service('date')->format($timestamp, $type, $format, $timezone, $langcode);
}
/**
* Returns an ISO8601 formatted date based on the given date.
*
* @param $date
* A UNIX timestamp.
*
* @return string
* An ISO8601 formatted date.
*/
function date_iso8601($date) {
// The DATE_ISO8601 constant cannot be used here because it does not match
// date('c') and produces invalid RDF markup.
return date('c', $date);
}
/**
* Translates a formatted date string.
*
* Callback for preg_replace_callback() within format_date().
*/
function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
// We cache translations to avoid redundant and rather costly calls to t().
static $cache, $langcode;
if (!isset($matches)) {
$langcode = $new_langcode;
return;
}
$code = $matches[1];
$string = $matches[2];
if (!isset($cache[$langcode][$code][$string])) {
$options = array(
'langcode' => $langcode,
);
if ($code == 'F') {
$options['context'] = 'Long month name';
}
if ($code == '') {
$cache[$langcode][$code][$string] = $string;
}
else {
$cache[$langcode][$code][$string] = t($string, array(), $options);
}
}
return $cache[$langcode][$code][$string];
}
/**
* Retrieves the correct datetime format type for this system.
*
* This value is sometimes required when the format type needs to be determined
* before a date can be created.
*
* @return string
* A string as defined in \DrupalComponent\Datetime\DateTimePlus.php: either
* 'intl' or 'php', depending on whether IntlDateFormatter is available.
*/
function datetime_default_format_type() {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['format_type'] = &drupal_static(__FUNCTION__);
}
$format_type = &$drupal_static_fast['format_type'];
if (!isset($format_type)) {
$date = new DrupalDateTime();
$format_type = $date->canUseIntl() ? DrupalDateTime::INTL : DrupalDateTime::PHP;
}
return $format_type;
}
/**
* @} End of "defgroup format".
*/
/**
* Generates an internal or external URL.
*
* When creating links in modules, consider whether l() could be a better
* alternative than url().
*
* @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromPath().
*/
function url($path = NULL, array $options = array()) {
$generator = \Drupal::urlGenerator();
try {
$url = $generator->generateFromPath($path, $options);
}
catch (GeneratorNotInitializedException $e) {
// Fallback to using globals.
// @todo Remove this once there is no code that calls url() when there is
// no request.
global $base_url, $base_path, $script_path;
$generator->setBasePath($base_path);
$generator->setBaseUrl($base_url . '/');
$generator->setScriptPath($script_path);
$url = $generator->generateFromPath($path, $options);
}
return $url;
}
/**
* Returns TRUE if a path is external to Drupal (e.g. http://example.com).
*
* If a path cannot be assessed by Drupal's menu handler, then we must
* treat it as potentially insecure.
*
* @param $path
* The internal path or external URL being linked to, such as "node/34" or
* "http://example.com/foo".
*
* @return
* Boolean TRUE or FALSE, where TRUE indicates an external path.
*/
function url_is_external($path) {
return UrlHelper::isExternal($path);
}
/**
* Formats an attribute string for an HTTP header.
*
* @param $attributes
* An associative array of attributes such as 'rel'.
*
* @return
* A ; separated string ready for insertion in a HTTP header. No escaping is
* performed for HTML entities, so this string is not safe to be printed.
*
* @see drupal_add_http_header()
*/
function drupal_http_header_attributes(array $attributes = array()) {
foreach ($attributes as $attribute => &$data) {
if (is_array($data)) {
$data = implode(' ', $data);
}
$data = $attribute . '="' . $data . '"';
}
return $attributes ? ' ' . implode('; ', $attributes) : '';
}
/**
* Formats an internal or external URL link as an HTML anchor tag.
*
* This function correctly handles aliased paths and adds an 'active' class
* attribute to links that point to the current page (for theming), so all
* internal links output by modules should be generated by this function if
* possible.
*
* However, for links enclosed in translatable text you should use t() and
* embed the HTML anchor tag directly in the translated string. For example:
* @code
* t('Visit the settings page', array('@url' => url('admin')));
* @endcode
* This keeps the context of the link title ('settings' in the example) for
* translators.
*
* This function does not support generating links from internal routes. For
* that use \Drupal\Core\Utility\LinkGenerator::generate(), which is exposed via
* the 'link_generator' service. It requires an internal route name and does not
* support external URLs. Using Drupal 7 style system paths should be avoided if
* possible but l() should still be used when rendering links to external URLs.
*
* @param string|array $text
* The link text for the anchor tag as a translated string or render array.
* @param string $path
* The internal path or external URL being linked to, such as "node/34" or
* "http://example.com/foo". After the url() function is called to construct
* the URL from $path and $options, the resulting URL is passed through
* \Drupal\Component\Utility\String::checkPlain() before it is inserted into
* the HTML anchor tag, to ensure well-formed HTML. See url() for more
* information and notes.
* @param array $options
* An associative array of additional options. Defaults to an empty array. It
* may contain the following elements.
* - 'attributes': An associative array of HTML attributes to apply to the
* anchor tag. If element 'class' is included, it must be an array; 'title'
* must be a string; other elements are more flexible, as they just need
* to work as an argument for the constructor of the class
* Drupal\Core\Template\Attribute($options['attributes']).
* - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
* example, to make an image tag into a link, this must be set to TRUE, or
* you will see the escaped HTML image tag. $text is not sanitized if
* 'html' is TRUE. The calling function must ensure that $text is already
* safe.
* - 'language': An optional language object. If the path being linked to is
* internal to the site, $options['language'] is used to determine whether
* the link is "active", or pointing to the current page (the language as
* well as the path must match). This element is also used by url().
* - 'set_active_class': Whether l() should compare the $path, language and
* query options to the current URL to determine whether the link is
* "active". Defaults to FALSE. If TRUE, an "active" class will be applied
* to the link. It is important to use this sparingly since it is usually
* unnecessary and requires extra processing.
* For anonymous users, the "active" class will be calculated on the server,
* because most sites serve each anonymous user the same cached page anyway.
* For authenticated users, the "active" class will be calculated on the
* client (through JavaScript), only data- attributes are added to links to
* prevent breaking the render cache. The JavaScript is added in
* system_page_build().
* - Additional $options elements used by the url() function.
*
* @return string
* An HTML string containing a link to the given path.
*
* @see url()
* @see system_page_build()
*/
function l($text, $path, array $options = array()) {
// Start building a structured representation of our link to be altered later.
$variables = array(
'text' => is_array($text) ? drupal_render($text) : $text,
'path' => $path,
'options' => $options,
);
// Merge in default options.
$variables['options'] += array(
'attributes' => array(),
'query' => array(),
'html' => FALSE,
'language' => NULL,
'set_active_class' => FALSE,
);
// Add a hreflang attribute if we know the language of this link's url and
// hreflang has not already been set.
if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) {
$variables['options']['attributes']['hreflang'] = $variables['options']['language']->id;
}
// Set the "active" class if the 'set_active_class' option is not empty.
if (!empty($variables['options']['set_active_class'])) {
// Add a "data-drupal-link-query" attribute to let the drupal.active-link
// library know the query in a standardized manner.
if (!empty($variables['options']['query'])) {
$query = $variables['options']['query'];
ksort($query);
$variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query);
}
// Add a "data-drupal-link-system-path" attribute to let the
// drupal.active-link library know the path in a standardized manner.
if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
$variables['options']['attributes']['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager.cached')->getSystemPath($path);
}
}
// Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
// only when a quick strpos() gives suspicion tags are present.
if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) {
$variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']);
}
// Allow other modules to modify the structure of the link.
\Drupal::moduleHandler()->alter('link', $variables);
// Move attributes out of options. url() doesn't need them.
$attributes = new Attribute($variables['options']['attributes']);
unset($variables['options']['attributes']);
// The result of url() is a plain-text URL. Because we are using it here
// in an HTML argument context, we need to encode it properly.
$url = String::checkPlain(url($variables['path'], $variables['options']));
// Sanitize the link text if necessary.
$text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
return '' . $text . '';
}
/**
* Attempts to set the PHP maximum execution time.
*
* This function is a wrapper around the PHP function set_time_limit().
* When called, set_time_limit() restarts the timeout counter from zero.
* In other words, if the timeout is the default 30 seconds, and 25 seconds
* into script execution a call such as set_time_limit(20) is made, the
* script will run for a total of 45 seconds before timing out.
*
* It also means that it is possible to decrease the total time limit if
* the sum of the new time limit and the current time spent running the
* script is inferior to the original time limit. It is inherent to the way
* set_time_limit() works, it should rather be called with an appropriate
* value every time you need to allocate a certain amount of time
* to execute a task than only once at the beginning of the script.
*
* Before calling set_time_limit(), we check if this function is available
* because it could be disabled by the server administrator. We also hide all
* the errors that could occur when calling set_time_limit(), because it is
* not possible to reliably ensure that PHP or a security extension will
* not issue a warning/error if they prevent the use of this function.
*
* @param $time_limit
* An integer specifying the new time limit, in seconds. A value of 0
* indicates unlimited execution time.
*
* @ingroup php_wrappers
*/
function drupal_set_time_limit($time_limit) {
if (function_exists('set_time_limit')) {
@set_time_limit($time_limit);
}
}
/**
* Returns the path to a system item (module, theme, etc.).
*
* @param $type
* The type of the item (i.e. theme, theme_engine, module, profile).
* @param $name
* The name of the item for which the path is requested.
*
* @return
* The path to the requested item or an empty string if the item is not found.
*/
function drupal_get_path($type, $name) {
return dirname(drupal_get_filename($type, $name));
}
/**
* Returns the base URL path (i.e., directory) of the Drupal installation.
*
* base_path() adds a "/" to the beginning and end of the returned path if the
* path is not empty. At the very least, this will return "/".
*
* Examples:
* - http://example.com returns "/" because the path is empty.
* - http://example.com/drupal/folder returns "/drupal/folder/".
*/
function base_path() {
return $GLOBALS['base_path'];
}
/**
* Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
*
* This function can be called as long the HTML header hasn't been sent, which
* on normal pages is up through the preprocess step of _theme('html'). Adding
* a link will overwrite a prior link with the exact same 'rel' and 'href'
* attributes.
*
* @param $attributes
* Associative array of element attributes including 'href' and 'rel'.
* @param $header
* Optional flag to determine if a HTTP 'Link:' header should be sent.
*/
function drupal_add_html_head_link($attributes, $header = FALSE) {
$element = array(
'#tag' => 'link',
'#attributes' => $attributes,
);
$href = $attributes['href'];
if ($header) {
// Also add a HTTP header "Link:".
$href = '<' . String::checkPlain($attributes['href']) . '>;';
unset($attributes['href']);
$element['#attached']['drupal_add_http_header'][] = array('Link', $href . drupal_http_header_attributes($attributes), TRUE);
}
drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . $href);
}
/**
* Adds a cascading stylesheet to the stylesheet queue.
*
* Calling drupal_static_reset('_drupal_add_css') will clear all cascading
* stylesheets added so far.
*
* If CSS aggregation/compression is enabled, all cascading style sheets added
* with $options['preprocess'] set to TRUE will be merged into one aggregate
* file and compressed by removing all extraneous white space.
* Preprocessed inline stylesheets will not be aggregated into this single file;
* instead, they are just compressed upon output on the page. Externally hosted
* stylesheets are never aggregated or compressed.
*
* The reason for aggregating the files is outlined quite thoroughly here:
* http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
* to request overhead, one bigger file just loads faster than two smaller ones
* half its size."
*
* $options['preprocess'] should be only set to TRUE when a file is required for
* all typical visitors and most pages of a site. It is critical that all
* preprocessed files are added unconditionally on every page, even if the
* files do not happen to be needed on a page. This is normally done by calling
* _drupal_add_css() in a hook_page_build() implementation.
*
* Non-preprocessed files should only be added to the page when they are
* actually needed.
*
* @param $data
* (optional) The stylesheet data to be added, depending on what is passed
* through to the $options['type'] parameter:
* - 'file': The path to the CSS file relative to the base_path(), or a
* stream wrapper URI. For example: "modules/devel/devel.css" or
* "public://generated_css/stylesheet_1.css". Note that Modules should
* always prefix the names of their CSS files with the module name; for
* example, system-menus.css rather than simply menus.css. Themes can
* override module-supplied CSS files based on their filenames, and this
* prefixing helps prevent confusing name collisions for theme developers.
* See drupal_get_css() where the overrides are performed.
* - 'inline': A string of CSS that should be placed in the given scope. Note
* that it is better practice to use 'file' stylesheets, rather than
* 'inline', as the CSS would then be aggregated and cached.
* - 'external': The absolute path to an external CSS file that is not hosted
* on the local server. These files will not be aggregated if CSS
* aggregation is enabled.
* @param $options
* (optional) A string defining the 'type' of CSS that is being added in the
* $data parameter ('file', 'inline', or 'external'), or an array which can
* have any or all of the following keys:
* - 'type': The type of stylesheet being added. Available options are 'file',
* 'inline' or 'external'. Defaults to 'file'.
* - 'basename': Force a basename for the file being added. Modules are
* expected to use stylesheets with unique filenames, but integration of
* external libraries may make this impossible. The basename of
* 'core/modules/node/node.css' is 'node.css'. If the external library
* "node.js" ships with a 'node.css', then a different, unique basename
* would be 'node.js.css'.
* - 'group': A number identifying the aggregation group in which to add the
* stylesheet. Available constants are:
* - CSS_AGGREGATE_DEFAULT: (default) Any module-layer CSS.
* - CSS_AGGREGATE_THEME: Any theme-layer CSS.
* The aggregate group number affects load order and the CSS cascade.
* Stylesheets in an aggregate with a lower group number will be output to
* the page before stylesheets in an aggregate with a higher group number,
* so CSS within higher aggregate groups can take precendence over CSS
* within lower aggregate groups.
* - 'every_page': For optimal front-end performance when aggregation is
* enabled, this should be set to TRUE if the stylesheet is present on every
* page of the website for users for whom it is present at all. This
* defaults to FALSE. It is set to TRUE for stylesheets added via module and
* theme .info.yml files. Modules that add stylesheets within
* hook_page_build() implementations, or from other code that ensures that
* the stylesheet is added to all website pages, should also set this flag
* to TRUE. All stylesheets within the same group that have the 'every_page'
* flag set to TRUE and do not have 'preprocess' set to FALSE are aggregated
* together into a single aggregate file, and that aggregate file can be
* reused across a user's entire site visit, leading to faster navigation
* between pages.
* However, stylesheets that are only needed on pages less frequently
* visited, can be added by code that only runs for those particular pages,
* and that code should not set the 'every_page' flag. This minimizes the
* size of the aggregate file that the user needs to download when first
* visiting the website. Stylesheets without the 'every_page' flag are
* aggregated into a separate aggregate file. This other aggregate file is
* likely to change from page to page, and each new aggregate file needs to
* be downloaded when first encountered, so it should be kept relatively
* small by ensuring that most commonly needed stylesheets are added to
* every page.
* - 'weight': The weight of the stylesheet specifies the order in which the
* CSS will appear relative to other stylesheets with the same aggregate
* group and 'every_page' flag. The exact ordering of stylesheets is as
* follows:
* - First by aggregate group.
* - Then by the 'every_page' flag, with TRUE coming before FALSE.
* - Then by weight.
* - Then by the order in which the CSS was added. For example, all else
* being the same, a stylesheet added by a call to _drupal_add_css() that
* happened later in the page request gets added to the page after one for
* which _drupal_add_css() happened earlier in the page request.
* Available constants are:
* - CSS_BASE: Styles for HTML elements ("base" styles).
* - CSS_LAYOUT: Styles that layout a page.
* - CSS_COMPONENT: Styles for design components (and their associated
* states and themes.)
* - CSS_STATE: Styles for states that are not included with components.
* - CSS_THEME: Styles for themes that are not included with components.
* The weight numbers follow the SMACSS convention of CSS categorization.
* See http://drupal.org/node/1887922
* - 'media': The media type for the stylesheet, e.g., all, print, screen.
* Defaults to 'all'. It is extremely important to leave this set to 'all'
* or it will negatively impact front-end peformance. Instead add a @media
* block to the included CSS file.
* - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the
* styles will be aggregated and compressed. Defaults to TRUE.
* - 'browsers': An array containing information specifying which browsers
* should load the CSS item. See drupal_pre_render_conditional_comments()
* for details.
*
* @return
* An array of queued cascading stylesheets.
*
* @deprecated as of Drupal 8.0. Use the #attached key in render arrays instead.
*
* @see drupal_get_css()
*/
function _drupal_add_css($data = NULL, $options = NULL) {
$css = &drupal_static(__FUNCTION__, array());
// Construct the options, taking the defaults into consideration.
if (isset($options)) {
if (!is_array($options)) {
$options = array('type' => $options);
}
}
else {
$options = array();
}
// Create an array of CSS files for each media type first, since each type needs to be served
// to the browser differently.
if (isset($data)) {
$options += array(
'type' => 'file',
'group' => CSS_AGGREGATE_DEFAULT,
'weight' => 0,
'every_page' => FALSE,
'media' => 'all',
'preprocess' => TRUE,
'data' => $data,
'browsers' => array(),
);
$options['browsers'] += array(
'IE' => TRUE,
'!IE' => TRUE,
);
// Files with a query string cannot be preprocessed.
if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
$options['preprocess'] = FALSE;
}
// Always add a tiny value to the weight, to conserve the insertion order.
$options['weight'] += count($css) / 1000;
// Add the data to the CSS array depending on the type.
switch ($options['type']) {
case 'inline':
// For inline stylesheets, we don't want to use the $data as the array
// key as $data could be a very long string of CSS.
$css[] = $options;
break;
case 'file':
// Local CSS files are keyed by basename; if a file with the same
// basename is added more than once, it gets overridden.
// By default, take over the filename as basename.
if (!isset($options['basename'])) {
$options['basename'] = drupal_basename($data);
}
$css[$options['basename']] = $options;
break;
default:
// External files are keyed by their full URI, so the same CSS file is
// not added twice.
$css[$data] = $options;
}
}
return $css;
}
/**
* Returns a themed representation of all stylesheets to attach to the page.
*
* It loads the CSS in order, with 'module' first, then 'theme' afterwards.
* This ensures proper cascading of styles so themes can easily override
* module styles through CSS selectors.
*
* Themes may replace module-defined CSS files by adding a stylesheet with the
* same filename. For example, themes/bartik/system-menus.css would replace
* modules/system/system-menus.css. This allows themes to override complete
* CSS files, rather than specific selectors, when necessary.
*
* @param $css
* (optional) An array of CSS files. If no array is provided, the default
* stylesheets array is used instead.
* @param $skip_alter
* (optional) If set to TRUE, this function skips calling
* \Drupal::moduleHandler->alter() on $css, useful when the calling function
* passes a $css array that has already been altered.
*
* @return
* A string of XHTML CSS tags.
*
* @see _drupal_add_css()
*/
function drupal_get_css($css = NULL, $skip_alter = FALSE) {
global $theme_info;
if (!isset($css)) {
$css = _drupal_add_css();
}
// Allow modules and themes to alter the CSS items.
if (!$skip_alter) {
\Drupal::moduleHandler()->alter('css', $css);
}
// Sort CSS items, so that they appear in the correct order.
uasort($css, 'drupal_sort_css_js');
// Allow themes to remove CSS files by basename.
if (!empty($theme_info->stylesheets_remove)) {
foreach ($css as $key => $options) {
if (isset($options['basename']) && isset($theme_info->stylesheets_remove[$options['basename']])) {
unset($css[$key]);
}
}
}
// Allow themes to conditionally override CSS files by basename.
if (!empty($theme_info->stylesheets_override)) {
foreach ($css as $key => $options) {
if (isset($options['basename']) && isset($theme_info->stylesheets_override[$options['basename']])) {
$css[$key]['data'] = $theme_info->stylesheets_override[$options['basename']];
}
}
}
// Render the HTML needed to load the CSS.
$styles = array(
'#type' => 'styles',
'#items' => $css,
);
if (!empty($setting)) {
$styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
}
return drupal_render($styles);
}
/**
* Sorts CSS and JavaScript resources.
*
* Callback for uasort() within:
* - drupal_get_css()
* - drupal_get_js()
*
* This sort order helps optimize front-end performance while providing modules
* and themes with the necessary control for ordering the CSS and JavaScript
* appearing on a page.
*
* @param $a
* First item for comparison. The compared items should be associative arrays
* of member items from _drupal_add_css() or _drupal_add_js().
* @param $b
* Second item for comparison.
*
* @see _drupal_add_css()
* @see _drupal_add_js()
*/
function drupal_sort_css_js($a, $b) {
// First order by group, so that all items in the CSS_AGGREGATE_DEFAULT group
// appear before items in the CSS_AGGREGATE_THEME group. Modules may create
// additional groups by defining their own constants.
if ($a['group'] < $b['group']) {
return -1;
}
elseif ($a['group'] > $b['group']) {
return 1;
}
// Within a group, order all infrequently needed, page-specific files after
// common files needed throughout the website. Separating this way allows for
// the aggregate file generated for all of the common files to be reused
// across a site visit without being cut by a page using a less common file.
elseif ($a['every_page'] && !$b['every_page']) {
return -1;
}
elseif (!$a['every_page'] && $b['every_page']) {
return 1;
}
// Finally, order by weight.
elseif ($a['weight'] < $b['weight']) {
return -1;
}
elseif ($a['weight'] > $b['weight']) {
return 1;
}
else {
return 0;
}
}
/**
* Pre-render callback: Adds the elements needed for CSS tags to be rendered.
*
* For production websites, LINK tags are preferable to STYLE tags with @import
* statements, because:
* - They are the standard tag intended for linking to a resource.
* - On Firefox 2 and perhaps other browsers, CSS files included with @import
* statements don't get saved when saving the complete web page for offline
* use: http://drupal.org/node/145218.
* - On IE, if only LINK tags and no @import statements are used, all the CSS
* files are downloaded in parallel, resulting in faster page load, but if
* @import statements are used and span across multiple STYLE tags, all the
* ones from one STYLE tag must be downloaded before downloading begins for
* the next STYLE tag. Furthermore, IE7 does not support media declaration on
* the @import statement, so multiple STYLE tags must be used when different
* files are for different media types. Non-IE browsers always download in
* parallel, so this is an IE-specific performance quirk:
* http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
*
* However, IE has an annoying limit of 31 total CSS inclusion tags
* (http://drupal.org/node/228818) and LINK tags are limited to one file per
* tag, whereas STYLE tags can contain multiple @import statements allowing
* multiple files to be loaded per tag. When CSS aggregation is disabled, a
* Drupal site can easily have more than 31 CSS files that need to be loaded, so
* using LINK tags exclusively would result in a site that would display
* incorrectly in IE. Depending on different needs, different strategies can be
* employed to decide when to use LINK tags and when to use STYLE tags.
*
* The strategy employed by this function is to use LINK tags for all aggregate
* files and for all files that cannot be aggregated (e.g., if 'preprocess' is
* set to FALSE or the type is 'external'), and to use STYLE tags for groups
* of files that could be aggregated together but aren't (e.g., if the site-wide
* aggregation setting is disabled). This results in all LINK tags when
* aggregation is enabled, a guarantee that as many or only slightly more tags
* are used with aggregation disabled than enabled (so that if the limit were to
* be crossed with aggregation enabled, the site developer would also notice the
* problem while aggregation is disabled), and an easy way for a developer to
* view HTML source while aggregation is disabled and know what files will be
* aggregated together when aggregation becomes enabled.
*
* This function evaluates the aggregation enabled/disabled condition on a group
* by group basis by testing whether an aggregate file has been made for the
* group rather than by testing the site-wide aggregation setting. This allows
* this function to work correctly even if modules have implemented custom
* logic for grouping and aggregating files.
*
* @param $element
* A render array containing:
* - '#items': The CSS items as returned by _drupal_add_css() and altered by
* drupal_get_css().
*
* @return
* A render array that will render to a string of XHTML CSS tags.
*
* @see drupal_get_css()
*/
function drupal_pre_render_styles($elements) {
$css_assets = $elements['#items'];
// Aggregate the CSS if necessary, but only during normal site operation.
if (!defined('MAINTENANCE_MODE') && \Drupal::config('system.performance')->get('css.preprocess')) {
$css_assets = \Drupal::service('asset.css.collection_optimizer')->optimize($css_assets);
}
return \Drupal::service('asset.css.collection_renderer')->render($css_assets);
}
/**
* Deletes old cached CSS files.
*/
function drupal_clear_css_cache() {
\Drupal::state()->delete('drupal_css_cache_files');
file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
}
/**
* Deletes files modified more than a set time ago.
*
* Callback for file_scan_directory() within:
* - drupal_clear_css_cache()
* - drupal_clear_js_cache()
*/
function drupal_delete_file_if_stale($uri) {
// Default stale file threshold is 30 days.
if (REQUEST_TIME - filemtime($uri) > \Drupal::config('system.performance')->get('stale_file_threshold')) {
file_unmanaged_delete($uri);
}
}
/**
* Prepares a string for use as a CSS identifier (element, class, or ID name).
*
* http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
* CSS identifiers (including element names, classes, and IDs in selectors.)
*
* @param $identifier
* The identifier to clean.
* @param $filter
* An array of string replacements to use on the identifier.
*
* @return
* The cleaned identifier.
*/
function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '__' => '__', '/' => '-', '[' => '-', ']' => '')) {
// By default, we filter using Drupal's coding standards.
$identifier = strtr($identifier, $filter);
// Valid characters in a CSS identifier are:
// - the hyphen (U+002D)
// - a-z (U+0030 - U+0039)
// - A-Z (U+0041 - U+005A)
// - the underscore (U+005F)
// - 0-9 (U+0061 - U+007A)
// - ISO 10646 characters U+00A1 and higher
// We strip out any character not in the above list.
$identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
// Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
$identifier = preg_replace(array('/^[0-9]/', '/^(-[0-9])|^(--)/'), array('_', '__') , $identifier);
return $identifier;
}
/**
* Prepares a string for use as a valid class name.
*
* Do not pass one string containing multiple classes as they will be
* incorrectly concatenated with dashes, i.e. "one two" will become "one-two".
*
* @param $class
* The class name to clean.
*
* @return
* The cleaned class name.
*/
function drupal_html_class($class) {
// The output of this function will never change, so this uses a normal
// static instead of drupal_static().
static $classes = array();
if (!isset($classes[$class])) {
$classes[$class] = drupal_clean_css_identifier(drupal_strtolower($class));
}
return $classes[$class];
}
/**
* Prepares a string for use as a valid HTML ID and guarantees uniqueness.
*
* This function ensures that each passed HTML ID value only exists once on the
* page. By tracking the already returned ids, this function enables forms,
* blocks, and other content to be output multiple times on the same page,
* without breaking (X)HTML validation.
*
* For already existing IDs, a counter is appended to the ID string. Therefore,
* JavaScript and CSS code should not rely on any value that was generated by
* this function and instead should rely on manually added CSS classes or
* similarly reliable constructs.
*
* Two consecutive hyphens separate the counter from the original ID. To manage
* uniqueness across multiple Ajax requests on the same page, Ajax requests
* POST an array of all IDs currently present on the page, which are used to
* prime this function's cache upon first invocation.
*
* To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive
* hyphens in the originally passed $id are replaced with a single hyphen.
*
* @param $id
* The ID to clean.
*
* @return
* The cleaned ID.
*/
function drupal_html_id($id) {
// If this is an Ajax request, then content returned by this page request will
// be merged with content already on the base page. The HTML IDs must be
// unique for the fully merged content. Therefore, initialize $seen_ids to
// take into account IDs that are already in use on the base page.
$seen_ids_init = &drupal_static(__FUNCTION__ . ':init');
if (!isset($seen_ids_init)) {
$ajax_html_ids = \Drupal::request()->request->get('ajax_html_ids');
// Ideally, Drupal would provide an API to persist state information about
// prior page requests in the database, and we'd be able to add this
// function's $seen_ids static variable to that state information in order
// to have it properly initialized for this page request. However, no such
// page state API exists, so instead, ajax.js adds all of the in-use HTML
// IDs to the POST data of Ajax submissions. Direct use of $_POST is
// normally not recommended as it could open up security risks, but because
// the raw POST data is cast to a number before being returned by this
// function, this usage is safe.
if (empty($ajax_html_ids)) {
$seen_ids_init = array();
}
else {
// This function ensures uniqueness by appending a counter to the base id
// requested by the calling function after the first occurrence of that
// requested id. $_POST['ajax_html_ids'] contains the ids as they were
// returned by this function, potentially with the appended counter, so
// we parse that to reconstruct the $seen_ids array.
$ajax_html_ids = explode(' ', $ajax_html_ids);
foreach ($ajax_html_ids as $seen_id) {
// We rely on '--' being used solely for separating a base id from the
// counter, which this function ensures when returning an id.
$parts = explode('--', $seen_id, 2);
if (!empty($parts[1]) && is_numeric($parts[1])) {
list($seen_id, $i) = $parts;
}
else {
$i = 1;
}
if (!isset($seen_ids_init[$seen_id]) || ($i > $seen_ids_init[$seen_id])) {
$seen_ids_init[$seen_id] = $i;
}
}
}
}
$seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init);
$id = drupal_clean_id_identifier($id);
// Ensure IDs are unique by appending a counter after the first occurrence.
// The counter needs to be appended with a delimiter that does not exist in
// the base ID. Requiring a unique delimiter helps ensure that we really do
// return unique IDs and also helps us re-create the $seen_ids array during
// Ajax requests.
if (isset($seen_ids[$id])) {
$id = $id . '--' . ++$seen_ids[$id];
}
else {
$seen_ids[$id] = 1;
}
return $id;
}
/**
* Prepares a string for use as a valid HTML ID.
*
* Only use this function when you want to intentionally skip the uniqueness
* guarantee of drupal_html_id().
*
* @param string $id
* The ID to clean.
*
* @return string
* The cleaned ID.
*
* @see drupal_html_id()
*/
function drupal_clean_id_identifier($id) {
$id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
// As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
// only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
// colons (":"), and periods ("."). We strip out any character not in that
// list. Note that the CSS spec doesn't allow colons or periods in identifiers
// (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two
// characters as well.
$id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id);
// Removing multiple consecutive hyphens.
$id = preg_replace('/\-+/', '-', $id);
return $id;
}
/**
* Adds a JavaScript file, setting, or inline code to the page.
*
* The behavior of this function depends on the parameters it is called with.
* Generally, it handles the addition of JavaScript to the page, either as
* reference to an existing file or as inline code. The following actions can be
* performed using this function:
* - Add a file ('file'): Adds a reference to a JavaScript file to the page.
* - Add inline JavaScript code ('inline'): Executes a piece of JavaScript code
* on the current page by placing the code directly in the page (for example,
* to tell the user that a new message arrived, by opening a pop up, alert
* box, etc.). This should only be used for JavaScript that cannot be executed
* from a file. When adding inline code, make sure that you are not relying on
* $() being the jQuery function. Wrap your code in
* @code (function ($) {... })(jQuery); @endcode
* or use jQuery() instead of $().
* - Add external JavaScript ('external'): Allows the inclusion of external
* JavaScript files that are not hosted on the local server. Note that these
* external JavaScript references do not get aggregated when preprocessing is
* on.
* - Add settings ('setting'): Adds settings to Drupal's global storage of
* JavaScript settings. Per-page settings are required by some modules to
* function properly. All settings will be accessible at drupalSettings.
*
* Examples:
* @code
* _drupal_add_js('core/misc/collapse.js');
* _drupal_add_js('core/misc/collapse.js', 'file');
* _drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline');
* _drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });',
* array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)
* );
* _drupal_add_js('http://example.com/example.js', 'external');
* _drupal_add_js(array('myModule' => array('key' => 'value')), 'setting');
* @endcode
*
* Calling drupal_static_reset('_drupal_add_js') will clear all JavaScript added
* so far.
*
* If JavaScript aggregation is enabled, all JavaScript files added with
* $options['preprocess'] set to TRUE will be merged into one aggregate file.
* Preprocessed inline JavaScript will not be aggregated into this single file.
* Externally hosted JavaScripts are never aggregated.
*
* The reason for aggregating the files is outlined quite thoroughly here:
* http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
* to request overhead, one bigger file just loads faster than two smaller ones
* half its size."
*
* $options['preprocess'] should be only set to TRUE when a file is required for
* all typical visitors and most pages of a site. It is critical that all
* preprocessed files are added unconditionally on every page, even if the
* files are not needed on a page. This is normally done by calling
* _drupal_add_js() in a hook_page_build() implementation.
*
* Non-preprocessed files should only be added to the page when they are
* actually needed.
*
* @param $data
* (optional) If given, the value depends on the $options parameter, or
* $options['type'] if $options is passed as an associative array:
* - 'file': Path to the file relative to base_path().
* - 'inline': The JavaScript code that should be placed in the given scope.
* - 'external': The absolute path to an external JavaScript file that is not
* hosted on the local server. These files will not be aggregated if
* JavaScript aggregation is enabled.
* - 'setting': An associative array with configuration options. The array is
* merged directly into drupalSettings. All modules should wrap their
* actual configuration settings in another variable to prevent conflicts in
* the drupalSettings namespace. Items added with a string key will replace
* existing settings with that key; items with numeric array keys will be
* added to the existing settings array.
* @param $options
* (optional) A string defining the type of JavaScript that is being added in
* the $data parameter ('file'/'setting'/'inline'/'external'), or an
* associative array. JavaScript settings should always pass the string
* 'setting' only. Other types can have the following elements in the array:
* - type: The type of JavaScript that is to be added to the page. Allowed
* values are 'file', 'inline', 'external' or 'setting'. Defaults
* to 'file'.
* - scope: The location in which you want to place the script. Possible
* values are 'header' or 'footer'. If your theme implements different
* regions, you can also use these. Defaults to 'header'.
* - group: A number identifying the group in which to add the JavaScript.
* Available constants are:
* - JS_LIBRARY: Any libraries, settings, or jQuery plugins.
* - JS_DEFAULT: Any module-layer JavaScript.
* - JS_THEME: Any theme-layer JavaScript.
* The group number serves as a weight: JavaScript within a lower weight
* group is presented on the page before JavaScript within a higher weight
* group.
* - every_page: For optimal front-end performance when aggregation is
* enabled, this should be set to TRUE if the JavaScript is present on every
* page of the website for users for whom it is present at all. This
* defaults to FALSE. It is set to TRUE for JavaScript files that are added
* via module and theme .info.yml files. Modules that add JavaScript within
* hook_page_build() implementations, or from other code that ensures that
* the JavaScript is added to all website pages, should also set this flag
* to TRUE. All JavaScript files within the same group and that have the
* 'every_page' flag set to TRUE and do not have 'preprocess' set to FALSE
* are aggregated together into a single aggregate file, and that aggregate
* file can be reused across a user's entire site visit, leading to faster
* navigation between pages. However, JavaScript that is only needed on
* pages less frequently visited, can be added by code that only runs for
* those particular pages, and that code should not set the 'every_page'
* flag. This minimizes the size of the aggregate file that the user needs
* to download when first visiting the website. JavaScript without the
* 'every_page' flag is aggregated into a separate aggregate file. This
* other aggregate file is likely to change from page to page, and each new
* aggregate file needs to be downloaded when first encountered, so it
* should be kept relatively small by ensuring that most commonly needed
* JavaScript is added to every page.
* - weight: A number defining the order in which the JavaScript is added to
* the page relative to other JavaScript with the same 'scope', 'group',
* and 'every_page' value. In some cases, the order in which the JavaScript
* is presented on the page is very important. jQuery, for example, must be
* added to the page before any jQuery code is run, so jquery.js uses the
* JS_LIBRARY group and a weight of -20, jquery.once.js (a library drupal.js
* depends on) uses the JS_LIBRARY group and a weight of -19, drupal.js uses
* the JS_LIBRARY group and a weight of -1, other libraries use the
* JS_LIBRARY group and a weight of 0 or higher, and all other scripts use
* one of the other group constants. The exact ordering of JavaScript is as
* follows:
* - First by scope, with 'header' first, 'footer' last, and any other
* scopes provided by a custom theme coming in between, as determined by
* the theme.
* - Then by group.
* - Then by the 'every_page' flag, with TRUE coming before FALSE.
* - Then by weight.
* - Then by the order in which the JavaScript was added. For example, all
* else being the same, JavaScript added by a call to _drupal_add_js() that
* happened later in the page request gets added to the page after one for
* which _drupal_add_js() happened earlier in the page request.
* - cache: If set to FALSE, the JavaScript file is loaded anew on every page
* call; in other words, it is not cached. Used only when 'type' references
* a JavaScript file. Defaults to TRUE.
* - preprocess: If TRUE and JavaScript aggregation is enabled, the script
* file will be aggregated. Defaults to TRUE.
* - attributes: An associative array of attributes for the