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

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

24 25 26 27 28 29 30 31
/**
 * @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.
 */

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

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

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

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

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

/**
84
 * The default aggregation group for theme CSS files added to the page.
85
 */
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
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;

/**
 * The default weight for CSS rules that style design components (and their associated states and skins.)
 */
const CSS_COMPONENT = 0;

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

/**
 * The default weight for CSS rules that style skins and are not included with components.
 */
const CSS_SKIN = 200;
112

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

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

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

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

133
/**
134 135 136
 * @defgroup block_caching Block Caching
 * @{
 * Constants that define each block's caching state.
137
 *
138 139 140 141 142 143 144 145 146 147
 * Modules specify how their blocks can be cached in their hook_block_info()
 * implementations. Caching can be turned off (DRUPAL_NO_CACHE), managed by the
 * module declaring the block (DRUPAL_CACHE_CUSTOM), or managed by the core
 * Block module. If the Block module is managing the cache, you can specify that
 * the block is the same for every page and user (DRUPAL_CACHE_GLOBAL), or that
 * it can change depending on the page (DRUPAL_CACHE_PER_PAGE) or by user
 * (DRUPAL_CACHE_PER_ROLE or DRUPAL_CACHE_PER_USER). Page and user settings can
 * be combined with a bitwise-binary or operator; for example,
 * DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE means that the block can change
 * depending on the user role or page it is on.
148
 *
149 150
 * The block cache is cleared when the 'content' cache tag is invalidated,
 * following the same pattern as the page cache (node, comment, user, taxonomy
151
 * added or updated...).
152 153 154 155 156
 *
 * Note that user 1 is excluded from block caching.
 */

/**
157 158 159 160 161 162 163
 * The block should not get cached.
 *
 * This setting should be used:
 * - For simple blocks (notably those that do not perform any db query), where
 *   querying the db cache would be more expensive than directly generating the
 *   content.
 * - For blocks that change too frequently.
164
 */
165
const DRUPAL_NO_CACHE = -1;
166 167

/**
168 169
 * The block is handling its own caching in its hook_block_view().
 *
170 171
 * This setting is useful when time based expiration is needed or a site uses a
 * node access which invalidates standard block cache.
172
 */
173
const DRUPAL_CACHE_CUSTOM = -2;
174 175

/**
176 177 178 179
 * The block or element can change depending on the user's roles.
 *
 * This is the default setting for blocks, used when the block does not specify
 * anything.
180
 */
181
const DRUPAL_CACHE_PER_ROLE = 0x0001;
182 183

/**
184 185
 * The block or element can change depending on the user.
 *
186 187 188
 * This setting can be resource-consuming for sites with large number of users,
 * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
 */
189
const DRUPAL_CACHE_PER_USER = 0x0002;
190 191 192 193

/**
 * The block or element can change depending on the page being viewed.
 */
194
const DRUPAL_CACHE_PER_PAGE = 0x0004;
195 196

/**
197
 * The block or element is the same for every user and page that it is visible.
198
 */
199
const DRUPAL_CACHE_GLOBAL = 0x0008;
200

201 202 203 204
/**
 * @} End of "defgroup block_caching".
 */

205 206 207 208 209 210 211 212 213
/**
 * 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";

214
/**
215
 * Adds content to a specified region.
216 217
 *
 * @param $region
218
 *   Page region the content is added to.
219
 * @param $data
220
 *   Content to be added.
221
 */
222
function drupal_add_region_content($region = NULL, $data = NULL) {
223 224
  static $content = array();

225
  if (isset($region) && isset($data)) {
226 227 228 229 230 231
    $content[$region][] = $data;
  }
  return $content;
}

/**
232
 * Gets assigned content for a given region.
233 234
 *
 * @param $region
235 236
 *   A specified region to fetch content for. If NULL, all regions will be
 *   returned.
237
 * @param $delimiter
238
 *   Content to be inserted between imploded array elements.
239
 */
