Code coverage for /20081101/includes/common.inc

Line #Times calledCode
1
<?php
2
// $Id: common.inc,v 1.815 2008/11/01 21:21:34 dries Exp $
3
4
/**
5
 * @file
6
 * Common functions that many Drupal modules will need to reference.
7
 *
8
 * The functions that are critical and need to be available even when
serving
9
 * a cached page are instead located in bootstrap.inc.
10
 */
11
12
/**
13
 * Return status for saving which involved creating a new item.
14
 */
152366
define('SAVED_NEW', 1);
16
17
/**
18
 * Return status for saving which involved an update to an existing item.
19
 */
202366
define('SAVED_UPDATED', 2);
21
22
/**
23
 * Return status for saving which deleted an existing item.
24
 */
252366
define('SAVED_DELETED', 3);
26
27
/**
28
 * Set content for a specified region.
29
 *
30
 * @param $region
31
 *   Page region the content is assigned to.
32
 * @param $data
33
 *   Content to be set.
34
 */
352366
function drupal_set_content($region = NULL, $data = NULL) {
361741
  static $content = array();
37
381741
  if (!is_null($region) && !is_null($data)) {
3922
    $content[$region][] = $data;
4022
  }
411741
  return $content;
420
}
43
44
/**
45
 * Get assigned content.
46
 *
47
 * @param $region
48
 *   A specified region to fetch content for. If NULL, all regions will be
49
 *   returned.
50
 * @param $delimiter
51
 *   Content to be inserted between exploded array elements.
52
 */
532366
function drupal_get_content($region = NULL, $delimiter = ' ') {
541741
  $content = drupal_set_content();
551741
  if (isset($region)) {
561741
    if (isset($content[$region]) && is_array($content[$region])) {
5722
      return implode($delimiter, $content[$region]);
580
    }
591719
  }
60
  else {
611
    foreach (array_keys($content) as $region) {
621
      if (is_array($content[$region])) {
631
        $content[$region] = implode($delimiter, $content[$region]);
641
      }
651
    }
661
    return $content;
67
  }
681719
}
69
70
/**
71
 * Set the breadcrumb trail for the current page.
72
 *
73
 * @param $breadcrumb
74
 *   Array of links, starting with "home" and proceeding up to but not
including
75
 *   the current page.
76
 */
772366
function drupal_set_breadcrumb($breadcrumb = NULL) {
781756
  static $stored_breadcrumb;
79
801756
  if (!is_null($breadcrumb)) {
81123
    $stored_breadcrumb = $breadcrumb;
82123
  }
831756
  return $stored_breadcrumb;
840
}
85
86
/**
87
 * Get the breadcrumb trail for the current page.
88
 */
892366
function drupal_get_breadcrumb() {
901740
  $breadcrumb = drupal_set_breadcrumb();
91
921740
  if (is_null($breadcrumb)) {
931633
    $breadcrumb = menu_get_active_breadcrumb();
941633
  }
95
961740
  return $breadcrumb;
970
}
98
99
/**
100
 * Add output to the head tag of the HTML page.
101
 *
102
 * This function can be called as long the headers aren't sent.
103
 */
1042366
function drupal_set_html_head($data = NULL) {
1051754
  static $stored_head = '';
106
1071754
  if (!is_null($data)) {
1081748
    $stored_head .= $data . "\n";
1091748
  }
1101754
  return $stored_head;
1110
}
112
113
/**
114
 * Retrieve output to be displayed in the head tag of the HTML page.
115
 */
1162366
function drupal_get_html_head() {
1171746
  $output = "<meta http-equiv=\"Content-Type\" content=\"text/html;
charset=utf-8\" />\n";
1181746
  return $output . drupal_set_html_head();
1190
}
120
121
/**
122
 * Reset the static variable which holds the aliases mapped for this
request.
123
 */
1242366
function drupal_clear_path_cache() {
12511
  drupal_lookup_path('wipe');
12611
}
127
128
/**
129
 * Set an HTTP response header for the current page.
130
 *
131
 * Note: When sending a Content-Type header, always include a 'charset'
type,
132
 * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
133
 */
1342366
function drupal_set_header($header = NULL) {
135
  // We use an array to guarantee there are no leading or trailing
delimiters.
136
  // Otherwise, header('') could get called when serving the page later,
which
137
  // ends HTTP headers prematurely on some PHP versions.
1382366
  static $stored_headers = array();
139
1402366
  if (strlen($header)) {
1412366
    header($header);
1422366
    $stored_headers[] = $header;
1432366
  }
1442366
  return implode("\n", $stored_headers);
1450
}
146
147
/**
148
 * Get the HTTP response headers for the current page.
149
 */
1502366
function drupal_get_headers() {
1511
  return drupal_set_header();
1520
}
153
154
/**
155
 * Add a feed URL for the current page.
156
 *
157
 * This function can be called as long the HTML header hasn't been sent.
158
 *
159
 * @param $url
160
 *   A url for the feed.
161
 * @param $title
162
 *   The title of the feed.
163
 */
1642366
function drupal_add_feed($url = NULL, $title = '') {
1651740
  static $stored_feed_links = array();
166
1671740
  if (!is_null($url) && !isset($stored_feed_links[$url])) {
16843
    $stored_feed_links[$url] = theme('feed_icon', $url, $title);
169
17043
    drupal_add_link(array('rel' => 'alternate',
17143
                          'type' => 'application/rss+xml',
17243
                          'title' => $title,
17343
                          'href' => $url));
17443
  }
1751740
  return $stored_feed_links;
1760
}
177
178
/**
179
 * Get the feed URLs for the current page.
180
 *
181
 * @param $delimiter
182
 *   A delimiter to split feeds by.
183
 */
1842366
function drupal_get_feeds($delimiter = "\n") {
1851740
  $feeds = drupal_add_feed();
1861740
  return implode($feeds, $delimiter);
1870
}
188
189
/**
190
 * @name HTTP handling
191
 * @{
192
 * Functions to properly handle HTTP responses.
193
 */
194
195
/**
196
 * Parse an array into a valid urlencoded query string.
197
 *
198
 * @param $query
199
 *   The array to be processed e.g. $_GET.
200
 * @param $exclude
201
 *   The array filled with keys to be excluded. Use parent[child] to
exclude
202
 *   nested items.
203
 * @param $parent
204
 *   Should not be passed, only used in recursive calls.
205
 * @return
206
 *   An urlencoded string which can be appended to/as the URL query string.
207
 */
2082366
function drupal_query_string_encode($query, $exclude = array(), $parent =
'') {
209549
  $params = array();
210
211549
  foreach ($query as $key => $value) {
212549
    $key = drupal_urlencode($key);
213549
    if ($parent) {
2147
      $key = $parent . '[' . $key . ']';
2157
    }
216
217549
    if (in_array($key, $exclude)) {
218541
      continue;
2190
    }
220
22145
    if (is_array($value)) {
2227
      $params[] = drupal_query_string_encode($value, $exclude, $key);
2237
    }
224
    else {
22545
      $params[] = $key . '=' . drupal_urlencode($value);
226
    }
22745
  }
228
229549
  return implode('&', $params);
2300
}
231
232
/**
233
 * Prepare a destination query string for use in combination with
drupal_goto().
234
 *
235
 * Used to direct the user back to the referring page after completing a
form.
236
 * By default the current URL is returned. If a destination exists in the
237
 * previous request, that destination is returned. As such, a destination
can
238
 * persist across multiple pages.
239
 *
240
 * @see drupal_goto()
241
 */
2422366
function drupal_get_destination() {
243325
  if (isset($_REQUEST['destination'])) {
2449
    return 'destination=' . urlencode($_REQUEST['destination']);
2450
  }
246
  else {
247
    // Use $_GET here to retrieve the original path in source form.
248316
    $path = isset($_GET['q']) ? $_GET['q'] : '';
249316
    $query = drupal_query_string_encode($_GET, array('q'));
250316
    if ($query != '') {
2511
      $path .= '?' . $query;
2521
    }
253316
    return 'destination=' . urlencode($path);
254
  }
2550
}
256
257
/**
258
 * Send the user to a different Drupal page.
259
 *
260
 * This issues an on-site HTTP redirect. The function makes sure the
redirected
261
 * URL is formatted correctly.
262
 *
263
 * Usually the redirected URL is constructed from this function's input
264
 * parameters. However you may override that behavior by setting a
265
 * destination in either the $_REQUEST-array (i.e. by using
266
 * the query string of an URI) This is used to direct the user back to
267
 * the proper page after completing a form. For example, after editing
268
 * a post on the 'admin/content/node'-page or after having logged on using
the
269
 * 'user login'-block in a sidebar. The function drupal_get_destination()
270
 * can be used to help set the destination URL.
271
 *
272
 * Drupal will ensure that messages set by drupal_set_message() and other
273
 * session data are written to the database before the user is redirected.
274
 *
275
 * This function ends the request; use it rather than a print theme('page')
276
 * statement in your menu callback.
277
 *
278
 * @param $path
279
 *   A Drupal path or a full URL.
280
 * @param $query
281
 *   A query string component, if any.
282
 * @param $fragment
283
 *   A destination fragment identifier (named anchor).
284
 * @param $http_response_code
285
 *   Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
286
 *   - 301 Moved Permanently (the recommended value for most redirects)
287
 *   - 302 Found (default in Drupal and PHP, sometimes used for spamming
search
288
 *         engines)
289
 *   - 303 See Other
290
 *   - 304 Not Modified
291
 *   - 305 Use Proxy
292
 *   - 307 Temporary Redirect (alternative to "503 Site Down for
Maintenance")
293
 *   Note: Other values are defined by RFC 2616, but are rarely used and
poorly
294
 *   supported.
295
 * @see drupal_get_destination()
296
 */
2972366
function drupal_goto($path = '', $query = NULL, $fragment = NULL,
$http_response_code = 302) {
298
299577
  if (isset($_REQUEST['destination'])) {
3003
    extract(parse_url(urldecode($_REQUEST['destination'])));
3013
  }
302
303577
  $url = url($path, array('query' => $query, 'fragment' => $fragment,
'absolute' => TRUE));
304
  // Remove newlines from the URL to avoid header injection attacks.
305577
  $url = str_replace(array("\n", "\r"), '', $url);
306
307
  // Allow modules to react to the end of the page request before
redirecting.
308
  // We do not want this while running update.php.
309577
  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
310577
    module_invoke_all('exit', $url);
311577
  }
312
313
  // Even though session_write_close() is registered as a shutdown
function, we
314
  // need all session data written to the database before redirecting.
315577
  session_write_close();
316
317577
  header('Location: ' . $url, TRUE, $http_response_code);
318
319
  // The "Location" header sends a redirect status code to the HTTP daemon.
In
320
  // some cases this can be wrong, so we make sure none of the code below
the
321
  // drupal_goto() call gets executed upon redirection.
322577
  exit();
3230
}
324
325
/**
326
 * Generates a site offline message.
327
 */
3282366
function drupal_site_offline() {
3290
  drupal_maintenance_theme();
3300
  drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service
unavailable');
3310
  drupal_set_title(t('Site offline'));
3320
  print theme('maintenance_page',
filter_xss_admin(variable_get('site_offline_message',
3330
    t('@site is currently under maintenance. We should be back shortly.
Thank you for your patience.', array('@site' => variable_get('site_name',
'Drupal'))))));
3340
}
335
336
/**
337
 * Generates a 404 error if the request can not be handled.
338
 */
3392366
function drupal_not_found() {
34016
  drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
341
34216
  watchdog('page not found', check_plain($_GET['q']), NULL,
WATCHDOG_WARNING);
343
344
  // Keep old path for reference.
34516
  if (!isset($_REQUEST['destination'])) {
34616
    $_REQUEST['destination'] = $_GET['q'];
34716
  }
348
34916
  $path = drupal_get_normal_path(variable_get('site_404', ''));
35016
  if ($path && $path != $_GET['q']) {
351
    // Set the active item in case there are tabs to display, or other
352
    // dependencies on the path.
3532
    menu_set_active_item($path);
3542
    $return = menu_execute_active_handler($path);
3552
  }
356
35716
  if (empty($return) || $return == MENU_NOT_FOUND || $return ==
MENU_ACCESS_DENIED) {
35814
    drupal_set_title(t('Page not found'));
35914
    $return = t('The requested page could not be found.');
36014
  }
361
362
  // To conserve CPU and bandwidth, omit the blocks.
36316
  print theme('page', $return, FALSE);
36416
}
365
366
/**
367
 * Generates a 403 error if the request is not allowed.
368
 */
3692366
function drupal_access_denied() {
37051
  drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
37151
  watchdog('access denied', check_plain($_GET['q']), NULL,
WATCHDOG_WARNING);
372
373
  // Keep old path for reference.
37451
  if (!isset($_REQUEST['destination'])) {
37551
    $_REQUEST['destination'] = $_GET['q'];
37651
  }
377
37851
  $path = drupal_get_normal_path(variable_get('site_403', ''));
37951
  if ($path && $path != $_GET['q']) {
380
    // Set the active item in case there are tabs to display or other
381
    // dependencies on the path.
3822
    menu_set_active_item($path);
3832
    $return = menu_execute_active_handler($path);
3842
  }
385
38651
  if (empty($return) || $return == MENU_NOT_FOUND || $return ==
MENU_ACCESS_DENIED) {
38749
    drupal_set_title(t('Access denied'));
38849
    $return = t('You are not authorized to access this page.');
38949
  }
39051
  print theme('page', $return);
39151
}
392
393
/**
394
 * Perform an HTTP request.
395
 *
396
 * This is a flexible and powerful HTTP client implementation. Correctly
handles
397
 * GET, POST, PUT or any other HTTP requests. Handles redirects.
398
 *
399
 * @param $url
400
 *   A string containing a fully qualified URI.
401
 * @param $headers
402
 *   An array containing an HTTP header => value pair.
403
 * @param $method
404
 *   A string defining the HTTP request to use.
405
 * @param $data
406
 *   A string containing data to include in the request.
407
 * @param $retry
408
 *   An integer representing how many times to retry the request in case of
a
409
 *   redirect.
410
 * @return
411
 *   An object containing the HTTP request headers, response code, headers,
412
 *   data and redirect status.
413
 */
4142366
function drupal_http_request($url, $headers = array(), $method = 'GET',
$data = NULL, $retry = 3) {
41510
  global $db_prefix;
41610
  static $self_test = FALSE;
41710
  $result = new stdClass();
418
  // Try to clear the drupal_http_request_fails variable if it's set. We
419
  // can't tie this call to any error because there is no surefire way to
420
  // tell whether a request has failed, so we add the check to places where
421
  // some parsing has failed.
42210
  if (!$self_test && variable_get('drupal_http_request_fails', FALSE)) {
4230
    $self_test = TRUE;
4240
    $works = module_invoke('system', 'check_http_request');
4250
    $self_test = FALSE;
4260
    if (!$works) {
427
      // Do not bother with further operations if we already know that we
428
      // have no chance.
4290
      $result->error = t("The server can't issue HTTP requests");
4300
      return $result;
4310
    }
4320
  }
433
434
  // Parse the URL and make sure we can handle the schema.
43510
  $uri = @parse_url($url);
436
43710
  if ($uri == FALSE) {
4381
    $result->error = 'unable to parse URL';
4391
    return $result;
4400
  }
441
44210
  if (!isset($uri['scheme'])) {
4431
    $result->error = 'missing schema';
4441
    return $result;
4450
  }
446
44710
  switch ($uri['scheme']) {
44810
    case 'http':
44910
      $port = isset($uri['port']) ? $uri['port'] : 80;
45010
      $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
45110
      $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15);
45210
      break;
4531
    case 'https':
454
      // Note: Only works for PHP 4.3 compiled with OpenSSL.
4550
      $port = isset($uri['port']) ? $uri['port'] : 443;
4560
      $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
4570
      $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr,
20);
4580
      break;
4591
    default:
4601
      $result->error = 'invalid schema ' . $uri['scheme'];
4611
      return $result;
4621
  }
463
464
  // Make sure the socket opened properly.
46510
  if (!$fp) {
466
    // When a network error occurs, we use a negative number so it does not
467
    // clash with the HTTP status codes.
4680
    $result->code = -$errno;
4690
    $result->error = trim($errstr);
4700
    return $result;
4710
  }
472
473
  // Construct the path to act on.
47410
  $path = isset($uri['path']) ? $uri['path'] : '/';
47510
  if (isset($uri['query'])) {
4763
    $path .= '?' . $uri['query'];
4773
  }
478
479
  // Create HTTP request.
480
  $defaults = array(
481
    // RFC 2616: "non-standard ports MUST, default ports MAY be included".
482
    // We don't add the port to prevent from breaking rewrite rules
checking the
483
    // host that do not take into account the port number.
48410
    'Host' => "Host: $host",
48510
    'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
48610
    'Content-Length' => 'Content-Length: ' . strlen($data)
48710
  );
488
489
  // If the server url has a user then attempt to use basic authentication
49010
  if (isset($uri['user'])) {
4911
    $defaults['Authorization'] = 'Authorization: Basic ' .
base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] :
''));
4921
  }
493
494
  // If the database prefix is being used by SimpleTest to run the tests in
a copied
495
  // database then set the user-agent header to the database prefix so that
any
496
  // calls to other Drupal pages will run the SimpleTest prefixed database.
The
497
  // user-agent is used to ensure that multiple testing sessions running at
the
498
  // same time won't interfere with each other as they would if the
database
499
  // prefix were stored statically in a file or database variable.
50010
  if (preg_match("/simpletest\d+/", $db_prefix, $matches)) {
50110
    $headers['User-Agent'] = $matches[0];
50210
  }
503
50410
  foreach ($headers as $header => $value) {
50510
    $defaults[$header] = $header . ': ' . $value;
50610
  }
507
50810
  $request = $method . ' ' . $path . " HTTP/1.0\r\n";
50910
  $request .= implode("\r\n", $defaults);
51010
  $request .= "\r\n\r\n";
51110
  if ($data) {
5123
    $request .= $data . "\r\n";
5133
  }
51410
  $result->request = $request;
515
51610
  fwrite($fp, $request);
517
518
  // Fetch response.
51910
  $response = '';
52010
  while (!feof($fp) && $chunk = fread($fp, 1024)) {
52110
    $response .= $chunk;
52210
  }
52310
  fclose($fp);
