common.inc 166 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\Crypt;
15
use Drupal\Component\Utility\Number;
16
use Drupal\Component\Utility\SortArray;
17
use Drupal\Component\Utility\String;
18
use Drupal\Component\Utility\Tags;
19
use Drupal\Component\Utility\UrlHelper;
20
use Drupal\Core\Cache\Cache;
21
use Drupal\Core\Language\Language;
22
use Drupal\Core\Site\Settings;
23 24
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
25
use Drupal\Core\PhpStorage\PhpStorageFactory;
26
use Drupal\Component\Utility\NestedArray;
27
use Drupal\Core\Datetime\DrupalDateTime;
28
use Drupal\Core\EventSubscriber\HtmlViewSubscriber;
29
use Drupal\Core\Routing\GeneratorNotInitializedException;
30
use Drupal\Core\Template\Attribute;
31
use Drupal\Core\Render\Element;
32
use Drupal\Core\Session\AnonymousUserSession;
33

34 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 61
/**
 * @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
 *   $my_substring = drupal_substr($original_string, 0, 5);
 * @endcode
 *
62
 * @}
63 64
 */

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

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

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

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

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

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

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

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

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

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

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

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

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

144
/**
145
 * Adds content to a specified region.
146 147
 *
 * @param $region
148
 *   Page region the content is added to.
149
 * @param $data
150
 *   Content to be added.
151
 */
152
function drupal_add_region_content($region = NULL, $data = NULL) {
153 154
  static $content = array();

155
  if (isset($region) && isset($data)) {
156 157 158 159 160 161
    $content[$region][] = $data;
  }
  return $content;
}

/**
162
 * Gets assigned content for a given region.
163 164
 *
 * @param $region
165 166
 *   A specified region to fetch content for. If NULL, all regions will be
 *   returned.
167
 * @param $delimiter
168
 *   Content to be inserted between imploded array elements.
169
 */
170 171
function drupal_get_region_content($region = NULL, $delimiter = ' ') {
  $content = drupal_add_region_content();
172 173
  if (isset($region)) {
    if (isset($content[$region]) && is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
174
      return implode($delimiter, $content[$region]);
175
    }
176 177 178 179
  }
  else {
    foreach (array_keys($content) as $region) {
      if (is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
180
        $content[$region] = implode($delimiter, $content[$region]);
181 182 183 184 185 186
      }
    }
    return $content;
  }
}

187
/**
188
 * Gets the name of the currently active installation profile.
189 190 191
 *
 * 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
192 193
 * installation state. At all other times, the "install_profile" setting will be
 * available in settings.php.
194
 *
195 196 197 198
 * @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.
199 200 201 202
 */
function drupal_get_profile() {
  global $install_state;

203 204 205 206 207 208
  if (drupal_installation_attempted()) {
    // If the profile has been selected return it.
    if (isset($install_state['parameters']['profile'])) {
      $profile = $install_state['parameters']['profile'];
    }
    else {
209
      $profile = NULL;
210
    }
211 212
  }
  else {
213 214
    // Fall back to NULL, if there is no 'install_profile' setting.
    $profile = Settings::get('install_profile');
215 216 217 218 219
  }

  return $profile;
}

Dries's avatar
Dries committed
220
/**
221
 * Adds output to the HEAD tag of the HTML page.
222
 *
223
 * This function can be called as long as the headers aren't sent. Pass no
224 225 226 227 228 229 230 231 232 233 234 235
 * 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.
 *
236
 * @see drupal_pre_render_html_tag()
Dries's avatar
Dries committed
237
 */
238 239
function drupal_add_html_head($data = NULL, $key = NULL) {
  $stored_head = &drupal_static(__FUNCTION__);
Dries's avatar
Dries committed
240

241 242 243 244 245 246 247 248 249 250
  if (!isset($stored_head)) {
    // Make sure the defaults, including Content-Type, come first.
    $stored_head = _drupal_default_html_head();
  }

  if (isset($data) && isset($key)) {
    if (!isset($data['#type'])) {
      $data['#type'] = 'html_tag';
    }
    $stored_head[$key] = $data;
Dries's avatar
Dries committed
251 252 253 254
  }
  return $stored_head;
}

Dries's avatar
 
