common.inc 112 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\Cache\Cache;
24
use Drupal\Core\Language\LanguageInterface;
25
use Drupal\Core\Site\Settings;
26 27
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
28
use Drupal\Core\PhpStorage\PhpStorageFactory;
29
use Drupal\Component\Utility\NestedArray;
30
use Drupal\Core\Datetime\DrupalDateTime;
31
use Drupal\Core\Routing\GeneratorNotInitializedException;
32
use Drupal\Core\Template\Attribute;
33
use Drupal\Core\Render\Element;
34
use Drupal\Core\Session\AnonymousUserSession;
35

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
/**
 * @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
61
 *   $my_substring = Unicode::substr($original_string, 0, 5);
62 63
 * @endcode
 *
64
 * @}
65 66
 */

67 68 69
/**
 * Return status for saving which involved creating a new item.
 */
70
const SAVED_NEW = 1;
71 72 73 74

/**
 * Return status for saving which involved an update to an existing item.
 */
75
const SAVED_UPDATED = 2;
76 77 78 79

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

82
/**
83
 * The default aggregation group for CSS files added to the page.
84
 */
85
const CSS_AGGREGATE_DEFAULT = 0;
86 87

/**
88
 * The default aggregation group for theme CSS files added to the page.
89
 */
90 91 92 93 94 95 96 97 98 99 100 101 102
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;

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

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

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

117 118 119 120 121
/**
 * The default group for JavaScript settings added to the page.
 */
const JS_SETTING = -200;

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

/**
128
 * The default group for module JavaScript code added to the page.
129
 */
130
const JS_DEFAULT = 0;
131 132

/**
133
 * The default group for theme JavaScript code added to the page.
134
 */
135
const JS_THEME = 100;
136

137 138 139 140 141 142 143 144 145
/**
 * 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";

146
/**
147
 * Gets the name of the currently active installation profile.
148 149 150
 *
 * When this function is called during Drupal's initial installation process,
 * the name of the profile that's about to be installed is stored in the global
151 152
 * installation state. At all other times, the "install_profile" setting will be
 * available in settings.php.
153
 *
154 155 156 157
 * @return string|null $profile
 *   The name of the installation profile or NULL if no installation profile is
 *   currently active. This is the case for example during the first steps of
 *   the installer or during unit tests.
158 159 160 161
 */
function drupal_get_profile() {
  global $install_state;

162 163 164 165 166 167
  if (drupal_installation_attempted()) {
    // If the profile has been selected return it.
    if (isset($install_state['parameters']['profile'])) {
      $profile = $install_state['parameters']['profile'];
    }
    else {
168
      $profile = NULL;
169
    }
170 171
  }
  else {
172 173
    // Fall back to NULL, if there is no 'install_profile' setting.
    $profile = Settings::get('install_profile');
174 175 176 177 178
  }

  return $profile;
}

Dries's avatar
Dries committed
179
/**
180
 * Adds output to the HEAD tag of the HTML page.
181
 *
182
 * This function can be called as long as the headers aren't sent. Pass no
183 184 185 186 187 188 189 190 191 192 193 194
 * 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.
 *
195
 * @see \Drupal\Core\Render\Element\HtmlTag::preRenderHtmlTag()
196 197 198
 *
 * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0
 *   Use #attached on render arrays.
Dries's avatar
Dries committed
199
 */
200
function _drupal_add_html_head($data = NULL, $key = NULL) {
201
  $stored_head = &drupal_static(__FUNCTION__, array());
202 203 204 205 206 207

  if (isset($data) && isset($key)) {
    if (!isset($data['#type'])) {
      $data['#type'] = 'html_tag';
    }
    $stored_head[$key] = $data;
Dries's avatar
Dries committed
208 209 210 211
  }
  return $stored_head;
}

212
/**
213
 * Retrieves output to be displayed in the HEAD tag of the HTML page.
214 215 216 217 218 219
 *
 * @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.
220 221 222
 *
 * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0
 *   Use #attached on render arrays.
223
 */
224
function drupal_get_html_head($render = TRUE) {
225
  $elements = _drupal_add_html_head();
226
  \Drupal::moduleHandler()->alter('html_head', $elements);
227 228 229 230 231 232
  if ($render) {
    return drupal_render($elements);
  }
  else {
    return $elements;
  }
Dries's avatar
Dries committed
233 234
}