524
525
  // Parse response.
52610
  list($split, $result->data) = explode("\r\n\r\n", $response, 2);
52710
  $split = preg_split("/\r\n|\n|\r/", $split);
528
52910
  list($protocol, $code, $text) = explode(' ', trim(array_shift($split)),
3);
53010
  $result->headers = array();
531
532
  // Parse headers.
53310
  while ($line = trim(array_shift($split))) {
53410
    list($header, $value) = explode(':', $line, 2);
53510
    if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
536
      // RFC 2109: the Set-Cookie response header comprises the token Set-
537
      // Cookie:, followed by a comma-separated list of one or more
cookies.
5381
      $result->headers[$header] .= ',' . trim($value);
5391
    }
540
    else {
54110
      $result->headers[$header] = trim($value);
542
    }
54310
  }
544
545
  $responses = array(
54610
    100 => 'Continue', 101 => 'Switching Protocols',
54710
    200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 =>
'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset
Content', 206 => 'Partial Content',
54810
    300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found',
303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 =>
'Temporary Redirect',
54910
    400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required',
403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 =>
'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request
Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412
=> 'Precondition Failed', 413 => 'Request Entity Too Large', 414 =>
'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested
range not satisfiable', 417 => 'Expectation Failed',
55010
    500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad
Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 =>
'HTTP Version not supported'
55110
  );
552
  // RFC 2616 states that all unknown HTTP codes must be treated the same
as the
553
  // base code in their class.
55410
  if (!isset($responses[$code])) {
5550
    $code = floor($code / 100) * 100;
5560
  }
557
558
  switch ($code) {
55910
    case 200: // OK
56010
    case 304: // Not modified
56110
      break;
5621
    case 301: // Moved permanently
5631
    case 302: // Moved temporarily
5641
    case 307: // Moved temporarily
5651
      $location = $result->headers['Location'];
566
5671
      if ($retry) {
5681
        $result = drupal_http_request($location, $headers, $method, $data,
--$retry);
5691
        $result->redirect_code = $code;
5701
      }
5711
      $result->redirect_url = $location;
572
5731
      break;
5740
    default:
5750
      $result->error = $text;
5760
  }
577
57810
  $result->code = $code;
57910
  return $result;
5800
}
581
/**
582
 * @} End of "HTTP handling".
583
 */
584
585
/**
586
 * Custom PHP error handler.
587
 *
588
 * @param $error_level
589
 *   The level of the error raised.
590
 * @param $message
591
 *   The error message.
592
 * @param $filename
593
 *   The filename that the error was raised in.
594
 * @param $line
595
 *   The line number the error was raised at.
596
 * @param $context
597
 *   An array that points to the active symbol table at the point the error
occurred.
598
 */
5992366
function _drupal_error_handler($error_level, $message, $filename, $line,
$context) {
600159
  if ($error_level & error_reporting()) {
601
    // All these constants are documented at
http://php.net/manual/en/errorfunc.constants.php
602
    $types = array(
60354
      E_ERROR => 'Error',
60454
      E_WARNING => 'Warning',
60554
      E_PARSE => 'Parse error',
60654
      E_NOTICE => 'Notice',
60754
      E_CORE_ERROR => 'Core error',
60854
      E_CORE_WARNING => 'Core warning',
60954
      E_COMPILE_ERROR => 'Compile error',
61054
      E_COMPILE_WARNING => 'Compile warning',
61154
      E_USER_ERROR => 'User error',
61254
      E_USER_WARNING => 'User warning',
61354
      E_USER_NOTICE => 'User notice',
61454
      E_STRICT => 'Strict warning',
61554
      E_RECOVERABLE_ERROR => 'Recoverable fatal error'
61654
    );
61754
    $backtrace = debug_backtrace();
618
    // We treat recoverable errors as fatal.
61954
    _drupal_log_error(isset($types[$error_level]) ? $types[$error_level] :
'Unknown error', $message, $backtrace, $error_level ==
E_RECOVERABLE_ERROR);
62054
  }
621159
}
622
623
/**
624
 * Custom PHP exception handler.
625
 *
626
 * Uncaught exceptions are those not enclosed in a try/catch block. They
are
627
 * always fatal: the execution of the script will stop as soon as the
exception
628
 * handler exits.
629
 *
630
 * @param $exception
631
 *   The exception object that was thrown.
632
 */
6332366
function _drupal_exception_handler($exception) {
6342
  $backtrace = $exception->getTrace();
635
  // Add the line throwing the exception to the backtrace.
6362
  array_unshift($backtrace, array('line' => $exception->getLine(), 'file'
=> $exception->getFile()));
637
638
  // For PDOException errors, we try to return the initial caller,
639
  // skipping internal functions of the database layer.
6402
  if ($exception instanceof PDOException) {
641
    // The first element in the stack is the call, the second element gives
us the caller.
642
    // We skip calls that occurred in one of the classes of the database
layer
643
    // or in one of its global functions.
6441
    $db_functions = array('db_query', 'pager_query', 'db_query_range',
'db_query_temporary', 'update_sql');
6451
    while (($caller = $backtrace[1]) &&
6461
         ((isset($caller['class']) && (strpos($caller['class'], 'Query')
!== FALSE || strpos($caller['class'], 'Database') !== FALSE)) ||
6471
         in_array($caller['function'], $db_functions))) {
648
      // We remove that call.
6491
      array_shift($backtrace);
6501
    }
6511
  }
652
653
  // Log the message to the watchdog and return an error page to the user.
6542
  _drupal_log_error(get_class($exception), $exception->getMessage(),
$backtrace, TRUE);
6550
}
656
657
/**
658
 * Log a PHP error or exception, display an error page in fatal cases.
659
 *
660
 * @param $type
661
 *   The type of the error (Error, Warning, ...).
662
 * @param $message
663
 *   The message associated to the error.
664
 * @param $backtrace
665
 *   The backtrace of function calls that led to this error.
666
 * @param $fatal
667
 *   TRUE if the error is fatal.
668
 */
6692366
function _drupal_log_error($type, $message, $backtrace, $fatal) {
67056
  $caller = _drupal_get_last_caller($backtrace);
671
672
  // Initialize a maintenance theme early if the boostrap was not complete.
673
  // Do it early because drupal_set_message() triggers an init_theme().
67456
  if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
6750
    unset($GLOBALS['theme']);
6760
    define('MAINTENANCE_MODE', 'error');
6770
    drupal_maintenance_theme();
6780
  }
679
680
  // Force display of error messages in update.php.
68156
  if (variable_get('error_level', 1) == 1 || (defined('MAINTENANCE_MODE')
&& MAINTENANCE_MODE == 'update')) {
68256
    drupal_set_message(t('@type: %message in %function (line %line of
%file).', array('@type' => $type, '%message' => $message, '%function' =>
$caller['function'], '%line' => $caller['line'], '%file' =>
$caller['file'])), 'error');
68356
  }
684
68556
  watchdog('php', '%type: %message in %function (line %line of %file).',
array('%type' => $type, '%message' => $message, '%function' =>
$caller['function'], '%file' => $caller['file'], '%line' =>
$caller['line']), WATCHDOG_ERROR);
686
68756
  if ($fatal) {
6882
    drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' Service
unavailable');
6892
    drupal_set_title(t('Error'));
6902
    if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
6912
      print theme('page', t('The website encountered an unexpected error.
Please try again later.'), FALSE);
6922
    }
693
    else {
6940
      print theme('maintenance_page', t('The website encountered an
unexpected error. Please try again later.'), FALSE);
695
    }
6962
    exit;
6970
  }
69854
}
699
700
/**
701
 * Gets the last caller from a backtrace.
702
 *
703
 * @param $backtrace
704
 *   A standard PHP backtrace.
705
 * @return
706
 *   An associative array with keys 'file', 'line' and 'function'.
707
 */
7082366
function _drupal_get_last_caller($backtrace) {
709
  // Errors that occur inside PHP internal functions
710
  // do not generate information about file and line.
711187
  while ($backtrace && !isset($backtrace[0]['line'])) {
7127
    array_shift($backtrace);
7137
  }
714
715
  // The first trace is the call itself.
716
  // It gives us the line and the file of the last call.
717187
  $call = $backtrace[0];
718
719
  // The second call give us the function where the call originated.
720187
  if (isset($backtrace[1])) {
721187
    if (isset($backtrace[1]['class'])) {
722136
      $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] .
$backtrace[1]['function'] . '()';
723136
    }
724
    else {
72555
      $call['function'] = $backtrace[1]['function'] . '()';
726
    }
727187
  }
728
  else {
7290
    $call['function'] = 'main()';
730
  }
731187
  return $call;
7320
}
733
7342366
function _fix_gpc_magic(&$item) {
7350
  if (is_array($item)) {
7360
    array_walk($item, '_fix_gpc_magic');
7370
  }
738
  else {
7390
    $item = stripslashes($item);
740
  }
7410
}
742
743
/**
744
 * Helper function to strip slashes from $_FILES skipping over the tmp_name
keys
745
 * since PHP generates single backslashes for file paths on Windows
systems.
746
 *
747
 * tmp_name does not have backslashes added see
748
 * http://php.net/manual/en/features.file-upload.php#42280
749
 */
7502366
function _fix_gpc_magic_files(&$item, $key) {
7510
  if ($key != 'tmp_name') {
7520
    if (is_array($item)) {
7530
      array_walk($item, '_fix_gpc_magic_files');
7540
    }
755
    else {
7560
      $item = stripslashes($item);
757
    }
7580
  }
7590
}
760
761
/**
762
 * Fix double-escaping problems caused by "magic quotes" in some PHP
installations.
763
 */
7642366
function fix_gpc_magic() {
7652366
  static $fixed = FALSE;
7662366
  if (!$fixed && ini_get('magic_quotes_gpc')) {
7670
    array_walk($_GET, '_fix_gpc_magic');
7680
    array_walk($_POST, '_fix_gpc_magic');
7690
    array_walk($_COOKIE, '_fix_gpc_magic');
7700
    array_walk($_REQUEST, '_fix_gpc_magic');
7710
    array_walk($_FILES, '_fix_gpc_magic_files');
7720
    $fixed = TRUE;
7730
  }
7742366
}
775
776
/**
777
 * Translate strings to the page language or a given language.
778
 *
779
 * All human-readable text that will be displayed somewhere within a page
should
780
 * be run through the t() function.
781
 *
782
 * Examples:
783
 * @code
784
 *   if (!$info || !$info['extension']) {
785
 *     form_set_error('picture_upload', t('The uploaded file was not an
image.'));
786
 *   }
787
 *
788
 *   $form['submit'] = array(
789
 *     '#type' => 'submit',
790
 *     '#value' => t('Log in'),
791
 *   );
792
 * @endcode
793
 *
794
 * Any text within t() can be extracted by translators and changed into
795
 * the equivalent text in their native language.
796
 *
797
 * Special variables called "placeholders" are used to signal dynamic
798
 * information in a string which should not be translated. Placeholders
799
 * can also be used for text that may change from time to time
800
 * (such as link paths) to be changed without requiring updates to
translations.
801
 *
802
 * For example:
803
 * @code
804
 *   $output = t('There are currently %members and %visitors online.',
array(
805
 *     '%members' => format_plural($total_users, '1 user', '@count users'),
806
 *     '%visitors' => format_plural($guests->count, '1 guest', '@count
guests')));
807
 * @endcode
808
 *
809
 * There are three styles of placeholders:
810
 * - !variable, which indicates that the text should be inserted as-is.
This is
811
 *   useful for inserting variables into things like e-mail.
812
 *   @code
813
 *     $message[] = t("If you don't want to receive such e-mails, you can
change your settings at !url.", array('!url' => url("user/$account->uid",
array('absolute' => TRUE))));
814
 *   @endcode
815
 *
816
 * - @variable, which indicates that the text should be run through
check_plain,
817
 *   to escape HTML characters. Use this for any output that's displayed
within
818
 *   a Drupal page.
819
 *   @code
820
 *     drupal_set_title($title = t("@name's blog", array('@name' =>
$account->name)), PASS_THROUGH);
821
 *   @endcode
822
 *
823
 * - %variable, which indicates that the string should be HTML escaped and
824
 *   highlighted with theme_placeholder() which shows up by default as
825
 *   <em>emphasized</em>.
826
 *   @code
827
 *     $message = t('%name-from sent %name-to an e-mail.',
array('%name-from' => $user->name, '%name-to' => $account->name));
828
 *   @endcode
829
 *
830
 * When using t(), try to put entire sentences and strings in one t() call.
831
 * This makes it easier for translators, as it provides context as to what
each
832
 * word refers to. HTML markup within translation strings is allowed, but
should
833
 * be avoided if possible. The exception are embedded links; link titles
add a
834
 * context for translators, so should be kept in the main string.
835
 *
836
 * Here is an example of incorrect usage of t():
837
 * @code
838
 *   $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page'
=> l(t('contact page'), 'contact')));
839
 * @endcode
840
 *
841
 * Here is an example of t() used correctly:
842
 * @code
843
 *   $output .= '<p>' . t('Go to the <a href="@contact-page">contact
page</a>.', array('@contact-page' => url('contact'))) . '</p>';
844
 * @endcode
845
 *
846
 * Also avoid escaping quotation marks wherever possible.
847
 *
848
 * Incorrect:
849
 * @code
850
 *   $output .= t('Don\'t click me.');
851
 * @endcode
852
 *
853
 * Correct:
854
 * @code
855
 *   $output .= t("Don't click me.");
856
 * @endcode
857
 *
858
 * @param $string
859
 *   A string containing the English string to translate.
860
 * @param $args
861
 *   An associative array of replacements to make after translation.
Incidences
862
 *   of any key in this array are replaced with the corresponding value.
863
 *   Based on the first character of the key, the value is escaped and/or
themed:
864
 *    - !variable: inserted as is
865
 *    - @variable: escape plain text to HTML (check_plain)
866
 *    - %variable: escape text and theme as a placeholder for
user-submitted
867
 *      content (check_plain + theme_placeholder)
868
 * @param $langcode
869
 *   Optional language code to translate to a language other than what is
used
870
 *   to display the page.
871
 * @return
872
 *   The translated string.
873
 */
8742366
function t($string, $args = array(), $langcode = NULL) {
8752497
  global $language;
8762497
  static $custom_strings;
877
8782497
  if (!isset($langcode)) {
8792497
    $langcode = $language->language;
8802497
  }
881
882
  // First, check for an array of customized strings. If present, use the
array
883
  // *instead of* database lookups. This is a high performance way to
provide a
884
  // handful of string replacements. See settings.php for examples.
885
  // Cache the $custom_strings variable to improve performance.
8862497
  if (!isset($custom_strings[$langcode])) {
8872367
    $custom_strings[$langcode] = variable_get('locale_custom_strings_' .
$langcode, array());
8882367
  }
889
  // Custom strings work for English too, even if locale module is
disabled.
8902497
  if (isset($custom_strings[$langcode][$string])) {
8910
    $string = $custom_strings[$langcode][$string];
8920
  }
893
  // Translate with locale module if enabled.
8942497
  elseif (function_exists('locale') && $langcode != 'en') {
8952
    $string = locale($string, $langcode);
8962
  }
8972497
  if (empty($args)) {
8982485
    return $string;
8990
  }
900
  else {
901
    // Transform arguments before inserting them.
9022188
    foreach ($args as $key => $value) {
9032188
      switch ($key[0]) {
9042188
        case '@':
905
          // Escaped only.
9061820
          $args[$key] = check_plain($value);
9071820
          break;
908
9091576
        case '%':
9101576
        default:
911
          // Escaped and placeholder.
912672
          $args[$key] = theme('placeholder', $value);
913672
          break;
914
9151194
        case '!':
916
          // Pass-through.
9171194
      }
9182188
    }
9192188
    return strtr($string, $args);
920
  }
9210
}
922
923
/**
924
 * @defgroup validation Input validation
925
 * @{
926
 * Functions to validate user input.
927
 */
928
929
/**
930
 * Verify the syntax of the given e-mail address.
931
 *
932
 * Empty e-mail addresses are allowed. See RFC 2822 for details.
933
 *
934
 * @param $mail
935
 *   A string containing an e-mail address.
936
 * @return
937
 *   TRUE if the address is in a valid format.
938
 */
9392366
function valid_email_address($mail) {
94037
  return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
9410
}
942
943
/**
944
 * Verify the syntax of the given URL.
945
 *
946
 * This function should only be used on actual URLs. It should not be used
for
947
 * Drupal menu paths, which can contain arbitrary characters.
948
 *
949
 * @param $url
950
 *   The URL to verify.
951
 * @param $absolute
952
 *   Whether the URL is absolute (beginning with a scheme such as "http:").
953
 * @return
954
 *   TRUE if the URL is in a valid format.
955
 */
9562366
function valid_url($url, $absolute = FALSE) {
95713
  $allowed_characters = '[a-z0-9\/:_\-_\.\?\$,;~=#&%\+]';
95813
  if ($absolute) {
95913
    return (bool)preg_match("/^(http|https|ftp):\/\/" . $allowed_characters
. "+$/i", $url);
9600
  }
961
  else {
9620
    return (bool)preg_match("/^" . $allowed_characters . "+$/i", $url);
963
  }
9640
}
965
966
/**
967
 * @} End of "defgroup validation".
968
 */
969
970
/**
971
 * Register an event for the current visitor (hostname/IP) to the flood
control mechanism.
972
 *
973
 * @param $name
974
 *   The name of an event.
975
 */
9762366
function flood_register_event($name) {
9777
  db_insert('flood')
9787
    ->fields(array(
9797
      'event' => $name,
9807
      'hostname' => ip_address(),
9817
      'timestamp' => REQUEST_TIME,
9827
    ))
9837
    ->execute();
9847
}
985
986
/**
987
 * Check if the current visitor (hostname/IP) is allowed to proceed with
the specified event.
988
 *
989
 * The user is allowed to proceed if he did not trigger the specified event
more
990
 * than $threshold times per hour.
991
 *
992
 * @param $name
993
 *   The name of the event.
994
 * @param $number
995
 *   The maximum number of the specified event per hour (per visitor).
996
 * @return
997
 *   True if the user did not exceed the hourly threshold. False otherwise.
998
 */
9992366
function flood_is_allowed($name, $threshold) {
100032
  $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND
hostname = :hostname AND timestamp > :timestamp", array(
100132
    ':event' => $name,
100232
    ':hostname' => ip_address(),
100332
    ':timestamp' => REQUEST_TIME - 3600))