240 241
function drupal_get_region_content($region = NULL, $delimiter = ' ') {
  $content = drupal_add_region_content();
242 243
  if (isset($region)) {
    if (isset($content[$region]) && is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
244
      return implode($delimiter, $content[$region]);
245
    }
246 247 248 249
  }
  else {
    foreach (array_keys($content) as $region) {
      if (is_array($content[$region])) {
Steven Wittens's avatar
Steven Wittens committed
250
        $content[$region] = implode($delimiter, $content[$region]);
251 252 253 254 255 256
      }
    }
    return $content;
  }
}

257
/**
258
 * Gets the name of the currently active installation profile.
259 260 261
 *
 * 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
262 263
 * installation state. At all other times, the "install_profile" setting will be
 * available in settings.php.
264 265
 *
 * @return $profile
266
 *   The name of the installation profile.
267 268 269 270
 */
function drupal_get_profile() {
  global $install_state;

271 272 273 274 275 276 277 278
  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 = '';
    }
279 280
  }
  else {
281
    $profile = settings()->get('install_profile') ?: 'standard';
282 283 284 285 286
  }

  return $profile;
}

Dries's avatar
Dries committed
287
/**
288
 * Adds output to the HEAD tag of the HTML page.
289
 *
290
 * This function can be called as long as the headers aren't sent. Pass no
291 292 293 294 295 296 297 298 299 300 301 302
 * 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.
 *
303
 * @see drupal_pre_render_html_tag()
Dries's avatar
Dries committed
304
 */
305 306
function drupal_add_html_head($data = NULL, $key = NULL) {
  $stored_head = &drupal_static(__FUNCTION__);
Dries's avatar
Dries committed
307

308 309 310 311 312 313 314 315 316 317
  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
318 319 320 321
  }
  return $stored_head;
}

322
/**
323 324 325 326 327 328 329 330 331 332
 * 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(
333
      'charset' => 'utf-8',
334 335 336 337 338 339
    ),
    // 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.
340
  list($version, ) = explode('.', \Drupal::VERSION);
341 342 343 344 345 346 347 348 349 350 351 352 353 354
  $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;
}

/**
355
 * Retrieves output to be displayed in the HEAD tag of the HTML page.
356
  */
Dries's avatar
Dries committed
357
function drupal_get_html_head() {
358 359 360
  $elements = drupal_add_html_head();
  drupal_alter('html_head', $elements);
  return drupal_render($elements);
Dries's avatar
Dries committed
361 362
}

363
/**
364
 * Adds a feed URL for the current page.
365
 *
366 367
 * This function can be called as long the HTML header hasn't been sent.
 *
368
 * @param $url
369
 *   An internal system path or a fully qualified external URL of the feed.
370
 * @param $title
371
 *   The title of the feed.
372
 */
373
function drupal_add_feed($url = NULL, $title = '') {
374
  $stored_feed_links = &drupal_static(__FUNCTION__, array());
375

376
  if (isset($url)) {
377
    $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
378

379
    $build['#attached']['drupal_add_html_head_link'][][] = array(
380 381 382 383 384 385
      '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)),
386 387
    );
    drupal_render($build);
388 389 390 391 392
  }
  return $stored_feed_links;
}

/**
393
 * Gets the feed URLs for the current page.
394 395
 *
 * @param $delimiter
396
 *   A delimiter to split feeds by.
397 398 399 400 401 402
 */
function drupal_get_feeds($delimiter = "\n") {
  $feeds = drupal_add_feed();
  return implode($feeds, $delimiter);
}

403
/**
404
 * @defgroup http_handling HTTP handling
405
 * @{
406
 * Functions to properly handle HTTP responses.
407 408
 */

409
/**
410
 * Processes a URL query parameter array to remove unwanted elements.
411 412
 *
 * @param $query
413 414
 *   (optional) An array to be processed. Defaults to \Drupal::request()->query
 *   parameters.
415
 * @param $exclude
416
 *   (optional) A list of $query array keys to remove. Use "parent[child]" to
417
 *   exclude nested items.
418
 * @param $parent
419 420
 *   Internal use only. Used to build the $query array key for nested items.
 *
421
 * @return
422
 *   An array containing query parameters, which can be used for url().
423 424
 *
 * @deprecated as of Drupal 8.0. Use Url::filterQueryParameters() instead.
425
 */