235
/**
236
 * Prepares a 'destination' URL query parameter for use with url().
237
 *
238 239 240 241
 * 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.
242
 *
243 244 245 246 247 248
 * @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()
249
 * @ingroup form_api
250 251
 */
function drupal_get_destination() {
252 253 254 255 256 257
  $destination = &drupal_static(__FUNCTION__);

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

258
  $query = \Drupal::request()->query;
259 260
  if ($query->has('destination')) {
    $destination = array('destination' => $query->get('destination'));
261 262
  }
  else {
263
    $path = current_path();
264
    $query = UrlHelper::buildQuery(UrlHelper::filterQueryParameters($query->all()));
265
    if ($query != '') {
266
      $path .= '?' . $query;
267
    }
268 269 270 271 272
    $destination = array('destination' => $path);
  }
  return $destination;
}

Kjartan's avatar
Kjartan committed
273
/**
Dries's avatar
 
Dries committed
274
 * @defgroup validation Input validation
Dries's avatar
 
Dries committed
275
 * @{
Dries's avatar
 
Dries committed
276
 * Functions to validate user input.
Kjartan's avatar
Kjartan committed
277 278
 */

279
/**
280
 * Verifies the syntax of the given email address.
Dries's avatar
 
Dries committed
281
 *
282
 * This uses the
283
 * @link http://php.net/manual/filter.filters.validate.php PHP email validation filter. @endlink
284
 *
Dries's avatar
 
Dries committed
285
 * @param $mail
286
 *   A string containing an email address.
287
 *
Dries's avatar
 
Dries committed
288
 * @return
Dries's avatar
 
Dries committed
289
 *   TRUE if the address is in a valid format.
290
 */
Dries's avatar
 
Dries committed
291
function valid_email_address($mail) {
292
  return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
293 294
}

295 296 297 298
/**
 * @} End of "defgroup validation".
 */

299 300 301 302
/**
 * @defgroup sanitization Sanitization functions
 * @{
 * Functions to sanitize values.
303 304 305
 *
 * See http://drupal.org/writing-secure-code for information
 * on writing secure code.
306 307
 */

308
/**
309
 * Strips dangerous protocols from a URI and encodes it for output to HTML.
310 311 312 313 314 315 316
 *
 * @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
317 318 319
 *   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
320
 *   Drupal\Core\Template\Attribute, call
321
 *   \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() instead.
322
 *
323
 * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
324
 * @see \Drupal\Component\Utility\String::checkPlain()
Dries's avatar
Dries committed
325 326
 */
function check_url($uri) {
327
  return String::checkPlain(UrlHelper::stripDangerousProtocols($uri));
Dries's avatar
Dries committed
328 329
}

330 331 332 333
/**
 * @} End of "defgroup sanitization".
 */

Dries's avatar
 
Dries committed
334
/**
Dries's avatar
 
Dries committed
335
 * @defgroup format Formatting
Dries's avatar
 
Dries committed
336
 * @{
Dries's avatar
 
Dries committed
337
 * Functions to format numbers, strings, dates, etc.
Dries's avatar
 
Dries committed
338 339
 */