100432
    ->fetchField();
100532
  return ($number < $threshold);
10060
}
1007
10082366
function check_file($filename) {
10090
  return is_uploaded_file($filename);
10100
}
1011
1012
/**
1013
 * Prepare a URL for use in an HTML attribute. Strips harmful protocols.
1014
 */
10152366
function check_url($uri) {
10161949
  return filter_xss_bad_protocol($uri, FALSE);
10170
}
1018
1019
/**
1020
 * @defgroup format Formatting
1021
 * @{
1022
 * Functions to format numbers, strings, dates, etc.
1023
 */
1024
1025
/**
1026
 * Formats an RSS channel.
1027
 *
1028
 * Arbitrary elements may be added using the $args associative array.
1029
 */
10302366
function format_rss_channel($title, $link, $description, $items, $langcode
= NULL, $args = array()) {
103110
  global $language;
103210
  $langcode = $langcode ? $langcode : $language->language;
1033
103410
  $output = "<channel>\n";
103510
  $output .= ' <title>' . check_plain($title) . "</title>\n";
103610
  $output .= ' <link>' . check_url($link) . "</link>\n";
1037
1038
  // The RSS 2.0 "spec" doesn't indicate HTML can be used in the
description.
1039
  // We strip all HTML tags, but need to prevent double encoding from
properly
1040
  // escaped source data (such as &amp becoming &amp;amp;).
104110
  $output .= ' <description>' .
check_plain(decode_entities(strip_tags($description))) .
"</description>\n";
104210
  $output .= ' <language>' . check_plain($langcode) . "</language>\n";
104310
  $output .= format_xml_elements($args);
104410
  $output .= $items;
104510
  $output .= "</channel>\n";
1046
104710
  return $output;
10480
}
1049
1050
/**
1051
 * Format a single RSS item.
1052
 *
1053
 * Arbitrary elements may be added using the $args associative array.
1054
 */
10552366
function format_rss_item($title, $link, $description, $args = array()) {
10560
  $output = "<item>\n";
10570
  $output .= ' <title>' . check_plain($title) . "</title>\n";
10580
  $output .= ' <link>' . check_url($link) . "</link>\n";
10590
  $output .= ' <description>' . check_plain($description) .
"</description>\n";
10600
  $output .= format_xml_elements($args);
10610
  $output .= "</item>\n";
1062
10630
  return $output;
10640
}
1065
1066
/**
1067
 * Format XML elements.
1068
 *
1069
 * @param $array
1070
 *   An array where each item represent an element and is either a:
1071
 *   - (key => value) pair (<key>value</key>)
1072
 *   - Associative array with fields:
1073
 *     - 'key': element name
1074
 *     - 'value': element contents
1075
 *     - 'attributes': associative array of element attributes
1076
 *
1077
 * In both cases, 'value' can be a simple string, or it can be another
array
1078
 * with the same format as $array itself for nesting.
1079
 */
10802366
function format_xml_elements($array) {
108110
  $output = '';
108210
  foreach ($array as $key => $value) {
10830
    if (is_numeric($key)) {
10840
      if ($value['key']) {
10850
        $output .= ' <' . $value['key'];
10860
        if (isset($value['attributes']) && is_array($value['attributes']))
{
10870
          $output .= drupal_attributes($value['attributes']);
10880
        }
1089
10900
        if (isset($value['value']) && $value['value'] != '') {
10910
          $output .= '>' . (is_array($value['value']) ?
format_xml_elements($value['value']) : check_plain($value['value'])) . '</'
. $value['key'] . ">\n";
10920
        }
1093
        else {
10940
          $output .= " />\n";
1095
        }
10960
      }
10970
    }
1098
    else {
10990
      $output .= ' <' . $key . '>' . (is_array($value) ?
format_xml_elements($value) : check_plain($value)) . "</$key>\n";
1100
    }
11010
  }
110210
  return $output;
11030
}
1104
1105
/**
1106
 * Format a string containing a count of items.
1107
 *
1108
 * This function ensures that the string is pluralized correctly. Since t()
is
1109
 * called by this function, make sure not to pass already-localized strings
to
1110
 * it.
1111
 *
1112
 * For example:
1113
 * @code
1114
 *   $output = format_plural($node->comment_count, '1 comment', '@count
comments');
1115
 * @endcode
1116
 *
1117
 * Example with additional replacements:
1118
 * @code
1119
 *   $output = format_plural($update_count,
1120
 *     'Changed the content type of 1 post from %old-type to %new-type.',
1121
 *     'Changed the content type of @count posts from %old-type to
%new-type.',
1122
 *     array('%old-type' => $info->old_type, '%new-type' =>
$info->new_type)));
1123
 * @endcode
1124
 *
1125
 * @param $count
1126
 *   The item count to display.
1127
 * @param $singular
1128
 *   The string for the singular case. Please make sure it is clear this is
1129
 *   singular, to ease translation (e.g. use "1 new comment" instead of "1
new").
1130
 *   Do not use @count in the singular string.
1131
 * @param $plural
1132
 *   The string for the plural case. Please make sure it is clear this is
plural,
1133
 *   to ease translation. Use @count in place of the item count, as in
"@count
1134
 *   new comments".
1135
 * @param $args
1136
 *   An associative array of replacements to make after translation.
Incidences
1137
 *   of any key in this array are replaced with the corresponding value.
1138
 *   Based on the first character of the key, the value is escaped and/or
themed:
1139
 *    - !variable: inserted as is
1140
 *    - @variable: escape plain text to HTML (check_plain)
1141
 *    - %variable: escape text and theme as a placeholder for
user-submitted
1142
 *      content (check_plain + theme_placeholder)
1143
 *   Note that you do not need to include @count in this array.
1144
 *   This replacement is done automatically for the plural case.
1145
 * @param $langcode
1146
 *   Optional language code to translate to a language other than
1147
 *   what is used to display the page.
1148
 * @return
1149
 *   A translated string.
1150
 */
11512366
function format_plural($count, $singular, $plural, $args = array(),
$langcode = NULL) {
1152309
  $args['@count'] = $count;
1153309
  if ($count == 1) {
1154128
    return t($singular, $args, $langcode);
11550
  }
1156
1157
  // Get the plural index through the gettext formula.
1158300
  $index = (function_exists('locale_get_plural')) ?
locale_get_plural($count, $langcode) : -1;
1159
  // Backwards compatibility.
1160300
  if ($index < 0) {
1161300
    return t($plural, $args, $langcode);
11620
  }
1163
  else {
1164
    switch ($index) {
11650
      case "0":
11660
        return t($singular, $args, $langcode);
11670
      case "1":
11680
        return t($plural, $args, $langcode);
11690
      default:
11700
        unset($args['@count']);
11710
        $args['@count[' . $index . ']'] = $count;
11720
        return t(strtr($plural, array('@count' => '@count[' . $index .
']')), $args, $langcode);
11730
    }
1174
  }
11750
}
1176
1177
/**
1178
 * Parse a given byte count.
1179
 *
1180
 * @param $size
1181
 *   A size expressed as a number of bytes with optional SI size and unit
1182
 *   suffix (e.g. 2, 3K, 5MB, 10G).
1183
 * @return
1184
 *   An integer representation of the size.
1185
 */
11862366
function parse_size($size) {
1187
  $suffixes = array(
118813
    '' => 1,
118913
    'k' => 1024,
119013
    'm' => 1048576, // 1024 * 1024
119113
    'g' => 1073741824, // 1024 * 1024 * 1024
119213
  );
119313
  if (preg_match('/([0-9]+)\s*(k|m|g)?(b?(ytes?)?)/i', $size, $match)) {
119413
    return $match[1] * $suffixes[drupal_strtolower($match[2])];
11950
  }
11960
}
1197
1198
/**
1199
 * Generate a string representation for the given byte count.
1200
 *
1201
 * @param $size
1202
 *   A size in bytes.
1203
 * @param $langcode
1204
 *   Optional language code to translate to a language other than what is
used
1205
 *   to display the page.
1206
 * @return
1207
 *   A translated string representation of the size.
1208
 */
12092366
function format_size($size, $langcode = NULL) {
121027
  if ($size < 1000) {
12113
    return format_plural($size, '1 byte', '@count bytes', array(),
$langcode);
12120
  }
1213
  else {
121426
    $size = $size / 1000; // convert bytes to kilobytes (1000 bytes)
1215
    $units = array(
121626
      t('@size KB', array(), $langcode),
121726
      t('@size MB', array(), $langcode),
121826
      t('@size GB', array(), $langcode),
121926
      t('@size TB', array(), $langcode),
122026
      t('@size PB', array(), $langcode),
122126
      t('@size EB', array(), $langcode),
122226
      t('@size ZB', array(), $langcode),
122326
      t('@size YB', array(), $langcode),
122426
    );
122526
    foreach ($units as $unit) {
122626
      if (round($size, 2) >= 1000) {
122718
        $size = $size / 1000;
122818
      }
1229
      else {
123026
        break;
1231
      }
123218
    }
123326
    return str_replace('@size', round($size, 2), $unit);
1234
  }
12350
}
1236
1237
/**
1238
 * Format a time interval with the requested granularity.
1239
 *
1240
 * @param $timestamp
1241
 *   The length of the interval in seconds.
1242
 * @param $granularity
1243
 *   How many different units to display in the string.
1244
 * @param $langcode
1245
 *   Optional language code to translate to a language other than
1246
 *   what is used to display the page.
1247
 * @return
1248
 *   A translated string representation of the interval.
1249
 */
12502366
function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
1251279
  $units = array('1 year|@count years' => 31536000, '1 week|@count weeks'
=> 604800, '1 day|@count days' => 86400, '1 hour|@count hours' => 3600, '1
min|@count min' => 60, '1 sec|@count sec' => 1);
1252279
  $output = '';
1253279
  foreach ($units as $key => $value) {
1254279
    $key = explode('|', $key);
1255279
    if ($timestamp >= $value) {
1256279
      $output .= ($output ? ' ' : '') . format_plural(floor($timestamp /
$value), $key[0], $key[1], array(), $langcode);
1257279
      $timestamp %= $value;
1258279
      $granularity--;
1259279
    }
1260
1261279
    if ($granularity == 0) {
1262104
      break;
12630
    }
1264279
  }
1265279
  return $output ? $output : t('0 sec', array(), $langcode);
12660
}
1267
1268
/**
1269
 * Format a date with the given configured format or a custom format
string.
1270
 *
1271
 * Drupal allows administrators to select formatting strings for 'small',
1272
 * 'medium' and 'large' date formats. This function can handle these
formats,
1273
 * as well as any custom format.
1274
 *
1275
 * @param $timestamp
1276
 *   The exact date to format, as a UNIX timestamp.
1277
 * @param $type
1278
 *   The format to use. Can be "small", "medium" or "large" for the
preconfigured
1279
 *   date formats. If "custom" is specified, then $format is required as
well.
1280
 * @param $format
1281
 *   A PHP date format string as required by date(). A backslash should be
used
1282
 *   before a character to avoid interpreting the character as part of a
date
1283
 *   format.
1284
 * @param $timezone
1285
 *   Time zone offset in seconds; if omitted, the user's time zone is used.
1286
 * @param $langcode
1287
 *   Optional language code to translate to a language other than what is
used
1288
 *   to display the page.
1289
 * @return
1290
 *   A translated date string in the requested format.
1291
 */
12922366
function format_date($timestamp, $type = 'medium', $format = '', $timezone
= NULL, $langcode = NULL) {
1293403
  if (!isset($timezone)) {
1294385
    global $user;
1295385
    if (variable_get('configurable_timezones', 1) && $user->uid &&
strlen($user->timezone)) {
12960
      $timezone = $user->timezone;
12970
    }
1298
    else {
1299385
      $timezone = variable_get('date_default_timezone', 0);
1300
    }
1301385
  }
1302
1303403
  $timestamp += $timezone;
1304
1305
  switch ($type) {
1306403
    case 'small':
130728
      $format = variable_get('date_format_short', 'm/d/Y - H:i');
130828
      break;
1309375
    case 'large':
13100
      $format = variable_get('date_format_long', 'l, F j, Y - H:i');
13110
      break;
1312375
    case 'custom':
1313
      // No change to format.
1314109
      break;
1315269
    case 'medium':
1316269
    default:
1317269
      $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
1318269
  }
1319
1320403
  $max = strlen($format);
1321403
  $date = '';
1322403
  for ($i = 0; $i < $max; $i++) {
1323403
    $c = $format[$i];
1324403
    if (strpos('AaDlM', $c) !== FALSE) {
1325289
      $date .= t(gmdate($c, $timestamp), array(), $langcode);
1326289
    }
1327400
    elseif ($c == 'F') {
1328
      // Special treatment for long month names: May is both an
abbreviation
1329
      // and a full month name in English, but other languages have
1330
      // different abbreviations.
133115
      $date .= trim(t('!long-month-name ' . gmdate($c, $timestamp),
array('!long-month-name' => ''), $langcode));
133215
    }
1333400
    elseif (strpos('BdgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) {
1334400
      $date .= gmdate($c, $timestamp);
1335400
    }
1336398
    elseif ($c == 'r') {
13370
      $date .= format_date($timestamp - $timezone, 'custom', 'D, d M Y
H:i:s O', $timezone, $langcode);
13380
    }
1339398
    elseif ($c == 'O') {
1340104
      $date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'),
abs($timezone / 3600), abs($timezone % 3600) / 60);
1341104
    }
1342398
    elseif ($c == 'Z') {
13430
      $date .= $timezone;
13440
    }
1345398
    elseif ($c == '\\') {
13460
      $date .= $format[++$i];
13470
    }
1348
    else {
1349398
      $date .= $c;
1350
    }
1351403
  }
1352
1353403
  return $date;
13540
}
1355
1356
/**
1357
 * @} End of "defgroup format".
1358
 */
1359
1360
/**
1361
 * Generate a URL from a Drupal menu path. Will also pass-through existing
URLs.
1362
 *
1363
 * @param $path
1364
 *   The Drupal path being linked to, such as "admin/content/node", or an
1365
 *   existing URL like "http://drupal.org/".  The special path
1366
 *   '<front>' may also be given and will generate the site's base URL.
1367
 * @param $options
1368
 *   An associative array of additional options, with the following keys:
1369
 *   - 'query'
1370
 *       A query string to append to the link, or an array of query
key/value
1371
 *       properties.
1372
 *   - 'fragment'
1373
 *       A fragment identifier (or named anchor) to append to the link.
1374
 *       Do not include the '#' character.
1375
 *   - 'absolute' (default FALSE)
1376
 *       Whether to force the output to be an absolute link (beginning with
1377
 *       http:). Useful for links that will be displayed outside the site,
such
1378
 *       as in an RSS feed.
1379
 *   - 'alias' (default FALSE)
1380
 *       Whether the given path is an alias already.
1381
 *   - 'external'
1382
 *       Whether the given path is an external URL.
1383
 *   - 'language'
1384
 *       An optional language object. Used to build the URL to link to and
1385
 *       look up the proper alias for the link.
1386
 *   - 'base_url'
1387
 *       Only used internally, to modify the base URL when a language
dependent
1388
 *       URL requires so.
1389
 *   - 'prefix'
1390
 *       Only used internally, to modify the path when a language dependent
URL
1391
 *       requires so.
1392
 * @return
1393
 *   A string containing a URL to the given path.
1394
 *
1395
 * When creating links in modules, consider whether l() could be a better
1396
 * alternative than url().
1397
 */
13982366
function url($path = NULL, array $options = array()) {
1399
  // Merge in defaults.
1400
  $options += array(
14012415
    'fragment' => '',
14022415
    'query' => '',
14032415
    'absolute' => FALSE,
14042415
    'alias' => FALSE,
1405
    'prefix' => ''
14062415
  );
14072415
  if (!isset($options['external'])) {
1408
    // Return an external link if $path contains an allowed absolute URL.
1409
    // Only call the slow filter_xss_bad_protocol if $path contains a ':'
before
1410
    // any / ? or #.
14112414
    $colonpos = strpos($path, ':');
14122414
    $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!',
substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) ==
check_plain($path));
14132414
  }
1414
1415
  // May need language dependent rewriting if language.inc is present.
14162415
  if (function_exists('language_url_rewrite')) {
141773
    language_url_rewrite($path, $options);
141873
  }
14192415
  if ($options['fragment']) {
1420224
    $options['fragment'] = '#' . $options['fragment'];
1421224
  }
14222415
  if (is_array($options['query'])) {
14238
    $options['query'] = drupal_query_string_encode($options['query']);
14248
  }
1425
14262415
  if ($options['external']) {
1427
    // Split off the fragment.
14281760
    if (strpos($path, '#') !== FALSE) {
14290
      list($path, $old_fragment) = explode('#', $path, 2);
14300
      if (isset($old_fragment) && !$options['fragment']) {
14310
        $options['fragment'] = '#' . $old_fragment;
14320
      }
14330
    }
1434
    // Append the query.
14351760
    if ($options['query']) {
14364
      $path .= (strpos($path, '?') !== FALSE ? '&' : '?') .
$options['query'];
14374
    }
1438
    // Reassemble.
14391760
    return $path . $options['fragment'];
14400
  }
1441
14422413
  global $base_url;
14432413
  static $script;
14442413
  static $clean_url;
1445
14462413
  if (!isset($script)) {
1447
    // On some web servers, such as IIS, we can't omit "index.php". So, we
1448
    // generate "index.php?q=foo" instead of "?q=foo" on anything that is
not
1449
    // Apache.
14502413
    $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ?
'index.php' : '';
14512413
  }
1452
1453
  // Cache the clean_url variable to improve performance.
14542413
  if (!isset($clean_url)) {
14552413
    $clean_url = (bool)variable_get('clean_url', '0');
14562413
  }
1457
14582413
  if (!isset($options['base_url'])) {
1459
    // The base_url might be rewritten from the language rewrite in domain
mode.
14602413
    $options['base_url'] = $base_url;
14612413
  }
1462
1463
  // Preserve the original path before aliasing.
14642413
  $original_path = $path;
1465
1466
  // The special path '<front>' links to the default front page.
14672413
  if ($path == '<front>') {
14681338
    $path = '';
14691338
  }
