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

3
use Drupal\Component\Utility\Crypt;
4
use Drupal\Component\Utility\Json;
5
use Drupal\Component\Utility\Number;
6
use Drupal\Component\Utility\Settings;
7
use Drupal\Component\Utility\SortArray;
8
use Drupal\Component\Utility\String;
9
use Drupal\Component\Utility\Tags;
10
use Drupal\Component\Utility\UrlHelper;
11
use Drupal\Component\Utility\Xss;
12
use Drupal\Core\Cache\Cache;
13
use Drupal\Core\Language\Language;
14 15
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
16 17
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Exception\ParseException;
18
use Drupal\Component\PhpStorage\PhpStorageFactory;
19
use Drupal\Component\Utility\NestedArray;
20
use Drupal\Core\Datetime\DrupalDateTime;
21
use Drupal\Core\Routing\GeneratorNotInitializedException;
22
use Drupal\Core\Template\Attribute;
23
use Drupal\Core\Render\Element;
24

Dries's avatar
 
Dries committed
25 26 27 28 29 30 31 32
/**
 * @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.
 */

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
/**
 * @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
 *
61
 * @}
62 63
 */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

200 201 202 203 204 205 206 207
  if (drupal_installation_attempted()) {
    // If the profile has been selected return it.
    if (isset($install_state['parameters']['profile'])) {
      $profile = $install_state['parameters']['profile'];
    }
    else {
      $profile = '';
    }
208 209
  }
  else {
210
    $profile = Settings::get('install_profile') ?: 'standard';
211 212 213 214 215
  }

  return $profile;
}

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

237 238 239 240 241 242 243 244 245 246
  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
247 248 249 250
  }
  return $stored_head;
}

Dries's avatar
 
Dries committed
251
/**
252 253 254 255 256 257 258 259 260 261
 * 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(
262
      'charset' => 'utf-8',
263 264 265 266 267 268
    ),
    // 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.
269
  list($version, ) = explode('.', \Drupal::VERSION);
270 271 272 273 274 275 276 277 278 279 280 281 282 283
  $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;
}

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

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

305
  if (isset($url)) {
306 307 308 309 310
    $feed_icon = array(
      '#theme' => 'feed_icon',
      '#url' => $url,
      '#title' => $title,
    );
311

312
    $feed_icon['#attached']['drupal_add_html_head_link'][][] = array(
313 314 315 316 317 318
      '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)),
319
    );
320 321

    $stored_feed_links[$url] = drupal_render($feed_icon);
322 323 324 325 326
  }
  return $stored_feed_links;
}

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

Dries's avatar
 
Dries committed
337
/**
338
 * @defgroup http_handling HTTP handling
Dries's avatar
 
Dries committed
339
 * @{
Dries's avatar
 
Dries committed
340
 * Functions to properly handle HTTP responses.
Dries's avatar
 
Dries committed
341 342
 */

343
/**
344
 * Processes a URL query parameter array to remove unwanted elements.
345 346
 *
 * @param $query
347 348
 *   (optional) An array to be processed. Defaults to \Drupal::request()->query
 *   parameters.
349
 * @param $exclude
350
 *   (optional) A list of $query array keys to remove. Use "parent[child]" to
351
 *   exclude nested items.
352
 * @param $parent
353 354
 *   Internal use only. Used to build the $query array key for nested items.
 *
355
 * @return
356
 *   An array containing query parameters, which can be used for url().
357
 *
358
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
359
 *   Use \Drupal\Component\Utility\UrlHelper::filterQueryParameters().
360
 */
361
function drupal_get_query_parameters(array $query = NULL, array $exclude = array(), $parent = '') {
362
  if (!isset($query)) {
363
    $query = \Drupal::request()->query->all();
364
  }
365
  return UrlHelper::filterQueryParameters($query, $exclude, $parent);
366 367
}