340
/**
341
 * Formats XML elements.
342
 *
343 344 345
 * Note: It is the caller's responsibility to sanitize any input parameters.
 * This function does not perform sanitization.
 *
346
 * @param $array
347
 *   An array where each item represents an element and is either a:
348 349
 *   - (key => value) pair (<key>value</key>)
 *   - Associative array with fields:
350 351
 *     - 'key': The element name. Element names are not sanitized, so do not
 *       pass user input.
352 353 354 355 356 357 358
 *     - '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) {
359
  $output = '';
360 361
  foreach ($array as $key => $value) {
    if (is_numeric($key)) {
Dries's avatar
 
Dries committed
362
      if ($value['key']) {
363
        $output .= ' <' . $value['key'];
364
        if (isset($value['attributes']) && is_array($value['attributes'])) {
365
          $output .= new Attribute($value['attributes']);
Dries's avatar
 
Dries committed
366 367
        }

368
        if (isset($value['value']) && $value['value'] != '') {
369
          $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : String::checkPlain($value['value'])) . '</' . $value['key'] . ">\n";
Dries's avatar
 
Dries committed
370 371 372 373 374 375 376
        }
        else {
          $output .= " />\n";
        }
      }
    }
    else {
377
      $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : String::checkPlain($value)) . "</$key>\n";
Dries's avatar
 
Dries committed
378
    }
Dries's avatar
 
Dries committed
379
  }
380 381 382 383 384
  // @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
385 386
}

Dries's avatar
 
Dries committed
387
/**
388
 * Formats a string containing a count of items.
Dries's avatar
 
Dries committed
389
 *
Dries's avatar
 
Dries committed
390
 * This function ensures that the string is pluralized correctly. Since t() is
391 392
 * called by this function, make sure not to pass already-localized strings to
 * it.
Dries's avatar
 
Dries committed
393
 *
394 395 396 397 398 399 400 401 402 403
 * 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.',
404
 *     array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
405 406
 * @endcode
 *
Dries's avatar
 
Dries committed
407 408 409
 * @param $count
 *   The item count to display.
 * @param $singular
410 411 412
 *   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.
Dries's avatar
 
Dries committed
413
 * @param $plural
414 415 416
 *   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".
417
 * @param $args
418
 *   An associative array of replacements to make after translation. Instances
419
 *   of any key in this array are replaced with the corresponding value.
420 421 422
 *   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.
423
 * @param $options
424 425
 *   An associative array of additional options. See t() for allowed keys.
 *
Dries's avatar
 
Dries committed
426 427
 * @return
 *   A translated string.
428 429 430
 *
 * @see t()
 * @see format_string()
431
 * @see \Drupal\Core\StringTranslation\TranslationManager->formatPlural()
432
 *
433 434
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal::translation()->formatPlural().
Dries's avatar
 
Dries committed
435
 */
436
function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
437
  return \Drupal::translation()->formatPlural($count, $singular, $plural, $args, $options);
Dries's avatar
 
Dries committed
438 439
}

Dries's avatar
 
Dries committed
440
/**
441
 * Generates a string representation for the given byte count.
Dries's avatar
 
Dries committed
442
 *
Dries's avatar
 
Dries committed
443
 * @param $size
444
 *   A size in bytes.
445
 * @param $langcode
446 447
 *   Optional language code to translate to a language other than what is used
 *   to display the page.
448
 *
Dries's avatar
 
Dries committed
449 450
 * @return
 *   A translated string representation of the size.
Dries's avatar
 
Dries committed
451
 */
452
function format_size($size, $langcode = NULL) {
453
  if ($size < Bytes::KILOBYTE) {
454
    return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
Dries's avatar
 
Dries committed
455
  }
456
  else {
457
    $size = $size / Bytes::KILOBYTE; // Convert bytes to kilobytes.
458
    $units = array(
459 460 461 462 463 464 465 466
      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)),
467 468
    );
    foreach ($units as $unit) {
469 470
      if (round($size, 2) >= Bytes::KILOBYTE) {
        $size = $size / Bytes::KILOBYTE;
471 472 473 474
      }
      else {
        break;
      }
475
    }
476
    return str_replace('@size', round($size, 2), $unit);
Dries's avatar
 
Dries committed
477 478 479
  }
}

Dries's avatar
 
Dries committed
480
/**
481
 * Formats a date, using a date type or a custom date format string.
Dries's avatar
 
Dries committed
482
 *
Dries's avatar
 
Dries committed
483
 * @param $timestamp
484
 *   A UNIX timestamp to format.
Dries's avatar
 
Dries committed
485
 * @param $type
486
 *   (optional) The format to use, one of:
487 488 489
 *   - One of the built-in formats: 'short', 'medium',
 *     'long', 'html_datetime', 'html_date', 'html_time',
 *     'html_yearless_date', 'html_week', 'html_month', 'html_year'.
490
 *   - The name of a date type defined by a date format config entity.
491 492 493
 *   - The machine name of an administrator-defined date format.
 *   - 'custom', to use $format.
 *   Defaults to 'medium'.
Dries's avatar
 
Dries committed
494
 * @param $format
495 496 497
 *   (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
498
 * @param $timezone
499
 *   (optional) Time zone identifier, as described at
500
 *   http://php.net/manual/timezones.php Defaults to the time zone used to
501
 *   display the page.
502
 * @param $langcode
503 504 505
 *   (optional) Language code to translate to. Defaults to the language used to
 *   display the page.
 *
Dries's avatar
 
Dries committed
506 507
 * @return
 *   A translated date string in the requested format.
508
 *
509
 * @see \Drupal\Component\Datetime\DateFormatter::format()
Dries's avatar
 
Dries committed
510
 */