Dries committed
255
/**
256 257 258 259 260 261 262 263 264 265
 * Returns elements that are always displayed in the HEAD tag of the HTML page.
 */
function _drupal_default_html_head() {
  // Add default elements. Make sure the Content-Type comes first because the
  // IE browser may be vulnerable to XSS via encoding attacks from any content
  // that comes before this META tag, such as a TITLE tag.
  $elements['system_meta_content_type'] = array(
    '#type' => 'html_tag',
    '#tag' => 'meta',
    '#attributes' => array(
266
      'charset' => 'utf-8',
267 268 269 270 271 272
    ),
    // 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.
273
  list($version, ) = explode('.', \Drupal::VERSION);
274 275 276 277 278 279 280 281 282 283 284 285 286 287
  $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;
}

/**
288
 * Retrieves output to be displayed in the HEAD tag of the HTML page.
289
 */
Dries's avatar
Dries committed
290
function drupal_get_html_head() {
291
  $elements = drupal_add_html_head();
292
  \Drupal::moduleHandler()->alter('html_head', $elements);
293
  return drupal_render($elements);
Dries's avatar
Dries committed
294 295
}

296
/**
297
 * Adds a feed URL for the current page.
298
 *
299 300
 * This function can be called as long the HTML header hasn't been sent.
 *
301
 * @param $url
302
 *   An internal system path or a fully qualified external URL of the feed.
303
 * @param $title
304
 *   The title of the feed.
305
 */
306
function drupal_add_feed($url = NULL, $title = '') {
307
  $stored_feed_links = &drupal_static(__FUNCTION__, array());
308

309
  if (isset($url)) {
310 311 312 313 314
    $feed_icon = array(
      '#theme' => 'feed_icon',
      '#url' => $url,
      '#title' => $title,
    );
315

316
    $feed_icon['#attached']['drupal_add_html_head_link'][][] = array(
317 318 319 320 321 322
      'rel' => 'alternate',
      'type' => 'application/rss+xml',
      'title' => $title,
      // Force the URL to be absolute, for consistency with other <link> tags
      // output by Drupal.
      'href' => url($url, array('absolute' => TRUE)),
323
    );
324 325

    $stored_feed_links[$url] = drupal_render($feed_icon);
326 327 328 329 330
  }
  return $stored_feed_links;
}

/**
331
 * Gets the feed URLs for the current page.
332 333
 *
 * @param $delimiter
334
 *   A delimiter to split feeds by.
335 336 337 338 339 340
 */
function drupal_get_feeds($delimiter = "\n") {
  $feeds = drupal_add_feed();
  return implode($feeds, $delimiter);
}

Dries's avatar
 
Dries committed
341
/**
342
 * @defgroup http_handling HTTP handling
Dries's avatar
 
Dries committed
343
 * @{
Dries's avatar
 
Dries committed
344
 * Functions to properly handle HTTP responses.
Dries's avatar
 
Dries committed
345 346
 */

347
/**
348
 * Prepares a 'destination' URL query parameter for use with url().
349
 *
350 351 352 353
 * 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.
354
 *
355 356 357 358 359 360
 * @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()
361 362
 */
function drupal_get_destination() {
363 364 365 366 367 368
  $destination = &drupal_static(__FUNCTION__);

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

369
  $query = \Drupal::request()->query;
370 371
  if ($query->has('destination')) {
    $destination = array('destination' => $query->get('destination'));
372 373
  }
  else {
374
    $path = current_path();
375
    $query = UrlHelper::buildQuery(UrlHelper::filterQueryParameters($query->all()));
376
    if ($query != '') {
377
      $path .= '?' . $query;
378
    }
379 380 381 382 383
    $destination = array('destination' => $path);
  }
  return $destination;
}

Dries's avatar
 
Dries committed
384
/**
385
 * @} End of "defgroup http_handling".
Dries's avatar
 
Dries committed
386
 */
Dries's avatar
 
Dries committed
387

Kjartan's avatar
Kjartan committed
388
/**
Dries's avatar
 
Dries committed
389
 * @defgroup validation Input validation
Dries's avatar
 
Dries committed
390
 * @{
Dries's avatar
 
Dries committed
391
 * Functions to validate user input.
Kjartan's avatar
Kjartan committed
392 393
 */

394
/**
395
 * Verifies the syntax of the given e-mail address.
Dries's avatar
 
Dries committed
396
 *
397 398
 * This uses the
 * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
399
 *
Dries's avatar
 
Dries committed
400
 * @param $mail
401
 *   A string containing an e-mail address.
402
 *
Dries's avatar
 
Dries committed
403
 * @return
Dries's avatar
 
Dries committed
404
 *   TRUE if the address is in a valid format.
405
 */
Dries's avatar
 
Dries committed
406
function valid_email_address($mail) {
407
  return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
408 409
}

410 411 412 413
/**
 * @} End of "defgroup validation".
 */

414 415 416 417
/**
 * @defgroup sanitization Sanitization functions
 * @{
 * Functions to sanitize values.
418 419 420
 *
 * See http://drupal.org/writing-secure-code for information
 * on writing secure code.
421 422
 */

423
/**
424
 * Strips dangerous protocols from a URI and encodes it for output to HTML.
425 426 427 428 429 430 431
 *
 * @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
432 433 434
 *   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
435
 *   Drupal\Core\Template\Attribute, call
436
 *   \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() instead.
437
 *
438
 * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
439
 * @see \Drupal\Component\Utility\String::checkPlain()
Dries's avatar
Dries committed
440 441
 */
function check_url($uri) {
442
  return String::checkPlain(UrlHelper::stripDangerousProtocols($uri));
Dries's avatar
Dries committed
443 444
}

445 446 447 448
/**
 * @} End of "defgroup sanitization".
 */

Dries's avatar
 
Dries committed
449
/**
Dries's avatar
 
Dries committed
450
 * @defgroup format Formatting
Dries's avatar
 
Dries committed
451
 * @{
Dries's avatar
 
Dries committed
452
 * Functions to format numbers, strings, dates, etc.
Dries's avatar
 
Dries committed
453 454
 */

Dries's avatar
 
Dries committed
455 456 457 458 459
/**
 * Formats an RSS channel.
 *
 * Arbitrary elements may be added using the $args associative array.
 */
460
function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
461
  $langcode = $langcode ? $langcode : \Drupal::languageManager()->getCurrentLanguage(Language::TYPE_CONTENT)->id;
Dries's avatar
 
Dries committed
462

Dries's avatar
Dries committed
463
  $output = "<channel>\n";
464
  $output .= ' <title>' . String::checkPlain($title) . "</title>\n";
465
  $output .= ' <link>' . check_url($link) . "</link>\n";
466 467 468 469

  // 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 &amp becoming &amp;amp;).
470 471
  $output .= ' <description>' . String::checkPlain(decode_entities(strip_tags($description))) . "</description>\n";
  $output .= ' <language>' . String::checkPlain($langcode) . "</language>\n";
472
  $output .= format_xml_elements($args);
Dries's avatar
 
Dries committed
473 474 475 476 477 478
  $output .= $items;
  $output .= "</channel>\n";

  return $output;
}