426
function drupal_get_query_parameters(array $query = NULL, array $exclude = array(), $parent = '') {
427
  if (!isset($query)) {
428
    $query = \Drupal::request()->query->all();
429
  }
430
  return Url::filterQueryParameters($query, $exclude, $parent);
431 432
}

433
/**
434
 * Parses an array into a valid, rawurlencoded query string.
435 436
 *
 * @see drupal_get_query_parameters()
437
 * @deprecated as of Drupal 8.0. Use Url::buildQuery() instead.
438
 * @ingroup php_wrappers
439 440
 *
 * @deprecated as of Drupal 8.0. Use Url::buildQuery() instead.
441 442
 */
function drupal_http_build_query(array $query, $parent = '') {
443
  return Url::buildQuery($query, $parent);
444 445
}

446
/**
447
 * Prepares a 'destination' URL query parameter for use with url().
448
 *
449 450 451 452
 * 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.
453
 *
454 455 456 457 458 459
 * @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()
460 461
 */
function drupal_get_destination() {
462 463 464 465 466 467
  $destination = &drupal_static(__FUNCTION__);

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

468
  $query = \Drupal::request()->query;
469 470
  if ($query->has('destination')) {
    $destination = array('destination' => $query->get('destination'));
471 472
  }
  else {
473
    $path = current_path();
474
    $query = Url::buildQuery(Url::filterQueryParameters($query->all()));
475
    if ($query != '') {
476
      $path .= '?' . $query;
477
    }
478 479 480 481 482 483
    $destination = array('destination' => $path);
  }
  return $destination;
}

/**
484
 * Parses a system URL string into an associative array suitable for url().
485 486
 *
 * This function should only be used for URLs that have been generated by the
487 488
 * system, such as via url(). It should not be used for URLs that come from
 * external sources, or URLs that link to external resources.
489 490 491 492
 *
 * The returned array contains a 'path' that may be passed separately to url().
 * For example:
 * @code
493
 *   $options = drupal_parse_url(\Drupal::request()->query->get('destination'));
494 495 496 497 498 499 500 501 502 503
 *   $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
504
 *   The URL string to parse.
505 506 507 508 509 510 511 512 513 514
 *
 * @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
515 516
 *
 * @deprecated as of Drupal 8.0. Use Url::parse() instead.
517 518
 */
function drupal_parse_url($url) {
519
  return Url::parse($url);
520 521 522
}

/**
523
 * Encodes a Drupal path for use in a URL.
524
 *
525
 * For aesthetic reasons slashes are not escaped.
526
 *
527 528
 * Note that url() takes care of calling this function, so a path passed to that
 * function should not be encoded in advance.
529 530
 *
 * @param $path
531
 *   The Drupal path to encode.
532 533
 *
 * @deprecated as of Drupal 8.0. Use Url::encodePath() instead.
534 535
 */
function drupal_encode_path($path) {
536
  return Url::encodePath($path);
537 538
}

539 540 541 542 543 544 545 546
/**
 * 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.
547 548
 *
 * @deprecated as of Drupal 8.0. Use Url::externalIsLocal() instead.
549 550
 */
function _external_url_is_local($url) {
551
  return Url::externalIsLocal($url, base_path());
552 553
}

554 555 556 557 558 559 560
/**
 * 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) {
561
  $proxy_exceptions = settings()->get('proxy_exceptions', array('localhost', '127.0.0.1'));
562 563 564
  return !in_array(strtolower($host), $proxy_exceptions, TRUE);
}

565
/**
566
 * @} End of "defgroup http_handling".
567
 */
568

569
/**
570
 * @defgroup validation Input validation
571
 * @{
572
 * Functions to validate user input.
573 574
 */