368
/**
369
 * Parses an array into a valid, rawurlencoded query string.
370 371 372
 *
 * @see drupal_get_query_parameters()
 * @ingroup php_wrappers
373
 *
374
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
375
 *   Use \Drupal\Component\Utility\UrlHelper::buildQuery().
376 377
 */
function drupal_http_build_query(array $query, $parent = '') {
378
  return UrlHelper::buildQuery($query, $parent);
379 380
}

381
/**
382
 * Prepares a 'destination' URL query parameter for use with url().
383
 *
384 385 386 387
 * 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.
388
 *
389 390 391 392 393 394
 * @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()
395 396
 */
function drupal_get_destination() {
397 398 399 400 401 402
  $destination = &drupal_static(__FUNCTION__);

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

403
  $query = \Drupal::request()->query;
404 405
  if ($query->has('destination')) {
    $destination = array('destination' => $query->get('destination'));
406 407
  }
  else {
408
    $path = current_path();
409
    $query = UrlHelper::buildQuery(UrlHelper::filterQueryParameters($query->all()));
410
    if ($query != '') {
411
      $path .= '?' . $query;
412
    }
413 414 415 416 417 418
    $destination = array('destination' => $path);
  }
  return $destination;
}

/**
419
 * Parses a system URL string into an associative array suitable for url().
420 421
 *
 * This function should only be used for URLs that have been generated by the
422 423
 * system, such as via url(). It should not be used for URLs that come from
 * external sources, or URLs that link to external resources.
424 425 426 427
 *
 * The returned array contains a 'path' that may be passed separately to url().
 * For example:
 * @code
428
 *   $options = drupal_parse_url(\Drupal::request()->query->get('destination'));
429 430 431 432 433 434 435 436 437 438
 *   $my_url = url($options['path'], $options);
 *   $my_link = l('Example link', $options['path'], $options);
 * @endcode
 *
 * This is required, because url() does not support relative URLs containing a
 * query string or fragment in its $path argument. Instead, any query string
 * needs to be parsed into an associative query parameter array in
 * $options['query'] and the fragment into $options['fragment'].
 *
 * @param $url
439
 *   The URL string to parse.
440 441 442 443 444 445 446 447 448 449
 *
 * @return
 *   An associative array containing the keys:
 *   - 'path': The path of the URL. If the given $url is external, this includes
 *     the scheme and host.
 *   - 'query': An array of query parameters of $url, if existent.
 *   - 'fragment': The fragment of $url, if existent.
 *
 * @see url()
 * @ingroup php_wrappers
450
 *
451
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
452
 *   Use \Drupal\Component\Utility\UrlHelper::parse().
453 454
 */
function drupal_parse_url($url) {
455
  return UrlHelper::parse($url);
456 457 458
}

/**
459
 * Encodes a Drupal path for use in a URL.
460
 *
461
 * For aesthetic reasons slashes are not escaped.
462
 *
463 464
 * Note that url() takes care of calling this function, so a path passed to that
 * function should not be encoded in advance.
465 466
 *
 * @param $path
467
 *   The Drupal path to encode.
468
 *
469
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
470
 *   Use \Drupal\Component\Utility\UrlHelper::encodePath().
471 472
 */
function drupal_encode_path($path) {
473
  return UrlHelper::encodePath($path);
474 475
}

476 477 478 479 480 481 482 483
/**
 * Determines if an external URL points to this Drupal installation.
 *
 * @param $url
 *   A string containing an external URL, such as "http://example.com/foo".
 *
 * @return
 *   TRUE if the URL has the same domain and base path.
484
 *
485
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
486
 *   Use \Drupal\Component\Utility\UrlHelper::externalIsLocal().
487 488
 */
function _external_url_is_local($url) {
489
  return UrlHelper::externalIsLocal($url, base_path());
490 491
}

492 493 494 495 496 497 498
/**
 * Helper function for determining hosts excluded from needing a proxy.
 *
 * @return
 *   TRUE if a proxy should be used for this host.
 */