Dries's avatar
 
Dries committed
479
/**
480
 * Formats a single RSS item.
Dries's avatar
 
Dries committed
481 482 483
 *
 * Arbitrary elements may be added using the $args associative array.
 */
Dries's avatar
 
Dries committed
484
function format_rss_item($title, $link, $description, $args = array()) {
Dries's avatar
Dries committed
485
  $output = "<item>\n";
486
  $output .= ' <title>' . String::checkPlain($title) . "</title>\n";
487
  $output .= ' <link>' . check_url($link) . "</link>\n";
488
  $output .= ' <description>' . String::checkPlain($description) . "</description>\n";
489 490 491 492 493 494 495
  $output .= format_xml_elements($args);
  $output .= "</item>\n";

  return $output;
}

/**
496
 * Formats XML elements.
497 498
 *
 * @param $array
499
 *   An array where each item represents an element and is either a:
500 501 502 503 504 505 506 507 508 509
 *   - (key => value) pair (<key>value</key>)
 *   - 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) {
510
  $output = '';
511 512
  foreach ($array as $key => $value) {
    if (is_numeric($key)) {
Dries's avatar
 
Dries committed
513
      if ($value['key']) {
514
        $output .= ' <' . $value['key'];
515
        if (isset($value['attributes']) && is_array($value['attributes'])) {
516
          $output .= new Attribute($value['attributes']);
Dries's avatar
 
Dries committed
517 518
        }

519
        if (isset($value['value']) && $value['value'] != '') {
520
          $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : String::checkPlain($value['value'])) . '</' . $value['key'] . ">\n";
Dries's avatar
 
Dries committed
521 522 523 524 525 526 527
        }
        else {
          $output .= " />\n";
        }
      }
    }
    else {
528
      $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : String::checkPlain($value)) . "</$key>\n";
Dries's avatar
 
Dries committed
529
    }
Dries's avatar
 
Dries committed
530
  }
Dries's avatar
 
Dries committed
531 532 533
  return $output;
}

Dries's avatar
 
Dries committed
534
/**
535
 * Formats a string containing a count of items.
Dries's avatar
 
Dries committed
536
 *
Dries's avatar
 
Dries committed
537
 * This function ensures that the string is pluralized correctly. Since t() is
538 539
 * called by this function, make sure not to pass already-localized strings to
 * it.
Dries's avatar
 
Dries committed
540
 *
541 542 543 544 545 546 547 548 549 550
 * 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.',
551
 *     array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
552 553
 * @endcode
 *
Dries's avatar
 
Dries committed
554 555 556
 * @param $count
 *   The item count to display.
 * @param $singular
557 558 559
 *   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
560
 * @param $plural
561 562 563
 *   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".
564
 * @param $args
565
 *   An associative array of replacements to make after translation. Instances
566
 *   of any key in this array are replaced with the corresponding value.
567 568 569
 *   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.
570
 * @param $options
571 572
 *   An associative array of additional options. See t() for allowed keys.
 *
Dries's avatar
 
Dries committed
573 574
 * @return
 *   A translated string.
575 576 577
 *
 * @see t()
 * @see format_string()
578
 * @see \Drupal\Core\StringTranslation\TranslationManager->formatPlural()
579
 *
580 581
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal::translation()->formatPlural().
Dries's avatar
 
Dries committed
582
 */