14702413
  elseif (!empty($path) && !$options['alias']) {
14712317
    $path = drupal_get_path_alias($path, isset($options['language']) ?
$options['language']->language : '');
14722317
  }
1473
14742413
  if (function_exists('custom_url_rewrite_outbound')) {
1475
    // Modules may alter outbound links by reference.
14760
    custom_url_rewrite_outbound($path, $options, $original_path);
14770
  }
1478
14792413
  $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
14802413
  $prefix = empty($path) ? rtrim($options['prefix'], '/') :
$options['prefix'];
14812413
  $path = drupal_urlencode($prefix . $path);
1482
14832413
  if ($clean_url) {
1484
    // With Clean URLs.
14852413
    if ($options['query']) {
1486383
      return $base . $path . '?' . $options['query'] .
$options['fragment'];
14870
    }
1488
    else {
14892411
      return $base . $path . $options['fragment'];
1490
    }
14910
  }
1492
  else {
1493
    // Without Clean URLs.
14940
    $variables = array();
14950
    if (!empty($path)) {
14960
      $variables[] = 'q=' . $path;
14970
    }
14980
    if (!empty($options['query'])) {
14990
      $variables[] = $options['query'];
15000
    }
15010
    if ($query = join('&', $variables)) {
15020
      return $base . $script . '?' . $query . $options['fragment'];
15030
    }
1504
    else {
15050
      return $base . $options['fragment'];
1506
    }
1507
  }
15080
}
1509
1510
/**
1511
 * Format an attribute string to insert in a tag.
1512
 *
1513
 * @param $attributes
1514
 *   An associative array of HTML attributes.
1515
 * @return
1516
 *   An HTML string ready for insertion in a tag.
1517
 */
15182366
function drupal_attributes($attributes = array()) {
15191951
  if (is_array($attributes)) {
15201951
    $t = '';
15211951
    foreach ($attributes as $key => $value) {
15221732
      $t .= " $key=" . '"' . check_plain($value) . '"';
15231732
    }
15241951
    return $t;
15250
  }
15261740
}
1527
1528
/**
1529
 * Format an internal Drupal link.
1530
 *
1531
 * This function correctly handles aliased paths, and allows themes to
highlight
1532
 * links to the current page correctly, so all internal links output by
modules
1533
 * should be generated by this function if possible.
1534
 *
1535
 * @param $text
1536
 *   The text to be enclosed with the anchor tag.
1537
 * @param $path
1538
 *   The Drupal path being linked to, such as "admin/content/node". Can be
an
1539
 *   external or internal URL.
1540
 *     - If you provide the full URL, it will be considered an external
URL.
1541
 *     - If you provide only the path (e.g. "admin/content/node"), it is
1542
 *       considered an internal link. In this case, it must be a system URL
1543
 *       as the url() function will generate the alias.
1544
 *     - If you provide '<front>', it generates a link to the site's
1545
 *       base URL (again via the url() function).
1546
 *     - If you provide a path, and 'alias' is set to TRUE (see below), it
is
1547
 *       used as is.
1548
 * @param $options
1549
 *   An associative array of additional options, with the following keys:
1550
 *     - 'attributes'
1551
 *       An associative array of HTML attributes to apply to the anchor
tag.
1552
 *     - 'query'
1553
 *       A query string to append to the link, or an array of query
key/value
1554
 *       properties.
1555
 *     - 'fragment'
1556
 *       A fragment identifier (named anchor) to append to the link.
1557
 *       Do not include the '#' character.
1558
 *     - 'absolute' (default FALSE)
1559
 *       Whether to force the output to be an absolute link (beginning with
1560
 *       http:). Useful for links that will be displayed outside the site,
such
1561
 *       as in an RSS feed.
1562
 *     - 'html' (default FALSE)
1563
 *       Whether the title is HTML, or just plain-text. For example for
making
1564
 *       an image a link, this must be set to TRUE, or else you will see
the
1565
 *       escaped HTML.
1566
 *     - 'alias' (default FALSE)
1567
 *       Whether the given path is an alias already.
1568
 * @return
1569
 *   an HTML string containing a link to the given path.
1570
 */
15712366
function l($text, $path, array $options = array()) {
1572
  // Merge in defaults.
1573
  $options += array(
15741934
      'attributes' => array(),
15751934
      'html' => FALSE,
15760
    );
1577
1578
  // Append active class.
15791934
  if ($path == $_GET['q'] || ($path == '<front>' &&
drupal_is_front_page())) {
15801404
    if (isset($options['attributes']['class'])) {
15810
      $options['attributes']['class'] .= ' active';
15820
    }
1583
    else {
15841404
      $options['attributes']['class'] = 'active';
1585
    }
15861404
  }
1587
1588
  // Remove all HTML and PHP tags from a tooltip. For best performance, we
act only
1589
  // if a quick strpos() pre-check gave a suspicion (because strip_tags()
is expensive).
15901934
  if (isset($options['attributes']['title']) &&
strpos($options['attributes']['title'], '<') !== FALSE) {
159172
    $options['attributes']['title'] =
strip_tags($options['attributes']['title']);
159272
  }
1593
15941934
  return '<a href="' . check_url(url($path, $options)) . '"' .
drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text
: check_plain($text)) . '</a>';
15950
}
1596
1597
/**
1598
 * Perform end-of-request tasks.
1599
 *
1600
 * This function sets the page cache if appropriate, and allows modules to
1601
 * react to the closing of the page by calling hook_exit().
1602
 */
16032366
function drupal_page_footer() {
1604
16051756
  if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
16061
    page_set_cache();
16071
  }
1608
16091756
  module_invoke_all('exit');
1610
16111756
  module_implements(MODULE_IMPLEMENTS_WRITE_CACHE);
16121756
  registry_cache_path_files();
16131756
}
1614
1615
/**
1616
 * Form an associative array from a linear array.
1617
 *
1618
 * This function walks through the provided array and constructs an
associative
1619
 * array out of it. The keys of the resulting array will be the values of
the
1620
 * input array. The values will be the same as the keys unless a function
is
1621
 * specified, in which case the output of the function is used for the
values
1622
 * instead.
1623
 *
1624
 * @param $array
1625
 *   A linear array.
1626
 * @param $function
1627
 *   A name of a function to apply to all values before output.
1628
 * @result
1629
 *   An associative array.
1630
 */
16312366
function drupal_map_assoc($array, $function = NULL) {
1632188
  if (!isset($function)) {
1633181
    $result = array();
1634181
    foreach ($array as $value) {
1635178
      $result[$value] = $value;
1636178
    }
1637181
    return $result;
16380
  }
163961
  elseif (function_exists($function)) {
164061
    $result = array();
164161
    foreach ($array as $value) {
164261
      $result[$value] = $function($value);
164361
    }
164461
    return $result;
16450
  }
16460
}
1647
1648
/**
1649
 * Evaluate a string of PHP code.
1650
 *
1651
 * This is a wrapper around PHP's eval(). It uses output buffering to
capture both
1652
 * returned and printed text. Unlike eval(), we require code to be
surrounded by
1653
 * <?php ?> tags; in other words, we evaluate the code as if it were a
stand-alone
1654
 * PHP file.
1655
 *
1656
 * Using this wrapper also ensures that the PHP code which is evaluated can
not
1657
 * overwrite any variables in the calling code, unlike a regular eval()
call.
1658
 *
1659
 * @param $code
1660
 *   The code to evaluate.
1661
 * @return
1662
 *   A string containing the printed output of the code, followed by the
returned
1663
 *   output of the code.
1664
 */
16652366
function drupal_eval($code) {
16661
  global $theme_path, $theme_info, $conf;
1667
1668
  // Store current theme path.
16691
  $old_theme_path = $theme_path;
1670
1671
  // Restore theme_path to the theme, as long as drupal_eval() executes,
1672
  // so code evaluted will not see the caller module as the current theme.
1673
  // If theme info is not initialized get the path from theme_default.
16741
  if (!isset($theme_info)) {
16751
    $theme_path = drupal_get_path('theme', $conf['theme_default']);
16761
  }
1677
  else {
16780
    $theme_path = dirname($theme_info->filename);
1679
  }
1680
16811
  ob_start();
16821
  print eval('?>' . $code);
16831
  $output = ob_get_contents();
16841
  ob_end_clean();
1685
1686
  // Recover original theme path.
16871
  $theme_path = $old_theme_path;
1688
16891
  return $output;
16900
}
1691
1692
/**
1693
 * Returns the path to a system item (module, theme, etc.).
1694
 *
1695
 * @param $type
1696
 *   The type of the item (i.e. theme, theme_engine, module).
1697
 * @param $name
1698
 *   The name of the item for which the path is requested.
1699
 *
1700
 * @return
1701
 *   The path to the requested item.
1702
 */
17032366
function drupal_get_path($type, $name) {
17042496
  return dirname(drupal_get_filename($type, $name));
17050
}
1706
1707
/**
1708
 * Returns the base URL path of the Drupal installation.
1709
 * At the very least, this will always default to /.
1710
 */
17112366
function base_path() {
17122039
  return $GLOBALS['base_path'];
17130
}
1714
1715
/**
1716
 * Add a <link> tag to the page's HEAD.
1717
 *
1718
 * This function can be called as long the HTML header hasn't been sent.
1719
 */
17202366
function drupal_add_link($attributes) {
172164
  drupal_set_html_head('<link' . drupal_attributes($attributes) . " />\n");
172264
}
1723
1724
/**
1725
 * Adds a CSS file to the stylesheet queue.
1726
 *
1727
 * @param $path
1728
 *   (optional) The path to the CSS file relative to the base_path(), e.g.,
1729
 *   /modules/devel/devel.css.
1730
 *
1731
 *   Modules should always prefix the names of their CSS files with the
module
1732
 *   name, for example: system-menus.css rather than simply menus.css.
Themes
1733
 *   can override module-supplied CSS files based on their filenames, and
this
1734
 *   prefixing helps prevent confusing name collisions for theme
developers.
1735
 *   See drupal_get_css where the overrides are performed.
1736
 *
1737
 *   If the direction of the current language is right-to-left (Hebrew,
1738
 *   Arabic, etc.), the function will also look for an RTL CSS file and
append
1739
 *   it to the list. The name of this file should have an '-rtl.css'
suffix.
1740
 *   For example a CSS file called 'name.css' will have a 'name-rtl.css'
1741
 *   file added to the list, if exists in the same directory. This CSS file
1742
 *   should contain overrides for properties which should be reversed or
1743
 *   otherwise different in a right-to-left display.
1744
 * @param $options
1745
 *   (optional) A string defining the type of CSS that is being added in
the
1746
 *   $path parameter ('module' or 'theme'), or an associative array of
1747
 *   additional options, with the following keys:
1748
 *     - 'type'
1749
 *       The type of stylesheet that is being added. Types are: module or
1750
 *       theme. Defaults to 'module'.
1751
 *     - 'media'
1752
 *       The media type for the stylesheet, e.g., all, print, screen.
Defaults
1753
 *       to 'all'.
1754
 *     - 'preprocess':
1755
 *       Allow this CSS file to be aggregated and compressed if the
Optimize
1756
 *       CSS feature has been turned on under the performance section.
Defaults
1757
 *       to TRUE.
1758
 *
1759
 *       What does this actually mean?
1760
 *       CSS preprocessing is the process of aggregating a bunch of
separate CSS
1761
 *       files into one file that is then compressed by removing all
extraneous
1762
 *       white space.
1763
 *
1764
 *       The reason for merging the CSS files is outlined quite thoroughly
here:
1765
 *       http://www.die.net/musings/page_load_time/
1766
 *       "Load fewer external objects. Due to request overhead, one bigger
file
1767
 *       just loads faster than two smaller ones half its size."
1768
 *
1769
 *       However, you should *not* preprocess every file as this can lead
to
1770
 *       redundant caches. You should set $preprocess = FALSE when your
styles
1771
 *       are only used rarely on the site. This could be a special admin
page,
1772
 *       the homepage, or a handful of pages that does not represent the
1773
 *       majority of the pages on your site.
1774
 *
1775
 *       Typical candidates for caching are for example styles for nodes
across
1776
 *       the site, or used in the theme.
1777
 * @param $reset
1778
 *   (optional) Resets the currently loaded cascading stylesheets.
1779
 * @return
1780
 *   An array of CSS files.
1781
 */
17822366
function drupal_add_css($path = NULL, $options = NULL, $reset = FALSE) {
17832496
  static $css = array();
17842496
  global $language;
1785
1786
  // Request made to reset the CSS added so far.
17872496
  if ($reset) {
17881
    $css = array();
17891
  }
1790
1791
  // Create an array of CSS files for each media type first, since each
type needs to be served
1792
  // to the browser differently.
17932496
  if (isset($path)) {
1794
    // Construct the options, taking the defaults into consideration.
17952496
    if (isset($options)) {
17962163
      if (!is_array($options)) {
17970
        $options = array('type' => $options);
17980
      }
17992163
    }
1800
    else {
18012367
      $options = array();
1802
    }
1803
    $options += array(
18042496
      'type' => 'module',
18052496
      'media' => 'all',
1806
      'preprocess' => TRUE
18072496
    );
18082496
    $media = $options['media'];
18092496
    $type = $options['type'];
1810
1811
    // This check is necessary to ensure proper cascading of styles and is
faster than an asort().
18122496
    if (!isset($css[$media])) {
18132496
      $css[$media] = array('module' => array(), 'theme' => array());
18142496
    }
18152496
    $css[$media][$type][$path] = $options['preprocess'];
1816
1817
    // If the current language is RTL, add the CSS file with RTL overrides.
18182496
    if (defined('LANGUAGE_RTL') && $language->direction == LANGUAGE_RTL) {
18190
      $rtl_path = str_replace('.css', '-rtl.css', $path);
18200
      if (file_exists($rtl_path)) {
18210
        $css[$media][$type][$rtl_path] = $options['preprocess'];
18220
      }
18230
    }
18242496
  }
1825
18262496
  return $css;
18270
}
1828
1829
/**
1830
 * Returns a themed representation of all stylesheets that should be
attached to the page.
1831
 *
1832
 * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
1833
 * This ensures proper cascading of styles so themes can easily override
1834
 * module styles through CSS selectors.
1835
 *
1836
 * Themes may replace module-defined CSS files by adding a stylesheet with
the
1837
 * same filename. For example, themes/garland/system-menus.css would
replace
1838
 * modules/system/system-menus.css. This allows themes to override complete
1839
 * CSS files, rather than specific selectors, when necessary.
1840
 *
1841
 * If the original CSS file is being overridden by a theme, the theme is
1842
 * responsible for supplying an accompanying RTL CSS file to replace the
1843
 * module's.
1844
 *
1845
 * @param $css
1846
 *   (optional) An array of CSS files. If no array is provided, the default
1847
 *   stylesheets array is used instead.
1848
 * @return
1849
 *   A string of XHTML CSS tags.
1850
 */
18512366
function drupal_get_css($css = NULL) {
18521741
  $output = '';
18531741
  if (!isset($css)) {
18541741
    $css = drupal_add_css();
18551741
  }
18561741
  $no_module_preprocess = '';
18571741
  $no_theme_preprocess = '';
1858
18591741
  $preprocess_css = (variable_get('preprocess_css', FALSE) &&
(!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
18601741
  $directory = file_directory_path();
18611741
  $is_writable = is_dir($directory) && is_writable($directory) &&
(variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) ==
FILE_DOWNLOADS_PUBLIC);
1862
1863
  // A dummy query-string is added to filenames, to gain control over
1864
  // browser-caching. The string changes on every update or full cache
1865
  // flush, forcing browsers to load a new copy of the files, as the
1866
  // URL changed.
18671741
  $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0,
1);
1868
18691741
  foreach ($css as $media => $types) {
1870
    // If CSS preprocessing is off, we still need to output the styles.
1871
    // Additionally, go through any remaining styles if CSS preprocessing
is on and output the non-cached ones.
18721741
    foreach ($types as $type => $files) {
18731741
      if ($type == 'module') {
1874
        // Setup theme overrides for module styles.
18751741
        $theme_styles = array();
18761741
        foreach (array_keys($css[$media]['theme']) as $theme_style) {
18771740
          $theme_styles[] = basename($theme_style);
18781740
        }
18791741
      }
18801741
      foreach ($types[$type] as $file => $preprocess) {
1881
        // If the theme supplies its own style using the name of the module
style, skip its inclusion.
1882
        // This includes any RTL styles associated with its main LTR
counterpart.
18831741
        if ($type == 'module' && in_array(str_replace('-rtl.css', '.css',
basename($file)), $theme_styles)) {
1884
          // Unset the file to prevent its inclusion when CSS aggregation
is enabled.
18850
          unset($types[$type][$file]);
18860
          continue;
18870
        }
1888
        // Only include the stylesheet if it exists.
18891741
        if (file_exists($file)) {
18901741
          if (!$preprocess || !($is_writable && $preprocess_css)) {
1891
            // If a CSS file is not to be preprocessed and it's a module
CSS file, it needs to *always* appear at the *top*,
1892
            // regardless of whether preprocessing is on or off.
18931741
            if (!$preprocess && $type == 'module') {
189470
              $no_module_preprocess .= '<link type="text/css"
rel="stylesheet" media="' . $media . '" href="' . base_path() . $file .
$query_string . '" />' . "\n";
189570
            }
1896
            // If a CSS file is not to be preprocessed and it's a theme CSS
file, it needs to *always* appear at the *bottom*,
1897
            // regardless of whether preprocessing is on or off.
18981741
            elseif (!$preprocess && $type == 'theme') {
18990
              $no_theme_preprocess .= '<link type="text/css"
rel="stylesheet" media="' . $media . '" href="' . base_path() . $file .
$query_string . '" />' . "\n";
19000
            }
1901
            else {
19021741
              $output .= '<link type="text/css" rel="stylesheet" media="' .
$media . '" href="' . base_path() . $file . $query_string . '" />' . "\n";
1903
            }
19041741
          }
19051741
        }
19061741
      }
19071741
    }
1908
19091741
    if ($is_writable && $preprocess_css) {
19100
      $filename = md5(serialize($types) . $query_string) . '.css';
19110
      $preprocess_file = drupal_build_css_cache($types, $filename);
19120
      $output .= '<link type="text/css" rel="stylesheet" media="' . $media
. '" href="' . base_path() . $preprocess_file . '" />' . "\n";
19130
    }
19141741
  }
1915
19161741
  return $no_module_preprocess . $output . $no_theme_preprocess;
19170
}
1918
1919
/**
1920
 * Aggregate and optimize CSS files, putting them in the files directory.
1921
 *
1922
 * @param $types
1923
 *   An array of types of CSS files (e.g., screen, print) to aggregate and
1924
 *   compress into one file.
1925
 * @param $filename
1926
 *   The name of the aggregate CSS file.
1927
 * @return
1928
 *   The name of the CSS file.
1929
 */
