common.inc 212 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\String;
6
use Drupal\Component\Utility\Tags;
7
8
use Drupal\Component\Utility\UrlValidator;
use Drupal\Component\Utility\Xss;
9
use Drupal\Core\Cache\Cache;
10
use Drupal\Core\Language\Language;
11
use Symfony\Component\DependencyInjection\Container;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\Yaml\Parser;
14
15
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
16
use Drupal\Component\PhpStorage\PhpStorageFactory;
17
use Drupal\Component\Utility\MapArray;
18
use Drupal\Component\Utility\NestedArray;
19
use Drupal\Component\Utility\Unicode;
20
use Drupal\Core\Cache\CacheBackendInterface;
21
use Drupal\Core\Datetime\DrupalDateTime;
22
use Drupal\Core\Database\Database;
23
use Drupal\Core\Routing\GeneratorNotInitializedException;
24
use Drupal\Core\SystemListingInfo;
25
use Drupal\Core\Template\Attribute;
26
27
use Zend\Feed\Writer\Writer;
use Zend\Feed\Reader\Reader;
28

Dries's avatar
   
Dries committed
29
30
31
32
33
34
35
36
/**
 * @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.
 */

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
62
63
64
/**
 * @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
 *
65
 * @}
66
67
 */

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

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

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

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

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

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

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

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

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

138
/**
139
140
141
 * @defgroup block_caching Block Caching
 * @{
 * Constants that define each block's caching state.
142
 *
143
144
145
146
147
148
149
150
151
152
 * 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.
153
 *
154
155
 * The block cache is cleared when the 'content' cache tag is invalidated,
 * following the same pattern as the page cache (node, comment, user, taxonomy
156
 * added or updated...).
157
158
159
160
161
 *
 * Note that user 1 is excluded from block caching.
 */

/**
162
163
164
165
166
167
168
 * 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.
169
 */
170
const DRUPAL_NO_CACHE = -1;
171
172

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

/**
181
182
183
184
 * 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.
185
 */
186
const DRUPAL_CACHE_PER_ROLE = 0x0001;
187
188

/**
189
190
 * The block or element can change depending on the user.
 *
191
192
193
 * 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.
 */
194
const DRUPAL_CACHE_PER_USER = 0x0002;
195
196
197
198

/**
 * The block or element can change depending on the page being viewed.
 */
199
const DRUPAL_CACHE_PER_PAGE = 0x0004;
200
201

/**
202
 * The block or element is the same for every user and page that it is visible.
203
 */
204
const DRUPAL_CACHE_GLOBAL = 0x0008;
205

206
207
208
209
/**
 * @} End of "defgroup block_caching".
 */

210
211
212
213
214
215
216
217
218
/**
 * 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";

219
/**
220
 * Adds content to a specified region.
221
222
 *
 * @param $region
223
 *   Page region the content is added to.
224
 * @param $data
225
 *   Content to be added.
226
 */
227
function drupal_add_region_content($region = NULL, $data = NULL) {
228
229
  static $content = array();

230
  if (isset($region) && isset($data)) {
231
232
233
234
235
236
    $content[$region][] = $data;
  }
  return $content;
}

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

262
/**
263
 * Gets the name of the currently active installation profile.
264
265
266
267
 *
 * 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
 * installation state. At all other times, the standard Drupal systems variable
268
269
 * table contains the name of the current profile, and we can call
 * variable_get() to determine what one is active.
270
271
 *
 * @return $profile
272
 *   The name of the installation profile.
273
274
275
276
277
278
279
280
 */
function drupal_get_profile() {
  global $install_state;

  if (isset($install_state['parameters']['profile'])) {
    $profile = $install_state['parameters']['profile'];
  }
  else {
281
    $profile = variable_get('install_profile', 'standard');
282
283
284
285
286
287
  }

  return $profile;
}


Dries's avatar
   
Dries committed
288
/**
289
 * Sets the breadcrumb trail for the current page.
Dries's avatar
   
Dries committed
290
 *
Dries's avatar
   
Dries committed
291
292
293
 * @param $breadcrumb
 *   Array of links, starting with "home" and proceeding up to but not including
 *   the current page.
294
295
296
297
298
 *
 * @deprecated This will be removed in 8.0. Instead, register a new breadcrumb
 *   builder service.
 *
 * @see Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface
Kjartan's avatar
Kjartan committed
299
 */