575
/**
576
 * Verifies the syntax of the given e-mail address.
577
 *
578 579
 * This uses the
 * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
580
 *
581
 * @param $mail
582
 *   A string containing an e-mail address.
583
 *
584
 * @return
585
 *   TRUE if the address is in a valid format.
586
 */
587
function valid_email_address($mail) {
588
  return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
589 590
}

591
/**
592
 * Verifies the syntax of the given URL.
593
 *
594 595
 * This function should only be used on actual URLs. It should not be used for
 * Drupal menu paths, which can contain arbitrary characters.
596
 * Valid values per RFC 3986.
597
 * @param $url
598
 *   The URL to verify.
599
 * @param $absolute
600
 *   Whether the URL is absolute (beginning with a scheme such as "http:").
601
 *
602
 * @return
603
 *   TRUE if the URL is in a valid format.
604
 *
605
 * @see \Drupal\Component\Utility\Url::isValid()
606
 *
607
 * @deprecated as of Drupal 8.0. Use Url::isValid() instead.
608
 */
609
function valid_url($url, $absolute = FALSE) {
610
  return Url::isValid($url, $absolute);
611 612
}

613 614 615
/**
 * Verifies that a number is a multiple of a given step.
 *
616
 * @see \Drupal\Component\Utility\Number::validStep()
617
 *
618
 * @param numeric $value
619
 *   The value that needs to be checked.
620
 * @param numeric $step
621
 *   The step scale factor. Must be positive.
622
 * @param numeric $offset
623 624 625 626
 *   (optional) An offset, to which the difference must be a multiple of the
 *   given step.
 *
 * @return bool
627
 *   TRUE if no step mismatch has occurred, or FALSE otherwise.
628
 *
629 630
 * @deprecated as of Drupal 8.0. Use
 *   \Drupal\Component\Utility\Number::validStep() directly instead
631 632
 */
function valid_number_step($value, $step, $offset = 0.0) {
633
  return Number::validStep($value, $step, $offset);
634 635
}

636 637 638 639
/**
 * @} End of "defgroup validation".
 */

640 641 642 643
/**
 * @defgroup sanitization Sanitization functions
 * @{
 * Functions to sanitize values.
644 645 646
 *
 * See http://drupal.org/writing-secure-code for information
 * on writing secure code.
647 648
 */

Dries's avatar
Dries committed
649
/**
650 651 652 653 654 655
 * Strips dangerous protocols (e.g. 'javascript:') from a URI.
 *
 * This function must be called for all URIs within user-entered input prior
 * to being output to an HTML attribute value. It is often called as part of
 * check_url() or filter_xss(), but those functions return an HTML-encoded
 * string, so this function can be called independently when the output needs to
656 657
 * be a plain-text string for passing to t(), l(),
 * Drupal\Core\Template\Attribute, or another function that will call
658
 * \Drupal\Component\Utility\String::checkPlain() separately.
659 660 661 662 663 664 665
 *
 * @param $uri
 *   A plain-text URI that might contain dangerous protocols.
 *
 * @return
 *   A plain-text URI stripped of dangerous protocols. As with all plain-text
 *   strings, this return value must not be output to an HTML page without
666 667
 *   \Drupal\Component\Utility\String::checkPlain() being called on it. However,
 *   it can be passed to functions expecting plain-text strings.
668
 *
669
 * @see \Drupal\Component\Utility\Url::stripDangerousProtocols()
670 671
 */
function drupal_strip_dangerous_protocols($uri) {
672
  return Url::stripDangerousProtocols($uri);
673 674 675
}

/**
676
 * Strips dangerous protocols from a URI and encodes it for output to HTML.
677 678 679 680 681 682 683
 *
 * @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
684 685 686
 *   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
687 688
 *   Drupal\Core\Template\Attribute, call
 *   \Drupal\Component\Utility\Url::stripDangerousProtocols() instead.
689
 *
690 691
 * @see \Drupal\Component\Utility\Url::stripDangerousProtocols()
 * @see \Drupal\Component\Utility\String::checkPlain()
Dries's avatar
Dries committed
692 693
 */