583
function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
584
  return \Drupal::translation()->formatPlural($count, $singular, $plural, $args, $options);
Dries's avatar
 
Dries committed
585 586
}

587
/**
588
 * Parses a given byte count.
589 590
 *
 * @param $size
591 592
 *   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).
593
 *
594
 * @return
595
 *   An integer representation of the size in bytes.
596 597
 */
function parse_size($size) {
598 599 600 601 602 603 604 605
  $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);
606 607 608
  }
}

Dries's avatar
 
Dries committed
609
/**
610
 * Generates a string representation for the given byte count.
Dries's avatar
 
Dries committed
611
 *
Dries's avatar
 
Dries committed
612
 * @param $size
613
 *   A size in bytes.
614
 * @param $langcode
615 616
 *   Optional language code to translate to a language other than what is used
 *   to display the page.
617
 *
Dries's avatar
 
Dries committed
618 619
 * @return
 *   A translated string representation of the size.
Dries's avatar
 
Dries committed
620
 */
621
function format_size($size, $langcode = NULL) {
622
  if ($size < DRUPAL_KILOBYTE) {
623
    return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
Dries's avatar
 
Dries committed
624
  }
625
  else {
626
    $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
627
    $units = array(
628 629 630 631 632 633 634 635
      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)),
636 637
    );
    foreach ($units as $unit) {
638 639
      if (round($size, 2) >= DRUPAL_KILOBYTE) {
        $size = $size / DRUPAL_KILOBYTE;
640 641 642 643
      }
      else {
        break;
      }
644
    }
645
    return str_replace('@size', round($size, 2), $unit);
Dries's avatar
 
Dries committed
646 647 648
  }
}

Dries's avatar
 
Dries committed
649
/**
650
 * Formats a time interval with the requested granularity.
Dries's avatar
 
Dries committed
651
 *
652
 * @param $interval
Dries's avatar
 
Dries committed
653 654 655
 *   The length of the interval in seconds.
 * @param $granularity
 *   How many different units to display in the string.
656 657 658
 * @param $langcode
 *   Optional language code to translate to a language other than
 *   what is used to display the page.
659
 *
Dries's avatar
 
Dries committed
660 661
 * @return
 *   A translated string representation of the interval.
662
 *
663 664 665 666
 * @see \Drupal\Core\Datetime\Date::formatInterval()
 *
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal::service('date')->formatInterval().
Dries's avatar
 
Dries committed
667
 */
