common.inc 67.5 KB
Newer Older
Dries's avatar
 
Dries committed
1 2
<?php

3 4 5 6 7 8 9 10
/**
 * @file
 * Common functions that many Drupal modules will need to reference.
 *
 * The functions that are critical and need to be available even when serving
 * a cached page are instead located in bootstrap.inc.
 */

11
use Drupal\Component\Serialization\Json;
12 13
use Drupal\Component\Serialization\Yaml;
use Drupal\Component\Serialization\Exception\InvalidDataTypeException;
14
use Drupal\Component\Utility\Bytes;
15
use Drupal\Component\Utility\Crypt;
16
use Drupal\Component\Utility\Html;
17
use Drupal\Component\Utility\Number;
18
use Drupal\Component\Utility\SafeMarkup;
19
use Drupal\Component\Utility\SortArray;
20
use Drupal\Component\Utility\String;
21
use Drupal\Component\Utility\Tags;
22
use Drupal\Component\Utility\UrlHelper;
23
use Drupal\Core\Asset\AttachedAssets;
24
use Drupal\Core\Cache\Cache;
25
use Drupal\Core\Language\LanguageInterface;
26
use Drupal\Core\Site\Settings;
27
use Drupal\Core\Url;
28 29
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
30
use Drupal\Core\PhpStorage\PhpStorageFactory;
31
use Drupal\Component\Utility\NestedArray;
32
use Drupal\Core\Datetime\DrupalDateTime;
33
use Drupal\Core\Routing\GeneratorNotInitializedException;
34
use Drupal\Core\Template\Attribute;
35
use Drupal\Core\Render\Element;
36
use Drupal\Core\Render\Renderer;
37
use Drupal\Core\Session\AnonymousUserSession;
38

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
/**
 * @defgroup php_wrappers PHP wrapper functions
 * @{
 * Functions that are wrappers or custom implementations of PHP functions.
 *
 * Certain PHP functions should not be used in Drupal. Instead, Drupal's
 * replacement functions should be used.
 *
 * For example, for improved or more secure UTF8-handling, or RFC-compliant
 * handling of URLs in Drupal.
 *
 * For ease of use and memorizing, all these wrapper functions use the same name
 * as the original PHP function, but prefixed with "drupal_". Beware, however,
 * that not all wrapper functions support the same arguments as the original
 * functions.
 *
 * You should always use these wrapper functions in your code.
 *
 * Wrong:
 * @code
 *   $my_substring = substr($original_string, 0, 5);
 * @endcode
 *
 * Correct:
 * @code
64
 *   $my_substring = Unicode::substr($original_string, 0, 5);
65 66
 * @endcode
 *
67
 * @}
68 69
 */

70 71 72
/**
 * Return status for saving which involved creating a new item.
 */
73
const SAVED_NEW = 1;
74 75 76 77

/**
 * Return status for saving which involved an update to an existing item.
 */
78
const SAVED_UPDATED = 2;
79 80 81 82

/**
 * Return status for saving which deleted an existing item.
 */
83
const SAVED_DELETED = 3;
84

85
/**
86
 * The default aggregation group for CSS files added to the page.
87
 */
88
const CSS_AGGREGATE_DEFAULT = 0;
89 90

/**
91
 * The default aggregation group for theme CSS files added to the page.
92
 */
93 94 95 96 97 98 99 100 101 102 103 104 105
const CSS_AGGREGATE_THEME = 100;

/**
 * The default weight for CSS rules that style HTML elements ("base" styles).
 */
const CSS_BASE = -200;

/**
 * The default weight for CSS rules that layout a page.
 */
const CSS_LAYOUT = -100;

/**
106
 * The default weight for CSS rules that style design components (and their associated states and themes.)
107 108 109 110 111 112 113 114 115
 */
const CSS_COMPONENT = 0;

/**
 * The default weight for CSS rules that style states and are not included with components.
 */
const CSS_STATE = 100;

/**
116
 * The default weight for CSS rules that style themes and are not included with components.
117
 */
118
const CSS_THEME = 200;
119

120 121 122 123 124
/**
 * The default group for JavaScript settings added to the page.
 */
const JS_SETTING = -200;

125
/**
126
 * The default group for JavaScript and jQuery libraries added to the page.
127
 */
128
const JS_LIBRARY = -100;
129 130

/**
131
 * The default group for module JavaScript code added to the page.
132
 */