function check_url($uri) {
694
  return String::checkPlain(Url::stripDangerousProtocols($uri));
Dries's avatar
Dries committed
695 696
}

697
/**
698
 * Applies a very permissive XSS/HTML filter for admin-only use.
699 700 701
 *
 * Use only for fields where it is impractical to use the
 * whole filter system, but where some (mainly inline) mark-up
702 703
 * is desired (so \Drupal\Component\Utility\String::checkPlain() is not
 * acceptable).
704 705 706
 *
 * Allows all tags that can be used inside an HTML body, save
 * for scripts and styles.
707 708 709 710 711 712 713 714
 *
 * @param string $string
 *   The string to apply the filter to.
 *
 * @return string
 *   The filtered string.
 *
 * @see \Drupal\Component\Utility\Xss::filterAdmin()
715 716
 */
function filter_xss_admin($string) {
717
  return Xss::filterAdmin($string);
718 719 720
}

/**
721
 * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
722
 *
723 724
 * 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.
725 726
 *
 * This code does four things:
727 728 729 730 731
 * - 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:).
732 733
 *
 * @param $string
734 735
 *   The string with raw HTML in it. It will be stripped of everything that can
 *   cause an XSS attack.
736 737
 * @param $allowed_tags
 *   An array of allowed tags.
738 739 740 741 742
 *
 * @return
 *   An XSS safe version of $string, or an empty string if $string is not
 *   valid UTF-8.
 *
743 744
 * @see \Drupal\Component\Utility\Xss::filter()
 *
745
 * @ingroup sanitization
746 747
 */
function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
748
  return Xss::filter($string, $allowed_tags);
749 750 751
}

/**
752
 * Processes an HTML attribute value and strips dangerous protocols from URLs.
753
 *
754
 * @param string $string
755
 *   The string with the attribute value.
756
 *
757
 * @return string
758
 *   Cleaned up and HTML-escaped version of $string.
759 760
 *
 * @see \Drupal\Component\Utility\Url::filterBadProtocol()
761
 */
762
function filter_xss_bad_protocol($string) {
763
  return Url::filterBadProtocol($string);
764 765 766 767 768 769
}

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

770
/**
771
 * @defgroup format Formatting
772
 * @{
773
 * Functions to format numbers, strings, dates, etc.
774 775
 */

776 777 778 779 780
/**
 * Formats an RSS channel.
 *
 * Arbitrary elements may be added using the $args associative array.
 */
781
function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
782
  $langcode = $langcode ? $langcode : language(Language::TYPE_CONTENT)->id;
Dries's avatar
Dries committed
783

Dries's avatar
Dries committed
784
  $output = "<channel>\n";
785
  $output .= ' <title>' . String::checkPlain($title) . "</title>\n";
786
  $output .= ' <link>' . check_url($link) . "</link>\n";
787 788 789 790

  // 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;).
791 792
  $output .= ' <description>' . String::checkPlain(decode_entities(strip_tags($description))) . "</description>\n";
  $output .= ' <language>' . String::checkPlain($langcode) . "</language>\n";
793
  $output .= format_xml_elements($args);
Dries's avatar
Dries committed
794 795 796 797 798 799
  $output .= $items;
  $output .= "</channel>\n";

  return $output;
}

800
/**
801
 * Formats a single RSS item.
802 803 804
 *
 * Arbitrary elements may be added using the $args associative array.
 */
Dries's avatar
Dries committed
805
function format_rss_item($title, $link, $description, $args = array()) {
Dries's avatar
Dries committed
806
  $output = "<item>\n";
807
  $output .= ' <title>' . String::checkPlain($title) . "</title>\n";
808
  $output .= ' <link>' . check_url($link) . "</link>\n";
809
  $output .= ' <description>' . String::checkPlain($description) . "</description>\n";
810 811 812 813 814 815 816
  $output .= format_xml_elements($args);
  $output .= "</item>\n";

  return $output;
}