Dries's avatar
   
Dries committed
300
function drupal_set_breadcrumb($breadcrumb = NULL) {
301
  $stored_breadcrumb = &drupal_static(__FUNCTION__);
Dries's avatar
   
Dries committed
302

303
  if (isset($breadcrumb)) {
Dries's avatar
   
Dries committed
304
305
306
307
308
    $stored_breadcrumb = $breadcrumb;
  }
  return $stored_breadcrumb;
}

Dries's avatar
Dries committed
309
/**
310
 * Adds output to the HEAD tag of the HTML page.
311
 *
312
 * This function can be called as long as the headers aren't sent. Pass no
313
314
315
316
317
318
319
320
321
322
323
324
 * 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.
 *
325
 * @see drupal_pre_render_html_tag()
Dries's avatar
Dries committed
326
 */
327
328
function drupal_add_html_head($data = NULL, $key = NULL) {
  $stored_head = &drupal_static(__FUNCTION__);
Dries's avatar
Dries committed
329

330
331
332
333
334
335
336
337
338
339
  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
340
341
342
343
  }
  return $stored_head;
}

Dries's avatar
   
Dries committed
344
/**
345
346
347
348
349
350
351
352
353
354
 * 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(
355
      'charset' => 'utf-8',
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
    ),
    // 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.
  list($version, ) = explode('.', VERSION);
  $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;
}

/**
377
 * Retrieves output to be displayed in the HEAD tag of the HTML page.
Dries's avatar
   
Dries committed
378
 */
Dries's avatar
Dries committed
379
function drupal_get_html_head() {
380
381
382
  $elements = drupal_add_html_head();
  drupal_alter('html_head', $elements);
  return drupal_render($elements);
Dries's avatar
Dries committed
383
384
}

385
/**
386
 * Adds a feed URL for the current page.
387
 *
388
389
 * This function can be called as long the HTML header hasn't been sent.
 *
390
 * @param $url
391
 *   An internal system path or a fully qualified external URL of the feed.
392
 * @param $title
393
 *   The title of the feed.
394
 */
395
function drupal_add_feed($url = NULL, $title = '') {
396
  $stored_feed_links = &drupal_static(__FUNCTION__, array());
397

398
  if (isset($url)) {
399
    $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
400

401
402
403
404
405
406
407
408
    drupal_add_html_head_link(array(
      '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)),
    ));
409
410
411
412
413
  }
  return $stored_feed_links;
}

/**
414
 * Gets the feed URLs for the current page.
415
416
 *
 * @param $delimiter
417
 *   A delimiter to split feeds by.
418
419
420
421
422
423
 */
function drupal_get_feeds($delimiter = "\n") {
  $feeds = drupal_add_feed();
  return implode($feeds, $delimiter);
}

Dries's avatar
   
Dries committed
424
/**
425
 * @defgroup http_handling HTTP handling
Dries's avatar
   
Dries committed
426
 * @{
Dries's avatar
   
Dries committed
427
 * Functions to properly handle HTTP responses.
Dries's avatar
   
Dries committed
428
429
 */

430
/**
431
 * Processes a URL query parameter array to remove unwanted elements.
432
433
 *
 * @param $query
434
 *   (optional) An array to be processed. Defaults to $_GET.
435
 * @param $exclude
436
 *   (optional) A list of $query array keys to remove. Use "parent[child]" to
437
 *   exclude nested items.
438
 * @param $parent
439
440
 *   Internal use only. Used to build the $query array key for nested items.
 *
441
 * @return
442
 *   An array containing query parameters, which can be used for url().
443
 */
444
function drupal_get_query_parameters(array $query = NULL, array $exclude = array(), $parent = '') {
445
446
447
448
449
450
451
452
453
454
455
  // Set defaults, if none given.
  if (!isset($query)) {
    $query = $_GET;
  }
  // If $exclude is empty, there is nothing to filter.
  if (empty($exclude)) {
    return $query;
  }
  elseif (!$parent) {
    $exclude = array_flip($exclude);
  }
456

457
  $params = array();
458
  foreach ($query as $key => $value) {
459
460
461
    $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
    if (isset($exclude[$string_key])) {
      continue;
462
463
    }

464
465
466
467
468
    if (is_array($value)) {
      $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
    }
    else {
      $params[$key] = $value;
469
    }
470
471
472
473
474
  }

  return $params;
}