function _drupal_http_use_proxy($host) {
499
  $proxy_exceptions = Settings::get('proxy_exceptions', array('localhost', '127.0.0.1'));
500 501 502
  return !in_array(strtolower($host), $proxy_exceptions, TRUE);
}

Dries's avatar
 
Dries committed
503
/**
504
 * @} End of "defgroup http_handling".
Dries's avatar
 
Dries committed
505
 */
Dries's avatar
 
Dries committed
506

Kjartan's avatar
Kjartan committed
507
/**
Dries's avatar
 
Dries committed
508
 * @defgroup validation Input validation
Dries's avatar
 
Dries committed
509
 * @{
Dries's avatar
 
Dries committed
510
 * Functions to validate user input.
Kjartan's avatar
Kjartan committed
511 512
 */

513
/**
514
 * Verifies the syntax of the given e-mail address.
Dries's avatar
 
Dries committed
515
 *
516 517
 * This uses the
 * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
518
 *
Dries's avatar
 
Dries committed
519
 * @param $mail
520
 *   A string containing an e-mail address.
521
 *
Dries's avatar
 
Dries committed
522
 * @return
Dries's avatar
 
Dries committed
523
 *   TRUE if the address is in a valid format.
524
 */
Dries's avatar
 
Dries committed
525
function valid_email_address($mail) {
526
  return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
527 528
}

Dries's avatar
 
Dries committed
529
/**
530
 * Verifies the syntax of the given URL.
Dries's avatar
 
Dries committed
531
 *
532 533
 * This function should only be used on actual URLs. It should not be used for
 * Drupal menu paths, which can contain arbitrary characters.
534
 * Valid values per RFC 3986.
Dries's avatar
 
Dries committed
535
 * @param $url
Dries's avatar
 
Dries committed
536
 *   The URL to verify.
Dries's avatar
 
Dries committed
537
 * @param $absolute
Dries's avatar
 
Dries committed
538
 *   Whether the URL is absolute (beginning with a scheme such as "http:").
539
 *
Dries's avatar
 
Dries committed
540
 * @return
Dries's avatar
 
Dries committed
541
 *   TRUE if the URL is in a valid format.
542
 *
543
 * @see \Drupal\Component\Utility\UrlHelper::isValid()
544
 *
545
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
546
 *   Use \Drupal\Component\Utility\UrlHelper::isValid().
Dries's avatar
 
Dries committed
547
 */
Dries's avatar
 
Dries committed
548
function valid_url($url, $absolute = FALSE) {
549
  return UrlHelper::isValid($url, $absolute);
Dries's avatar
 
Dries committed
550 551
}

552 553 554 555
/**
 * @} End of "defgroup validation".
 */

556 557 558 559
/**
 * @defgroup sanitization Sanitization functions
 * @{
 * Functions to sanitize values.
560 561 562
 *
 * See http://drupal.org/writing-secure-code for information
 * on writing secure code.
563 564
 */

565
/**
566
 * Strips dangerous protocols from a URI and encodes it for output to HTML.
567 568 569 570 571 572 573
 *
 * @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
574 575 576
 *   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
577
 *   Drupal\Core\Template\Attribute, call
578
 *   \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() instead.
579
 *
580
 * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
581
 * @see \Drupal\Component\Utility\String::checkPlain()
Dries's avatar
Dries committed
582 583
 */
function check_url($uri) {
584
  return String::checkPlain(UrlHelper::stripDangerousProtocols($uri));
Dries's avatar
Dries committed
585 586
}

587
/**
588
 * Applies a very permissive XSS/HTML filter for admin-only use.
589 590 591
 *
 * Use only for fields where it is impractical to use the
 * whole filter system, but where some (mainly inline) mark-up
592 593
 * is desired (so \Drupal\Component\Utility\String::checkPlain() is not
 * acceptable).
594 595 596
 *
 * Allows all tags that can be used inside an HTML body, save
 * for scripts and styles.
597 598 599 600 601 602 603 604
 *
 * @param string $string
 *   The string to apply the filter to.
 *
 * @return string
 *   The filtered string.
 *
 * @see \Drupal\Component\Utility\Xss::filterAdmin()
605 606
 */