511
function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
512
  return \Drupal::service('date.formatter')->format($timestamp, $type, $format, $timezone, $langcode);
513 514
}

515 516 517 518 519
/**
 * Returns an ISO8601 formatted date based on the given date.
 *
 * @param $date
 *   A UNIX timestamp.
520
 *
521 522 523 524 525 526 527 528 529
 * @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);
}

530
/**
531 532 533
 * Translates a formatted date string.
 *
 * Callback for preg_replace_callback() within format_date().
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
 */
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
554
    }
555 556 557

    if ($code == '') {
      $cache[$langcode][$code][$string] = $string;
558
    }
Dries's avatar
 
Dries committed
559
    else {
560
      $cache[$langcode][$code][$string] = t($string, array(), $options);
Dries's avatar
 
Dries committed
561
    }
Dries's avatar
 
Dries committed
562
  }
563
  return $cache[$langcode][$code][$string];
Dries's avatar
 
Dries committed
564 565
}

Dries's avatar
 
Dries committed
566 567 568
/**
 * @} End of "defgroup format".
 */
Dries's avatar
 
Dries committed
569

Dries's avatar
 
Dries committed
570
/**
571 572 573 574
 * 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
575
 *
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591
 * @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
 *   be prepended with base://. For example:
 * @code
 * $installer_url = \Drupal\Core\Url::fromUri('base://core/install.php')->toString();
 * $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
592
 */
593
function _url($path = NULL, array $options = array()) {
594
  return \Drupal::urlGenerator()->generateFromPath($path, $options);
Dries's avatar
 
Dries committed
595 596
}

597
/**
598
 * Formats an attribute string for an HTTP header.
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
 *
 * @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
617
/**
618
 * Formats an internal or external URL link as an HTML anchor tag.
Dries's avatar
 
Dries committed
619
 *
620
 * This function correctly handles aliased paths and adds an 'active' class
621 622 623
 * 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
624
 *
625 626 627
 * 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
628
 * t('Visit the <a href="@url">settings</a> page', array('@url' => \Drupal::url('system.admin')));
629 630 631 632
 * @endcode
 * This keeps the context of the link title ('settings' in the example) for
 * translators.
 *
633 634 635 636 637 638
 * 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.
 *
639 640
 * @param string|array $text
 *   The link text for the anchor tag as a translated string or render array.
641
 * @param string $path
642 643 644
 *   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
645 646 647
 *   \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.
648
 * @param array $options
649 650
 *   An associative array of additional options. Defaults to an empty array. It
 *   may contain the following elements.
651
 *   - 'attributes': An associative array of HTML attributes to apply to the
652 653
 *     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
654 655
 *     to work as an argument for the constructor of the class
 *     Drupal\Core\Template\Attribute($options['attributes']).
656 657
 *   - '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
658 659 660
 *     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.
661 662 663 664
 *   - '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().
665 666 667 668 669 670 671 672 673 674
 *   - '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
675
 *     system_page_attachments().
676
 *   - Additional $options elements used by the url() function.
677
 *
678
 * @return string
679
 *   An HTML string containing a link to the given path.
680
 *
681
 * @see _url()
682
 * @see system_page_attachments()
683
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.0.
684
 *   Use \Drupal::l($text, $url) where $url is an instance of
685 686 687 688 689 690 691 692 693
 *   \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
 *   be prepended with base://. For example:
 * @code
 * $installer_url = \Drupal\Core\Url::fromUri('base://core/install.php')->toString();
694
 * $installer_link = \Drupal::l($text, $installer_url);
695
 * $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']])->toString();
696
 * $external_link = \Drupal::l($text, $external_url);
697
 * $internal_url = \Drupal\Core\Url::fromRoute('system.admin')->toString();
698
 * $internal_link = \Drupal::l($text, $internal_url);
699
 * @endcode
Dries's avatar
 
Dries committed
700
 */