475
/**
476
 * Splits a URL-encoded query string into an array.
477
478
479
480
481
 *
 * @param $query
 *   The query string to split.
 *
 * @return
482
 *   An array of URL decoded couples $param_name => $value.
483
484
485
486
487
488
 */
function drupal_get_query_array($query) {
  $result = array();
  if (!empty($query)) {
    foreach (explode('&', $query) as $param) {
      $param = explode('=', $param);
489
      $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
490
491
492
493
494
    }
  }
  return $result;
}

495
/**
496
 * Parses an array into a valid, rawurlencoded query string.
497
 *
498
 * @see \Drupal\Core\Routing\PathBasedGeneratorInterface::httpBuildQuery()
499
 * @see drupal_get_query_parameters()
500
501
 * @deprecated as of Drupal 8.0. Use
 *   Drupal::urlGenerator()->httpBuildQuery() instead.
502
503
504
 * @ingroup php_wrappers
 */
function drupal_http_build_query(array $query, $parent = '') {
505
  return Drupal::urlGenerator()->httpBuildQuery($query, $parent);
506
507
}

508
/**
509
 * Prepares a 'destination' URL query parameter for use with url().
510
 *
511
512
513
514
 * 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.
515
 *
516
517
518
519
520
521
 * @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()
522
523
 */
function drupal_get_destination() {
524
525
526
527
528
529
  $destination = &drupal_static(__FUNCTION__);

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

530
  if (isset($_GET['destination'])) {
531
    $destination = array('destination' => $_GET['destination']);
532
533
  }
  else {
534
    $path = current_path();
535
    $query = Drupal::urlGenerator()->httpBuildQuery(drupal_get_query_parameters());
536
    if ($query != '') {
537
      $path .= '?' . $query;
538
    }
539
540
541
542
543
544
    $destination = array('destination' => $path);
  }
  return $destination;
}

/**
545
 * Parses a system URL string into an associative array suitable for url().
546
547
 *
 * This function should only be used for URLs that have been generated by the
548
549
 * system, such as via url(). It should not be used for URLs that come from
 * external sources, or URLs that link to external resources.
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
 *
 * The returned array contains a 'path' that may be passed separately to url().
 * For example:
 * @code
 *   $options = drupal_parse_url($_GET['destination']);
 *   $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
 *   The URL string to parse, f.e. $_GET['destination'].
 *
 * @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
 */
function drupal_parse_url($url) {
  $options = array(
    'path' => NULL,
    'query' => array(),
    'fragment' => '',
  );

  // External URLs: not using parse_url() here, so we do not have to rebuild
  // the scheme, host, and path without having any use for it.
  if (strpos($url, '://') !== FALSE) {
    // Split off everything before the query string into 'path'.
    $parts = explode('?', $url);
    $options['path'] = $parts[0];
    // If there is a query string, transform it into keyed query parameters.
    if (isset($parts[1])) {
      $query_parts = explode('#', $parts[1]);
      parse_str($query_parts[0], $options['query']);
      // Take over the fragment, if there is any.
      if (isset($query_parts[1])) {
        $options['fragment'] = $query_parts[1];
      }
    }
  }
  // Internal URLs.
  else {
602
603
604
605
606
    // parse_url() does not support relative URLs, so make it absolute. E.g. the
    // relative URL "foo/bar:1" isn't properly parsed.
    $parts = parse_url('http://example.com/' . $url);
    // Strip the leading slash that was just added.
    $options['path'] = substr($parts['path'], 1);
607
608
609
610
611
612
613
614
615
616
617
618
    if (isset($parts['query'])) {
      parse_str($parts['query'], $options['query']);
    }
    if (isset($parts['fragment'])) {
      $options['fragment'] = $parts['fragment'];
    }
  }

  return $options;
}

/**
619
 * Encodes a Drupal path for use in a URL.
620
 *
621
 * For aesthetic reasons slashes are not escaped.
622
 *
623
624
 * Note that url() takes care of calling this function, so a path passed to that
 * function should not be encoded in advance.
625
626
 *
 * @param $path
627
 *   The Drupal path to encode.
628
629
 */