/**
817
 * Formats XML elements.
818 819
 *
 * @param $array
820
 *   An array where each item represents an element and is either a:
821 822 823 824 825 826 827 828 829 830
 *   - (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) {
831
  $output = '';
832 833
  foreach ($array as $key => $value) {
    if (is_numeric($key)) {
834
      if ($value['key']) {
835
        $output .= ' <' . $value['key'];
836
        if (isset($value['attributes']) && is_array($value['attributes'])) {
837
          $output .= new Attribute($value['attributes']);
838 839
        }

840
        if (isset($value['value']) && $value['value'] != '') {
841
          $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : String::checkPlain($value['value'])) . '</' . $value['key'] . ">\n";
842 843 844 845 846 847 848
        }
        else {
          $output .= " />\n";
        }
      }
    }
    else {
849
      $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : String::checkPlain($value)) . "</$key>\n";
850
    }
Dries's avatar
Dries committed
851
  }
Dries's avatar
Dries committed
852 853 854
  return $output;
}

855
/**
856
 * Formats a string containing a count of items.
857
 *
858
 * This function ensures that the string is pluralized correctly. Since t() is
859 860
 * called by this function, make sure not to pass already-localized strings to
 * it.
861
 *
862 863 864 865 866 867 868 869 870 871
 * 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.',
872
 *     array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
873 874
 * @endcode
 *
875 876 877
 * @param $count
 *   The item count to display.
 * @param $singular
878 879 880
 *   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.
881
 * @param $plural
882 883 884
 *   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".
885
 * @param $args
886
 *   An associative array of replacements to make after translation. Instances
887
 *   of any key in this array are replaced with the corresponding value.
888 889 890
 *   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.
891
 * @param $options
892 893
 *   An associative array of additional options. See t() for allowed keys.
 *
894 895
 * @return
 *   A translated string.
896 897 898
 *
 * @see t()
 * @see format_string()
899 900
 *
 * @deprecated as of Drupal 8.0. Use \Drupal::translation()->formatPlural()
901
 */
902
function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
903
  return \Drupal::translation()->formatPlural($count, $singular, $plural, $args, $options);
Dries's avatar
Dries committed
904 905
}

906
/**
907
 * Parses a given byte count.
908 909
 *
 * @param $size
910 911
 *   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).
912
 *
913
 * @return
914
 *   An integer representation of the size in bytes.
915 916
 */
function parse_size($size) {
917 918 919 920 921 922 923 924
  $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);
925 926 927
  }
}

928
/**
929
 * Generates a string representation for the given byte count.
930
 *
931
 * @param $size
932
 *   A size in bytes.
933
 * @param $langcode
934 935
 *   Optional language code to translate to a language other than what is used
 *   to display the page.
936
 *
937 938
 * @return
 *   A translated string representation of the size.
939
 */
940
function format_size($size, $langcode = NULL) {
941
  if ($size < DRUPAL_KILOBYTE) {
942
    return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
Dries's avatar
Dries committed
943
  }
944
  else {
945
    $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
946
    $units = array(
947 948 949 950 951 952 953 954
      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)),
955 956
    );
    foreach ($units as $unit) {
957 958
      if (round($size, 2) >= DRUPAL_KILOBYTE) {
        $size = $size / DRUPAL_KILOBYTE;
959 960 961 962
      }
      else {
        break;
      }
963
    }
964
    return str_replace('@size', round($size, 2), $unit);
Dries's avatar
Dries committed
965 966 967
  }
}

968
/**
969
 * Formats a time interval with the requested granularity.
970
 *
971
 * @param $interval
972 973 974
 *   The length of the interval in seconds.
 * @param $granularity
 *   How many different units to display in the string.
975 976 977
 * @param $langcode
 *   Optional language code to translate to a language other than
 *   what is used to display the page.
978
 *
979 980
 * @return
 *   A translated string representation of the interval.
981 982
 *
 * @deprecated as of Drupal 8.0. Use \Drupal::service('date')->formatInterval().
983
 */
Dries's avatar