19302366
function drupal_build_css_cache($types, $filename) {
19310
  $data = '';
1932
1933
  // Create the css/ within the files folder.
19340
  $csspath = file_create_path('css');
19350
  file_check_directory($csspath, FILE_CREATE_DIRECTORY);
1936
19370
  if (!file_exists($csspath . '/' . $filename)) {
1938
    // Build aggregate CSS file.
19390
    foreach ($types as $type) {
19400
      foreach ($type as $file => $cache) {
19410
        if ($cache) {
19420
          $contents = drupal_load_stylesheet($file, TRUE);
1943
          // Return the path to where this CSS file originated from.
19440
          $base = base_path() . dirname($file) . '/';
19450
          _drupal_build_css_path(NULL, $base);
1946
          // Prefix all paths within this CSS file, ignoring external and
absolute paths.
19470
          $data .=
preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i',
'_drupal_build_css_path', $contents);
19480
        }
19490
      }
19500
    }
1951
1952
    // Per the W3C specification at
http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
1953
    // @import rules must proceed any other style, so we move those to the
top.
19540
    $regexp = '/@import[^;]+;/i';
19550
    preg_match_all($regexp, $data, $matches);
19560
    $data = preg_replace($regexp, '', $data);
19570
    $data = implode('', $matches[0]) . $data;
1958
1959
    // Create the CSS file.
19600
    file_unmanaged_save_data($data, $csspath . '/' . $filename,
FILE_EXISTS_REPLACE);
19610
  }
19620
  return $csspath . '/' . $filename;
19630
}
1964
1965
/**
1966
 * Helper function for drupal_build_css_cache().
1967
 *
1968
 * This function will prefix all paths within a CSS file.
1969
 */
19702366
function _drupal_build_css_path($matches, $base = NULL) {
19710
  static $_base;
1972
  // Store base path for preg_replace_callback.
19730
  if (isset($base)) {
19740
    $_base = $base;
19750
  }
1976
1977
  // Prefix with base and remove '../' segments where possible.
19780
  $path = $_base . $matches[1];
19790
  $last = '';
19800
  while ($path != $last) {
19810
    $last = $path;
19820
    $path = preg_replace('`(^|/)(?!../)([^/]+)/../`', '$1', $path);
19830
  }
19840
  return 'url(' . $path . ')';
19850
}
1986
1987
/**
1988
 * Loads the stylesheet and resolves all @import commands.
1989
 *
1990
 * Loads a stylesheet and replaces @import commands with the contents of
the
1991
 * imported file. Use this instead of file_get_contents when processing
1992
 * stylesheets.
1993
 *
1994
 * The returned contents are compressed removing white space and comments
only
1995
 * when CSS aggregation is enabled. This optimization will not apply for
1996
 * color.module enabled themes with CSS aggregation turned off.
1997
 *
1998
 * @param $file
1999
 *   Name of the stylesheet to be processed.
2000
 * @param $optimize
2001
 *   Defines if CSS contents should be compressed or not.
2002
 * @return
2003
 *   Contents of the stylesheet including the imported stylesheets.
2004
 */
20052366
function drupal_load_stylesheet($file, $optimize = NULL) {
20060
  static $_optimize;
2007
  // Store optimization parameter for preg_replace_callback with nested
@import loops.
20080
  if (isset($optimize)) {
20090
    $_optimize = $optimize;
20100
  }
2011
20120
  $contents = '';
20130
  if (file_exists($file)) {
2014
    // Load the local CSS stylesheet.
20150
    $contents = file_get_contents($file);
2016
2017
    // Change to the current stylesheet's directory.
20180
    $cwd = getcwd();
20190
    chdir(dirname($file));
2020
2021
    // Replaces @import commands with the actual stylesheet content.
2022
    // This happens recursively but omits external files.
20230
    $contents =
preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/',
'_drupal_load_stylesheet', $contents);
2024
    // Remove multiple charset declarations for standards compliance (and
fixing Safari problems).
20250
    $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '',
$contents);
2026
20270
    if ($_optimize) {
2028
      // Perform some safe CSS optimizations.
20290
      $contents = preg_replace('<
2030
        \s*([@{}:;,]|\)\s|\s\()\s* |  # Remove whitespace around
separators, but keep space around parentheses.
2031
        /\*([^*\\\\]|\*(?!/))+\*/ |   # Remove comments that are not CSS
hacks.
2032
        [\n\r]                        # Remove line breaks.
20330
        >x', '\1', $contents);
20340
    }
2035
2036
    // Change back directory.
20370
    chdir($cwd);
20380
  }
2039
20400
  return $contents;
20410
}
2042
2043
/**
2044
 * Loads stylesheets recursively and returns contents with corrected paths.
2045
 *
2046
 * This function is used for recursive loading of stylesheets and
2047
 * returns the stylesheet content with all url() paths corrected.
2048
 */
20492366
function _drupal_load_stylesheet($matches) {
20500
  $filename = $matches[1];
2051
  // Load the imported stylesheet and replace @import commands in there as
well.
20520
  $file = drupal_load_stylesheet($filename);
2053
  // Alter all url() paths, but not external.
20540
  return preg_replace('/url\(([\'"]?)(?![a-z]+:)([^\'")]+)[\'"]?\)?;/i',
'url(\1' . dirname($filename) . '/', $file);
20550
}
2056
2057
/**
2058
 * Delete all cached CSS files.
2059
 */
20602366
function drupal_clear_css_cache() {
20616
  file_scan_directory(file_create_path('css'), '/.*/', array('.', '..',
'CVS'), 'file_unmanaged_delete', TRUE);
20626
}
2063
2064
/**
2065
 * Add a JavaScript file, setting or inline code to the page.
2066
 *
2067
 * The behavior of this function depends on the parameters it is called
with.
2068
 * Generally, it handles the addition of JavaScript to the page, either as
2069
 * reference to an existing file or as inline code. The following actions
can be
2070
 * performed using this function:
2071
 *
2072
 * - Add a file ('core', 'module' and 'theme'):
2073
 *   Adds a reference to a JavaScript file to the page. JavaScript files
2074
 *   are placed in a certain order, from 'core' first, to 'module' and
finally
2075
 *   'theme' so that files, that are added later, can override previously
added
2076
 *   files with ease.
2077
 *
2078
 * - Add inline JavaScript code ('inline'):
2079
 *   Executes a piece of JavaScript code on the current page by placing the
code
2080
 *   directly in the page. This can, for example, be useful to tell the
user that
2081
 *   a new message arrived, by opening a pop up, alert box etc.
2082
 *
2083
 * - Add settings ('setting'):
2084
 *   Adds a setting to Drupal's global storage of JavaScript settings.
Per-page
2085
 *   settings are required by some modules to function properly. The
settings
2086
 *   will be accessible at Drupal.settings.
2087
 *
2088
 * Examples:
2089
 * @code
2090
 *   drupal_add_js('misc/collapse.js');
2091
 *   drupal_add_js('misc/collapse.js', 'module');
2092
 *   drupal_add_js('$(document).ready(function(){alert("Hello!");});',
2093
 *     array('type' => 'inline', 'scope' => 'footer')
2094
 *   );
2095
 * @endcode
2096
 *
2097
 * @param $data
2098
 *   (optional) If given, the value depends on the $options parameter:
2099
 *   - 'core', 'module' or 'theme': Path to the file relative to
base_path().
2100
 *   - 'inline': The JavaScript code that should be placed in the given
scope.
2101
 *   - 'setting': An array with configuration options as associative array.
The
2102
 *       array is directly placed in Drupal.settings. You might want to
wrap your
2103
 *       actual configuration settings in another variable to prevent the
pollution
2104
 *       of the Drupal.settings namespace.
2105
 * @param $options
2106
 *   (optional) A string defining the type of JavaScript that is being
added
2107
 *   in the $data parameter ('core', 'module', 'theme', 'setting',
'inline'),
2108
 *   or an array which can have any or all of the following keys (these are
2109
 *   not valid with type => 'setting'):
2110
 *   - type
2111
 *       The type of JavaScript that should be added to the page. Allowed
2112
 *       values are 'core', 'module', 'theme', 'inline' and 'setting'.
Defaults
2113
 *       to 'module'.
2114
 *   - scope
2115
 *       The location in which you want to place the script. Possible
2116
 *       values are 'header' and 'footer'. If your theme implements
different
2117
 *       locations, however, you can also use these. Defaults to 'header'.
2118
 *   - defer
2119
 *       If set to TRUE, the defer attribute is set on the <script> tag.
2120
 *       Defaults to FALSE. This parameter is not used with 'type' =>
'setting'.
2121
 *   - cache
2122
 *       If set to FALSE, the JavaScript file is loaded anew on every page
2123
 *       call, that means, it is not cached. Used only when type references
2124
 *       a JavaScript file. Defaults to TRUE.
2125
 *   - preprocess
2126
 *       Aggregate the JavaScript if the JavaScript optimization setting
has
2127
 *       been toggled in admin/settings/performance. Defaults to TRUE.
2128
 * @param $reset
2129
 *   (optional) Resets the currently loaded JavaScript.
2130
 * @return
2131
 *   The contructed array of JavaScript files.
2132
 * @see drupal_get_js()
2133
 */
21342366
function drupal_add_js($data = NULL, $options = NULL, $reset = FALSE) {
21351774
  static $javascript = array();
2136
2137
  // Construct the options, taking the defaults into consideration.
21381774
  if (isset($options)) {
21391767
    if (!is_array($options)) {
2140220
      $options = array('type' => $options);
2141220
    }
21421767
  }
2143
  else {
2144620
    $options = array();
2145
  }
2146
  $options += array(
21471774
    'type' => 'module',
2148
    // Default to a header scope only if we're adding some data.
21491774
    'scope' => isset($data) ? 'header' : NULL,
21501774
    'cache' => TRUE,
21511774
    'defer' => FALSE,
2152
    'preprocess' => TRUE
21531774
  );
2154
  // Preprocess can only be set if caching is enabled.
21551774
  $options['preprocess'] = $options['cache'] ? $options['preprocess'] :
FALSE;
21561774
  $type = $options['type'];
21571774
  $scope = $options['scope'];
21581774
  unset($options['type'], $options['scope']);
2159
2160
  // Request made to reset the JavaScript added so far.
21611774
  if ($reset) {
21621
    $javascript = array();
21631
  }
2164
21651774
  if (isset($data)) {
2166
    // Add jquery.js and drupal.js, as well as the basePath setting, the
2167
    // first time a Javascript file is added.
2168590
    if (empty($javascript)) {
2169
      $javascript = array(
2170
        'header' => array(
2171
          'core' => array(
2172590
            'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE,
'preprocess' => TRUE),
2173590
            'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE,
'preprocess' => TRUE),
2174590
          ),
2175590
          'module' => array(),
2176590
          'theme' => array(),
2177
          'setting' => array(
2178590
            array('basePath' => base_path()),
2179590
          ),
2180590
          'inline' => array(),
2181
        )
2182590
      );
2183590
    }
2184
2185590
    if (isset($scope) && !isset($javascript[$scope])) {
21861
      $javascript[$scope] = array('core' => array(), 'module' => array(),
'theme' => array(), 'setting' => array(), 'inline' => array());
21871
    }
2188
2189590
    if (isset($type) && isset($scope) &&
!isset($javascript[$scope][$type])) {
21900
      $javascript[$scope][$type] = array();
21910
    }
2192
2193
    switch ($type) {
2194590
      case 'setting':
2195212
        $javascript[$scope][$type][] = $data;
2196212
        break;
2197590
      case 'inline':
219816
        $javascript[$scope][$type][] = array('code' => $data, 'defer' =>
$options['defer']);
219916
        break;
2200590
      default:
2201590
        $javascript[$scope][$type][$data] = $options;
2202590
    }
2203590
  }
2204
22051774
  if (isset($scope)) {
22061774
    return isset($javascript[$scope]) ? $javascript[$scope] : array();
22070
  }
2208
  else {
220971
    return $javascript;
2210
  }
22110
}
2212
2213
/**
2214
 * Returns a themed presentation of all JavaScript code for the current
page.
2215
 *
2216
 * References to JavaScript files are placed in a certain order: first, all
2217
 * 'core' files, then all 'module' and finally all 'theme' JavaScript files
2218
 * are added to the page. Then, all settings are output, followed by
'inline'
2219
 * JavaScript code. If running update.php, all preprocessing is disabled.
2220
 *
2221
 * @param $scope
2222
 *   (optional) The scope for which the JavaScript rules should be
returned.
2223
 *   Defaults to 'header'.
2224
 * @param $javascript
2225
 *   (optional) An array with all JavaScript code. Defaults to the default
2226
 *   JavaScript array for the given scope.
2227
 * @return
2228
 *   All JavaScript code segments and includes for the scope as HTML tags.
2229
 * @see drupal_add_js()
2230
 */
22312366
function drupal_get_js($scope = 'header', $javascript = NULL) {
22321741
  if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') &&
function_exists('locale_update_js_files')) {
223370
    locale_update_js_files();
223470
  }
2235
22361741
  if (!isset($javascript)) {
22371741
    $javascript = drupal_add_js(NULL, array('scope' => $scope));
22381741
  }
2239
22401741
  if (empty($javascript)) {
22411740
    return '';
22420
  }
2243
2244557
  $output = '';
2245557
  $preprocessed = '';
2246557
  $no_preprocess = array('core' => '', 'module' => '', 'theme' => '');
2247557
  $files = array();
2248557
  $preprocess_js = (variable_get('preprocess_js', FALSE) &&
(!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
2249557
  $directory = file_directory_path();
2250557
  $is_writable = is_dir($directory) && is_writable($directory) &&
(variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) ==
FILE_DOWNLOADS_PUBLIC);
2251
2252
  // A dummy query-string is added to filenames, to gain control over
2253
  // browser-caching. The string changes on every update or full cache
2254
  // flush, forcing browsers to load a new copy of the files, as the
2255
  // URL changed. Files that should not be cached (see drupal_add_js())
2256
  // get REQUEST_TIME as query-string instead, to enforce reload on every
2257
  // page request.
2258557
  $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0,
1);
2259
2260
  // For inline Javascript to validate as XHTML, all Javascript containing
2261
  // XHTML needs to be wrapped in CDATA. To make that backwards compatible
2262
  // with HTML 4, we need to comment out the CDATA-tag.
2263557
  $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
2264557
  $embed_suffix = "\n//--><!]]>\n";