133
const JS_DEFAULT = 0;
134 135

/**
136
 * The default group for theme JavaScript code added to the page.
137
 */
138
const JS_THEME = 100;
139

140 141 142 143 144 145 146 147 148
/**
 * The delimiter used to split plural strings.
 *
 * This is the ETX (End of text) character and is used as a minimal means to
 * separate singular and plural variants in source and translation text. It
 * was found to be the most compatible delimiter for the supported databases.
 */
const LOCALE_PLURAL_DELIMITER = "\03";

Dries's avatar
Dries committed
149
/**
150
 * Adds output to the HEAD tag of the HTML page.
151
 *
152
 * This function can be called as long as the headers aren't sent. Pass no
153 154 155 156 157 158 159 160 161 162 163 164
 * arguments (or NULL for both) to retrieve the currently stored elements.
 *
 * @param $data
 *   A renderable array. If the '#type' key is not set then 'html_tag' will be
 *   added as the default '#type'.
 * @param $key
 *   A unique string key to allow implementations of hook_html_head_alter() to
 *   identify the element in $data. Required if $data is not NULL.
 *
 * @return
 *   An array of all stored HEAD elements.
 *
165
 * @see \Drupal\Core\Render\Element\HtmlTag::preRenderHtmlTag()
166 167 168
 *
 * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0
 *   Use #attached on render arrays.
Dries's avatar
Dries committed
169
 */
170
function _drupal_add_html_head($data = NULL, $key = NULL) {
171
  $stored_head = &drupal_static(__FUNCTION__, array());
172 173 174 175 176 177

  if (isset($data) && isset($key)) {
    if (!isset($data['#type'])) {
      $data['#type'] = 'html_tag';
    }
    $stored_head[$key] = $data;
Dries's avatar
Dries committed
178 179 180 181
  }
  return $stored_head;
}

182
/**
183
 * Retrieves output to be displayed in the HEAD tag of the HTML page.
184 185 186 187 188 189
 *
 * @param bool $render
 *   If TRUE render the HEAD elements, otherwise return just the elements.
 *
 * @return string|array
 *   Return the rendered HTML head or the elements itself.
190 191 192
 *
 * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0
 *   Use #attached on render arrays.
193
 */
194
function drupal_get_html_head($render = TRUE) {
195
  $elements = _drupal_add_html_head();
196
  \Drupal::moduleHandler()->alter('html_head', $elements);
197 198 199 200 201 202
  if ($render) {
    return drupal_render($elements);
  }
  else {
    return $elements;
  }
Dries's avatar
Dries committed
203 204
}

205
/**
206
 * Prepares a 'destination' URL query parameter for use with url().
207
 *
208 209 210 211
 * 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.
212
 *
213 214 215 216 217
 * @return
 *   An associative array containing the key:
 *   - destination: The path provided via the destination query string or, if
 *     not available, the current path.
 *
218
 * @ingroup form_api
219 220
 */
function drupal_get_destination() {
221 222 223 224 225 226
  $destination = &drupal_static(__FUNCTION__);

  if (isset($destination)) {
    return $destination;
  }

227
  $query = \Drupal::request()->query;
228 229
  if ($query->has('destination')) {
    $destination = array('destination' => $query->get('destination'));
230 231
  }
  else {
232
    $path = \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : '';
233
    $query = UrlHelper::buildQuery(UrlHelper::filterQueryParameters($query->all()));
234
    if ($query != '') {
235
      $path .= '?' . $query;
236
    }
237 238 239 240 241
    $destination = array('destination' => $path);
  }
  return $destination;
}

Kjartan's avatar
Kjartan committed
242
/**
Dries's avatar
 
Dries committed
243
 * @defgroup validation Input validation
Dries's avatar
 
Dries committed
244
 * @{
Dries's avatar
 
Dries committed
245
 * Functions to validate user input.
Kjartan's avatar
Kjartan committed
246 247
 */

248
/**
249
 * Verifies the syntax of the given email address.
Dries's avatar
 
Dries committed
250
 *
251
 * @param string $mail
252
 *   A string containing an email address.
253
 *
254
 * @return bool
Dries's avatar
 
Dries committed
255
 *   TRUE if the address is in a valid format.
256 257 258
 *
 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
 *   Use \Drupal::service('email.validator')->isValid().
259
 */
Dries's avatar
 