701
function _l($text, $path, array $options = array()) {
702
  // Start building a structured representation of our link to be altered later.
703
  $variables = array(
704
    'text' => is_array($text) ? drupal_render($text) : $text,
705 706 707
    'path' => $path,
    'options' => $options,
  );
708

709 710
  // Merge in default options.
  $variables['options'] += array(
711
    'attributes' => array(),
712
    'query' => array(),
713
    'html' => FALSE,
714
    'language' => NULL,
715
    'set_active_class' => FALSE,
716
  );
717

718 719 720
  // 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'])) {
721
    $variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId();
722 723
  }

724 725 726 727 728 729 730 731 732
  // 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);
    }
733

734 735 736
    // 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'])) {
737
      $variables['options']['attributes']['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager')->getPathByAlias($path);
738
    }
739 740 741 742 743 744
  }

  // 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']);
745
  }
746 747

  // Allow other modules to modify the structure of the link.
748
  \Drupal::moduleHandler()->alter('link', $variables);
749 750 751 752 753

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

754 755
  // 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.
756
  $url = String::checkPlain(_url($variables['path'], $variables['options']));
757 758

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

763 764 765 766 767 768 769 770
/**
 * 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.
771
 *
772 773 774 775 776
 * 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
777
 * to execute a task than only once at the beginning of the script.
778
 *
779 780 781 782 783 784 785 786 787
 * 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.
788 789
 *
 * @ingroup php_wrappers
790 791 792
 */
function drupal_set_time_limit($time_limit) {
  if (function_exists('set_time_limit')) {
793 794
    $current = ini_get('max_execution_time');
    // Do not set time limit if it is currently unlimited.
795
    if ($current != 0) {
796 797
      @set_time_limit($time_limit);
    }
798 799 800
  }
}

Dries's avatar
Dries committed
801 802 803 804
/**
 * Returns the path to a system item (module, theme, etc.).
 *
 * @param $type
805 806
 *   The type of the item; one of 'core', 'profile', 'module', 'theme', or
 *   'theme_engine'.
Dries's avatar
Dries committed
807
 * @param $name
808 809
 *   The name of the item for which the path is requested. Ignored for
 *   $type 'core'.
Dries's avatar
Dries committed
810 811
 *
 * @return
812
 *   The path to the requested item or an empty string if the item is not found.
Dries's avatar
Dries committed
813 814 815 816 817
 */
function drupal_get_path($type, $name) {
  return dirname(drupal_get_filename($type, $name));
}

818
/**
819
 * Returns the base URL path (i.e., directory) of the Drupal installation.
820
 *
821 822
 * 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 "/".
823
 *
824 825 826
 * Examples:
 * - http://example.com returns "/" because the path is empty.
 * - http://example.com/drupal/folder returns "/drupal/folder/".
827 828
 */
function base_path() {
829
  return $GLOBALS['base_path'];
830 831
}

832
/**
833
 * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
834
 *
835
 * This function can be called as long the HTML header hasn't been sent, which
836
 * on normal pages is up through the preprocess step of _theme('html'). Adding
837 838
 * a link will overwrite a prior link with the exact same 'rel' and 'href'
 * attributes.
839
 *
840 841 842 843
 * @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.
844 845 846
 *
 * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.0.0
 *   Use #attached on render arrays.
847
 */
848
function _drupal_add_html_head_link($attributes, $header = FALSE) {
849 850 851 852 853 854 855 856
  $element = array(
    '#tag' => 'link',
    '#attributes' => $attributes,
  );
  $href = $attributes['href'];

  if ($header) {
    // Also add a HTTP header "Link:".
857
    $href = '<' . String::checkPlain($attributes['href']) . '>;';
858
    unset($attributes['href']);
859
    $element['#attached']['http_header'][] = array('Link',  $href . drupal_http_header_attributes($attributes), TRUE);
860 861
  }

862
  _drupal_add_html_head($element, 'html_head_link:' . $attributes['rel'] . ':' . $href);
863 864
}