668
function format_interval($interval, $granularity = 2, $langcode = NULL) {
669
  return \Drupal::service('date')->formatInterval($interval, $granularity, $langcode);
Dries's avatar
 
Dries committed
670 671
}

Dries's avatar
 
Dries committed
672
/**
673
 * Formats a date, using a date type or a custom date format string.
Dries's avatar
 
Dries committed
674
 *
Dries's avatar
 
Dries committed
675
 * @param $timestamp
676
 *   A UNIX timestamp to format.
Dries's avatar
 
Dries committed
677
 * @param $type
678
 *   (optional) The format to use, one of:
679 680 681 682 683
 *   - 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.
684 685 686
 *   - The machine name of an administrator-defined date format.
 *   - 'custom', to use $format.
 *   Defaults to 'medium'.
Dries's avatar
 
Dries committed
687
 * @param $format
688 689 690
 *   (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
691
 * @param $timezone
692
 *   (optional) Time zone identifier, as described at
693
 *   http://php.net/manual/timezones.php Defaults to the time zone used to
694
 *   display the page.
695
 * @param $langcode
696 697 698
 *   (optional) Language code to translate to. Defaults to the language used to
 *   display the page.
 *
Dries's avatar
 
Dries committed
699 700
 * @return
 *   A translated date string in the requested format.
701 702
 *
 * @see \Drupal\Component\Datetime\Date::format()
Dries's avatar
 
Dries committed
703
 */
704
function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
705
  return \Drupal::service('date')->format($timestamp, $type, $format, $timezone, $langcode);
706 707
}

708 709 710 711 712
/**
 * Returns an ISO8601 formatted date based on the given date.
 *
 * @param $date
 *   A UNIX timestamp.
713
 *
714 715 716 717 718 719 720 721 722
 * @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);
}

723
/**
724 725 726
 * Translates a formatted date string.
 *
 * Callback for preg_replace_callback() within format_date().
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
 */
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
747
    }
748 749 750

    if ($code == '') {
      $cache[$langcode][$code][$string] = $string;
751
    }
Dries's avatar
 
Dries committed
752
    else {
753
      $cache[$langcode][$code][$string] = t($string, array(), $options);
Dries's avatar
 
Dries committed
754
    }
Dries's avatar
 
Dries committed
755
  }
756
  return $cache[$langcode][$code][$string];
Dries's avatar
 
Dries committed
757 758
}

759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782
/**
 * 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;
}

Dries's avatar
 
Dries committed
783 784 785
/**
 * @} End of "defgroup format".
 */
Dries's avatar
 
Dries committed
786

Dries's avatar
 
Dries committed
787
/**
788 789 790 791
 * 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
792
 *
793
 * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromPath().
Dries's avatar
 
Dries committed
794
 */
795
function url($path = NULL, array $options = array()) {
796
  $generator = \Drupal::urlGenerator();
797 798
  try {
    $url = $generator->generateFromPath($path, $options);
799
  }
800 801 802 803 804 805 806 807 808
  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);
809
  }
810
  return $url;
Dries's avatar
 
Dries committed
811 812
}

813
/**
814
 * Formats an attribute string for an HTTP header.
815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
 *
 * @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) : '';
}

Dries's avatar
 
Dries committed
835
/**
836
 * Formats an internal or external URL link as an HTML anchor tag.
Dries's avatar
 
Dries committed
837
 *
838
 * This function correctly handles aliased paths and adds an 'active' class
839 840 841
 * 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
842
 *
843 844 845 846 847 848 849 850
 * 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 <a href="@url">settings</a> page', array('@url' => url('admin')));
 * @endcode
 * This keeps the context of the link title ('settings' in the example) for
 * translators.
 *
851 852 853 854 855 856
 * 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.
 *
857 858
 * @param string|array $text
 *   The link text for the anchor tag as a translated string or render array.
859
 * @param string $path
860 861 862
 *   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
863 864 865
 *   \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.
866
 * @param array $options
867 868
 *   An associative array of additional options. Defaults to an empty array. It
 *   may contain the following elements.
869
 *   - 'attributes': An associative array of HTML attributes to apply to the
870 871
 *     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
872 873
 *     to work as an argument for the constructor of the class
 *     Drupal\Core\Template\Attribute($options['attributes']).
874 875
 *   - '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
876 877 878
 *     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.
879 880 881 882
 *   - '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().
883 884 885 886 887 888 889 890 891 892 893
 *   - '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().
894
 *   - Additional $options elements used by the url() function.
895
 *
896
 * @return string
897
 *   An HTML string containing a link to the given path.
898 899
 *
 * @see url()
900
 * @see system_page_build()
Dries's avatar
 
Dries committed
901
 */