function filter_xss_admin($string) {
607
  return Xss::filterAdmin($string);
608 609 610
}

/**
611
 * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
612
 *
613 614
 * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
 * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
615 616
 *
 * This code does four things:
617 618 619 620 621
 * - Removes characters and constructs that can trick browsers.
 * - Makes sure all HTML entities are well-formed.
 * - Makes sure all HTML tags and attributes are well-formed.
 * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
 *   javascript:).
622 623
 *
 * @param $string
624 625
 *   The string with raw HTML in it. It will be stripped of everything that can
 *   cause an XSS attack.
626 627
 * @param $allowed_tags
 *   An array of allowed tags.
628 629 630 631 632
 *
 * @return
 *   An XSS safe version of $string, or an empty string if $string is not
 *   valid UTF-8.
 *
633
 * @see \Drupal\Component\Utility\Xss::filter()
634 635
 */
function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
636
  return Xss::filter($string, $allowed_tags);
637 638 639
}

/**
640
 * Processes an HTML attribute value and strips dangerous protocols from URLs.
641
 *
642
 * @param string $string
643
 *   The string with the attribute value.
644
 *
645
 * @return string
646
 *   Cleaned up and HTML-escaped version of $string.
647
 *
648
 * @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
649
 */
650
function filter_xss_bad_protocol($string) {
651
  return UrlHelper::filterBadProtocol($string);
652 653 654 655 656 657
}

/**
 * @} End of "defgroup sanitization".
 */

Dries's avatar
 
Dries committed
658
/**
Dries's avatar
 
Dries committed
659
 * @defgroup format Formatting
Dries's avatar
 
Dries committed
660
 * @{
Dries's avatar
 
Dries committed
661
 * Functions to format numbers, strings, dates, etc.
Dries's avatar
 
Dries committed
662 663
 */

Dries's avatar
 
Dries committed
664 665 666 667 668
/**
 * Formats an RSS channel.
 *
 * Arbitrary elements may be added using the $args associative array.
 */
669
function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
670
  $langcode = $langcode ? $langcode : \Drupal::languageManager()->getCurrentLanguage(Language::TYPE_CONTENT)->id;
Dries's avatar
 
Dries committed
671

Dries's avatar
Dries committed
672
  $output = "<channel>\n";
673
  $output .= ' <title>' . String::checkPlain($title) . "</title>\n";
674
  $output .= ' <link>' . check_url($link) . "</link>\n";
675 676 677 678

  // 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;).
679 680
  $output .= ' <description>' . String::checkPlain(decode_entities(strip_tags($description))) . "</description>\n";
  $output .= ' <language>' . String::checkPlain($langcode) . "</language>\n";
681
  $output .= format_xml_elements($args);
Dries's avatar
 
Dries committed
682 683 684 685 686 687
  $output .= $items;
  $output .= "</channel>\n";

  return $output;
}

Dries's avatar
 
Dries committed
688
/**
689
 * Formats a single RSS item.
Dries's avatar
 
Dries committed
690 691 692
 *
 * Arbitrary elements may be added using the $args associative array.
 */
Dries's avatar
 
Dries committed
693
function format_rss_item($title, $link, $description, $args = array()) {
Dries's avatar
Dries committed
694
  $output = "<item>\n";
695
  $output .= ' <title>' . String::checkPlain($title) . "</title>\n";
696
  $output .= ' <link>' . check_url($link) . "</link>\n";
697
  $output .= ' <description>' . String::checkPlain($description) . "</description>\n";
698 699 700 701 702 703 704
  $output .= format_xml_elements($args);
  $output .= "</item>\n";

  return $output;
}