function drupal_encode_path($path) {
630
  return str_replace('%2F', '/', rawurlencode($path));
631
632
}

633
634
635
636
637
638
639
640
641
642
/**
 * 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.
 */
function _external_url_is_local($url) {
643
644
  $url_parts = parse_url($url);
  $base_host = parse_url($GLOBALS['base_url'], PHP_URL_HOST);
645

646
647
648
649
  if (!isset($url_parts['path'])) {
    return ($url_parts['host'] == $base_host);
  }
  else {
650
651
652
653
    // When comparing base paths, we need a trailing slash to make sure a
    // partial URL match isn't occuring. Since base_path() always returns with
    // a trailing slash, we don't need to add the trailing slash here.
    return ($url_parts['host'] == $base_host && stripos($url_parts['path'], base_path()) === 0);
654
  }
655
656
}

657
658
659
660
661
662
663
/**
 * 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) {
664
  $proxy_exceptions = settings()->get('proxy_exceptions', array('localhost', '127.0.0.1'));
665
666
667
  return !in_array(strtolower($host), $proxy_exceptions, TRUE);
}

Dries's avatar
   
Dries committed
668
/**
669
 * @} End of "defgroup http_handling".
Dries's avatar
   
Dries committed
670
 */
Dries's avatar
   
Dries committed
671

Kjartan's avatar
Kjartan committed
672
/**
Dries's avatar
   
Dries committed
673
 * @defgroup validation Input validation
Dries's avatar
   
Dries committed
674
 * @{
Dries's avatar
   
Dries committed
675
 * Functions to validate user input.
Kjartan's avatar
Kjartan committed
676
677
 */

678
/**
679
 * Verifies the syntax of the given e-mail address.
Dries's avatar
   
Dries committed
680
 *
681
682
 * This uses the
 * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
683
 *
Dries's avatar
   
Dries committed
684
 * @param $mail
685
 *   A string containing an e-mail address.
686
 *
Dries's avatar
   
Dries committed
687
 * @return
Dries's avatar
   
Dries committed
688
 *   TRUE if the address is in a valid format.
689
 */
Dries's avatar
   
Dries committed
690
function valid_email_address($mail) {
691
  return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
692
693
}

Dries's avatar
   
Dries committed
694
/**
695
 * Verifies the syntax of the given URL.
Dries's avatar
   
Dries committed
696
 *
697
698
 * This function should only be used on actual URLs. It should not be used for
 * Drupal menu paths, which can contain arbitrary characters.
699
 * Valid values per RFC 3986.
Dries's avatar
   
Dries committed
700
 * @param $url
Dries's avatar
   
Dries committed
701
 *   The URL to verify.
Dries's avatar
   
Dries committed
702
 * @param $absolute
Dries's avatar
   
Dries committed
703
 *   Whether the URL is absolute (beginning with a scheme such as "http:").
704
 *
Dries's avatar
   
Dries committed
705
 * @return
Dries's avatar
   
Dries committed
706
 *   TRUE if the URL is in a valid format.
707
708
 *
 * @see \Drupal\Component\Utility\UrlValidator::isValid()
709
710
 *
 * @deprecated as of Drupal 8.0. Use UrlValidator::isValid() instead.
Dries's avatar
   
Dries committed
711
 */
Dries's avatar
   
Dries committed
712
function valid_url($url, $absolute = FALSE) {
713
  return UrlValidator::isValid($url, $absolute);
Dries's avatar
   
Dries committed
714
715
}

716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
/**
 * Verifies that a number is a multiple of a given step.
 *
 * The implementation assumes it is dealing with IEEE 754 double precision
 * floating point numbers that are used by PHP on most systems.
 *
 * This is based on the number/range verification methods of webkit.
 *
 * @param $value
 *   The value that needs to be checked.
 * @param $step
 *   The step scale factor. Must be positive.
 * @param $offset
 *   (optional) An offset, to which the difference must be a multiple of the
 *   given step.
 *
 * @return bool
 *   TRUE if no step mismatch has occured, or FALSE otherwise.
 *
 * @see http://opensource.apple.com/source/WebCore/WebCore-1298/html/NumberInputType.cpp
 */