902
function l($text, $path, array $options = array()) {
903
  // Start building a structured representation of our link to be altered later.
904
  $variables = array(
905
    'text' => is_array($text) ? drupal_render($text) : $text,
906 907 908
    'path' => $path,
    'options' => $options,
  );
909

910 911
  // Merge in default options.
  $variables['options'] += array(
912
    'attributes' => array(),
913
    'query' => array(),
914
    'html' => FALSE,
915
    'language' => NULL,
916
    'set_active_class' => FALSE,
917
  );
918

919 920 921 922 923 924
  // 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;
  }

925 926 927 928 929 930 931 932 933
  // 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);
    }
934

935 936 937
    // 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'])) {
938
      $variables['options']['attributes']['data-drupal-link-system-path'] = \Drupal::service('path.alias_manager.cached')->getPathByAlias($path);
939
    }
940 941 942 943 944 945
  }

  // 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']);
946
  }
947 948

  // Allow other modules to modify the structure of the link.
949
  \Drupal::moduleHandler()->alter('link', $variables);
950 951 952 953 954

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

955 956
  // 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.
957
  $url = String::checkPlain(url($variables['path'], $variables['options']));
958 959

  // Sanitize the link text if necessary.
960
  $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
961 962

  return '<a href="' . $url . '"' . $attributes . '>' . $text . '</a>';
Dries's avatar
 
Dries committed
963 964
}

965 966 967 968 969 970 971 972
/**
 * 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.
973
 *
974 975 976 977 978
 * 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
979
 * to execute a task than only once at the beginning of the script.
980
 *
981 982 983 984 985 986 987 988 989
 * 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.
990 991
 *
 * @ingroup php_wrappers
992 993 994
 */
function drupal_set_time_limit($time_limit) {
  if (function_exists('set_time_limit')) {
995 996
    $current = ini_get('max_execution_time');
    // Do not set time limit if it is currently unlimited.
997
    if ($current != 0) {
998 999
      @set_time_limit($time_limit);
    }
1000 1001 1002
  }
}

Dries's avatar
Dries committed
1003 1004 1005 1006
/**
 * Returns the path to a system item (module, theme, etc.).
 *
 * @param $type
1007 1008
 *   The type of the item; one of 'core', 'profile', 'module', 'theme', or
 *   'theme_engine'.
Dries's avatar
Dries committed
1009
 * @param $name
1010 1011
 *   The name of the item for which the path is requested. Ignored for
 *   $type 'core'.
Dries's avatar
Dries committed
1012 1013
 *
 * @return
1014
 *   The path to the requested item or an empty string if the item is not found.
Dries's avatar
Dries committed
1015 1016 1017 1018 1019
 */
function drupal_get_path($type, $name) {
  return dirname(drupal_get_filename($type, $name));
}

1020
/**
1021
 * Returns the base URL path (i.e., directory) of the Drupal installation.
1022
 *
1023 1024
 * 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 "/".
1025
 *
1026 1027 1028
 * Examples:
 * - http://example.com returns "/" because the path is empty.
 * - http://example.com/drupal/folder returns "/drupal/folder/".
1029 1030
 */
function base_path() {
1031
  return $GLOBALS['base_path'];
1032 1033
}

1034
/**
1035
 * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
1036
 *
1037
 * This function can be called as long the HTML header hasn't been sent, which
1038
 * on normal pages is up through the preprocess step of _theme('html'). Adding
1039 1040
 * a link will overwrite a prior link with the exact same 'rel' and 'href'
 * attributes.
1041
 *
1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
 * @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:".
1056
    $href = '<' . String::checkPlain($attributes['href']) . '>;';
1057 1058 1059 1060 1061
    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);
1062 1063
}