Dries committed
260
function valid_email_address($mail) {
261
  return \Drupal::service('email.validator')->isValid($mail);
262 263
}

264 265 266 267
/**
 * @} End of "defgroup validation".
 */

268 269 270 271
/**
 * @defgroup sanitization Sanitization functions
 * @{
 * Functions to sanitize values.
272 273 274
 *
 * See http://drupal.org/writing-secure-code for information
 * on writing secure code.
275 276
 */

277
/**
278
 * Strips dangerous protocols from a URI and encodes it for output to HTML.
279 280 281 282 283 284 285
 *
 * @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
286 287 288
 *   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
289
 *   Drupal\Core\Template\Attribute, call
290
 *   \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() instead.
291
 *
292
 * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
293
 * @see \Drupal\Component\Utility\String::checkPlain()
Dries's avatar
Dries committed
294 295
 */
function check_url($uri) {
296
  return String::checkPlain(UrlHelper::stripDangerousProtocols($uri));
Dries's avatar
Dries committed
297 298
}

299 300 301 302
/**
 * @} End of "defgroup sanitization".
 */

Dries's avatar
 
Dries committed
303
/**
Dries's avatar
 
Dries committed
304
 * @defgroup format Formatting
Dries's avatar
 
Dries committed
305
 * @{
Dries's avatar
 
Dries committed
306
 * Functions to format numbers, strings, dates, etc.
Dries's avatar
 
Dries committed
307 308
 */

309
/**
310
 * Formats XML elements.
311
 *
312 313 314
 * Note: It is the caller's responsibility to sanitize any input parameters.
 * This function does not perform sanitization.
 *
315
 * @param $array
316
 *   An array where each item represents an element and is either a:
317 318
 *   - (key => value) pair (<key>value</key>)
 *   - Associative array with fields:
319 320
 *     - 'key': The element name. Element names are not sanitized, so do not
 *       pass user input.
321 322 323 324 325 326 327
 *     - '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) {
328
  $output = '';
329 330
  foreach ($array as $key => $value) {
    if (is_numeric($key)) {
Dries's avatar
 
Dries committed
331
      if ($value['key']) {
332
        $output .= ' <' . $value['key'];
333
        if (isset($value['attributes']) && is_array($value['attributes'])) {
334
          $output .= new Attribute($value['attributes']);
Dries's avatar
 
Dries committed
335 336
        }

337
        if (isset($value['value']) && $value['value'] != '') {
338
          $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : String::checkPlain($value['value'])) . '</' . $value['key'] . ">\n";
Dries's avatar
 
Dries committed
339 340 341 342 343 344 345
        }
        else {
          $output .= " />\n";
        }
      }
    }
    else {
346
      $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : String::checkPlain($value)) . "</$key>\n";
Dries's avatar
 
Dries committed
347
    }
Dries's avatar
 
Dries committed
348
  }
349 350 351 352 353
  // @todo This is marking the output string as safe HTML, but we have only
  //   sanitized the attributes and tag values, not the tag names, and we
  //   cannot guarantee the assembled markup is safe. Consider a fix in:
  //   https://www.drupal.org/node/2296885
  return SafeMarkup::set($output);
Dries's avatar
 
Dries committed
354 355
}

Dries's avatar
 
Dries committed
356
/**
357
 * Generates a string representation for the given byte count.
Dries's avatar
 
Dries committed
358
 *
Dries's avatar
 
Dries committed
359
 * @param $size
360
 *   A size in bytes.
361
 * @param $langcode
362 363
 *   Optional language code to translate to a language other than what is used
 *   to display the page.
364
 *
Dries's avatar
 
Dries committed
365 366
 * @return
 *   A translated string representation of the size.
Dries's avatar
 
Dries committed
367
 */
368
function format_size($size, $langcode = NULL) {
369
  if ($size < Bytes::KILOBYTE) {
370
    return \Drupal::translation()->formatPlural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
Dries's avatar
 
Dries committed
371
  }
372
  else {
373
    $size = $size / Bytes::KILOBYTE; // Convert bytes to kilobytes.
374
    $units = array(
375 376 377 378 379 380 381 382
      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)),
383 384
    );
    foreach ($units as $unit) {
385 386
      if (round($size, 2) >= Bytes::KILOBYTE) {
        $size = $size / Bytes::KILOBYTE;
387 388 389 390
      }
      else {
        break;
      }
391
    }