865
/**
866 867
 * Adds a cascading stylesheet to the stylesheet queue.
 *
868
 * Calling drupal_static_reset('_drupal_add_css') will clear all cascading
869 870
 * stylesheets added so far.
 *
871 872 873 874 875 876 877 878
 * 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:
879 880 881 882
 * 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."
 *
883 884 885
 * $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
886
 * files do not happen to be needed on a page.
887
 *
888 889
 * Non-preprocessed files should only be added to the page when they are
 * actually needed.
890
 *
891 892 893
 * @param $data
 *   (optional) The stylesheet data to be added, depending on what is passed
 *   through to the $options['type'] parameter:
894 895 896 897 898 899 900
 *   - '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.
901
 *     See drupal_get_css() where the overrides are performed.
902
 *   - 'inline': A string of CSS that should be placed in the given scope. Note
903 904
 *     that it is better practice to use 'file' stylesheets, rather than
 *     'inline', as the CSS would then be aggregated and cached.
905
 *   - 'external': The absolute path to an external CSS file that is not hosted
906 907
 *     on the local server. These files will not be aggregated if CSS
 *     aggregation is enabled.
908
 * @param $options
909
 *   (optional) A string defining the 'type' of CSS that is being added in the
910 911
 *   $data parameter ('file', 'inline', or 'external'), or an array which can
 *   have any or all of the following keys:
912 913
 *   - 'type': The type of stylesheet being added. Available options are 'file',
 *     'inline' or 'external'. Defaults to 'file'.
914 915 916
 *   - '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
917 918 919
 *     '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'.
920 921 922 923 924 925 926
 *   - '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,
927
 *     so CSS within higher aggregate groups can take precedence over CSS
928
 *     within lower aggregate groups.
929 930 931 932
 *   - '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
933
 *     theme .info.yml files. Modules that add stylesheets within
934 935
 *     hook_page_attachments() implementations, or from other code that ensures
 *     that the stylesheet is added to all website pages, should also set this flag
936 937 938 939 940 941
 *     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
942 943 944 945 946 947 948 949 950 951
 *     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
952 953 954 955
 *     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.
956 957 958
 *     - 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
959
 *       being the same, a stylesheet added by a call to _drupal_add_css() that
960
 *       happened later in the page request gets added to the page after one for
961
 *       which _drupal_add_css() happened earlier in the page request.
962 963 964 965
 *     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
966
 *       states and themes.)
967
 *     - CSS_STATE: Styles for states that are not included with components.
968
 *     - CSS_THEME: Styles for themes that are not included with components.
969 970
 *     The weight numbers follow the SMACSS convention of CSS categorization.
 *     See http://drupal.org/node/1887922
971
 *   - 'media': The media type for the stylesheet, e.g., all, print, screen.
972
 *     Defaults to 'all'. It is extremely important to leave this set to 'all'
973
 *     or it will negatively impact front-end performance. Instead add a @media
974
 *     block to the included CSS file.
975
 *   - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the
976
 *     styles will be aggregated and compressed. Defaults to TRUE.
977
 *   - 'browsers': An array containing information specifying which browsers
978 979 980
 *     should load the CSS item. See
 *     \Drupal\Core\Render\Element\HtmlTag::preRenderConditionalComments() for
 *     details.
981
 *
982
 * @return
983
 *   An array of queued cascading stylesheets.
984
 *
985 986
 * @deprecated as of Drupal 8.0. Use the #attached key in render arrays instead.
 *
987
 * @see drupal_get_css()
988
 */
989
function _drupal_add_css($data = NULL, $options = NULL) {
990
  $css = &drupal_static(__FUNCTION__, array());
991

992 993 994 995 996 997 998 999 1000 1001
  // Construct the options, taking the defaults into consideration.
  if (isset($options)) {
    if (!is_array($options)) {
      $options = array('type' => $options);
    }
  }
  else {
    $options = array();
  }

1002 1003
  // Create an array of CSS files for each media type first, since each type needs to be served
  // to the browser differently.
1004
  if (isset($data)) {
1005
    $options += array(
1006
      'type' => 'file',
1007
      'group' => CSS_AGGREGATE_DEFAULT,
1008 1009
      'weight' => 0,
      'every_page' => FALSE,
1010
      'media' => 'all',
1011
      'preprocess' => TRUE,
1012
      'data' => $data,
1013 1014 1015 1016 1017
      'browsers' => array(),
    );
    $options['browsers'] += array(
      'IE' => TRUE,
      '!IE' => TRUE,
1018
    );
1019

1020 1021 1022 1023 1024
    // Files with a query string cannot be preprocessed.
    if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
      $options['preprocess'] = FALSE;
    }

1025 1026
    // Always add a tiny value to the weight, to conserve the insertion order.
    $options['weight'] += count($css) / 1000;
1027

1028 1029 1030 1031 1032 1033 1034
    // 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;
1035 1036 1037 1038 1039