/**
705
 * Formats XML elements.
706 707
 *
 * @param $array
708
 *   An array where each item represents an element and is either a:
709 710 711 712 713 714 715 716 717 718
 *   - (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) {
719
  $output = '';
720 721
  foreach ($array as $key => $value) {
    if (is_numeric($key)) {
Dries's avatar
 
Dries committed
722
      if ($value['key']) {
723
        $output .= ' <' . $value['key'];
724
        if (isset($value['attributes']) && is_array($value['attributes'])) {
725
          $output .= new Attribute($value['attributes']);
Dries's avatar
 
Dries committed
726 727
        }

728
        if (isset($value['value']) && $value['value'] != '') {
729
          $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : String::checkPlain($value['value'])) . '</' . $value['key'] . ">\n";
Dries's avatar
 
Dries committed
730 731 732 733 734 735 736
        }
        else {
          $output .= " />\n";
        }
      }
    }
    else {
737
      $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : String::checkPlain($value)) . "</$key>\n";
Dries's avatar
 
Dries committed
738
    }
Dries's avatar
 
Dries committed
739
  }
Dries's avatar
 
Dries committed
740 741 742
  return $output;
}

Dries's avatar
 
Dries committed
743
/**
744
 * Formats a string containing a count of items.
Dries's avatar
 
Dries committed
745
 *
Dries's avatar
 
Dries committed
746
 * This function ensures that the string is pluralized correctly. Since t() is
747 748
 * called by this function, make sure not to pass already-localized strings to
 * it.
Dries's avatar
 
Dries committed
749
 *
750 751 752 753 754 755 756 757 758 759
 * 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.',
760
 *     array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
761 762
 * @endcode
 *
Dries's avatar
 
Dries committed
763 764 765
 * @param $count
 *   The item count to display.
 * @param $singular
766 767 768
 *   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
769
 * @param $plural
770 771 772
 *   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".
773
 * @param $args
774
 *   An associative array of replacements to make after translation. Instances
775
 *   of any key in this array are replaced with the corresponding value.
776 777 778
 *   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.
779
 * @param $options
780 781
 *   An associative array of additional options. See t() for allowed keys.
 *
Dries's avatar
 
Dries committed
782 783
 * @return
 *   A translated string.
784 785 786
 *
 * @see t()
 * @see format_string()
787
 * @see \Drupal\Core\StringTranslation\TranslationManager->formatPlural()
788
 *
789 790
 * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
 *   Use \Drupal::translation()->formatPlural().
Dries's avatar
 
Dries committed
791
 */
792
function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
793
  return \Drupal::translation()->formatPlural($count, $singular, $plural, $args, $options);
Dries's avatar
 
Dries committed
794 795
}

796
/**
797
 * Parses a given byte count.
798 799
 *
 * @param $size
800 801
 *   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).
802
 *
803
 * @return
804
 *   An integer representation of the size in bytes.
805 806
 */
function parse_size($size) {
807 808 809 810 811 812 813 814
  $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);
815 816 817
  }
}

Dries's avatar
 
Dries committed
818
/**
819
 * Generates a string representation for the given byte count.
Dries's avatar
 
Dries committed
820
 *
Dries's avatar
 
Dries committed
821
 * @param $size
822
 *   A size in bytes.
823
 * @param $langcode
824 825
 *   Optional language code to translate to a language other than what is used
 *   to display the page.
826
 *
Dries's avatar
 
Dries committed
827 828
 * @return
 *   A translated string representation of the size.
Dries's avatar
 
Dries committed
829
 */
830
function format_size($size, $langcode = NULL) {
831
  if ($size < DRUPAL_KILOBYTE) {
832
    return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
Dries's avatar
 
Dries committed
833
  }
834
  else {
835
    $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
836
    $units = array(
837 838 839 840 841 842 843 844
      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)),
845 846
    );
    foreach ($units as $unit) {
847 848
      if (round($size, 2) >= DRUPAL_KILOBYTE) {
        $size = $size / DRUPAL_KILOBYTE;
849 850 851 852
      }
      else {
        break;
      }
853
    }