392
    return str_replace('@size', round($size, 2), $unit);
Dries's avatar
 
Dries committed
393 394 395
  }
}

Dries's avatar
 
Dries committed
396
/**
397
 * Formats a date, using a date type or a custom date format string.
Dries's avatar
 
Dries committed
398
 *
Dries's avatar
 
Dries committed
399
 * @param $timestamp
400
 *   A UNIX timestamp to format.
Dries's avatar
 
Dries committed
401
 * @param $type
402
 *   (optional) The format to use, one of:
403 404 405
 *   - One of the built-in formats: 'short', 'medium',
 *     'long', 'html_datetime', 'html_date', 'html_time',
 *     'html_yearless_date', 'html_week', 'html_month', 'html_year'.
406
 *   - The name of a date type defined by a date format config entity.
407 408 409
 *   - The machine name of an administrator-defined date format.
 *   - 'custom', to use $format.
 *   Defaults to 'medium'.
Dries's avatar
 
Dries committed
410
 * @param $format
411 412 413
 *   (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.
Dries's avatar
 
Dries committed
414
 * @param $timezone
415
 *   (optional) Time zone identifier, as described at
416
 *   http://php.net/manual/timezones.php Defaults to the time zone used to
417
 *   display the page.
418
 * @param $langcode
419 420 421
 *   (optional) Language code to translate to. Defaults to the language used to
 *   display the page.
 *
Dries's avatar
 
Dries committed
422 423
 * @return
 *   A translated date string in the requested format.
424
 *
425
 * @see \Drupal\Core\Datetime\DateFormatter::format()
Dries's avatar
 
Dries committed
426
 */
427
function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
428
  return \Drupal::service('date.formatter')->format($timestamp, $type, $format, $timezone, $langcode);
429 430
}

431 432 433 434 435
/**
 * Returns an ISO8601 formatted date based on the given date.
 *
 * @param $date
 *   A UNIX timestamp.
436
 *
437 438 439 440 441 442 443 444 445
 * @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);
}

446
/**
447 448 449
 * Translates a formatted date string.
 *
 * Callback for preg_replace_callback() within format_date().
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
 */
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';
Dries's avatar
 
Dries committed
470
    }
471 472 473

    if ($code == '') {
      $cache[$langcode][$code][$string] = $string;
474
    }
Dries's avatar
 
Dries committed
475
    else {
476
      $cache[$langcode][$code][$string] = t($string, array(), $options);
Dries's avatar
 
Dries committed
477
    }
Dries's avatar
 
Dries committed
478
  }
479
  return $cache[$langcode][$code][$string];
Dries's avatar
 
Dries committed
480 481
}

Dries's avatar
 
Dries committed
482 483 484
/**
 * @} End of "defgroup format".
 */
Dries's avatar
 
Dries committed
485

Dries's avatar
 
Dries committed
486
/**
487 488 489 490
 * Generates an internal or external URL.
 *
 * When creating links in modules, consider whether l() could be a better
 * alternative than url().
Dries's avatar
 
Dries committed
491
 *
492 493 494 495 496 497 498 499 500 501
 * @see \Drupal\Core\Url::fromUri()
 * @see \Drupal\Core\Url::fromRoute()
 * @see \Drupal\Core\Url::toString()
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
 *   Use \Drupal\Core\Url::fromRoute() for internal paths served by Drupal
 *   controllers or \Drupal\Core\Url::fromUri() for external paths or
 *   non-controller or sub-domain URIs such as core/install.php. Note that
 *   \Drupal\Core\Url::fromUri() expects a valid URI including the scheme. URIs
 *   from the same sub-domain that are not handled by Drupal controllers should
502
 *   use the base: scheme. For example:
503
 * @code
504
 * $installer_url = \Drupal\Core\Url::fromUri('base:core/install.php')->toString();
505 506 507
 * $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']])->toString();
 * $internal_url = \Drupal\Core\Url::fromRoute('system.admin')->toString();
 * @endcode
Dries's avatar
 
Dries committed
508
 */
509
function _url($path = NULL, array $options = array()) {
510
  return \Drupal::urlGenerator()->generateFromPath($path, $options);
Dries's avatar
 
Dries committed
511 512
}

513
/**
514
 * Formats an attribute string for an HTTP header.
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
 *
 * @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.
 */
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) : '';
}