function valid_number_step($value, $step, $offset = 0.0) {
  $double_value = (double) abs($value - $offset);

  // The fractional part of a double has 53 bits. The greatest number that could
  // be represented with that is 2^53. If the given value is even bigger than
  // $step * 2^53, then dividing by $step will result in a very small remainder.
  // Since that remainder can't even be represented with a single precision
  // float the following computation of the remainder makes no sense and we can
  // safely ignore it instead.
  if ($double_value / pow(2.0, 53) > $step) {
    return TRUE;
  }

  // Now compute that remainder of a division by $step.
  $remainder = (double) abs($double_value - $step * round($double_value / $step));

  // $remainder is a double precision floating point number. Remainders that
  // can't be represented with single precision floats are acceptable. The
  // fractional part of a float has 24 bits. That means remainders smaller than
  // $step * 2^-24 are acceptable.
  $computed_acceptable_error = (double)($step / pow(2.0, 24));

  return $computed_acceptable_error >= $remainder || $remainder >= ($step - $computed_acceptable_error);
}

762
763
764
765
/**
 * @} End of "defgroup validation".
 */

766
767
768
769
/**
 * @defgroup sanitization Sanitization functions
 * @{
 * Functions to sanitize values.
770
771
772
 *
 * See http://drupal.org/writing-secure-code for information
 * on writing secure code.
773
774
 */

Dries's avatar
Dries committed
775
/**
776
777
778
779
780
781
 * 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
782
783
784
 * be a plain-text string for passing to t(), l(),
 * Drupal\Core\Template\Attribute, or another function that will call
 * check_plain() separately.
785
786
787
788
789
790
791
792
793
794
 *
 * @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
 *   check_plain() being called on it. However, it can be passed to functions
 *   expecting plain-text strings.
 *
795
 * @see \Drupal\Component\Utility\Url::stripDangerousProtocols()
796
797
 */
function drupal_strip_dangerous_protocols($uri) {
798
  return UrlValidator::stripDangerousProtocols($uri);
799
800
801
}

/**
802
 * Strips dangerous protocols from a URI and encodes it for output to HTML.
803
804
805
806
807
808
809
 *
 * @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
810
811
812
813
814
 *   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
 *   Drupal\Core\Template\Attribute, call drupal_strip_dangerous_protocols()
 *   instead.
815
 *
816
817
 * @see \Drupal\Component\Utility\Url::stripDangerousProtocols()
 * @see \Drupal\Component\Utility\String::checkPlain()
Dries's avatar
Dries committed
818
819
 */
function check_url($uri) {
820
  return String::checkPlain(UrlValidator::stripDangerousProtocols($uri));
Dries's avatar
Dries committed
821
822
}

823
/**
824
 * Applies a very permissive XSS/HTML filter for admin-only use.
825
826
827
828
829
830
831
 *
 * Use only for fields where it is impractical to use the
 * whole filter system, but where some (mainly inline) mark-up
 * is desired (so check_plain() is not acceptable).
 *
 * Allows all tags that can be used inside an HTML body, save
 * for scripts and styles.
832
833
834
835
836
837
838
839
 *
 * @param string $string
 *   The string to apply the filter to.
 *
 * @return string
 *   The filtered string.
 *
 * @see \Drupal\Component\Utility\Xss::filterAdmin()
840
841
 */
function filter_xss_admin($string) {
842
  return Xss::filterAdmin($string);
843
844
845
}

/**
846
 * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
847
 *
848
849
 * 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.
850
851
 *
 * This code does four things:
852
853
854
855
856
 * - 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:).
857
858
 *
 * @param $string
859
860
 *   The string with raw HTML in it. It will be stripped of everything that can
 *   cause an XSS attack.
861
862
 * @param $allowed_tags
 *   An array of allowed tags.
863
864
865
866
867
 *
 * @return
 *   An XSS safe version of $string, or an empty string if $string is not
 *   valid UTF-8.
 *
868
869
 * @see \Drupal\Component\Utility\Xss::filter()
 *
870
 * @ingroup sanitization
871
872
 */
function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
873
  return Xss::filter($string, $allowed_tags);
874
875
876
}