854
    return str_replace('@size', round($size, 2), $unit);
Dries's avatar
 
Dries committed
855 856 857
  }
}

Dries's avatar
 
Dries committed
858
/**
859
 * Formats a time interval with the requested granularity.
Dries's avatar
 
Dries committed
860
 *
861
 * @param $interval
Dries's avatar
 
Dries committed
862 863 864
 *   The length of the interval in seconds.
 * @param $granularity
 *   How many different units to display in the string.
865 866 867
 * @param $langcode
 *   Optional language code to translate to a language other than
 *   what is used to display the page.
868
 *
Dries's avatar
 
Dries committed
869 870
 * @return
 *   A translated string representation of the interval.
871
 *
872 873 874 875
 * @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
876
 */
877
function format_interval($interval, $granularity = 2, $langcode = NULL) {
878
  return \Drupal::service('date')->formatInterval($interval, $granularity, $langcode);
Dries's avatar
 
Dries committed
879 880
}

Dries's avatar
 
Dries committed
881
/**
882
 * Formats a date, using a date type or a custom date format string.
Dries's avatar
 
Dries committed
883
 *
Dries's avatar
 
Dries committed
884
 * @param $timestamp
885
 *   A UNIX timestamp to format.
Dries's avatar
 
Dries committed
886
 * @param $type
887
 *   (optional) The format to use, one of:
888 889 890 891 892
 *   - 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.
893 894 895
 *   - The machine name of an administrator-defined date format.
 *   - 'custom', to use $format.
 *   Defaults to 'medium'.
Dries's avatar
 
Dries committed
896
 * @param $format
897 898 899
 *   (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
900
 * @param $timezone
901
 *   (optional) Time zone identifier, as described at
902
 *   http://php.net/manual/timezones.php Defaults to the time zone used to
903
 *   display the page.
904
 * @param $langcode
905 906 907
 *   (optional) Language code to translate to. Defaults to the language used to
 *   display the page.
 *
Dries's avatar
 
Dries committed
908 909
 * @return
 *   A translated date string in the requested format.
910 911
 *
 * @see \Drupal\Component\Datetime\Date::format()
Dries's avatar
 
Dries committed
912
 */
913
function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
914
  return \Drupal::service('date')->format($timestamp, $type, $format, $timezone, $langcode);
915 916
}

917 918 919 920 921
/**
 * Returns an ISO8601 formatted date based on the given date.
 *
 * @param $date
 *   A UNIX timestamp.
922
 *
923 924 925 926 927 928 929 930 931
 * @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);
}

932
/**
933 934 935
 * Translates a formatted date string.
 *
 * Callback for preg_replace_callback() within format_date().
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955
 */
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
956
    }
957 958 959

    if ($code == '') {
      $cache[$langcode][$code][$string] = $string;
960
    }
Dries's avatar
 
Dries committed
961
    else {
962
      $cache[$langcode][$code][$string] = t($string, array(), $options);
Dries's avatar
 
Dries committed
963
    }
Dries's avatar
 
Dries committed
964
  }
965
  return $cache[$langcode][$code][$string];
Dries's avatar
 
Dries committed
966 967
}

968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991
/**
 * 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
992 993 994
/**
 * @} End of "defgroup format".
 */
Dries's avatar
 
Dries committed
995

Dries's avatar
 
Dries committed
996
/**
997 998 999 1000
 * 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
1001
 *
1002
 * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromPath().
Dries's avatar
 
Dries committed
1003
 */
1004
function url($path = NULL, array $options = array()) {
1005
  $generator = \Drupal::urlGenerator();
1006 1007
  try {
    $url = $generator->generateFromPath($path, $options);
1008
  }
1009 1010 1011 1012 1013 1014 1015 1016 1017
  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(</