Dries's avatar
 
Dries committed
533
/**
534
 * Formats an internal or external URL link as an HTML anchor tag.
Dries's avatar
 
Dries committed
535
 *
536
 * This function correctly handles aliased paths and adds an 'active' class
537 538 539
 * 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.
Dries's avatar
 
Dries committed
540
 *
541 542 543
 * 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
544
 * t('Visit the <a href="@url">settings</a> page', array('@url' => \Drupal::url('system.admin')));
545 546 547 548
 * @endcode
 * This keeps the context of the link title ('settings' in the example) for
 * translators.
 *
549 550 551 552 553 554
 * 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.
 *
555 556
 * @param string|array $text
 *   The link text for the anchor tag as a translated string or render array.
557
 * @param string $path
558 559 560
 *   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
561 562 563
 *   \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.
564
 * @param array $options
565 566
 *   An associative array of additional options. Defaults to an empty array. It
 *   may contain the following elements.
567
 *   - 'attributes': An associative array of HTML attributes to apply to the
568 569
 *     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
570 571
 *     to work as an argument for the constructor of the class
 *     Drupal\Core\Template\Attribute($options['attributes']).
572 573 574 575 576
 *   - '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.
577 578 579 580
 *   - '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().
581 582 583 584 585 586 587 588 589 590
 *   - '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
591
 *     system_page_attachments().
592
 *   - Additional $options elements used by the url() function.
593
 *
594
 * @return string
595
 *   An HTML string containing a link to the given path.
596
 *
597
 * @see _url()
598
 * @see system_page_attachments()
599
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
600
 *   Use \Drupal::l($text, $url) where $url is an instance of
601 602 603 604 605 606
 *   \Drupal\Core\Url. To build a \Drupal\Core\Url object for internal paths
 *   served by Drupal controllers use \Drupal\Core\Url::fromRoute(). For
 *   external paths or non-controller or sub-domain URIs such as
 *   core/install.php use \Drupal\Core\Url::fromUri(). Note that
 *   \Drupal\Core\Url::fromUri() expects a valid URI including the scheme. URIs
 *   from the same sub-domain that are not handled by Drupal controllers should
607
 *   be prepended with base:. For example:
608
 * @code
609
 * $installer_url = \Drupal\Core\Url::fromUri('base:core/install.php')->toString();
610
 * $installer_link = \Drupal::l($text, $installer_url);
611
 * $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']])->toString();
612
 * $external_link = \Drupal::l($text, $external_url);
613
 * $internal_url = \Drupal\Core\Url::fromRoute('system.admin')->toString();
614
 * $internal_link = \Drupal::l($text, $internal_url);
615
 * @endcode
Dries's avatar
 
Dries committed
616
 */
617
function _l($text, $path, array $options = array()) {
618
  // Start building a structured representation of our link to be altered later.
619
  $variables = array(
620
    'text' => is_array($text) ? drupal_render($text) : $text,
621 622 623
    'path' => $path,
    'options' => $options,
  );
624

625 626
  // Merge in default options.
  $variables['options'] += array(
627
    'attributes' => array(),
628
    'query' => array(),
629
    'html' => FALSE,
630
    'language' => NULL,
631
    'set_active_class' => FALSE,
632
  );
633

634 635 636
  // 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'])) {
637
    $variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId();
638 639
  }

640 641 642 643 644 645 646 647 648
  // 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);
    }
649

650 651 652
    // 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'])) {
653
      $variables['options']['attributes']['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager')->getPathByAlias($path);
654
    }
655 656 657 658 659 660
  }

  // 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']);
661
  }
662 663

  // Allow other modules to modify the structure of the link.
664
  \Drupal::moduleHandler()->alter('link', $variables);
665 666 667 668 669

  // Move attributes out of options. url() doesn't need them.
  $attributes = new Attribute($variables['options']['attributes']);
  unset($variables['options']['attributes']);

670 671
  // 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.
672
  $url = String::checkPlain(_url($variables['path'], $variables['options']));
673

674 675
  // Sanitize the link text if necessary.
  $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
676
  return SafeMarkup::set('<a href="' . $url . '"' . $attributes . '>' . $text . '</a>');
Dries's avatar
 
Dries committed
677 678
}