2265
2266557
  foreach ($javascript as $type => $data) {
2267557
    if (!$data) continue;
2268
2269
    switch ($type) {
2270557
      case 'setting':
2271557
        $output .= '<script type="text/javascript">' . $embed_prefix .
'jQuery.extend(Drupal.settings, ' .
drupal_to_js(call_user_func_array('array_merge_recursive', $data)) . ");" .
$embed_suffix . "</script>\n";
2272557
        break;
2273557
      case 'inline':
227413
        foreach ($data as $info) {
227513
          $output .= '<script type="text/javascript"' . ($info['defer'] ? '
defer="defer"' : '') . '>' . $embed_prefix . $info['code'] . $embed_suffix
. "</script>\n";
227613
        }
227713
        break;
2278557
      default:
2279
        // If JS preprocessing is off, we still need to output the scripts.
2280
        // Additionally, go through any remaining scripts if JS
preprocessing is on and output the non-cached ones.
2281557
        foreach ($data as $path => $info) {
2282557
          if (!$info['preprocess'] || !$is_writable || !$preprocess_js) {
2283557
            $no_preprocess[$type] .= '<script type="text/javascript"' .
($info['defer'] ? ' defer="defer"' : '') . ' src="' . base_path() . $path .
($info['cache'] ? $query_string : '?' . REQUEST_TIME) . "\"></script>\n";
2284557
          }
2285
          else {
22860
            $files[$path] = $info;
2287
          }
2288557
        }
2289557
    }
2290557
  }
2291
2292
  // Aggregate any remaining JS files that haven't already been output.
2293557
  if ($is_writable && $preprocess_js && count($files) > 0) {
22940
    $filename = md5(serialize($files) . $query_string) . '.js';
22950
    $preprocess_file = drupal_build_js_cache($files, $filename);
22960
    $preprocessed .= '<script type="text/javascript" src="' . base_path() .
$preprocess_file . '"></script>' . "\n";
22970
  }
2298
2299
  // Keep the order of JS files consistent as some are preprocessed and
others are not.
2300
  // Make sure any inline or JS setting variables appear last after
libraries have loaded.
2301557
  $output = $preprocessed . implode('', $no_preprocess) . $output;
2302
2303557
  return $output;
23040
}
2305
2306
/**
2307
 * Assist in adding the tableDrag JavaScript behavior to a themed table.
2308
 *
2309
 * Draggable tables should be used wherever an outline or list of sortable
items
2310
 * needs to be arranged by an end-user. Draggable tables are very flexible
and
2311
 * can manipulate the value of form elements placed within individual
columns.
2312
 *
2313
 * To set up a table to use drag and drop in place of weight select-lists
or
2314
 * in place of a form that contains parent relationships, the form must be
2315
 * themed into a table. The table must have an id attribute set. If using
2316
 * theme_table(), the id may be set as such:
2317
 * @code
2318
 * $output = theme('table', $header, $rows, array('id' =>
'my-module-table'));
2319
 * return $output;
2320
 * @endcode
2321
 *
2322
 * In the theme function for the form, a special class must be added to
each
2323
 * form element within the same column, "grouping" them together.
2324
 *
2325
 * In a situation where a single weight column is being sorted in the
table, the
2326
 * classes could be added like this (in the theme function):
2327
 * @code
2328
 * $form['my_elements'][$delta]['weight']['#attributes']['class'] =
"my-elements-weight";
2329
 * @endcode
2330
 *
2331
 * Each row of the table must also have a class of "draggable" in order to
enable the
2332
 * drag handles:
2333
 * @code
2334
 * $row = array(...);
2335
 * $rows[] = array(
2336
 *   'data' => $row,
2337
 *   'class' => 'draggable',
2338
 * );
2339
 * @endcode
2340
 *
2341
 * When tree relationships are present, the two additional classes
2342
 * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the
behavior:
2343
 * - Rows with the 'tabledrag-leaf' class cannot have child rows.
2344
 * - Rows with the 'tabledrag-root' class cannot be nested under a parent
row.
2345
 *
2346
 * Calling drupal_add_tabledrag() would then be written as such:
2347
 * @code
2348
 * drupal_add_tabledrag('my-module-table', 'order', 'sibling',
'my-elements-weight');
2349
 * @endcode
2350
 *
2351
 * In a more complex case where there are several groups in one column
(such as
2352
 * the block regions on the admin/build/block page), a separate subgroup
class
2353
 * must also be added to differentiate the groups.
2354
 * @code
2355
 * $form['my_elements'][$region][$delta]['weight']['#attributes']['class']
= "my-elements-weight my-elements-weight-" . $region;
2356
 * @endcode
2357
 *
2358
 * $group is still 'my-element-weight', and the additional $subgroup
variable
2359
 * will be passed in as 'my-elements-weight-' . $region. This also means
that
2360
 * you'll need to call drupal_add_tabledrag() once for every region added.
2361
 *
2362
 * @code
2363
 * foreach ($regions as $region) {
2364
 *   drupal_add_tabledrag('my-module-table', 'order', 'sibling',
'my-elements-weight', 'my-elements-weight-' . $region);
2365
 * }
2366
 * @endcode
2367
 *
2368
 * In a situation where tree relationships are present, adding multiple
2369
 * subgroups is not necessary, because the table will contain indentations
that
2370
 * provide enough information about the sibling and parent relationships.
2371
 * See theme_menu_overview_form() for an example creating a table
containing
2372
 * parent relationships.
2373
 *
2374
 * Please note that this function should be called from the theme layer,
such as
2375
 * in a .tpl.php file, theme_ function, or in a template_preprocess
function,
2376
 * not in a form declartion. Though the same JavaScript could be added to
the
2377
 * page using drupal_add_js() directly, this function helps keep template
files
2378
 * clean and readable. It also prevents tabledrag.js from being added twice
2379
 * accidentally.
2380
 *
2381
 * @param $table_id
2382
 *   String containing the target table's id attribute. If the table does
not
2383
 *   have an id, one will need to be set, such as <table
id="my-module-table">.
2384
 * @param $action
2385
 *   String describing the action to be done on the form item. Either
'match'
2386
 *   'depth', or 'order'. Match is typically used for parent relationships.
2387
 *   Order is typically used to set weights on other form elements with the
same
2388
 *   group. Depth updates the target element with the current indentation.
2389
 * @param $relationship
2390
 *   String describing where the $action variable should be performed.
Either
2391
 *   'parent', 'sibling', 'group', or 'self'. Parent will only look for
fields
2392
 *   up the tree. Sibling will look for fields in the same group in rows
above
2393
 *   and below it. Self affects the dragged row itself. Group affects the
2394
 *   dragged row, plus any children below it (the entire dragged group).
2395
 * @param $group
2396
 *   A class name applied on all related form elements for this action.
2397
 * @param $subgroup
2398
 *   (optional) If the group has several subgroups within it, this string
should
2399
 *   contain the class name identifying fields in the same subgroup.
2400
 * @param $source
2401
 *   (optional) If the $action is 'match', this string should contain the
class
2402
 *   name identifying what field will be used as the source value when
matching
2403
 *   the value in $subgroup.
2404
 * @param $hidden
2405
 *   (optional) The column containing the field elements may be entirely
hidden
2406
 *   from view dynamically when the JavaScript is loaded. Set to FALSE if
the
2407
 *   column should not be hidden.
2408
 * @param $limit
2409
 *   (optional) Limit the maximum amount of parenting in this table.
2410
 * @see block-admin-display-form.tpl.php
2411
 * @see theme_menu_overview_form()
2412
 */
24132366
function drupal_add_tabledrag($table_id, $action, $relationship, $group,
$subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
241490
  static $js_added = FALSE;
241590
  if (!$js_added) {
241690
    drupal_add_js('misc/tabledrag.js', 'core');
241790
    $js_added = TRUE;
241890
  }
2419
2420
  // If a subgroup or source isn't set, assume it is the same as the group.
242190
  $target = isset($subgroup) ? $subgroup : $group;
242290
  $source = isset($source) ? $source : $target;
242390
  $settings['tableDrag'][$table_id][$group][] = array(
242490
    'target' => $target,
242590
    'source' => $source,
242690
    'relationship' => $relationship,
242790
    'action' => $action,
242890
    'hidden' => $hidden,
242990
    'limit' => $limit,
2430
  );
243190
  drupal_add_js($settings, 'setting');
243290
}
2433
2434
/**
2435
 * Aggregate JS files, putting them in the files directory.
2436
 *
2437
 * @param $files
2438
 *   An array of JS files to aggregate and compress into one file.
2439
 * @param $filename
2440
 *   The name of the aggregate JS file.
2441
 * @return
2442
 *   The name of the JS file.
2443
 */
24442366
function drupal_build_js_cache($files, $filename) {
24450
  $contents = '';
2446
2447
  // Create the js/ within the files folder.
24480
  $jspath = file_create_path('js');
24490
  file_check_directory($jspath, FILE_CREATE_DIRECTORY);
2450
24510
  if (!file_exists($jspath . '/' . $filename)) {
2452
    // Build aggregate JS file.
24530
    foreach ($files as $path => $info) {
24540
      if ($info['preprocess']) {
2455
        // Append a ';' after each JS file to prevent them from running
together.
24560
        $contents .= file_get_contents($path) . ';';
24570
      }
24580
    }
2459
2460
    // Create the JS file.
24610
    file_unmanaged_save_data($contents, $jspath . '/' . $filename,
FILE_EXISTS_REPLACE);
24620
  }
2463
24640
  return $jspath . '/' . $filename;
24650
}
2466
2467
/**
2468
 * Delete all cached JS files.
2469
 */
24702366
function drupal_clear_js_cache() {
24713
  file_scan_directory(file_create_path('js'), '/.*/', array('.', '..',
'CVS'), 'file_unmanaged_delete', TRUE);
24723
  variable_set('javascript_parsed', array());
24733
}
2474
2475
/**
2476
 * Converts a PHP variable into its Javascript equivalent.
2477
 *
2478
 * We use HTML-safe strings, i.e. with <, > and & escaped.
2479
 */
24802366
function drupal_to_js($var) {
2481
  // json_encode() does not escape <, > and &, so we do it with
str_replace()
2482559
  return str_replace(array("<", ">", "&"), array('\x3c', '\x3e', '\x26'),
json_encode($var));
24830
}
2484
2485
/**
2486
 * Return data in JSON format.
2487
 *
2488
 * This function should be used for JavaScript callback functions returning
2489
 * data in JSON format. It sets the header for JavaScript output.
2490
 *
2491
 * @param $var
2492
 *   (optional) If set, the variable will be converted to JSON and output.
2493
 */
24942366
function drupal_json($var = NULL) {
2495
  // We are returning JavaScript, so tell the browser.
24962
  drupal_set_header('Content-Type: text/javascript; charset=utf-8');
2497
24982
  if (isset($var)) {
24992
    echo drupal_to_js($var);
25002
  }
25012
}
2502
2503
/**
2504
 * Wrapper around urlencode() which avoids Apache quirks.
2505
 *
2506
 * Should be used when placing arbitrary data in an URL. Note that Drupal
paths
2507
 * are urlencoded() when passed through url() and do not require
urlencoding()
2508
 * of individual components.
2509
 *
2510
 * Notes:
2511
 * - For esthetic reasons, we do not escape slashes. This also avoids a
'feature'
2512
 *   in Apache where it 404s on any path containing '%2F'.
2513
 * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when
clean
2514
 *   URLs are used, which are interpreted as delimiters by PHP. These
2515
 *   characters are double escaped so PHP will still see the encoded
version.
2516
 * - With clean URLs, Apache changes '//' to '/', so every second slash is
2517
 *   double escaped.
2518
 *
2519
 * @param $text
2520
 *   String to encode
2521
 */
25222366
function drupal_urlencode($text) {
25232413
  if (variable_get('clean_url', '0')) {
25242413
    return str_replace(array('%2F', '%26', '%23', '//'),
25252413
                       array('/', '%2526', '%2523', '/%252F'),
25262413
                       rawurlencode($text));
25270
  }
2528
  else {
25290
    return str_replace('%2F', '/', rawurlencode($text));
2530
  }
25310
}
2532
2533
/**
2534
 * Returns a string of highly randomized bytes (over the full 8-bit range).
2535
 *
2536
 * This function is better than simply calling mt_rand() or any other
built-in
2537
 * PHP function because it can return a long string of bytes (compared to <
4
2538
 * bytes normally from mt_rand()) and uses the best available pseudo-random
source.
2539
 *
2540
 * @param $count
2541
 *   The number of characters (bytes) to return in the string.
2542
 */
25432366
function drupal_random_bytes($count)  {
2544157
  static $random_state;
2545
  // We initialize with the somewhat random PHP process ID on the first
call.
2546157
  if (empty($random_state)) {
2547157
    $random_state = getmypid();
2548157
  }
2549157
  $output = '';
2550
  // /dev/urandom is available on many *nix systems and is considered the
best
2551
  // commonly available pseudo-random source.
2552157
  if ($fh = @fopen('/dev/urandom', 'rb')) {
2553157
    $output = fread($fh, $count);
2554157
    fclose($fh);
2555157
  }
2556
  // If /dev/urandom is not available or returns no bytes, this loop will
2557
  // generate a good set of pseudo-random bytes on any system.
2558
  // Note that it may be important that our $random_state is passed
2559
  // through md5() prior to being rolled into $output, that the two md5()
2560
  // invocations are different, and that the extra input into the first one
-
2561
  // the microtime() - is prepended rather than appended.  This is to avoid
2562
  // directly leaking $random_state via the $output stream, which could
2563
  // allow for trivial prediction of further "random" numbers.
2564157
  while (strlen($output) < $count) {
25650
    $random_state = md5(microtime() . mt_rand() . $random_state);
25660
    $output .= md5(mt_rand() . $random_state, TRUE);
25670
  }
2568157
  return substr($output, 0, $count);
25690
}
2570
2571
/**
2572
 * Ensure the private key variable used to generate tokens is set.
2573
 *
2574
 * @return
2575
 *   The private key.
2576
 */
25772366
function drupal_get_private_key() {
2578848
  if (!($key = variable_get('drupal_private_key', 0))) {
257977
    $key = md5(drupal_random_bytes(64));
258077
    variable_set('drupal_private_key', $key);
258177
  }
2582848
  return $key;
25830
}
2584
2585
/**
2586
 * Generate a token based on $value, the current user session and private
key.
2587
 *
2588
 * @param $value
2589
 *   An additional value to base the token on.
2590
 */
25912366
function drupal_get_token($value = '') {
2592848
  $private_key = drupal_get_private_key();
2593848
  return md5(session_id() . $value . $private_key);
25940
}
2595
2596
/**
2597
 * Validate a token based on $value, the current user session and private
key.
2598
 *
2599
 * @param $token
2600
 *   The token to be validated.
2601
 * @param $value
2602
 *   An additional value to base the token on.
2603
 * @param $skip_anonymous
2604
 *   Set to true to skip token validation for anonymous users.
2605
 * @return
2606
 *   True for a valid token, false for an invalid token. When
$skip_anonymous
2607
 *   is true, the return value will always be true for anonymous users.
2608
 */
26092366
function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
2610338
  global $user;
2611338
  return (($skip_anonymous && $user->uid == 0) || ($token ==
md5(session_id() . $value . variable_get('drupal_private_key', ''))));
26120
}
2613
2614
/**
2615
 * Performs one or more XML-RPC request(s).
2616
 *
2617
 * @param $url
2618
 *   An absolute URL of the XML-RPC endpoint.
2619
 *     Example:
2620
 *     http://www.example.com/xmlrpc.php
2621
 * @param ...
2622
 *   For one request:
2623
 *     The method name followed by a variable number of arguments to the
method.
2624
 *   For multiple requests (system.multicall):
2625
 *     An array of call arrays. Each call array follows the pattern of the
single
2626
 *     request: method name followed by the arguments to the method.
2627
 * @return
2628
 *   For one request:
2629
 *     Either the return value of the method on success, or FALSE.
2630
 *     If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg().
2631
 *   For multiple requests:
2632
 *     An array of results. Each result will either be the result
2633
 *     returned by the method called, or an xmlrpc_error object if the call
2634
 *     failed. See xmlrpc_error().
2635
 */
26362366
function xmlrpc($url) {
26373
  require_once DRUPAL_ROOT . '/includes/xmlrpc.inc';
26383
  $args = func_get_args();
26393
  return call_user_func_array('_xmlrpc', $args);
26400
}
2641
26422366
function _drupal_bootstrap_full() {
26432366
  static $called;
2644
26452366
  if ($called) {
26460
    return;
26470
  }
26482366
  $called = 1;
26492366
  require_once DRUPAL_ROOT . '/includes/theme.inc';
26502366
  require_once DRUPAL_ROOT . '/includes/pager.inc';
26512366
  require_once DRUPAL_ROOT . '/includes/menu.inc';
26522366
  require_once DRUPAL_ROOT . '/includes/tablesort.inc';
26532366
  require_once DRUPAL_ROOT . '/includes/file.inc';
26542366
  require_once DRUPAL_ROOT . '/includes/unicode.inc';
26552366
  require_once DRUPAL_ROOT . '/includes/image.inc';
26562366
  require_once DRUPAL_ROOT . '/includes/form.inc';
26572366
  require_once DRUPAL_ROOT . '/includes/mail.inc';
26582366
  require_once DRUPAL_ROOT . '/includes/actions.inc';
2659
  // Set the Drupal custom error handler.
26602366
  set_error_handler('_drupal_error_handler');
26612366
  set_exception_handler('_drupal_exception_handler');
2662
2663
  // Emit the correct charset HTTP header.
26642366
  drupal_set_header('Content-Type: text/html; charset=utf-8');
2665
  // Detect string handling method
26662366
  unicode_check();
2667
  // Undo magic quotes
26682366
  fix_gpc_magic();
2669
  // Load all enabled modules
26702366
  module_load_all();
2671
2672
  // Let all modules take action before menu system handles the request
2673
  // We do not want this while running update.php.
26742366
  if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
26752366
    module_invoke_all('init');
26762366
  }
26772366
}
2678
2679
/**
2680
 * Store the current page in the cache.
2681
 *
2682
 * We try to store a gzipped version of the cache. This requires the
2683
 * PHP zlib extension (http://php.net/manual/en/ref.zlib.php).
2684
 * Presence of the extension is checked by testing for the function
2685
 * gzencode. There are two compression algorithms: gzip and deflate.
2686
 * The majority of all modern browsers support gzip or both of them.
2687
 * We thus only deal with the gzip variant and unzip the cache in case
2688
 * the browser does not accept gzip encoding.
2689
 *
2690
 * @see drupal_page_header
2691
 */
26922366
function page_set_cache() {
26931
  global $user, $base_root;
2694
26951
  if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' ||
$_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_get_messages(NULL,
FALSE)) == 0) {
2696
    // This will fail in some cases, see page_get_cache() for the
explanation.
26971
    if ($data = ob_get_contents()) {
26981
      $cache = TRUE;
26991
      if (variable_get('page_compression', TRUE) &&
function_exists('gzencode')) {
2700
        // We do not store the data in case the zlib mode is deflate.
2701
        // This should be rarely happening.
27021
        if (zlib_get_coding_type() == 'deflate') {
27030
          $cache = FALSE;
27040
        }
27051
        elseif (zlib_get_coding_type() == FALSE) {
27061
          $data = gzencode($data, 9, FORCE_GZIP);
27071
        }
2708
        // The remaining case is 'gzip' which means the data is
2709
        // already compressed and nothing left to do but to store it.
27101
      }
27111
      ob_end_flush();
27121
      if ($cache && $data) {
27131
        cache_set($base_root . request_uri(), $data, 'cache_page',
CACHE_TEMPORARY, drupal_get_headers());
27141
      }
27151
    }
27161
  }
27171
}
2718
2719
/**
2720
 * Executes a cron run when called
2721
 * @return
2722
 * Returns TRUE if ran successfully
2723
 */