/**
877
 * Processes an HTML attribute value and strips dangerous protocols from URLs.
878
 *
879
 * @param string $string
880
 *   The string with the attribute value.
881
 *
882
 * @return string
883
 *   Cleaned up and HTML-escaped version of $string.
884
885
 *
 * @see \Drupal\Component\Utility\Url::filterBadProtocol()
886
 */
887
888
function filter_xss_bad_protocol($string) {
  return UrlValidator::filterBadProtocol($string);
889
890
891
892
893
894
}

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

Dries's avatar
   
Dries committed
895
/**
Dries's avatar
   
Dries committed
896
 * @defgroup format Formatting
Dries's avatar
   
Dries committed
897
 * @{
Dries's avatar
   
Dries committed
898
 * Functions to format numbers, strings, dates, etc.
Dries's avatar
   
Dries committed
899
900
 */

Dries's avatar
   
Dries committed
901
902
903
904
905
/**
 * Formats an RSS channel.
 *
 * Arbitrary elements may be added using the $args associative array.
 */
906
function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
907
  $langcode = $langcode ? $langcode : language(Language::TYPE_CONTENT)->id;
Dries's avatar
   
Dries committed
908

Dries's avatar
Dries committed
909
  $output = "<channel>\n";
910
911
  $output .= ' <title>' . check_plain($title) . "</title>\n";
  $output .= ' <link>' . check_url($link) . "</link>\n";
912
913
914
915

  // 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;).
916
917
  $output .= ' <description>' . check_plain(decode_entities(strip_tags($description))) . "</description>\n";
  $output .= ' <language>' . check_plain($langcode) . "</language>\n";
918
  $output .= format_xml_elements($args);
Dries's avatar
   
Dries committed
919
920
921
922
923
924
  $output .= $items;
  $output .= "</channel>\n";

  return $output;
}

Dries's avatar
   
Dries committed
925
/**
926
 * Formats a single RSS item.
Dries's avatar
   
Dries committed
927
928
929
 *
 * Arbitrary elements may be added using the $args associative array.
 */
Dries's avatar
   
Dries committed
930
function format_rss_item($title, $link, $description, $args = array()) {
Dries's avatar
Dries committed
931
  $output = "<item>\n";
932
933
934
  $output .= ' <title>' . check_plain($title) . "</title>\n";
  $output .= ' <link>' . check_url($link) . "</link>\n";
  $output .= ' <description>' . check_plain($description) . "</description>\n";
935
936
937
938
939
940
941
  $output .= format_xml_elements($args);
  $output .= "</item>\n";

  return $output;
}

/**
942
 * Formats XML elements.
943
944
 *
 * @param $array
945
 *   An array where each item represents an element and is either a:
946
947
948
949
950
951
952
953
954
955
 *   - (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) {
956
  $output = '';
957
958
  foreach ($array as $key => $value) {
    if (is_numeric($key)) {
Dries's avatar
   
Dries committed
959
      if ($value['key']) {
960
        $output .= ' <' . $value['key'];
961
        if (isset($value['attributes']) && is_array($value['attributes'])) {
962
          $output .= new Attribute($value['attributes']);
Dries's avatar
   
Dries committed
963
964
        }

965
        if (isset($value['value']) && $value['value'] != '') {
966
          $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '</' . $value['key'] . ">\n";
Dries's avatar
   
Dries committed
967
968
969
970
971
972
973
        }
        else {
          $output .= " />\n";
        }
      }
    }
    else {
974
      $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : check_plain($value)) . "</$key>\n";
Dries's avatar
   
Dries committed
975
    }
Dries's avatar
   
Dries committed
976
  }
Dries's avatar
   
Dries committed
977
978
979
  return $output;
}

Dries's avatar
   
Dries committed
980
/**
981
 * Formats a string containing a count of items.
Dries's avatar
   
Dries committed
982
 *
Dries's avatar
   
Dries committed
983
 * This function ensures that the string is pluralized correctly. Since t() is
984
985
 * called by this function, make sure not to pass already-localized strings to
 * it.
Dries's avatar
   
Dries committed
986
 *
987
988
989
990
991
992
993
994
995
996
 * 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.',
997
 *     array('%old-type' => $info->old_type, '%new-type' => $info->new_type));
998
999
 * @endcode
 *
Dries's avatar
   
Dries committed
1000
 * @param $count