679 680 681 682 683 684 685 686
/**
 * 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.
687
 *
688 689 690 691 692
 * If the current time limit is not unlimited 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
693
 * to execute a task than only once at the beginning of the script.
694
 *
695 696 697 698 699 700 701 702 703
 * 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.
704 705
 *
 * @ingroup php_wrappers
706 707 708
 */
function drupal_set_time_limit($time_limit) {
  if (function_exists('set_time_limit')) {
709 710
    $current = ini_get('max_execution_time');
    // Do not set time limit if it is currently unlimited.
711
    if ($current != 0) {
712 713
      @set_time_limit($time_limit);
    }
714 715 716
  }
}

717
/**
718
 * Returns the base URL path (i.e., directory) of the Drupal installation.
719
 *
720 721
 * 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 "/".
722
 *
723 724 725
 * Examples:
 * - http://example.com returns "/" because the path is empty.
 * - http://example.com/drupal/folder returns "/drupal/folder/".
726 727
 */
function base_path() {
728
  return $GLOBALS['base_path'];
729 730
}

731
/**
732
 * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
733
 *
734
 * This function can be called as long the HTML header hasn't been sent, which
735
 * on normal pages is up through the preprocess step of _theme('html'). Adding
736 737
 * a link will overwrite a prior link with the exact same 'rel' and 'href'
 * attributes.
738
 *
739 740 741 742
 * @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.
743 744 745
 *
 * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0
 *   Use #attached on render arrays.
746
 */
747
function _drupal_add_html_head_link($attributes, $header = FALSE) {
748 749 750 751 752 753 754 755
  $element = array(
    '#tag' => 'link',
    '#attributes' => $attributes,
  );
  $href = $attributes['href'];

  if ($header) {
    // Also add a HTTP header "Link:".
756
    $href = '<' . String::checkPlain($attributes['href']) . '>;';
757
    unset($attributes['href']);
758
    $element['#attached']['http_header'][] = array('Link',  $href . drupal_http_header_attributes($attributes), TRUE);
759 760
  }

761
  _drupal_add_html_head($element, 'html_head_link:' . $attributes['rel'] . ':' . $href);
762 763
}

764
/**
765
 * Deletes old cached CSS files.
766
 *
767 768
 * @deprecated in Drupal 8.x, will be removed before Drupal 9.0.
 *   Use \Drupal\Core\Asset\AssetCollectionOptimizerInterface::deleteAll().
769
 */
770 771
function drupal_clear_css_cache() {
  \Drupal::service('asset.css.collection_optimizer')->deleteAll();
772 773
}

774
/**
775
 * Prepares a string for use as a valid HTML ID and guarantees uniqueness.
776
 *
777 778 779 780 781
 * 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.
 *
782
 * For already existing IDs, a counter is appended to the ID string. Therefore,
783 784 785 786
 * 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.
 *
787 788
 * Two consecutive hyphens separate the counter from the original ID. To manage
 * uniqueness across multiple Ajax requests on the same page, Ajax requests
789 790 791
 * POST an array of all IDs currently present on the page, which are used to
 * prime this function's cache upon first invocation.
 *
792
 * To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive
793 794
 * hyphens in the originally passed $id are replaced with a single hyphen.
 *
795 796
 * @param $id
 *   The ID to clean.
797
 *
798 799
 * @return
 *   The cleaned ID.
800 801 802
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
 *   Use \Drupal\Component\Utility\Html::getUniqueId()
803
 */
804
function drupal_html_id($id) {
805
  return Html::getUniqueId($id);
806 807
}

808 809 810 811 812 813 814 815 816 817 818 819 820
/**
 * 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()
821 822 823
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
 *   Use \Drupal\Component\Utility\Html::getId()
824 825
 */
function drupal_clean_id_identifier($id) {
826
  return Html::getId($id);
827 828
}

Steven Wittens's avatar
Steven Wittens committed
829
/**
830
 * Constructs an array of the defaults that are used for JavaScript assets.
831 832
 *
 * @param $data
833
 *   (optional) The default data parameter for the JavaScript asset array.
834
 *
835
 * @see hook_js_alter()
836 837 838 839
 */
function drupal_js_defaults($data = NULL) {
  return array(
    'type' => 'file',
840 841 842
    'group' => JS_DEFAULT,
    'every_page' => FALSE,
    'weight' => 0,
843 844
    'scope' => 'header',
    'cache' => TRUE,
845
    'preprocess' => TRUE,
846
    'attributes' => array(),