27242366
function drupal_cron_run() {
2725
  // Allow execution to continue even if the request gets canceled.
27262
  @ignore_user_abort(TRUE);
2727
2728
  // Increase the maximum execution time.
27292
  @set_time_limit(240);
2730
2731
  // Fetch the cron semaphore
27322
  $semaphore = variable_get('cron_semaphore', FALSE);
2733
27342
  if ($semaphore) {
27350
    if (REQUEST_TIME - $semaphore > 3600) {
2736
      // Either cron has been running for more than an hour or the
semaphore
2737
      // was not reset due to a database error.
27380
      watchdog('cron', 'Cron has been running for more than an hour and is
most likely stuck.', array(), WATCHDOG_ERROR);
2739
2740
      // Release cron semaphore
27410
      variable_del('cron_semaphore');
27420
    }
2743
    else {
2744
      // Cron is still running normally.
27450
      watchdog('cron', 'Attempting to re-run cron while it is already
running.', array(), WATCHDOG_WARNING);
2746
    }
27470
  }
2748
  else {
2749
    // Register shutdown callback
27502
    register_shutdown_function('drupal_cron_cleanup');
2751
2752
    // Lock cron semaphore
27532
    variable_set('cron_semaphore', REQUEST_TIME);
2754
2755
    // Iterate through the modules calling their cron handlers (if any):
27562
    module_invoke_all('cron');
2757
2758
    // Record cron time
27592
    variable_set('cron_last', REQUEST_TIME);
27602
    watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE);
2761
2762
    // Release cron semaphore
27632
    variable_del('cron_semaphore');
2764
2765
    // Return TRUE so other functions can check if it did run successfully
27662
    return TRUE;
2767
  }
27680
}
2769
2770
/**
2771
 * Shutdown function for cron cleanup.
2772
 */
27732366
function drupal_cron_cleanup() {
2774
  // See if the semaphore is still locked.
27750
  if (variable_get('cron_semaphore', FALSE)) {
27760
    watchdog('cron', 'Cron run exceeded the time limit and was aborted.',
array(), WATCHDOG_WARNING);
2777
2778
    // Release cron semaphore
27790
    variable_del('cron_semaphore');
27800
  }
27810
}
2782
2783
/**
2784
 * Return an array of system file objects.
2785
 *
2786
 * Returns an array of file objects of the given type from the site-wide
2787
 * directory (i.e. modules/), the all-sites directory (i.e.
2788
 * sites/all/modules/), the profiles directory, and site-specific directory
2789
 * (i.e. sites/somesite/modules/). The returned array will be keyed using
the
2790
 * key specified (name, basename, filename). Using name or basename will
cause
2791
 * site-specific files to be prioritized over similar files in the default
2792
 * directories. That is, if a file with the same name appears in both the
2793
 * site-wide directory and site-specific directory, only the site-specific
2794
 * version will be included.
2795
 *
2796
 * @param $mask
2797
 *   The preg_match() regular expression of the files to find.
2798
 * @param $directory
2799
 *   The subdirectory name in which the files are found. For example,
2800
 *   'modules' will search in both modules/ and
2801
 *   sites/somesite/modules/.
2802
 * @param $key
2803
 *   The key to be passed to file_scan_directory().
2804
 * @param $min_depth
2805
 *   Minimum depth of directories to return files from.
2806
 *
2807
 * @return
2808
 *   An array of file objects of the specified type.
2809
 */
28102366
function drupal_system_listing($mask, $directory, $key = 'name', $min_depth
= 1) {
2811193
  global $profile;
2812193
  $config = conf_path();
2813
2814
  // When this function is called during Drupal's initial installation
process,
2815
  // the name of the profile that's about to be installed is stored in the
global
2816
  // $profile variable. At all other times, the standard Drupal systems
variable
2817
  // table contains the name of the current profile, and we can call
variable_get()
2818
  // to determine what one is active.
2819193
  if (!isset($profile)) {
282063
    $profile = variable_get('install_profile', 'default');
282163
  }
2822193
  $searchdir = array($directory);
2823193
  $files = array();
2824
2825
  // Always search sites/all/* as well as the global directories
2826193
  $searchdir[] = 'sites/all/' . $directory;
2827
2828
  // The 'profiles' directory contains pristine collections of modules and
2829
  // themes as organized by a distribution.  It is pristine in the same way
2830
  // that /modules is pristine for core; users should avoid changing
anything
2831
  // there in favor of sites/all or sites/<domain> directories.
2832193
  if (file_exists("profiles/$profile/$directory")) {
28330
    $searchdir[] = "profiles/$profile/$directory";
28340
  }
2835
2836193
  if (file_exists("$config/$directory")) {
28370
    $searchdir[] = "$config/$directory";
28380
  }
2839
2840
  // Get current list of items
2841193
  foreach ($searchdir as $dir) {
2842193
    $files = array_merge($files, file_scan_directory($dir, $mask,
array('.', '..', 'CVS'), 0, TRUE, $key, $min_depth));
2843193
  }
2844
2845193
  return $files;
28460
}
2847
2848
/**
2849
 * Hands off structured Drupal arrays to type-specific *_alter
implementations.
2850
 *
2851
 * This dispatch function hands off structured Drupal arrays to
type-specific
2852
 * *_alter implementations. It ensures a consistent interface for all
altering
2853
 * operations.
2854
 *
2855
 * @param $type
2856
 *   The data type of the structured array. 'form', 'links',
2857
 *   'node_content', and so on are several examples.
2858
 * @param $data
2859
 *   The structured array to be altered.
2860
 * @param ...
2861
 *   Any additional params will be passed on to the called
2862
 *   hook_$type_alter functions.
2863
 */
28642366
function drupal_alter($type, &$data) {
2865
  // PHP's func_get_args() always returns copies of params, not references,
so
2866
  // drupal_alter() can only manipulate data that comes in via the required
first
2867
  // param. For the edge case functions that must pass in an arbitrary
number of
2868
  // alterable parameters (hook_form_alter() being the best example), an
array of
2869
  // those params can be placed in the __drupal_alter_by_ref key of the
$data
2870
  // array. This is somewhat ugly, but is an unavoidable consequence of a
flexible
2871
  // drupal_alter() function, and the limitations of func_get_args().
2872
  // @todo: Remove this in Drupal 7.
28732354
  if (is_array($data) && isset($data['__drupal_alter_by_ref'])) {
28741575
    $by_ref_parameters = $data['__drupal_alter_by_ref'];
28751575
    unset($data['__drupal_alter_by_ref']);
28761575
  }
2877
2878
  // Hang onto a reference to the data array so that it isn't blown away
later.
2879
  // Also, merge in any parameters that need to be passed by reference.
28802354
  $args = array(&$data);
28812354
  if (isset($by_ref_parameters)) {
28821575
    $args = array_merge($args, $by_ref_parameters);
28831575
  }
2884
2885
  // Now, use func_get_args() to pull in any additional parameters passed
into
2886
  // the drupal_alter() call.
28872354
  $additional_args = func_get_args();
28882354
  array_shift($additional_args);
28892354
  array_shift($additional_args);
28902354
  $args = array_merge($args, $additional_args);
2891
28922354
  foreach (module_implements($type . '_alter') as $module) {
28931595
    $function = $module . '_' . $type . '_alter';
28941595
    call_user_func_array($function, $args);
28951595
  }
28962354
}
2897
2898
/**
2899
 * Renders HTML given a structured array tree.
2900
 *
2901
 * Recursively iterates over each of the array elements, generating HTML
code.
2902
 * This function is usually called from within a another function, like
2903
 * drupal_get_form() or node_view().
2904
 *
2905
 * @param $elements
2906
 *   The structured array describing the data to be rendered.
2907
 * @return
2908
 *   The rendered HTML.
2909
 */
29102366
function drupal_render(&$elements) {
29111560
  if (!isset($elements) || (isset($elements['#access']) &&
!$elements['#access'])) {
2912108
    return NULL;
29130
  }
2914
2915
  // If the default values for this element haven't been loaded yet,
populate
2916
  // them.
29171560
  if (!isset($elements['#defaults_loaded']) ||
!$elements['#defaults_loaded']) {
2918468
    if ((!empty($elements['#type'])) && ($info =
_element_info($elements['#type']))) {
2919191
      $elements += $info;
2920191
    }
2921468
  }
2922
2923
  // Make any final changes to the element before it is rendered. This
means
2924
  // that the $element or the children can be altered or corrected before
the
2925
  // element is rendered into the final text.
29261560
  if (isset($elements['#pre_render'])) {
29270
    foreach ($elements['#pre_render'] as $function) {
29280
      if (drupal_function_exists($function)) {
29290
        $elements = $function($elements);
29300
      }
29310
    }
29320
  }
2933
29341560
  $content = '';
2935
  // Either the elements did not go through form_builder or one of the
children
2936
  // has a #weight.
29371560
  if (!isset($elements['#sorted'])) {
2938929
    uasort($elements, "element_sort");
2939929
  }
29401560
  $elements += array('#title' => NULL, '#description' => NULL);
29411560
  if (!isset($elements['#children'])) {
29421560
    $children = element_children($elements);
2943
    // Render all the children that use a theme function.
29441560
    if (isset($elements['#theme']) && empty($elements['#theme_used'])) {
2945406
      $elements['#theme_used'] = TRUE;
2946
2947406
      $previous = array();
2948406
      foreach (array('#type', '#prefix', '#suffix') as $key) {
2949406
        $previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL;
2950406
      }
2951
      // If we rendered a single element, then we will skip the renderer.
2952406
      if (empty($children)) {
295330
        $elements['#printed'] = TRUE;
295430
      }
2955
      else {
2956399
        $elements['#markup'] = '';
2957
      }
2958
2959406
      unset($elements['#prefix'], $elements['#suffix']);
2960406
      $content = theme($elements['#theme'], $elements);
2961
2962406
      foreach (array('#type', '#prefix', '#suffix') as $key) {
2963406
        $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL;
2964406
      }
2965406
    }
2966
    // Render each of the children using drupal_render and concatenate
them.
29671560
    if (!isset($content) || $content === '') {
29681560
      foreach ($children as $key) {
29691560
        $content .= drupal_render($elements[$key]);
29701560
      }
29711560
    }
29721560
  }
29731560
  if (isset($content) && $content !== '') {
29741560
    $elements['#children'] = $content;
29751560
  }
2976
2977
  // Until now, we rendered the children, here we render the element itself
29781560
  if (!isset($elements['#printed'])) {
29791560
    $content = theme(!empty($elements['#type']) ? $elements['#type'] :
'markup', $elements);
29801560
    $elements['#printed'] = TRUE;
29811560
  }
2982
29831560
  if (isset($content) && $content !== '') {
2984
    // Filter the outputted content and make any last changes before the
2985
    // content is sent to the browser. The changes are made on $content
2986
    // which allows the output'ed text to be filtered.
29871560
    if (isset($elements['#post_render'])) {
29880
      foreach ($elements['#post_render'] as $function) {
29890
        if (drupal_function_exists($function)) {
29900
          $content = $function($content, $elements);
29910
        }
29920
      }
29930
    }
29941560
    $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
29951560
    $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
29961560
    return $prefix . $content . $suffix;
29970
  }
2998713
}
2999
3000
/**
3001
 * Function used by uasort to sort structured arrays by weight.
3002
 */
30032366
function element_sort($a, $b) {
3004916
  $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
3005916
  $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
3006916
  if ($a_weight == $b_weight) {
3007913
    return 0;
30080
  }
3009761
  return ($a_weight < $b_weight) ? -1 : 1;
30100
}
3011
3012
/**
3013
 * Check if the key is a property.
3014
 */
30152366
function element_property($key) {
30160
  return $key[0] == '#';
30170
}
3018
3019
/**
3020
 * Get properties of a structured array element. Properties begin with '#'.
3021
 */
30222366
function element_properties($element) {
30230
  return array_filter(array_keys((array) $element), 'element_property');
30240
}
3025
3026
/**
3027
 * Check if the key is a child.
3028
 */
30292366
function element_child($key) {
30302020
  return !isset($key[0]) || $key[0] != '#';
30310
}
3032
3033
/**
3034
 * Get keys of a structured array tree element that are not properties
(i.e., do not begin with '#').
3035
 */
30362366
function element_children($element) {
30372020
  return array_filter(array_keys((array) $element), 'element_child');
30380
}
3039
3040
/**
3041
 * Provide theme registration for themes across .inc files.
3042
 */
30432366
function drupal_common_theme() {
3044
  return array(
3045
    // theme.inc
3046
    'placeholder' => array(
3047178
      'arguments' => array('text' => NULL)
3048178
    ),
3049
    'page' => array(
3050178
      'arguments' => array('content' => NULL, 'show_blocks' => TRUE,
'show_messages' => TRUE),
3051178
      'template' => 'page',
3052178
    ),
3053
    'maintenance_page' => array(
3054178
      'arguments' => array('content' => NULL, 'show_blocks' => TRUE,
'show_messages' => TRUE),
3055178
      'template' => 'maintenance-page',
3056178
    ),
3057
    'update_page' => array(
3058178
      'arguments' => array('content' => NULL, 'show_messages' => TRUE),
3059178
    ),
3060
    'install_page' => array(
3061178
      'arguments' => array('content' => NULL),
3062178
    ),
3063
    'task_list' => array(
3064178
      'arguments' => array('items' => NULL, 'active' => NULL),
3065178
    ),
3066
    'status_messages' => array(
3067178
      'arguments' => array('display' => NULL),
3068178
    ),
3069
    'links' => array(
3070178
      'arguments' => array('links' => NULL, 'attributes' => array('class'
=> 'links')),
3071178
    ),
3072
    'image' => array(
3073178
      'arguments' => array('path' => NULL, 'alt' => '', 'title' => '',
'attributes' => NULL, 'getsize' => TRUE),
3074178
    ),
3075
    'breadcrumb' => array(
3076178
      'arguments' => array('breadcrumb' => NULL),
3077178
    ),
3078
    'help' => array(
3079178
      'arguments' => array(),
3080178
    ),
3081
    'submenu' => array(
3082178
      'arguments' => array('links' => NULL),
3083178
    ),
3084
    'table' => array(
3085178
      'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes'
=> array(), 'caption' => NULL),
3086178
    ),
3087
    'table_select_header_cell' => array(
3088178
      'arguments' => array(),
3089178
    ),
3090
    'tablesort_indicator' => array(
3091178
      'arguments' => array('style' => NULL),
3092178
    ),
3093
    'box' => array(
3094178
      'arguments' => array('title' => NULL, 'content' => NULL, 'region' =>
'main'),
3095178
      'template' => 'box',
3096178
    ),
3097
    'block' => array(
3098178
      'arguments' => array('block' => NULL),
3099178
      'template' => 'block',
3100178
    ),
3101
    'mark' => array(
3102178
      'arguments' => array('type' => MARK_NEW),
3103178
    ),
3104
    'item_list' => array(
3105178
      'arguments' => array('items' => array(), 'title' => NULL, 'type' =>
'ul', 'attributes' => NULL),
3106178
    ),
3107
    'more_help_link' => array(
3108178
      'arguments' => array('url' => NULL),
3109178
    ),
3110
    'xml_icon' => array(
3111178
      'arguments' => array('url' => NULL),
3112178
    ),
3113
    'feed_icon' => array(
3114178
      'arguments' => array('url' => NULL, 'title' => NULL),
3115178
    ),
3116
    'more_link' => array(
3117178
      'arguments' => array('url' => NULL, 'title' => NULL)
3118178
    ),
3119
    'closure' => array(
3120178
      'arguments' => array('main' => 0),
3121178
    ),
3122
    'blocks' => array(
3123178
      'arguments' => array('region' => NULL),
3124178
    ),
3125
    'username' => array(
3126178
      'arguments' => array('object' => NULL),
3127178
    ),
3128
    'progress_bar' => array(
3129178
      'arguments' => array('percent' => NULL, 'message' => NULL),
3130178
    ),
3131
    'indentation' => array(
3132178
      'arguments' => array('size' => 1),
3133178
    ),
3134
    // from pager.inc
3135
    'pager' => array(
3136178
      'arguments' => array('tags' => array(), 'limit' => 10, 'element' =>
0, 'parameters' => array()),
3137178
    ),
3138
    'pager_first' => array(
3139178
      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0,
'parameters' => array()),
3140178
    ),
3141
    'pager_previous' => array(
3142178
      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0,
'interval' => 1, 'parameters' => array()),
3143178
    ),
3144
    'pager_next' => array(
3145178
      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0,
'interval' => 1, 'parameters' => array()),
3146178
    ),
3147
    'pager_last' => array(
3148178
      'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0,
'parameters' => array()),
3149178
    ),
3150
    'pager_link' => array(
3151178
      'arguments' => array('text' => NULL, 'page_new' => NULL, 'element' =>
NULL, 'parameters' => array(), 'attributes' => array()),
3152178
    ),
3153
    // from locale.inc
3154
    'locale_admin_manage_screen' => array(
3155178
      'arguments' => array('form' => NULL),
3156178
    ),
3157
    // from menu.inc
3158
    'menu_item_link' => array(
3159178
      'arguments' => array('item' => NULL),
3160178
    ),
3161
    'menu_tree' => array(
3162178
      'arguments' => array('tree' => NULL),
3163178
    ),
3164
    'menu_item' => array(
3165178
      'arguments' => array('link' => NULL, 'has_children' => NULL, 'menu'
=> ''),
3166178
    ),
3167
    'menu_local_task' => array(
3168178
      'arguments' => array('link' => NULL, 'active' => FALSE),
3169178
    ),
3170
    'menu_local_tasks' => array(
3171178
      'arguments' => array(),
3172178
    ),
3173
    // from form.inc
3174
    'select' => array(
3175178
      'arguments' => array('element' => NULL),
3176178
    ),
3177
    'fieldset' => array(
3178178
      'arguments' => array('element' => NULL),
3179178
    ),
3180
    'radio' => array(
3181178
      'arguments' => array('element' => NULL),
3182178
    ),
3183
    'radios' => array(
3184178
      'arguments' => array('element' => NULL),
3185178
    ),
3186
    'password_confirm' => array(
3187178
      'arguments' => array('element' => NULL),
3188178
    ),
3189
    'date' => array(
3190178
      'arguments' => array('element' => NULL),
3191178
    ),
3192
    'item' => array(
3193178
      'arguments' => array('element' => NULL),
3194178
    ),
3195
    'checkbox' => array(
3196178
      'arguments' => array('element' => NULL),
3197178
    ),
3198
    'checkboxes' => array(
3199178
      'arguments' => array('element' => NULL),
3200178
    ),
3201
    'submit' => array(
3202178
      'arguments' => array('element' => NULL),
3203178
    ),
3204
    'button' => array(
3205178
      'arguments' => array('element' => NULL),
3206178
    ),
3207
    'image_button' => array(
3208178
      'arguments' => array('element' => NULL),
3209178
    ),
3210
    'hidden' => array(
3211178
      'arguments' => array('element' => NULL),
3212178
    ),
3213
    'token' => array(
3214178
      'arguments' => array('element' => NULL),
3215178
    ),
3216
    'textfield' => array(
3217178
      'arguments' => array('element' => NULL),
3218178
    ),
3219
    'form' => array(
3220178
      'arguments' => array('element' => NULL),
3221178
    ),
3222
    'textarea' => array(
3223178
      'arguments' => array('element' => NULL),
3224178
    ),
3225
    'markup' => array(
3226178
      'arguments' => array('element' => NULL),
3227178
    ),
3228
    'password' => array(
3229178
      'arguments' => array('element' => NULL),
3230178
    ),
3231
    'file' => array(
3232178
      'arguments' => array('element' => NULL),
3233178
    ),
3234
    'form_element' => array(
3235178
      'arguments' => array('element' => NULL, 'value' => NULL),
3236178
    ),
3237178
  );
32380
}
3239
3240
/**
3241
 * @ingroup schemaapi
3242
 * @{
3243
 */
3244
3245
/**
3246
 * Create all tables that a module defines in its hook_schema().
3247
 *
3248
 * Note: This function does not pass the module's schema through
3249
 * hook_schema_alter(). The module's tables will be created exactly as the
3250
 * module defines them.
3251
 *
3252
 * @param $module
3253
 *   The module for which the tables will be created.
3254
 * @return
3255
 *   An array of arrays with the following key/value pairs:
3256
 *    - success: a boolean indicating whether the query succeeded.
3257
 *    - query: the SQL query(s) executed, passed through check_plain().
3258
 */
32592366
function drupal_install_schema($module) {
3260136
  $schema = drupal_get_schema_unprocessed($module);
3261136
  _drupal_initialize_schema($module, $schema);
3262
3263136
  $ret = array();
3264136
  foreach ($schema as $name => $table) {
3265136
    db_create_table($ret, $name, $table);
3266136
  }
3267136
  return $ret;
32680
}
3269
3270
/**
3271
 * Remove all tables that a module defines in its hook_schema().
3272
 *
3273
 * Note: This function does not pass the module's schema through
3274
 * hook_schema_alter(). The module's tables will be created exactly as the
3275
 * module defines them.
3276
 *
3277
 * @param $module
3278
 *   The module for which the tables will be removed.
3279
 * @return
3280
 *   An array of arrays with the following key/value pairs:
3281
 *    - success: a boolean indicating whether the query succeeded.
3282
 *    - query: the SQL query(s) executed, passed through check_plain().
3283
 */
32842366
function drupal_uninstall_schema($module) {
32851
  $schema = drupal_get_schema_unprocessed($module);
32861
  _drupal_initialize_schema($module, $schema);
3287
32881
  $ret = array();
32891
  foreach ($schema as $table) {
32901
    if (db_table_exists($table['name'])) {
32911
      db_drop_table($ret, $table['name']);
32921
    }
32931
  }
32941
  return $ret;
32950
}
3296
3297
/**
3298
 * Returns the unprocessed and unaltered version of a module's schema.
3299
 *
3300
 * Use this function only if you explicitly need the original
3301
 * specification of a schema, as it was defined in a module's
3302
 * hook_schema(). No additional default values will be set,
3303
 * hook_schema_alter() is not invoked and these unprocessed
3304
 * definitions won't be cached.
3305
 *
3306
 * This function can be used to retrieve a schema specification in
3307
 * hook_schema(), so it allows you to derive your tables from existing
3308
 * specifications.
3309
 *
3310
 * It is also used by drupal_install_schema() and
3311
 * drupal_uninstall_schema() to ensure that a module's tables are
3312
 * created exactly as specified without any changes introduced by a
3313
 * module that implements hook_schema_alter().
3314
 *
3315
 * @param $module
3316
 *   The module to which the table belongs.
3317
 * @param $table
3318
 *   The name of the table. If not given, the module's complete schema
3319
 *   is returned.
3320
 */
33212366
function drupal_get_schema_unprocessed($module, $table = NULL) {
3322
  // Load the .install file to get hook_schema.
3323137
  module_load_install($module);
3324137
  $schema = module_invoke($module, 'schema');
3325
3326137
  if (!is_null($table) && isset($schema[$table])) {
3327134
    return $schema[$table];
33280
  }
3329
  else {
3330137
    return $schema;
3331
  }
33320
}
3333
3334
/**
3335
 * Fill in required default values for table definitions returned by
hook_schema().
3336
 *
3337
 * @param $module
3338
 *   The module for which hook_schema() was invoked.
3339
 * @param $schema
3340
 *   The schema definition array as it was returned by the module's
3341
 *   hook_schema().
3342
 */
33432366
function _drupal_initialize_schema($module, &$schema) {
3344
  // Set the name and module key for all tables.
3345137
  foreach ($schema as $name => $table) {
3346137
    if (empty($table['module'])) {
3347137
      $schema[$name]['module'] = $module;
3348137
    }
3349137
    if (!isset($table['name'])) {
3350137
      $schema[$name]['name'] = $name;
3351137
    }
3352137
  }
3353137
}
3354
3355
/**
3356
 * Retrieve a list of fields from a table schema. The list is suitable for
use in a SQL query.
3357
 *
3358
 * @param $table
3359
 *   The name of the table from which to retrieve fields.
3360
 * @param
3361
 *   An optional prefix to to all fields.
3362
 *
3363
 * @return An array of fields.
3364
 **/
33652366
function drupal_schema_fields_sql($table, $prefix = NULL) {
3366441
  $schema = drupal_get_schema($table);
3367441
  $fields = array_keys($schema['fields']);
3368441
  if ($prefix) {
3369441
    $columns = array();
3370441
    foreach ($fields as $field) {
3371441
      $columns[] = "$prefix.$field";
3372441
    }
3373441
    return $columns;
33740
  }
3375
  else {
33760
    return $fields;
3377
  }
33780
}
3379
3380
/**
3381
 * Save a record to the database based upon the schema.
3382
 *
3383
 * Default values are filled in for missing items, and 'serial' (auto
increment)
3384
 * types are filled in with IDs.
3385
 *
3386
 * @param $table
3387
 *   The name of the table; this must exist in schema API.
3388
 * @param $object
3389
 *   The object to write. This is a reference, as defaults according to
3390
 *   the schema may be filled in on the object, as well as ID on the serial
3391
 *   type(s). Both array an object types may be passed.
3392
 * @param $primary_keys
3393
 *   If this is an update, specify the primary keys' field names. It is the
3394
 *   caller's responsibility to know if a record for this object already
3395
 *   exists in the database. If there is only 1 key, you may pass a simple
string.
3396
 * @return
3397
 *   Failure to write a record will return FALSE. Otherwise SAVED_NEW or
3398
 *   SAVED_UPDATED is returned depending on the operation performed. The
3399
 *   $object parameter contains values for any serial fields defined by
3400
 *   the $table. For example, $object->nid will be populated after
inserting
3401
 *   a new node.
3402
 */
34032366
function drupal_write_record($table, &$object, $primary_keys = array()) {
3404
  // Standardize $primary_keys to an array.
3405203
  if (is_string($primary_keys)) {
340658
    $primary_keys = array($primary_keys);
340758
  }
3408
3409203
  $schema = drupal_get_schema($table);
3410203
  if (empty($schema)) {
34110
    return FALSE;
34120
  }
3413
3414
  // Convert to an object if needed.
3415203
  if (is_array($object)) {
3416116
    $object = (object) $object;
3417116
    $array = TRUE;
3418116
  }
3419
  else {
3420106
    $array = FALSE;
3421
  }
3422
3423203
  $fields = array();
3424
3425
  // Go through our schema, build SQL, and when inserting, fill in defaults
for
3426
  // fields that are not set.
3427203
  foreach ($schema['fields'] as $field => $info) {
3428
    // Special case -- skip serial types if we are updating.
3429203
    if ($info['type'] == 'serial' && !empty($primary_keys)) {
343058
      continue;
34310
    }
3432
3433
    // For inserts, populate defaults from schema if not already provided.
3434203
    if (!isset($object->$field) && empty($primary_keys) &&
isset($info['default'])) {
3435126
      $object->$field = $info['default'];
3436126
    }
3437
3438
    // Track serial field so we can helpfully populate them after the
query.
3439
    // NOTE: Each table should come with one serial field only.
3440203
    if ($info['type'] == 'serial') {
3441162
      $serial = $field;
3442
      // Ignore values for serial when inserting data. Unsupported.
3443162
      unset($object->$field);
3444162
    }
3445
3446
    // Build arrays for the fields and values in our query.
3447203
    if (isset($object->$field)) {
3448203
      if (empty($info['serialize'])) {
3449193
        $fields[$field] = $object->$field;
3450193
      }
345119
      elseif (!empty($object->$field)) {
345218
        $fields[$field] = serialize($object->$field);
345318
      }
3454
      else {
34551
        $fields[$field] = '';
3456
      }
3457203
    }
3458
3459
    // We don't need to care about type casting if value does not exist.
3460203
    if (!isset($fields[$field])) {
3461200
      continue;
34620
    }
3463
3464
    // Special case -- skip null value if field allows null.
3465203
    if ($fields[$field] == NULL && $info['not null'] == FALSE) {
346684
      continue;
34670
    }
3468
3469
    // Type cast if field does not allow null. Required by DB API.
3470203
    if ($info['type'] == 'int' || $info['type'] == 'serial') {
3471192
      $fields[$field] = (int) $fields[$field];
3472192
    }
3473202
    elseif ($info['type'] == 'float') {
34740
      $fields[$field] = (float) $fields[$field];
34750
    }
3476
    else {
3477202
      $fields[$field] = (string) $fields[$field];
3478
    }
3479203
  }
3480
3481203
  if (empty($fields)) {
3482
    // No changes requested.
3483
    // If we began with an array, convert back so we don't surprise the
caller.
34840
    if ($array) {
34850
      $object = (array) $object;
34860
    }
34870
    return;
34880
  }
3489
3490
  // Build the SQL.
3491203
  if (empty($primary_keys)) {
3492162
    $query = db_insert($table)->fields($fields);
3493162
    $return = SAVED_NEW;
3494162
  }
3495
  else {
349658
    $query = db_update($table)->fields($fields);
349758
    foreach ($primary_keys as $key){
349858
      $query->condition($key, $object->$key);
349958
    }
350058
    $return = SAVED_UPDATED;
3501
  }
3502
3503
  // Execute the SQL.
3504203
  if ($last_insert_id = $query->execute()) {
3505198
    if (isset($serial)) {
3506
      // Populate the serial field.
3507162
      $object->$serial = $last_insert_id;
3508162
    }
3509198
  }
3510
  else {
35117
    $return = FALSE;
3512
  }
3513
3514
  // If we began with an array, convert back so we don't surprise the
caller.
3515203
  if ($array) {
3516116
    $object = (array) $object;
3517116
  }
3518
3519203
  return $return;
35200
}
3521
3522
/**
3523
 * @} End of "ingroup schemaapi".
3524
 */
3525
3526
/**
3527
 * Parse Drupal info file format.
3528
 *
3529
 * Files should use an ini-like format to specify values.
3530
 * White-space generally doesn't matter, except inside values.
3531
 * e.g.
3532
 *
3533
 * @verbatim
3534
 *   key = value
3535
 *   key = "value"
3536
 *   key = 'value'
3537
 *   key = "multi-line
3538
 *
3539
 *   value"
3540
 *   key = 'multi-line
3541
 *
3542
 *   value'
3543
 *   key
3544
 *   =
3545
 *   'value'
3546
 * @endverbatim
3547
 *
3548
 * Arrays are created using a GET-like syntax:
3549
 *
3550
 * @verbatim
3551
 *   key[] = "numeric array"
3552
 *   key[index] = "associative array"
3553
 *   key[index][] = "nested numeric array"
3554
 *   key[index][index] = "nested associative array"
3555
 * @endverbatim
3556
 *
3557
 * PHP constants are substituted in, but only when used as the entire
value:
3558
 *
3559
 * Comments should start with a semi-colon at the beginning of a line.
3560
 *
3561
 * This function is NOT for placing arbitrary module-specific settings. Use
3562
 * variable_get() and variable_set() for that.
3563
 *
3564
 * Information stored in the module.info file:
3565
 * - name: The real name of the module for display purposes.
3566
 * - description: A brief description of the module.
3567
 * - dependencies: An array of shortnames of other modules this module
depends on.
3568
 * - package: The name of the package of modules this module belongs to.
3569
 *
3570
 * Example of .info file:
3571
 * @verbatim
3572
 *   name = Forum
3573
 *   description = Enables threaded discussions about general topics.
3574
 *   dependencies[] = taxonomy
3575
 *   dependencies[] = comment
3576
 *   package = Core
3577
 *   version = VERSION
3578
 * @endverbatim
3579
 *
3580
 * @param $filename
3581
 *   The file we are parsing. Accepts file with relative or absolute path.
3582
 * @return
3583
 *   The info array.
3584
 */
35852366
function drupal_parse_info_file($filename) {
3586174
  $info = array();
3587
3588174
  if (!file_exists($filename)) {
35890
    return $info;
35900
  }
3591
3592174
  $data = file_get_contents($filename);
3593174
  if (preg_match_all('
3594
    @^\s*                           # Start at the beginning of a line,
ignoring leading whitespace
3595
    ((?:
3596
      [^=;\[\]]|                    # Key names cannot contain equal signs,
semi-colons or square brackets,
3597
      \[[^\[\]]*\]                  # unless they are balanced and not
nested
3598
    )+?)
3599
    \s*=\s*                         # Key/value pairs are separated by
equal signs (ignoring white-space)
3600
    (?:
3601
      ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may
contain slash-escaped quotes/slashes
3602
      (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may
contain slash-escaped quotes/slashes
3603
      ([^\r\n]*?)                   # Non-quoted string
3604
    )\s*$                           # Stop at the next end of a line,
ignoring trailing whitespace
3605174
    @msx', $data, $matches, PREG_SET_ORDER)) {
3606174
    foreach ($matches as $match) {
3607
      // Fetch the key and value string
3608174
      $i = 0;
3609174
      foreach (array('key', 'value1', 'value2', 'value3') as $var) {
3610174
        $$var = isset($match[++$i]) ? $match[$i] : '';
3611174
      }
3612174
      $value = stripslashes(substr($value1, 1, -1)) .
stripslashes(substr($value2, 1, -1)) . $value3;
3613
3614
      // Parse array syntax
3615174
      $keys = preg_split('/\]?\[/', rtrim($key, ']'));
3616174
      $last = array_pop($keys);
3617174
      $parent = &$info;
3618
3619
      // Create nested arrays
3620174
      foreach ($keys as $key) {
3621174
        if ($key == '') {
36220
          $key = count($parent);
36230
        }
3624174
        if (!isset($parent[$key]) || !is_array($parent[$key])) {
3625174
          $parent[$key] = array();
3626174
        }
3627174
        $parent = &$parent[$key];
3628174
      }
3629
3630
      // Handle PHP constants
3631174
      if (defined($value)) {
3632174
        $value = constant($value);
3633174
      }
3634
3635
      // Insert actual value
3636174
      if ($last == '') {
3637174
        $last = count($parent);
3638174
      }
3639174
      $parent[$last] = $value;
3640174
    }
3641174
  }
3642
3643174
  return $info;
36440
}
3645
3646
/**
3647
 * Severity levels, as defined in RFC 3164:
http://www.ietf.org/rfc/rfc3164.txt.
3648
 *
3649
 * @return
3650
 *   Array of the possible severity levels for log messages.
3651
 *
3652
 * @see watchdog()
3653
 */
36542366
function watchdog_severity_levels() {
3655
  return array(
36567
    WATCHDOG_EMERG    => t('emergency'),
36577
    WATCHDOG_ALERT    => t('alert'),
36587
    WATCHDOG_CRITICAL => t('critical'),
36597
    WATCHDOG_ERROR    => t('error'),
36607
    WATCHDOG_WARNING  => t('warning'),
36617
    WATCHDOG_NOTICE   => t('notice'),
36627
    WATCHDOG_INFO     => t('info'),
36637
    WATCHDOG_DEBUG    => t('debug'),
36647
  );
36650
}
3666
3667
3668
/**
3669
 * Explode a string of given tags into an array.
3670
 */
36712366
function drupal_explode_tags($tags) {
3672
  // This regexp allows the following types of user input:
3673
  // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
36745
  $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
36755
  preg_match_all($regexp, $tags, $matches);
36765
  $typed_tags = array_unique($matches[1]);
3677
36785
  $tags = array();
36795
  foreach ($typed_tags as $tag) {
3680
    // If a user has escaped a term (to demonstrate that it is a group,
3681
    // or includes a comma or quote character), we remove the escape
3682
    // formatting so to save the term into the database as the user
intends.
36835
    $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1',
$tag)));
36845
    if ($tag != "") {
36851
      $tags[] = $tag;
36861
    }
36875
  }
3688
36895
  return $tags;
36900
}
3691
3692
/**
3693
 * Implode an array of tags into a string.
3694
 */
36952366
function drupal_implode_tags($tags) {
36961
  $encoded_tags = array();
36971
  foreach ($tags as $tag) {
3698
    // Commas and quotes in tag names are special cases, so encode them.
36991
    if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
37001
      $tag = '"' . str_replace('"', '""', $tag) . '"';
37011
    }
3702
37031
    $encoded_tags[] = $tag;
37041
  }
37051
  return implode(', ', $encoded_tags);
37060
}
3707
3708
/**
3709
 * Flush all cached data on the site.
3710
 *
3711
 * Empties cache tables, rebuilds the menu cache and theme registries, and
3712
 * invokes a hook so that other modules' cache data can be cleared as well.
3713
 */
37142366
function drupal_flush_all_caches() {
3715
  // Change query-strings on css/js files to enforce reload for all users.
37160
  _drupal_flush_css_js();
3717
37180
  registry_rebuild();
37190
  drupal_clear_css_cache();
37200
  drupal_clear_js_cache();
37210
  system_theme_data();
37220
  drupal_theme_rebuild();
37230
  menu_rebuild();
37240
  node_types_rebuild();
3725
  // Don't clear cache_form - in-progress form submissions may break.
3726
  // Ordered so clearing the page cache will always be the last action.
37270
  $core = array('cache', 'cache_block', 'cache_filter', 'cache_registry',
'cache_page');
37280
  $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
37290
  foreach ($cache_tables as $table) {
37300
    cache_clear_all('*', $table, TRUE);
37310
  }
37320
}
3733
3734
/**
3735
 * Helper function to change query-strings on css/js files.
3736
 *
3737
 * Changes the character added to all css/js files as dummy query-string,
3738
 * so that all browsers are forced to reload fresh files. We keep
3739
 * 20 characters history (FIFO) to avoid repeats, but only the first
3740
 * (newest) character is actually used on urls, to keep them short.
3741
 * This is also called from update.php.
3742
 */
37432366
function _drupal_flush_css_js() {
3744134
  $string_history = variable_get('css_js_query_string',
'00000000000000000000');
3745134
  $new_character = $string_history[0];
3746134
  $characters =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
3747134
  while (strpos($string_history, $new_character) !== FALSE) {
3748134
    $new_character = $characters[mt_rand(0, strlen($characters) - 1)];
3749134
  }
3750134
  variable_set('css_js_query_string', $new_character .
substr($string_history, 0, 19));
3751134
}
37522366