[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/includes/ -> common.inc (source)

   1  <?php
   2  
   3  /**
   4   * @file
   5   * Common functions that many Drupal modules will need to reference.
   6   *
   7   * The functions that are critical and need to be available even when serving
   8   * a cached page are instead located in bootstrap.inc.
   9   */
  10  
  11  /**
  12   * Return status for saving which involved creating a new item.
  13   */
  14  define('SAVED_NEW', 1);
  15  
  16  /**
  17   * Return status for saving which involved an update to an existing item.
  18   */
  19  define('SAVED_UPDATED', 2);
  20  
  21  /**
  22   * Return status for saving which deleted an existing item.
  23   */
  24  define('SAVED_DELETED', 3);
  25  
  26  /**
  27   * Create E_DEPRECATED constant for older PHP versions (<5.3).
  28   */
  29  if (!defined('E_DEPRECATED')) {
  30    define('E_DEPRECATED', 8192);
  31  }
  32  
  33  /**
  34   * Error code indicating that the request made by drupal_http_request() exceeded
  35   * the specified timeout.
  36   */
  37  define('HTTP_REQUEST_TIMEOUT', -1);
  38  
  39  /**
  40   * Set content for a specified region.
  41   *
  42   * @param $region
  43   *   Page region the content is assigned to.
  44   * @param $data
  45   *   Content to be set.
  46   */
  47  function drupal_set_content($region = NULL, $data = NULL) {
  48    static $content = array();
  49  
  50    if (!is_null($region) && !is_null($data)) {
  51      $content[$region][] = $data;
  52    }
  53    return $content;
  54  }
  55  
  56  /**
  57   * Get assigned content.
  58   *
  59   * @param $region
  60   *   A specified region to fetch content for. If NULL, all regions will be
  61   *   returned.
  62   * @param $delimiter
  63   *   Content to be inserted between imploded array elements.
  64   */
  65  function drupal_get_content($region = NULL, $delimiter = ' ') {
  66    $content = drupal_set_content();
  67    if (isset($region)) {
  68      if (isset($content[$region]) && is_array($content[$region])) {
  69        return implode($delimiter, $content[$region]);
  70      }
  71    }
  72    else {
  73      foreach (array_keys($content) as $region) {
  74        if (is_array($content[$region])) {
  75          $content[$region] = implode($delimiter, $content[$region]);
  76        }
  77      }
  78      return $content;
  79    }
  80  }
  81  
  82  /**
  83   * Set the breadcrumb trail for the current page.
  84   *
  85   * @param $breadcrumb
  86   *   Array of links, starting with "home" and proceeding up to but not including
  87   *   the current page.
  88   */
  89  function drupal_set_breadcrumb($breadcrumb = NULL) {
  90    static $stored_breadcrumb;
  91  
  92    if (!is_null($breadcrumb)) {
  93      $stored_breadcrumb = $breadcrumb;
  94    }
  95    return $stored_breadcrumb;
  96  }
  97  
  98  /**
  99   * Get the breadcrumb trail for the current page.
 100   */
 101  function drupal_get_breadcrumb() {
 102    $breadcrumb = drupal_set_breadcrumb();
 103  
 104    if (is_null($breadcrumb)) {
 105      $breadcrumb = menu_get_active_breadcrumb();
 106    }
 107  
 108    return $breadcrumb;
 109  }
 110  
 111  /**
 112   * Add output to the head tag of the HTML page.
 113   *
 114   * This function can be called as long the headers aren't sent.
 115   */
 116  function drupal_set_html_head($data = NULL) {
 117    static $stored_head = '';
 118  
 119    if (!is_null($data)) {
 120      $stored_head .= $data ."\n";
 121    }
 122    return $stored_head;
 123  }
 124  
 125  /**
 126   * Retrieve output to be displayed in the head tag of the HTML page.
 127   */
 128  function drupal_get_html_head() {
 129    $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
 130    return $output . drupal_set_html_head();
 131  }
 132  
 133  /**
 134   * Reset the static variable which holds the aliases mapped for this request.
 135   */
 136  function drupal_clear_path_cache() {
 137    drupal_lookup_path('wipe');
 138  }
 139  
 140  /**
 141   * Set an HTTP response header for the current page.
 142   *
 143   * Note: When sending a Content-Type header, always include a 'charset' type,
 144   * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
 145   */
 146  function drupal_set_header($header = NULL) {
 147    // We use an array to guarantee there are no leading or trailing delimiters.
 148    // Otherwise, header('') could get called when serving the page later, which
 149    // ends HTTP headers prematurely on some PHP versions.
 150    static $stored_headers = array();
 151  
 152    if (strlen($header)) {
 153      header($header);
 154      $stored_headers[] = $header;
 155    }
 156    return implode("\n", $stored_headers);
 157  }
 158  
 159  /**
 160   * Get the HTTP response headers for the current page.
 161   */
 162  function drupal_get_headers() {
 163    return drupal_set_header();
 164  }
 165  
 166  /**
 167   * Make any final alterations to the rendered xhtml.
 168   */
 169  function drupal_final_markup($content) {
 170    // Make sure that the charset is always specified as the first element of the
 171    // head region to prevent encoding-based attacks.
 172    return preg_replace('/<head[^>]*>/i', "\$0\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />", $content, 1);
 173  }
 174  
 175  /**
 176   * Add a feed URL for the current page.
 177   *
 178   * @param $url
 179   *   A url for the feed.
 180   * @param $title
 181   *   The title of the feed.
 182   */
 183  function drupal_add_feed($url = NULL, $title = '') {
 184    static $stored_feed_links = array();
 185  
 186    if (!is_null($url) && !isset($stored_feed_links[$url])) {
 187      $stored_feed_links[$url] = theme('feed_icon', $url, $title);
 188  
 189      drupal_add_link(array('rel' => 'alternate',
 190                            'type' => 'application/rss+xml',
 191                            'title' => $title,
 192                            'href' => $url));
 193    }
 194    return $stored_feed_links;
 195  }
 196  
 197  /**
 198   * Get the feed URLs for the current page.
 199   *
 200   * @param $delimiter
 201   *   A delimiter to split feeds by.
 202   */
 203  function drupal_get_feeds($delimiter = "\n") {
 204    $feeds = drupal_add_feed();
 205    return implode($feeds, $delimiter);
 206  }
 207  
 208  /**
 209   * @defgroup http_handling HTTP handling
 210   * @{
 211   * Functions to properly handle HTTP responses.
 212   */
 213  
 214  /**
 215   * Parse an array into a valid urlencoded query string.
 216   *
 217   * @param $query
 218   *   The array to be processed e.g. $_GET.
 219   * @param $exclude
 220   *   The array filled with keys to be excluded. Use parent[child] to exclude
 221   *   nested items.
 222   * @param $parent
 223   *   Should not be passed, only used in recursive calls.
 224   * @return
 225   *   An urlencoded string which can be appended to/as the URL query string.
 226   */
 227  function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
 228    $params = array();
 229  
 230    foreach ($query as $key => $value) {
 231      $key = rawurlencode($key);
 232      if ($parent) {
 233        $key = $parent .'['. $key .']';
 234      }
 235  
 236      if (in_array($key, $exclude)) {
 237        continue;
 238      }
 239  
 240      if (is_array($value)) {
 241        $params[] = drupal_query_string_encode($value, $exclude, $key);
 242      }
 243      else {
 244        $params[] = $key .'='. rawurlencode($value);
 245      }
 246    }
 247  
 248    return implode('&', $params);
 249  }
 250  
 251  /**
 252   * Prepare a destination query string for use in combination with drupal_goto().
 253   *
 254   * Used to direct the user back to the referring page after completing a form.
 255   * By default the current URL is returned. If a destination exists in the
 256   * previous request, that destination is returned. As such, a destination can
 257   * persist across multiple pages.
 258   *
 259   * @see drupal_goto()
 260   */
 261  function drupal_get_destination() {
 262    if (isset($_REQUEST['destination'])) {
 263      return 'destination='. urlencode($_REQUEST['destination']);
 264    }
 265    else {
 266      // Use $_GET here to retrieve the original path in source form.
 267      $path = isset($_GET['q']) ? $_GET['q'] : '';
 268      $query = drupal_query_string_encode($_GET, array('q'));
 269      if ($query != '') {
 270        $path .= '?'. $query;
 271      }
 272      return 'destination='. urlencode($path);
 273    }
 274  }
 275  
 276  /**
 277   * Send the user to a different Drupal page.
 278   *
 279   * This issues an on-site HTTP redirect. The function makes sure the redirected
 280   * URL is formatted correctly.
 281   *
 282   * Usually the redirected URL is constructed from this function's input
 283   * parameters. However you may override that behavior by setting a
 284   * destination in either the $_REQUEST-array (i.e. by using
 285   * the query string of an URI) or the $_REQUEST['edit']-array (i.e. by
 286   * using a hidden form field). This is used to direct the user back to
 287   * the proper page after completing a form. For example, after editing
 288   * a post on the 'admin/content/node'-page or after having logged on using the
 289   * 'user login'-block in a sidebar. The function drupal_get_destination()
 290   * can be used to help set the destination URL.
 291   *
 292   * Drupal will ensure that messages set by drupal_set_message() and other
 293   * session data are written to the database before the user is redirected.
 294   *
 295   * This function ends the request; use it rather than a print theme('page')
 296   * statement in your menu callback.
 297   *
 298   * @param $path
 299   *   A Drupal path or a full URL.
 300   * @param $query
 301   *   A query string component, if any.
 302   * @param $fragment
 303   *   A destination fragment identifier (named anchor).
 304   * @param $http_response_code
 305   *   Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
 306   *   - 301 Moved Permanently (the recommended value for most redirects)
 307   *   - 302 Found (default in Drupal and PHP, sometimes used for spamming search
 308   *         engines)
 309   *   - 303 See Other
 310   *   - 304 Not Modified
 311   *   - 305 Use Proxy
 312   *   - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
 313   *   Note: Other values are defined by RFC 2616, but are rarely used and poorly
 314   *   supported.
 315   * @see drupal_get_destination()
 316   */
 317  function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
 318  
 319    $destination = FALSE;
 320    if (isset($_REQUEST['destination'])) {
 321      $destination = $_REQUEST['destination'];
 322    }
 323    else if (isset($_REQUEST['edit']['destination'])) {
 324      $destination = $_REQUEST['edit']['destination'];
 325    }
 326  
 327    if ($destination) {
 328      // Do not redirect to an absolute URL originating from user input.
 329      $colonpos = strpos($destination, ':');
 330      $absolute = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($destination, 0, $colonpos)));
 331      if (!$absolute) {
 332        extract(parse_url(urldecode($destination)));
 333      }
 334    }
 335  
 336    $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
 337    // Remove newlines from the URL to avoid header injection attacks.
 338    $url = str_replace(array("\n", "\r"), '', $url);
 339  
 340    // Allow modules to react to the end of the page request before redirecting.
 341    // We do not want this while running update.php.
 342    if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
 343      module_invoke_all('exit', $url);
 344    }
 345  
 346    // Even though session_write_close() is registered as a shutdown function, we
 347    // need all session data written to the database before redirecting.
 348    session_write_close();
 349  
 350    header('Location: '. $url, TRUE, $http_response_code);
 351  
 352    // The "Location" header sends a redirect status code to the HTTP daemon. In
 353    // some cases this can be wrong, so we make sure none of the code below the
 354    // drupal_goto() call gets executed upon redirection.
 355    exit();
 356  }
 357  
 358  /**
 359   * Generates a site off-line message.
 360   */
 361  function drupal_site_offline() {
 362    drupal_maintenance_theme();
 363    drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 503 Service unavailable');
 364    drupal_set_title(t('Site off-line'));
 365    print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
 366      t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
 367  }
 368  
 369  /**
 370   * Generates a 404 error if the request can not be handled.
 371   */
 372  function drupal_not_found() {
 373    drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 404 Not Found');
 374  
 375    watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
 376  
 377    // Keep old path for reference, and to allow forms to redirect to it.
 378    if (!isset($_REQUEST['destination'])) {
 379      $_REQUEST['destination'] = $_GET['q'];
 380    }
 381  
 382    $path = drupal_get_normal_path(variable_get('site_404', ''));
 383    if ($path && $path != $_GET['q']) {
 384      // Set the active item in case there are tabs to display, or other
 385      // dependencies on the path.
 386      menu_set_active_item($path);
 387      $return = menu_execute_active_handler($path);
 388    }
 389  
 390    if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
 391      drupal_set_title(t('Page not found'));
 392      $return = t('The requested page could not be found.');
 393    }
 394  
 395    // To conserve CPU and bandwidth, omit the blocks.
 396    print theme('page', $return, FALSE);
 397  }
 398  
 399  /**
 400   * Generates a 403 error if the request is not allowed.
 401   */
 402  function drupal_access_denied() {
 403    drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 403 Forbidden');
 404  
 405    watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
 406  
 407    // Keep old path for reference, and to allow forms to redirect to it.
 408    if (!isset($_REQUEST['destination'])) {
 409      $_REQUEST['destination'] = $_GET['q'];
 410    }
 411  
 412    $path = drupal_get_normal_path(variable_get('site_403', ''));
 413    if ($path && $path != $_GET['q']) {
 414      // Set the active item in case there are tabs to display or other
 415      // dependencies on the path.
 416      menu_set_active_item($path);
 417      $return = menu_execute_active_handler($path);
 418    }
 419  
 420    if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
 421      drupal_set_title(t('Access denied'));
 422      $return = t('You are not authorized to access this page.');
 423    }
 424    print theme('page', $return);
 425  }
 426  
 427  /**
 428   * Perform an HTTP request.
 429   *
 430   * This is a flexible and powerful HTTP client implementation. Correctly handles
 431   * GET, POST, PUT or any other HTTP requests. Handles redirects.
 432   *
 433   * @param $url
 434   *   A string containing a fully qualified URI.
 435   * @param $headers
 436   *   An array containing an HTTP header => value pair.
 437   * @param $method
 438   *   A string defining the HTTP request to use.
 439   * @param $data
 440   *   A string containing data to include in the request.
 441   * @param $retry
 442   *   An integer representing how many times to retry the request in case of a
 443   *   redirect.
 444   * @param $timeout
 445   *   A float representing the maximum number of seconds the function call may
 446   *   take. The default is 30 seconds. If a timeout occurs, the error code is set
 447   *   to the HTTP_REQUEST_TIMEOUT constant.
 448   * @return
 449   *   An object containing the HTTP request headers, response code, protocol,
 450   *   status message, headers, data and redirect status.
 451   */
 452  function drupal_http_request($url, $headers = array(), $method = 'GET', $data = NULL, $retry = 3, $timeout = 30.0) {
 453    global $db_prefix;
 454  
 455    $result = new stdClass();
 456  
 457    // Parse the URL and make sure we can handle the schema.
 458    $uri = parse_url($url);
 459  
 460    if ($uri == FALSE) {
 461      $result->error = 'unable to parse URL';
 462      $result->code = -1001;
 463      return $result;
 464    }
 465  
 466    if (!isset($uri['scheme'])) {
 467      $result->error = 'missing schema';
 468      $result->code = -1002;
 469      return $result;
 470    }
 471  
 472    timer_start(__FUNCTION__);
 473  
 474    switch ($uri['scheme']) {
 475      case 'http':
 476      case 'feed':
 477        $port = isset($uri['port']) ? $uri['port'] : 80;
 478        $host = $uri['host'] . ($port != 80 ? ':'. $port : '');
 479        $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $timeout);
 480        break;
 481      case 'https':
 482        // Note: Only works for PHP 4.3 compiled with OpenSSL.
 483        $port = isset($uri['port']) ? $uri['port'] : 443;
 484        $host = $uri['host'] . ($port != 443 ? ':'. $port : '');
 485        $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, $timeout);
 486        break;
 487      default:
 488        $result->error = 'invalid schema '. $uri['scheme'];
 489        $result->code = -1003;
 490        return $result;
 491    }
 492  
 493    // Make sure the socket opened properly.
 494    if (!$fp) {
 495      // When a network error occurs, we use a negative number so it does not
 496      // clash with the HTTP status codes.
 497      $result->code = -$errno;
 498      $result->error = trim($errstr);
 499  
 500      // Mark that this request failed. This will trigger a check of the web
 501      // server's ability to make outgoing HTTP requests the next time that
 502      // requirements checking is performed.
 503      // @see system_requirements()
 504      variable_set('drupal_http_request_fails', TRUE);
 505  
 506      return $result;
 507    }
 508  
 509    // Construct the path to act on.
 510    $path = isset($uri['path']) ? $uri['path'] : '/';
 511    if (isset($uri['query'])) {
 512      $path .= '?'. $uri['query'];
 513    }
 514  
 515    // Create HTTP request.
 516    $defaults = array(
 517      // RFC 2616: "non-standard ports MUST, default ports MAY be included".
 518      // We don't add the port to prevent from breaking rewrite rules checking the
 519      // host that do not take into account the port number.
 520      'Host' => "Host: $host",
 521      'User-Agent' => 'User-Agent: Drupal (+http://drupal.org/)',
 522    );
 523  
 524    // Only add Content-Length if we actually have any content or if it is a POST
 525    // or PUT request. Some non-standard servers get confused by Content-Length in
 526    // at least HEAD/GET requests, and Squid always requires Content-Length in
 527    // POST/PUT requests.
 528    $content_length = strlen($data);
 529    if ($content_length > 0 || $method == 'POST' || $method == 'PUT') {
 530      $defaults['Content-Length'] = 'Content-Length: '. $content_length;
 531    }
 532  
 533    // If the server url has a user then attempt to use basic authentication
 534    if (isset($uri['user'])) {
 535      $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : ''));
 536    }
 537  
 538    // If the database prefix is being used by SimpleTest to run the tests in a copied
 539    // database then set the user-agent header to the database prefix so that any
 540    // calls to other Drupal pages will run the SimpleTest prefixed database. The
 541    // user-agent is used to ensure that multiple testing sessions running at the
 542    // same time won't interfere with each other as they would if the database
 543    // prefix were stored statically in a file or database variable.
 544    if (is_string($db_prefix) && preg_match("/^simpletest\d+$/", $db_prefix, $matches)) {
 545      $defaults['User-Agent'] = 'User-Agent: ' . $matches[0];
 546    }
 547  
 548    foreach ($headers as $header => $value) {
 549      $defaults[$header] = $header .': '. $value;
 550    }
 551  
 552    $request = $method .' '. $path ." HTTP/1.0\r\n";
 553    $request .= implode("\r\n", $defaults);
 554    $request .= "\r\n\r\n";
 555    $request .= $data;
 556  
 557    $result->request = $request;
 558  
 559    // Calculate how much time is left of the original timeout value.
 560    $time_left = $timeout - timer_read(__FUNCTION__) / 1000;
 561    if ($time_left > 0) {
 562      stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
 563      fwrite($fp, $request);
 564    }
 565  
 566    // Fetch response.
 567    $response = '';
 568    while (!feof($fp)) {
 569      // Calculate how much time is left of the original timeout value.
 570      $time_left = $timeout - timer_read(__FUNCTION__) / 1000;
 571      if ($time_left <= 0) {
 572        $result->code = HTTP_REQUEST_TIMEOUT;
 573        $result->error = 'request timed out';
 574        return $result;
 575      }
 576      stream_set_timeout($fp, floor($time_left), floor(1000000 * fmod($time_left, 1)));
 577      $chunk = fread($fp, 1024);
 578      $response .= $chunk;
 579    }
 580    fclose($fp);
 581  
 582    // Parse response.
 583    list($split, $result->data) = explode("\r\n\r\n", $response, 2);
 584    $split = preg_split("/\r\n|\n|\r/", $split);
 585  
 586    list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3);
 587    $result->protocol = $protocol;
 588    $result->status_message = $status_message;
 589  
 590    $result->headers = array();
 591  
 592    // Parse headers.
 593    while ($line = trim(array_shift($split))) {
 594      list($header, $value) = explode(':', $line, 2);
 595      if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
 596        // RFC 2109: the Set-Cookie response header comprises the token Set-
 597        // Cookie:, followed by a comma-separated list of one or more cookies.
 598        $result->headers[$header] .= ','. trim($value);
 599      }
 600      else {
 601        $result->headers[$header] = trim($value);
 602      }
 603    }
 604  
 605    $responses = array(
 606      100 => 'Continue', 101 => 'Switching Protocols',
 607      200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content',
 608      300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect',
 609      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',
 610      500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported'
 611    );
 612    // RFC 2616 states that all unknown HTTP codes must be treated the same as the
 613    // base code in their class.
 614    if (!isset($responses[$code])) {
 615      $code = floor($code / 100) * 100;
 616    }
 617  
 618    switch ($code) {
 619      case 200: // OK
 620      case 304: // Not modified
 621        break;
 622      case 301: // Moved permanently
 623      case 302: // Moved temporarily
 624      case 307: // Moved temporarily
 625        $location = $result->headers['Location'];
 626        $timeout -= timer_read(__FUNCTION__) / 1000;
 627        if ($timeout <= 0) {
 628          $result->code = HTTP_REQUEST_TIMEOUT;
 629          $result->error = 'request timed out';
 630        }
 631        elseif ($retry) {
 632          $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry, $timeout);
 633          $result->redirect_code = $result->code;
 634        }
 635        $result->redirect_url = $location;
 636  
 637        break;
 638      default:
 639        $result->error = $status_message;
 640    }
 641  
 642    $result->code = $code;
 643    return $result;
 644  }
 645  /**
 646   * @} End of "HTTP handling".
 647   */
 648  
 649  /**
 650   * Log errors as defined by administrator.
 651   *
 652   * Error levels:
 653   * - 0 = Log errors to database.
 654   * - 1 = Log errors to database and to screen.
 655   */
 656  function drupal_error_handler($errno, $message, $filename, $line, $context) {
 657    // If the @ error suppression operator was used, error_reporting will have
 658    // been temporarily set to 0.
 659    if (error_reporting() == 0) {
 660      return;
 661    }
 662  
 663    if ($errno & (E_ALL ^ E_DEPRECATED ^ E_NOTICE)) {
 664      $types = array(1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning', 4096 => 'recoverable fatal error');
 665  
 666      // For database errors, we want the line number/file name of the place that
 667      // the query was originally called, not _db_query().
 668      if (isset($context[DB_ERROR])) {
 669        $backtrace = array_reverse(debug_backtrace());
 670  
 671        // List of functions where SQL queries can originate.
 672        $query_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
 673  
 674        // Determine where query function was called, and adjust line/file
 675        // accordingly.
 676        foreach ($backtrace as $index => $function) {
 677          if (in_array($function['function'], $query_functions)) {
 678            $line = $backtrace[$index]['line'];
 679            $filename = $backtrace[$index]['file'];
 680            break;
 681          }
 682        }
 683      }
 684  
 685      // Try to use filter_xss(). If it's too early in the bootstrap process for
 686      // filter_xss() to be loaded, use check_plain() instead.
 687      $entry = check_plain($types[$errno]) .': '. (function_exists('filter_xss') ? filter_xss($message) : check_plain($message)) .' in '. check_plain($filename) .' on line '. check_plain($line) .'.';
 688  
 689      // Force display of error messages in update.php.
 690      if (variable_get('error_level', 1) == 1 || strstr($_SERVER['SCRIPT_NAME'], 'update.php')) {
 691        drupal_set_message($entry, 'error');
 692      }
 693  
 694      watchdog('php', '%message in %file on line %line.', array('%error' => $types[$errno], '%message' => $message, '%file' => $filename, '%line' => $line), WATCHDOG_ERROR);
 695    }
 696  }
 697  
 698  function _fix_gpc_magic(&$item) {
 699    if (is_array($item)) {
 700      array_walk($item, '_fix_gpc_magic');
 701    }
 702    else {
 703      $item = stripslashes($item);
 704    }
 705  }
 706  
 707  /**
 708   * Helper function to strip slashes from $_FILES skipping over the tmp_name keys
 709   * since PHP generates single backslashes for file paths on Windows systems.
 710   *
 711   * tmp_name does not have backslashes added see
 712   * http://php.net/manual/en/features.file-upload.php#42280
 713   */
 714  function _fix_gpc_magic_files(&$item, $key) {
 715    if ($key != 'tmp_name') {
 716      if (is_array($item)) {
 717        array_walk($item, '_fix_gpc_magic_files');
 718      }
 719      else {
 720        $item = stripslashes($item);
 721      }
 722    }
 723  }
 724  
 725  /**
 726   * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
 727   */
 728  function fix_gpc_magic() {
 729    static $fixed = FALSE;
 730    if (!$fixed && ini_get('magic_quotes_gpc')) {
 731      array_walk($_GET, '_fix_gpc_magic');
 732      array_walk($_POST, '_fix_gpc_magic');
 733      array_walk($_COOKIE, '_fix_gpc_magic');
 734      array_walk($_REQUEST, '_fix_gpc_magic');
 735      array_walk($_FILES, '_fix_gpc_magic_files');
 736      $fixed = TRUE;
 737    }
 738  }
 739  
 740  /**
 741   * Translate strings to the page language or a given language.
 742   *
 743   * Human-readable text that will be displayed somewhere within a page should
 744   * be run through the t() function.
 745   *
 746   * Examples:
 747   * @code
 748   *   if (!$info || !$info['extension']) {
 749   *     form_set_error('picture_upload', t('The uploaded file was not an image.'));
 750   *   }
 751   *
 752   *   $form['submit'] = array(
 753   *     '#type' => 'submit',
 754   *     '#value' => t('Log in'),
 755   *   );
 756   * @endcode
 757   *
 758   * Any text within t() can be extracted by translators and changed into
 759   * the equivalent text in their native language.
 760   *
 761   * Special variables called "placeholders" are used to signal dynamic
 762   * information in a string which should not be translated. Placeholders
 763   * can also be used for text that may change from time to time (such as
 764   * link paths) to be changed without requiring updates to translations.
 765   *
 766   * For example:
 767   * @code
 768   *   $output = t('There are currently %members and %visitors online.', array(
 769   *     '%members' => format_plural($total_users, '1 user', '@count users'),
 770   *     '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
 771   * @endcode
 772   *
 773   * There are three styles of placeholders:
 774   * - !variable, which indicates that the text should be inserted as-is. This is
 775   *   useful for inserting variables into things like e-mail.
 776   *   @code
 777   *     $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))));
 778   *   @endcode
 779   *
 780   * - @variable, which indicates that the text should be run through
 781   *   check_plain, to escape HTML characters. Use this for any output that's
 782   *   displayed within a Drupal page.
 783   *   @code
 784   *     drupal_set_title($title = t("@name's blog", array('@name' => $account->name)));
 785   *   @endcode
 786   *
 787   * - %variable, which indicates that the string should be HTML escaped and
 788   *   highlighted with theme_placeholder() which shows up by default as
 789   *   <em>emphasized</em>.
 790   *   @code
 791   *     $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
 792   *   @endcode
 793   *
 794   * When using t(), try to put entire sentences and strings in one t() call.
 795   * This makes it easier for translators, as it provides context as to what
 796   * each word refers to. HTML markup within translation strings is allowed, but
 797   * should be avoided if possible. The exception are embedded links; link
 798   * titles add a context for translators, so should be kept in the main string.
 799   *
 800   * Here is an example of incorrect usage of t():
 801   * @code
 802   *   $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact')));
 803   * @endcode
 804   *
 805   * Here is an example of t() used correctly:
 806   * @code
 807   *   $output .= '<p>'. t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) .'</p>';
 808   * @endcode
 809   *
 810   * Avoid escaping quotation marks wherever possible.
 811   *
 812   * Incorrect:
 813   * @code
 814   *   $output .= t('Don\'t click me.');
 815   * @endcode
 816   *
 817   * Correct:
 818   * @code
 819   *   $output .= t("Don't click me.");
 820   * @endcode
 821   *
 822   * Because t() is designed for handling code-based strings, in almost all
 823   * cases, the actual string and not a variable must be passed through t().
 824   *
 825   * Extraction of translations is done based on the strings contained in t()
 826   * calls. If a variable is passed through t(), the content of the variable
 827   * cannot be extracted from the file for translation.
 828   *
 829   * Incorrect:
 830   * @code
 831   *   $message = 'An error occurred.';
 832   *   drupal_set_message(t($message), 'error');
 833   *   $output .= t($message);
 834   * @endcode
 835   *
 836   * Correct:
 837   * @code
 838   *   $message = t('An error occurred.');
 839   *   drupal_set_message($message, 'error');
 840   *   $output .= $message;
 841   * @endcode
 842   *
 843   * The only case in which variables can be passed safely through t() is when
 844   * code-based versions of the same strings will be passed through t() (or
 845   * otherwise extracted) elsewhere.
 846   *
 847   * In some cases, modules may include strings in code that can't use t()
 848   * calls. For example, a module may use an external PHP application that
 849   * produces strings that are loaded into variables in Drupal for output.
 850   * In these cases, module authors may include a dummy file that passes the
 851   * relevant strings through t(). This approach will allow the strings to be
 852   * extracted.
 853   *
 854   * Sample external (non-Drupal) code:
 855   * @code
 856   *   class Time {
 857   *     public $yesterday = 'Yesterday';
 858   *     public $today = 'Today';
 859   *     public $tomorrow = 'Tomorrow';
 860   *   }
 861   * @endcode
 862   *
 863   * Sample dummy file.
 864   * @code
 865   *   // Dummy function included in example.potx.inc.
 866   *   function example_potx() {
 867   *     $strings = array(
 868   *       t('Yesterday'),
 869   *       t('Today'),
 870   *       t('Tomorrow'),
 871   *     );
 872   *     // No return value needed, since this is a dummy function.
 873   *   }
 874   * @endcode
 875   *
 876   * Having passed strings through t() in a dummy function, it is then
 877   * okay to pass variables through t().
 878   *
 879   * Correct (if a dummy file was used):
 880   * @code
 881   *   $time = new Time();
 882   *   $output .= t($time->today);
 883   * @endcode
 884   *
 885   * However tempting it is, custom data from user input or other non-code
 886   * sources should not be passed through t(). Doing so leads to the following
 887   * problems and errors:
 888   *  - The t() system doesn't support updates to existing strings. When user
 889   *    data is updated, the next time it's passed through t() a new record is
 890   *    created instead of an update. The database bloats over time and any
 891   *    existing translations are orphaned with each update.
 892   *  - The t() system assumes any data it receives is in English. User data may
 893   *    be in another language, producing translation errors.
 894   *  - The "Built-in interface" text group in the locale system is used to
 895   *    produce translations for storage in .po files. When non-code strings are
 896   *    passed through t(), they are added to this text group, which is rendered
 897   *    inaccurate since it is a mix of actual interface strings and various user
 898   *    input strings of uncertain origin.
 899   *
 900   * Incorrect:
 901   * @code
 902   *   $item = item_load();
 903   *   $output .= check_plain(t($item['title']));
 904   * @endcode
 905   *
 906   * Instead, translation of these data can be done through the locale system,
 907   * either directly or through helper functions provided by contributed
 908   * modules.
 909   * @see hook_locale()
 910   *
 911   * During installation, st() is used in place of t(). Code that may be called
 912   * during installation or during normal operation should use the get_t()
 913   * helper function.
 914   * @see st()
 915   * @see get_t()
 916   *
 917   * @param $string
 918   *   A string containing the English string to translate.
 919   * @param $args
 920   *   An associative array of replacements to make after translation. Incidences
 921   *   of any key in this array are replaced with the corresponding value. Based
 922   *   on the first character of the key, the value is escaped and/or themed:
 923   *    - !variable: inserted as is
 924   *    - @variable: escape plain text to HTML (check_plain)
 925   *    - %variable: escape text and theme as a placeholder for user-submitted
 926   *      content (check_plain + theme_placeholder)
 927   * @param $langcode
 928   *   Optional language code to translate to a language other than what is used
 929   *   to display the page.
 930   * @return
 931   *   The translated string.
 932   */
 933  function t($string, $args = array(), $langcode = NULL) {
 934    global $language;
 935    static $custom_strings;
 936  
 937    $langcode = isset($langcode) ? $langcode : $language->language;
 938  
 939    // First, check for an array of customized strings. If present, use the array
 940    // *instead of* database lookups. This is a high performance way to provide a
 941    // handful of string replacements. See settings.php for examples.
 942    // Cache the $custom_strings variable to improve performance.
 943    if (!isset($custom_strings[$langcode])) {
 944      $custom_strings[$langcode] = variable_get('locale_custom_strings_'. $langcode, array());
 945    }
 946    // Custom strings work for English too, even if locale module is disabled.
 947    if (isset($custom_strings[$langcode][$string])) {
 948      $string = $custom_strings[$langcode][$string];
 949    }
 950    // Translate with locale module if enabled.
 951    elseif (function_exists('locale') && $langcode != 'en') {
 952      $string = locale($string, $langcode);
 953    }
 954    if (empty($args)) {
 955      return $string;
 956    }
 957    else {
 958      // Transform arguments before inserting them.
 959      foreach ($args as $key => $value) {
 960        switch ($key[0]) {
 961          case '@':
 962            // Escaped only.
 963            $args[$key] = check_plain($value);
 964            break;
 965  
 966          case '%':
 967          default:
 968            // Escaped and placeholder.
 969            $args[$key] = theme('placeholder', $value);
 970            break;
 971  
 972          case '!':
 973            // Pass-through.
 974        }
 975      }
 976      return strtr($string, $args);
 977    }
 978  }
 979  
 980  /**
 981   * @defgroup validation Input validation
 982   * @{
 983   * Functions to validate user input.
 984   */
 985  
 986  /**
 987   * Verifies the syntax of the given e-mail address.
 988   *
 989   * See RFC 2822 for details.
 990   *
 991   * @param $mail
 992   *   A string containing an e-mail address.
 993   * @return
 994   *   1 if the email address is valid, 0 if it is invalid or empty, and FALSE if
 995   *   there is an input error (such as passing in an array instead of a string).
 996   */
 997  function valid_email_address($mail) {
 998    $user = '[a-zA-Z0-9_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\']+';
 999    $domain = '(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])(\.[a-zA-Z0-9]+)*)+';
1000    $ipv4 = '[0-9]{1,3}(\.[0-9]{1,3}){3}';
1001    $ipv6 = '[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7}';
1002  
1003    return preg_match("/^$user@($domain|(\[($ipv4|$ipv6)\]))$/", $mail);
1004  }
1005  
1006  /**
1007   * Verify the syntax of the given URL.
1008   *
1009   * This function should only be used on actual URLs. It should not be used for
1010   * Drupal menu paths, which can contain arbitrary characters.
1011   * Valid values per RFC 3986.
1012   *
1013   * @param $url
1014   *   The URL to verify.
1015   * @param $absolute
1016   *   Whether the URL is absolute (beginning with a scheme such as "http:").
1017   * @return
1018   *   TRUE if the URL is in a valid format.
1019   */
1020  function valid_url($url, $absolute = FALSE) {
1021    if ($absolute) {
1022      return (bool)preg_match("
1023        /^                                                      # Start at the beginning of the text
1024        (?:ftp|https?|feed):\/\/                                # Look for ftp, http, https or feed schemes
1025        (?:                                                     # Userinfo (optional) which is typically
1026          (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)*      # a username or a username and password
1027          (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@          # combination
1028        )?
1029        (?:
1030          (?:[a-z0-9\-\.]|%[0-9a-f]{2})+                        # A domain name or a IPv4 address
1031          |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\])         # or a well formed IPv6 address
1032        )
1033        (?::[0-9]+)?                                            # Server port number (optional)
1034        (?:[\/|\?]
1035          (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})   # The path and query (optional)
1036        *)?
1037      $/xi", $url);
1038    }
1039    else {
1040      return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
1041    }
1042  }
1043  
1044  
1045  /**
1046   * @} End of "defgroup validation".
1047   */
1048  
1049  /**
1050   * Register an event for the current visitor (hostname/IP) to the flood control mechanism.
1051   *
1052   * @param $name
1053   *   The name of an event.
1054   */
1055  function flood_register_event($name) {
1056    db_query("INSERT INTO {flood} (event, hostname, timestamp) VALUES ('%s', '%s', %d)", $name, ip_address(), time());
1057  }
1058  
1059  /**
1060   * Check if the current visitor (hostname/IP) is allowed to proceed with the specified event.
1061   *
1062   * The user is allowed to proceed if he did not trigger the specified event more
1063   * than $threshold times per hour.
1064   *
1065   * @param $name
1066   *   The name of the event.
1067   * @param $threshold
1068   *   The maximum number of the specified event per hour (per visitor).
1069   * @return
1070   *   True if the user did not exceed the hourly threshold. False otherwise.
1071   */
1072  function flood_is_allowed($name, $threshold) {
1073    $number = db_result(db_query("SELECT COUNT(*) FROM {flood} WHERE event = '%s' AND hostname = '%s' AND timestamp > %d", $name, ip_address(), time() - 3600));
1074    return ($number < $threshold ? TRUE : FALSE);
1075  }
1076  
1077  function check_file($filename) {
1078    return is_uploaded_file($filename);
1079  }
1080  
1081  /**
1082   * Prepare a URL for use in an HTML attribute. Strips harmful protocols.
1083   */
1084  function check_url($uri) {
1085    return filter_xss_bad_protocol($uri, FALSE);
1086  }
1087  
1088  /**
1089   * @defgroup format Formatting
1090   * @{
1091   * Functions to format numbers, strings, dates, etc.
1092   */
1093  
1094  /**
1095   * Formats an RSS channel.
1096   *
1097   * Arbitrary elements may be added using the $args associative array.
1098   */
1099  function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
1100    global $language;
1101    $langcode = $langcode ? $langcode : $language->language;
1102  
1103    $output = "<channel>\n";
1104    $output .= ' <title>'. check_plain($title) ."</title>\n";
1105    $output .= ' <link>'. check_url($link) ."</link>\n";
1106  
1107    // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
1108    // We strip all HTML tags, but need to prevent double encoding from properly
1109    // escaped source data (such as &amp becoming &amp;amp;).
1110    $output .= ' <description>'. check_plain(decode_entities(strip_tags($description))) ."</description>\n";
1111    $output .= ' <language>'. check_plain($langcode) ."</language>\n";
1112    $output .= format_xml_elements($args);
1113    $output .= $items;
1114    $output .= "</channel>\n";
1115  
1116    return $output;
1117  }
1118  
1119  /**
1120   * Format a single RSS item.
1121   *
1122   * Arbitrary elements may be added using the $args associative array.
1123   */
1124  function format_rss_item($title, $link, $description, $args = array()) {
1125    $output = "<item>\n";
1126    $output .= ' <title>'. check_plain($title) ."</title>\n";
1127    $output .= ' <link>'. check_url($link) ."</link>\n";
1128    $output .= ' <description>'. check_plain($description) ."</description>\n";
1129    $output .= format_xml_elements($args);
1130    $output .= "</item>\n";
1131  
1132    return $output;
1133  }
1134  
1135  /**
1136   * Format XML elements.
1137   *
1138   * @param $array
1139   *   An array where each item represent an element and is either a:
1140   *   - (key => value) pair (<key>value</key>)
1141   *   - Associative array with fields:
1142   *     - 'key': element name
1143   *     - 'value': element contents
1144   *     - 'attributes': associative array of element attributes
1145   *
1146   * In both cases, 'value' can be a simple string, or it can be another array
1147   * with the same format as $array itself for nesting.
1148   */
1149  function format_xml_elements($array) {
1150    $output = '';
1151    foreach ($array as $key => $value) {
1152      if (is_numeric($key)) {
1153        if ($value['key']) {
1154          $output .= ' <'. $value['key'];
1155          if (isset($value['attributes']) && is_array($value['attributes'])) {
1156            $output .= drupal_attributes($value['attributes']);
1157          }
1158  
1159          if (isset($value['value']) && $value['value'] != '') {
1160            $output .= '>'. (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) .'</'. $value['key'] .">\n";
1161          }
1162          else {
1163            $output .= " />\n";
1164          }
1165        }
1166      }
1167      else {
1168        $output .= ' <'. $key .'>'. (is_array($value) ? format_xml_elements($value) : check_plain($value)) ."</$key>\n";
1169      }
1170    }
1171    return $output;
1172  }
1173  
1174  /**
1175   * Format a string containing a count of items.
1176   *
1177   * This function ensures that the string is pluralized correctly. Since t() is
1178   * called by this function, make sure not to pass already-localized strings to
1179   * it.
1180   *
1181   * For example:
1182   * @code
1183   *   $output = format_plural($node->comment_count, '1 comment', '@count comments');
1184   * @endcode
1185   *
1186   * Example with additional replacements:
1187   * @code
1188   *   $output = format_plural($update_count,
1189   *     'Changed the content type of 1 post from %old-type to %new-type.',
1190   *     'Changed the content type of @count posts from %old-type to %new-type.',
1191   *     array('%old-type' => $info->old_type, '%new-type' => $info->new_type)));
1192   * @endcode
1193   *
1194   * @param $count
1195   *   The item count to display.
1196   * @param $singular
1197   *   The string for the singular case. Please make sure it is clear this is
1198   *   singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
1199   *   Do not use @count in the singular string.
1200   * @param $plural
1201   *   The string for the plural case. Please make sure it is clear this is plural,
1202   *   to ease translation. Use @count in place of the item count, as in "@count
1203   *   new comments".
1204   * @param $args
1205   *   An associative array of replacements to make after translation. Incidences
1206   *   of any key in this array are replaced with the corresponding value.
1207   *   Based on the first character of the key, the value is escaped and/or themed:
1208   *    - !variable: inserted as is
1209   *    - @variable: escape plain text to HTML (check_plain)
1210   *    - %variable: escape text and theme as a placeholder for user-submitted
1211   *      content (check_plain + theme_placeholder)
1212   *   Note that you do not need to include @count in this array.
1213   *   This replacement is done automatically for the plural case.
1214   * @param $langcode
1215   *   Optional language code to translate to a language other than
1216   *   what is used to display the page.
1217   * @return
1218   *   A translated string.
1219   */
1220  function format_plural($count, $singular, $plural, $args = array(), $langcode = NULL) {
1221    $args['@count'] = $count;
1222    if ($count == 1) {
1223      return t($singular, $args, $langcode);
1224    }
1225  
1226    // Get the plural index through the gettext formula.
1227    $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, $langcode) : -1;
1228    // Backwards compatibility.
1229    if ($index < 0) {
1230      return t($plural, $args, $langcode);
1231    }
1232    else {
1233      switch ($index) {
1234        case "0":
1235          return t($singular, $args, $langcode);
1236        case "1":
1237          return t($plural, $args, $langcode);
1238        default:
1239          unset($args['@count']);
1240          $args['@count['. $index .']'] = $count;
1241          return t(strtr($plural, array('@count' => '@count['. $index .']')), $args, $langcode);
1242      }
1243    }
1244  }
1245  
1246  /**
1247   * Parse a given byte count.
1248   *
1249   * @param $size
1250   *   A size expressed as a number of bytes with optional SI size and unit
1251   *   suffix (e.g. 2, 3K, 5MB, 10G).
1252   * @return
1253   *   An integer representation of the size.
1254   */
1255  function parse_size($size) {
1256    $suffixes = array(
1257      '' => 1,
1258      'k' => 1024,
1259      'm' => 1048576, // 1024 * 1024
1260      'g' => 1073741824, // 1024 * 1024 * 1024
1261    );
1262    if (preg_match('/([0-9]+)\s*(k|m|g)?(b?(ytes?)?)/i', $size, $match)) {
1263      return $match[1] * $suffixes[drupal_strtolower($match[2])];
1264    }
1265  }
1266  
1267  /**
1268   * Generate a string representation for the given byte count.
1269   *
1270   * @param $size
1271   *   A size in bytes.
1272   * @param $langcode
1273   *   Optional language code to translate to a language other than what is used
1274   *   to display the page.
1275   * @return
1276   *   A translated string representation of the size.
1277   */
1278  function format_size($size, $langcode = NULL) {
1279    if ($size < 1024) {
1280      return format_plural($size, '1 byte', '@count bytes', array(), $langcode);
1281    }
1282    else {
1283      $size = round($size / 1024, 2);
1284      $suffix = t('KB', array(), $langcode);
1285      if ($size >= 1024) {
1286        $size = round($size / 1024, 2);
1287        $suffix = t('MB', array(), $langcode);
1288      }
1289      return t('@size @suffix', array('@size' => $size, '@suffix' => $suffix), $langcode);
1290    }
1291  }
1292  
1293  /**
1294   * Format a time interval with the requested granularity.
1295   *
1296   * @param $timestamp
1297   *   The length of the interval in seconds.
1298   * @param $granularity
1299   *   How many different units to display in the string.
1300   * @param $langcode
1301   *   Optional language code to translate to a language other than
1302   *   what is used to display the page.
1303   * @return
1304   *   A translated string representation of the interval.
1305   */
1306  function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
1307    $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);
1308    $output = '';
1309    foreach ($units as $key => $value) {
1310      $key = explode('|', $key);
1311      if ($timestamp >= $value) {
1312        $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), $langcode);
1313        $timestamp %= $value;
1314        $granularity--;
1315      }
1316  
1317      if ($granularity == 0) {
1318        break;
1319      }
1320    }
1321    return $output ? $output : t('0 sec', array(), $langcode);
1322  }
1323  
1324  /**
1325   * Format a date with the given configured format or a custom format string.
1326   *
1327   * Drupal allows administrators to select formatting strings for 'small',
1328   * 'medium' and 'large' date formats. This function can handle these formats,
1329   * as well as any custom format.
1330   *
1331   * @param $timestamp
1332   *   The exact date to format, as a UNIX timestamp.
1333   * @param $type
1334   *   The format to use. Can be "small", "medium" or "large" for the preconfigured
1335   *   date formats. If "custom" is specified, then $format is required as well.
1336   * @param $format
1337   *   A PHP date format string as required by date(). A backslash should be used
1338   *   before a character to avoid interpreting the character as part of a date
1339   *   format.
1340   * @param $timezone
1341   *   Time zone offset in seconds; if omitted, the user's time zone is used.
1342   * @param $langcode
1343   *   Optional language code to translate to a language other than what is used
1344   *   to display the page.
1345   * @return
1346   *   A translated date string in the requested format.
1347   */
1348  function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
1349    if (!isset($timezone)) {
1350      global $user;
1351      if (variable_get('configurable_timezones', 1) && $user->uid && strlen($user->timezone)) {
1352        $timezone = $user->timezone;
1353      }
1354      else {
1355        $timezone = variable_get('date_default_timezone', 0);
1356      }
1357    }
1358  
1359    $timestamp += $timezone;
1360  
1361    switch ($type) {
1362      case 'small':
1363        $format = variable_get('date_format_short', 'm/d/Y - H:i');
1364        break;
1365      case 'large':
1366        $format = variable_get('date_format_long', 'l, F j, Y - H:i');
1367        break;
1368      case 'custom':
1369        // No change to format.
1370        break;
1371      case 'medium':
1372      default:
1373        $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
1374    }
1375  
1376    $max = strlen($format);
1377    $date = '';
1378    for ($i = 0; $i < $max; $i++) {
1379      $c = $format[$i];
1380      if (strpos('AaDlM', $c) !== FALSE) {
1381        $date .= t(gmdate($c, $timestamp), array(), $langcode);
1382      }
1383      else if ($c == 'F') {
1384        // Special treatment for long month names: May is both an abbreviation
1385        // and a full month name in English, but other languages have
1386        // different abbreviations.
1387        $date .= trim(t('!long-month-name '. gmdate($c, $timestamp), array('!long-month-name' => ''), $langcode));
1388      }
1389      else if (strpos('BdgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) {
1390        $date .= gmdate($c, $timestamp);
1391      }
1392      else if ($c == 'r') {
1393        $date .= format_date($timestamp - $timezone, 'custom', 'D, d M Y H:i:s O', $timezone, $langcode);
1394      }
1395      else if ($c == 'O') {
1396        $date .= sprintf('%s%02d%02d', ($timezone < 0 ? '-' : '+'), abs($timezone / 3600), abs($timezone % 3600) / 60);
1397      }
1398      else if ($c == 'Z') {
1399        $date .= $timezone;
1400      }
1401      else if ($c == '\\') {
1402        $date .= $format[++$i];
1403      }
1404      else {
1405        $date .= $c;
1406      }
1407    }
1408  
1409    return $date;
1410  }
1411  
1412  /**
1413   * @} End of "defgroup format".
1414   */
1415  
1416  /**
1417   * Generates an internal or external URL.
1418   *
1419   * When creating links in modules, consider whether l() could be a better
1420   * alternative than url().
1421   *
1422   * @param $path
1423   *   The internal path or external URL being linked to, such as "node/34" or
1424   *   "http://example.com/foo". A few notes:
1425   *   - If you provide a full URL, it will be considered an external URL.
1426   *   - If you provide only the path (e.g. "node/34"), it will be
1427   *     considered an internal link. In this case, it should be a system URL,
1428   *     and it will be replaced with the alias, if one exists. Additional query
1429   *     arguments for internal paths must be supplied in $options['query'], not
1430   *     included in $path.
1431   *   - If you provide an internal path and $options['alias'] is set to TRUE, the
1432   *     path is assumed already to be the correct path alias, and the alias is
1433   *     not looked up.
1434   *   - The special string '<front>' generates a link to the site's base URL.
1435   *   - If your external URL contains a query (e.g. http://example.com/foo?a=b),
1436   *     then you can either URL encode the query keys and values yourself and
1437   *     include them in $path, or use $options['query'] to let this function
1438   *     URL encode them.
1439   * @param $options
1440   *   An associative array of additional options, with the following elements:
1441   *   - 'query': A URL-encoded query string to append to the link, or an array of
1442   *     query key/value-pairs without any URL-encoding.
1443   *   - 'fragment': A fragment identifier (named anchor) to append to the URL.
1444   *     Do not include the leading '#' character.
1445   *   - 'absolute' (default FALSE): Whether to force the output to be an absolute
1446   *     link (beginning with http:). Useful for links that will be displayed
1447   *     outside the site, such as in an RSS feed.
1448   *   - 'alias' (default FALSE): Whether the given path is a URL alias already.
1449   *   - 'external': Whether the given path is an external URL.
1450   *   - 'language': An optional language object. Used to build the URL to link
1451   *     to and look up the proper alias for the link.
1452   *   - 'base_url': Only used internally, to modify the base URL when a language
1453   *     dependent URL requires so.
1454   *   - 'prefix': Only used internally, to modify the path when a language
1455   *     dependent URL requires so.
1456   *
1457   * @return
1458   *   A string containing a URL to the given path.
1459   */
1460  function url($path = NULL, $options = array()) {
1461    // Merge in defaults.
1462    $options += array(
1463      'fragment' => '',
1464      'query' => '',
1465      'absolute' => FALSE,
1466      'alias' => FALSE,
1467      'prefix' => ''
1468    );
1469    if (!isset($options['external'])) {
1470      // Return an external link if $path contains an allowed absolute URL.
1471      // Only call the slow filter_xss_bad_protocol if $path contains a ':' before
1472      // any / ? or #.
1473      $colonpos = strpos($path, ':');
1474      $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path));
1475    }
1476  
1477    // May need language dependent rewriting if language.inc is present.
1478    if (function_exists('language_url_rewrite')) {
1479      language_url_rewrite($path, $options);
1480    }
1481    if ($options['fragment']) {
1482      $options['fragment'] = '#'. $options['fragment'];
1483    }
1484    if (is_array($options['query'])) {
1485      $options['query'] = drupal_query_string_encode($options['query']);
1486    }
1487  
1488    if ($options['external']) {
1489      // Split off the fragment.
1490      if (strpos($path, '#') !== FALSE) {
1491        list($path, $old_fragment) = explode('#', $path, 2);
1492        if (isset($old_fragment) && !$options['fragment']) {
1493          $options['fragment'] = '#'. $old_fragment;
1494        }
1495      }
1496      // Append the query.
1497      if ($options['query']) {
1498        $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query'];
1499      }
1500      // Reassemble.
1501      return $path . $options['fragment'];
1502    }
1503  
1504    global $base_url;
1505    static $script;
1506  
1507    if (!isset($script)) {
1508      // On some web servers, such as IIS, we can't omit "index.php". So, we
1509      // generate "index.php?q=foo" instead of "?q=foo" on anything that is not
1510      // Apache.
1511      $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : '';
1512    }
1513  
1514    if (!isset($options['base_url'])) {
1515      // The base_url might be rewritten from the language rewrite in domain mode.
1516      $options['base_url'] = $base_url;
1517    }
1518  
1519    // Preserve the original path before aliasing.
1520    $original_path = $path;
1521  
1522    // The special path '<front>' links to the default front page.
1523    if ($path == '<front>') {
1524      $path = '';
1525    }
1526    elseif (!empty($path) && !$options['alias']) {
1527      $path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : '');
1528    }
1529  
1530    if (function_exists('custom_url_rewrite_outbound')) {
1531      // Modules may alter outbound links by reference.
1532      custom_url_rewrite_outbound($path, $options, $original_path);
1533    }
1534  
1535    $base = $options['absolute'] ? $options['base_url'] .'/' : base_path();
1536    $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
1537    $path = drupal_urlencode($prefix . $path);
1538  
1539    if (variable_get('clean_url', '0')) {
1540      // With Clean URLs.
1541      if ($options['query']) {
1542        return $base . $path .'?'. $options['query'] . $options['fragment'];
1543      }
1544      else {
1545        return $base . $path . $options['fragment'];
1546      }
1547    }
1548    else {
1549      // Without Clean URLs.
1550      $variables = array();
1551      if (!empty($path)) {
1552        $variables[] = 'q='. $path;
1553      }
1554      if (!empty($options['query'])) {
1555        $variables[] = $options['query'];
1556      }
1557      if ($query = join('&', $variables)) {
1558        return $base . $script .'?'. $query . $options['fragment'];
1559      }
1560      else {
1561        return $base . $options['fragment'];
1562      }
1563    }
1564  }
1565  
1566  /**
1567   * Format an attribute string to insert in a tag.
1568   *
1569   * @param $attributes
1570   *   An associative array of HTML attributes.
1571   * @return
1572   *   An HTML string ready for insertion in a tag.
1573   */
1574  function drupal_attributes($attributes = array()) {
1575    if (is_array($attributes)) {
1576      $t = '';
1577      foreach ($attributes as $key => $value) {
1578        $t .= " $key=".'"'. check_plain($value) .'"';
1579      }
1580      return $t;
1581    }
1582  }
1583  
1584  /**
1585   * Formats an internal or external URL link as an HTML anchor tag.
1586   *
1587   * This function correctly handles aliased paths, and adds an 'active' class
1588   * attribute to links that point to the current page (for theming), so all
1589   * internal links output by modules should be generated by this function if
1590   * possible.
1591   *
1592   * @param $text
1593   *   The link text for the anchor tag.
1594   * @param $path
1595   *   The internal path or external URL being linked to, such as "node/34" or
1596   *   "http://example.com/foo". After the url() function is called to construct
1597   *   the URL from $path and $options, the resulting URL is passed through
1598   *   check_url() before it is inserted into the HTML anchor tag, to ensure
1599   *   well-formed HTML. See url() for more information and notes.
1600   * @param $options
1601   *   An associative array of additional options, with the following elements:
1602   *   - 'attributes': An associative array of HTML attributes to apply to the
1603   *     anchor tag.
1604   *   - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
1605   *     example, to make an image tag into a link, this must be set to TRUE, or
1606   *     you will see the escaped HTML image tag.
1607   *   - 'language': An optional language object. If the path being linked to is
1608   *     internal to the site, $options['language'] is used to look up the alias
1609   *     for the URL, and to determine whether the link is "active", or pointing
1610   *     to the current page (the language as well as the path must match).This
1611   *     element is also used by url().
1612   *   - Additional $options elements used by the url() function.
1613   *
1614   * @return
1615   *   An HTML string containing a link to the given path.
1616   */
1617  function l($text, $path, $options = array()) {
1618    global $language;
1619  
1620    // Merge in defaults.
1621    $options += array(
1622        'attributes' => array(),
1623        'html' => FALSE,
1624      );
1625  
1626    // Append active class.
1627    if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
1628        (empty($options['language']) || $options['language']->language == $language->language)) {
1629      if (isset($options['attributes']['class'])) {
1630        $options['attributes']['class'] .= ' active';
1631      }
1632      else {
1633        $options['attributes']['class'] = 'active';
1634      }
1635    }
1636  
1637    // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
1638    // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
1639    if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
1640      $options['attributes']['title'] = strip_tags($options['attributes']['title']);
1641    }
1642  
1643    return '<a href="'. check_url(url($path, $options)) .'"'. drupal_attributes($options['attributes']) .'>'. ($options['html'] ? $text : check_plain($text)) .'</a>';
1644  }
1645  
1646  /**
1647   * Perform end-of-request tasks.
1648   *
1649   * This function sets the page cache if appropriate, and allows modules to
1650   * react to the closing of the page by calling hook_exit().
1651   */
1652  function drupal_page_footer() {
1653    if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) {
1654      page_set_cache();
1655    }
1656  
1657    module_invoke_all('exit');
1658  }
1659  
1660  /**
1661   * Form an associative array from a linear array.
1662   *
1663   * This function walks through the provided array and constructs an associative
1664   * array out of it. The keys of the resulting array will be the values of the
1665   * input array. The values will be the same as the keys unless a function is
1666   * specified, in which case the output of the function is used for the values
1667   * instead.
1668   *
1669   * @param $array
1670   *   A linear array.
1671   * @param $function
1672   *   A name of a function to apply to all values before output.
1673   *
1674   * @return
1675   *   An associative array.
1676   */
1677  function drupal_map_assoc($array, $function = NULL) {
1678    if (!isset($function)) {
1679      $result = array();
1680      foreach ($array as $value) {
1681        $result[$value] = $value;
1682      }
1683      return $result;
1684    }
1685    elseif (function_exists($function)) {
1686      $result = array();
1687      foreach ($array as $value) {
1688        $result[$value] = $function($value);
1689      }
1690      return $result;
1691    }
1692  }
1693  
1694  /**
1695   * Evaluate a string of PHP code.
1696   *
1697   * This is a wrapper around PHP's eval(). It uses output buffering to capture both
1698   * returned and printed text. Unlike eval(), we require code to be surrounded by
1699   * <?php ?> tags; in other words, we evaluate the code as if it were a stand-alone
1700   * PHP file.
1701   *
1702   * Using this wrapper also ensures that the PHP code which is evaluated can not
1703   * overwrite any variables in the calling code, unlike a regular eval() call.
1704   *
1705   * @param $code
1706   *   The code to evaluate.
1707   * @return
1708   *   A string containing the printed output of the code, followed by the returned
1709   *   output of the code.
1710   */
1711  function drupal_eval($code) {
1712    global $theme_path, $theme_info, $conf;
1713  
1714    // Store current theme path.
1715    $old_theme_path = $theme_path;
1716  
1717    // Restore theme_path to the theme, as long as drupal_eval() executes,
1718    // so code evaluted will not see the caller module as the current theme.
1719    // If theme info is not initialized get the path from theme_default.
1720    if (!isset($theme_info)) {
1721      $theme_path = drupal_get_path('theme', $conf['theme_default']);
1722    }
1723    else {
1724      $theme_path = dirname($theme_info->filename);
1725    }
1726  
1727    ob_start();
1728    print eval('?>'. $code);
1729    $output = ob_get_contents();
1730    ob_end_clean();
1731  
1732    // Recover original theme path.
1733    $theme_path = $old_theme_path;
1734  
1735    return $output;
1736  }
1737  
1738  /**
1739   * Returns the path to a system item (module, theme, etc.).
1740   *
1741   * @param $type
1742   *   The type of the item (i.e. theme, theme_engine, module, profile).
1743   * @param $name
1744   *   The name of the item for which the path is requested.
1745   *
1746   * @return
1747   *   The path to the requested item.
1748   */
1749  function drupal_get_path($type, $name) {
1750    return dirname(drupal_get_filename($type, $name));
1751  }
1752  
1753  /**
1754   * Returns the base URL path of the Drupal installation.
1755   * At the very least, this will always default to /.
1756   */
1757  function base_path() {
1758    return $GLOBALS['base_path'];
1759  }
1760  
1761  /**
1762   * Provide a substitute clone() function for PHP4.
1763   */
1764  function drupal_clone($object) {
1765    return version_compare(phpversion(), '5.0') < 0 ? $object : clone($object);
1766  }
1767  
1768  /**
1769   * Add a <link> tag to the page's HEAD.
1770   */
1771  function drupal_add_link($attributes) {
1772    drupal_set_html_head('<link'. drupal_attributes($attributes) .' />');
1773  }
1774  
1775  /**
1776   * Adds a CSS file to the stylesheet queue.
1777   *
1778   * @param $path
1779   *   (optional) The path to the CSS file relative to the base_path(), e.g.,
1780   *   modules/devel/devel.css.
1781   *
1782   *   Modules should always prefix the names of their CSS files with the module
1783   *   name, for example: system-menus.css rather than simply menus.css. Themes
1784   *   can override module-supplied CSS files based on their filenames, and this
1785   *   prefixing helps prevent confusing name collisions for theme developers.
1786   *   See drupal_get_css where the overrides are performed.
1787   *
1788   *   If the direction of the current language is right-to-left (Hebrew,
1789   *   Arabic, etc.), the function will also look for an RTL CSS file and append
1790   *   it to the list. The name of this file should have an '-rtl.css' suffix.
1791   *   For example a CSS file called 'name.css' will have a 'name-rtl.css'
1792   *   file added to the list, if exists in the same directory. This CSS file
1793   *   should contain overrides for properties which should be reversed or
1794   *   otherwise different in a right-to-left display.
1795   * @param $type
1796   *   (optional) The type of stylesheet that is being added. Types are: module
1797   *   or theme.
1798   * @param $media
1799   *   (optional) The media type for the stylesheet, e.g., all, print, screen.
1800   * @param $preprocess
1801   *   (optional) Should this CSS file be aggregated and compressed if this
1802   *   feature has been turned on under the performance section?
1803   *
1804   *   What does this actually mean?
1805   *   CSS preprocessing is the process of aggregating a bunch of separate CSS
1806   *   files into one file that is then compressed by removing all extraneous
1807   *   white space.
1808   *
1809   *   The reason for merging the CSS files is outlined quite thoroughly here:
1810   *   http://www.die.net/musings/page_load_time/
1811   *   "Load fewer external objects. Due to request overhead, one bigger file
1812   *   just loads faster than two smaller ones half its size."
1813   *
1814   *   However, you should *not* preprocess every file as this can lead to
1815   *   redundant caches. You should set $preprocess = FALSE when:
1816   *
1817   *     - Your styles are only used rarely on the site. This could be a special
1818   *       admin page, the homepage, or a handful of pages that does not represent
1819   *       the majority of the pages on your site.
1820   *
1821   *   Typical candidates for caching are for example styles for nodes across
1822   *   the site, or used in the theme.
1823   *
1824   * @return
1825   *   An array of CSS files.
1826   *
1827   * @see drupal_get_css()
1828   */
1829  function drupal_add_css($path = NULL, $type = 'module', $media = 'all', $preprocess = TRUE) {
1830    static $css = array();
1831    global $language;
1832  
1833    // Create an array of CSS files for each media type first, since each type needs to be served
1834    // to the browser differently.
1835    if (isset($path)) {
1836      // This check is necessary to ensure proper cascading of styles and is faster than an asort().
1837      if (!isset($css[$media])) {
1838        $css[$media] = array('module' => array(), 'theme' => array());
1839      }
1840      $css[$media][$type][$path] = $preprocess;
1841  
1842      // If the current language is RTL, add the CSS file with RTL overrides.
1843      if ($language->direction == LANGUAGE_RTL) {
1844        $rtl_path = str_replace('.css', '-rtl.css', $path);
1845        if (file_exists($rtl_path)) {
1846          $css[$media][$type][$rtl_path] = $preprocess;
1847        }
1848      }
1849    }
1850  
1851    return $css;
1852  }
1853  
1854  /**
1855   * Returns a themed representation of all stylesheets that should be attached to the page.
1856   *
1857   * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
1858   * This ensures proper cascading of styles so themes can easily override
1859   * module styles through CSS selectors.
1860   *
1861   * Themes may replace module-defined CSS files by adding a stylesheet with the
1862   * same filename. For example, themes/garland/system-menus.css would replace
1863   * modules/system/system-menus.css. This allows themes to override complete
1864   * CSS files, rather than specific selectors, when necessary.
1865   *
1866   * If the original CSS file is being overridden by a theme, the theme is
1867   * responsible for supplying an accompanying RTL CSS file to replace the
1868   * module's.
1869   *
1870   * @param $css
1871   *   (optional) An array of CSS files. If no array is provided, the default
1872   *   stylesheets array is used instead.
1873   *
1874   * @return
1875   *   A string of XHTML CSS tags.
1876   *
1877   * @see drupal_add_css()
1878   */
1879  function drupal_get_css($css = NULL) {
1880    $output = '';
1881    if (!isset($css)) {
1882      $css = drupal_add_css();
1883    }
1884    $no_module_preprocess = '';
1885    $no_theme_preprocess = '';
1886  
1887    $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
1888    $directory = file_directory_path();
1889    $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
1890  
1891    // A dummy query-string is added to filenames, to gain control over
1892    // browser-caching. The string changes on every update or full cache
1893    // flush, forcing browsers to load a new copy of the files, as the
1894    // URL changed.
1895    $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
1896  
1897    foreach ($css as $media => $types) {
1898      // If CSS preprocessing is off, we still need to output the styles.
1899      // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones.
1900      foreach ($types as $type => $files) {
1901        if ($type == 'module') {
1902          // Setup theme overrides for module styles.
1903          $theme_styles = array();
1904          foreach (array_keys($css[$media]['theme']) as $theme_style) {
1905            $theme_styles[] = basename($theme_style);
1906          }
1907        }
1908        foreach ($types[$type] as $file => $preprocess) {
1909          // If the theme supplies its own style using the name of the module style, skip its inclusion.
1910          // This includes any RTL styles associated with its main LTR counterpart.
1911          if ($type == 'module' && in_array(str_replace('-rtl.css', '.css', basename($file)), $theme_styles)) {
1912            // Unset the file to prevent its inclusion when CSS aggregation is enabled.
1913            unset($types[$type][$file]);
1914            continue;
1915          }
1916          // Only include the stylesheet if it exists.
1917          if (file_exists($file)) {
1918            if (!$preprocess || !($is_writable && $preprocess_css)) {
1919              // If a CSS file is not to be preprocessed and it's a module CSS file, it needs to *always* appear at the *top*,
1920              // regardless of whether preprocessing is on or off.
1921              if (!$preprocess && $type == 'module') {
1922                $no_module_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
1923              }
1924              // If a CSS file is not to be preprocessed and it's a theme CSS file, it needs to *always* appear at the *bottom*,
1925              // regardless of whether preprocessing is on or off.
1926              else if (!$preprocess && $type == 'theme') {
1927                $no_theme_preprocess .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
1928              }
1929              else {
1930                $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $file . $query_string .'" />'."\n";
1931              }
1932            }
1933          }
1934        }
1935      }
1936  
1937      if ($is_writable && $preprocess_css) {
1938        // Prefix filename to prevent blocking by firewalls which reject files
1939        // starting with "ad*".
1940        $filename = 'css_'. md5(serialize($types) . $query_string) .'.css';
1941        $preprocess_file = drupal_build_css_cache($types, $filename);
1942        $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $preprocess_file .'" />'."\n";
1943      }
1944    }
1945  
1946    return $no_module_preprocess . $output . $no_theme_preprocess;
1947  }
1948  
1949  /**
1950   * Aggregate and optimize CSS files, putting them in the files directory.
1951   *
1952   * @param $types
1953   *   An array of types of CSS files (e.g., screen, print) to aggregate and
1954   *   compress into one file.
1955   * @param $filename
1956   *   The name of the aggregate CSS file.
1957   * @return
1958   *   The name of the CSS file.
1959   */
1960  function drupal_build_css_cache($types, $filename) {
1961    $data = '';
1962  
1963    // Create the css/ within the files folder.
1964    $csspath = file_create_path('css');
1965    file_check_directory($csspath, FILE_CREATE_DIRECTORY);
1966  
1967    if (!file_exists($csspath .'/'. $filename)) {
1968      // Build aggregate CSS file.
1969      foreach ($types as $type) {
1970        foreach ($type as $file => $cache) {
1971          if ($cache) {
1972            $contents = drupal_load_stylesheet($file, TRUE);
1973            // Return the path to where this CSS file originated from.
1974            $base = base_path() . dirname($file) .'/';
1975            _drupal_build_css_path(NULL, $base);
1976            // Prefix all paths within this CSS file, ignoring external and absolute paths.
1977            $data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents);
1978          }
1979        }
1980      }
1981  
1982      // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
1983      // @import rules must proceed any other style, so we move those to the top.
1984      $regexp = '/@import[^;]+;/i';
1985      preg_match_all($regexp, $data, $matches);
1986      $data = preg_replace($regexp, '', $data);
1987      $data = implode('', $matches[0]) . $data;
1988  
1989      // Create the CSS file.
1990      file_save_data($data, $csspath .'/'. $filename, FILE_EXISTS_REPLACE);
1991    }
1992    return $csspath .'/'. $filename;
1993  }
1994  
1995  /**
1996   * Helper function for drupal_build_css_cache().
1997   *
1998   * This function will prefix all paths within a CSS file.
1999   */
2000  function _drupal_build_css_path($matches, $base = NULL) {
2001    static $_base;
2002    // Store base path for preg_replace_callback.
2003    if (isset($base)) {
2004      $_base = $base;
2005    }
2006  
2007    // Prefix with base and remove '../' segments where possible.
2008    $path = $_base . $matches[1];
2009    $last = '';
2010    while ($path != $last) {
2011      $last = $path;
2012      $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
2013    }
2014    return 'url('. $path .')';
2015  }
2016  
2017  /**
2018   * Loads the stylesheet and resolves all @import commands.
2019   *
2020   * Loads a stylesheet and replaces @import commands with the contents of the
2021   * imported file. Use this instead of file_get_contents when processing
2022   * stylesheets.
2023   *
2024   * The returned contents are compressed removing white space and comments only
2025   * when CSS aggregation is enabled. This optimization will not apply for
2026   * color.module enabled themes with CSS aggregation turned off.
2027   *
2028   * @param $file
2029   *   Name of the stylesheet to be processed.
2030   * @param $optimize
2031   *   Defines if CSS contents should be compressed or not.
2032   * @return
2033   *   Contents of the stylesheet including the imported stylesheets.
2034   */
2035  function drupal_load_stylesheet($file, $optimize = NULL) {
2036    static $_optimize;
2037    // Store optimization parameter for preg_replace_callback with nested @import loops.
2038    if (isset($optimize)) {
2039      $_optimize = $optimize;
2040    }
2041  
2042    $contents = '';
2043    if (file_exists($file)) {
2044      // Load the local CSS stylesheet.
2045      $contents = file_get_contents($file);
2046  
2047      // Change to the current stylesheet's directory.
2048      $cwd = getcwd();
2049      chdir(dirname($file));
2050  
2051      // Replaces @import commands with the actual stylesheet content.
2052      // This happens recursively but omits external files.
2053      $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents);
2054      // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
2055      $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
2056  
2057      if ($_optimize) {
2058        // Perform some safe CSS optimizations.
2059        // Regexp to match comment blocks.
2060        $comment     = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
2061        // Regexp to match double quoted strings.
2062        $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
2063        // Regexp to match single quoted strings.
2064        $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
2065        $contents = preg_replace_callback(
2066          "<$double_quot|$single_quot|$comment>Ss",  // Match all comment blocks along
2067          "_process_comment",                        // with double/single quoted strings
2068          $contents);                                // and feed them to _process_comment().
2069        $contents = preg_replace(
2070          '<\s*([@{}:;,]|\)\s|\s\()\s*>S',           // Remove whitespace around separators,
2071          '\1', $contents);                          // but keep space around parentheses.
2072        // End the file with a new line.
2073        $contents .= "\n";
2074      }
2075  
2076      // Change back directory.
2077      chdir($cwd);
2078    }
2079  
2080    return $contents;
2081  }
2082  
2083  /**
2084   * Process comment blocks.
2085   *
2086   * This is the callback function for the preg_replace_callback()
2087   * used in drupal_load_stylesheet_content(). Support for comment
2088   * hacks is implemented here.
2089   */
2090  function _process_comment($matches) {
2091    static $keep_nextone = FALSE;
2092  
2093    // Quoted string, keep it.
2094    if ($matches[0][0] == "'" || $matches[0][0] == '"') {
2095      return $matches[0];
2096    }
2097    // End of IE-Mac hack, keep it.
2098    if ($keep_nextone) {
2099      $keep_nextone = FALSE;
2100      return $matches[0];
2101    }
2102    switch (strrpos($matches[0], '\\')) {
2103      case FALSE :
2104        // No backslash, strip it.
2105        return '';
2106  
2107      case drupal_strlen($matches[0])-3 :
2108        // Ends with \*/ so is a multi line IE-Mac hack, keep the next one also.
2109        $keep_nextone = TRUE;
2110        return '/*_\*/';
2111  
2112      default :
2113        // Single line IE-Mac hack.
2114        return '/*\_*/';
2115    }
2116  }
2117  
2118  /**
2119   * Loads stylesheets recursively and returns contents with corrected paths.
2120   *
2121   * This function is used for recursive loading of stylesheets and
2122   * returns the stylesheet content with all url() paths corrected.
2123   */
2124  function _drupal_load_stylesheet($matches) {
2125    $filename = $matches[1];
2126    // Load the imported stylesheet and replace @import commands in there as well.
2127    $file = drupal_load_stylesheet($filename);
2128    // Determine the file's directory.
2129    $directory = dirname($filename);
2130    // If the file is in the current directory, make sure '.' doesn't appear in
2131    // the url() path.
2132    $directory = $directory == '.' ? '' : $directory .'/';
2133  
2134    // Alter all internal url() paths. Leave external paths alone. We don't need
2135    // to normalize absolute paths here (i.e. remove folder/... segments) because
2136    // that will be done later.
2137    return preg_replace('/url\s*\(([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file);
2138  }
2139  
2140  /**
2141   * Delete all cached CSS files.
2142   */
2143  function drupal_clear_css_cache() {
2144    file_scan_directory(file_create_path('css'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
2145  }
2146  
2147  /**
2148   * Add a JavaScript file, setting or inline code to the page.
2149   *
2150   * The behavior of this function depends on the parameters it is called with.
2151   * Generally, it handles the addition of JavaScript to the page, either as
2152   * reference to an existing file or as inline code. The following actions can be
2153   * performed using this function:
2154   *
2155   * - Add a file ('core', 'module' and 'theme'):
2156   *   Adds a reference to a JavaScript file to the page. JavaScript files
2157   *   are placed in a certain order, from 'core' first, to 'module' and finally
2158   *   'theme' so that files, that are added later, can override previously added
2159   *   files with ease.
2160   *
2161   * - Add inline JavaScript code ('inline'):
2162   *   Executes a piece of JavaScript code on the current page by placing the code
2163   *   directly in the page. This can, for example, be useful to tell the user that
2164   *   a new message arrived, by opening a pop up, alert box etc.
2165   *
2166   * - Add settings ('setting'):
2167   *   Adds a setting to Drupal's global storage of JavaScript settings. Per-page
2168   *   settings are required by some modules to function properly. The settings
2169   *   will be accessible at Drupal.settings.
2170   *
2171   * @param $data
2172   *   (optional) If given, the value depends on the $type parameter:
2173   *   - 'core', 'module' or 'theme': Path to the file relative to base_path().
2174   *   - 'inline': The JavaScript code that should be placed in the given scope.
2175   *   - 'setting': An array with configuration options as associative array. The
2176   *       array is directly placed in Drupal.settings. You might want to wrap your
2177   *       actual configuration settings in another variable to prevent the pollution
2178   *       of the Drupal.settings namespace.
2179   * @param $type
2180   *   (optional) The type of JavaScript that should be added to the page. Allowed
2181   *   values are 'core', 'module', 'theme', 'inline' and 'setting'. You
2182   *   can, however, specify any value. It is treated as a reference to a JavaScript
2183   *   file. Defaults to 'module'.
2184   * @param $scope
2185   *   (optional) The location in which you want to place the script. Possible
2186   *   values are 'header' and 'footer' by default. If your theme implements
2187   *   different locations, however, you can also use these.
2188   * @param $defer
2189   *   (optional) If set to TRUE, the defer attribute is set on the <script> tag.
2190   *   Defaults to FALSE. This parameter is not used with $type == 'setting'.
2191   * @param $cache
2192   *   (optional) If set to FALSE, the JavaScript file is loaded anew on every page
2193   *   call, that means, it is not cached. Defaults to TRUE. Used only when $type
2194   *   references a JavaScript file.
2195   * @param $preprocess
2196   *   (optional) Should this JS file be aggregated if this
2197   *   feature has been turned on under the performance section?
2198   * @return
2199   *   If the first parameter is NULL, the JavaScript array that has been built so
2200   *   far for $scope is returned. If the first three parameters are NULL,
2201   *   an array with all scopes is returned.
2202   */
2203  function drupal_add_js($data = NULL, $type = 'module', $scope = 'header', $defer = FALSE, $cache = TRUE, $preprocess = TRUE) {
2204    static $javascript = array();
2205  
2206    if (isset($data)) {
2207  
2208      // Add jquery.js and drupal.js, as well as the basePath setting, the
2209      // first time a Javascript file is added.
2210      if (empty($javascript)) {
2211        $javascript['header'] = array(
2212          'core' => array(
2213            'misc/jquery.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
2214            'misc/drupal.js' => array('cache' => TRUE, 'defer' => FALSE, 'preprocess' => TRUE),
2215          ),
2216          'module' => array(),
2217          'theme' => array(),
2218          'setting' => array(
2219            array('basePath' => base_path()),
2220          ),
2221          'inline' => array(),
2222        );
2223      }
2224  
2225      if (isset($scope) && !isset($javascript[$scope])) {
2226        $javascript[$scope] = array('core' => array(), 'module' => array(), 'theme' => array(), 'setting' => array(), 'inline' => array());
2227      }
2228  
2229      if (isset($type) && isset($scope) && !isset($javascript[$scope][$type])) {
2230        $javascript[$scope][$type] = array();
2231      }
2232  
2233      switch ($type) {
2234        case 'setting':
2235          $javascript[$scope][$type][] = $data;
2236          break;
2237        case 'inline':
2238          $javascript[$scope][$type][] = array('code' => $data, 'defer' => $defer);
2239          break;
2240        default:
2241          // If cache is FALSE, don't preprocess the JS file.
2242          $javascript[$scope][$type][$data] = array('cache' => $cache, 'defer' => $defer, 'preprocess' => (!$cache ? FALSE : $preprocess));
2243      }
2244    }
2245  
2246    if (isset($scope)) {
2247  
2248      if (isset($javascript[$scope])) {
2249        return $javascript[$scope];
2250      }
2251      else {
2252        return array();
2253      }
2254    }
2255    else {
2256      return $javascript;
2257    }
2258  }
2259  
2260  /**
2261   * Returns a themed presentation of all JavaScript code for the current page.
2262   *
2263   * References to JavaScript files are placed in a certain order: first, all
2264   * 'core' files, then all 'module' and finally all 'theme' JavaScript files
2265   * are added to the page. Then, all settings are output, followed by 'inline'
2266   * JavaScript code. If running update.php, all preprocessing is disabled.
2267   *
2268   * @param $scope
2269   *   (optional) The scope for which the JavaScript rules should be returned.
2270   *   Defaults to 'header'.
2271   * @param $javascript
2272   *   (optional) An array with all JavaScript code. Defaults to the default
2273   *   JavaScript array for the given scope.
2274   * @return
2275   *   All JavaScript code segments and includes for the scope as HTML tags.
2276   */
2277  function drupal_get_js($scope = 'header', $javascript = NULL) {
2278    if ((!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') && function_exists('locale_update_js_files')) {
2279      locale_update_js_files();
2280    }
2281  
2282    if (!isset($javascript)) {
2283      $javascript = drupal_add_js(NULL, NULL, $scope);
2284    }
2285  
2286    if (empty($javascript)) {
2287      return '';
2288    }
2289  
2290    $output = '';
2291    $preprocessed = '';
2292    $no_preprocess = array('core' => '', 'module' => '', 'theme' => '');
2293    $files = array();
2294    $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
2295    $directory = file_directory_path();
2296    $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
2297  
2298    // A dummy query-string is added to filenames, to gain control over
2299    // browser-caching. The string changes on every update or full cache
2300    // flush, forcing browsers to load a new copy of the files, as the
2301    // URL changed. Files that should not be cached (see drupal_add_js())
2302    // get time() as query-string instead, to enforce reload on every
2303    // page request.
2304    $query_string = '?'. substr(variable_get('css_js_query_string', '0'), 0, 1);
2305  
2306    // For inline Javascript to validate as XHTML, all Javascript containing
2307    // XHTML needs to be wrapped in CDATA. To make that backwards compatible
2308    // with HTML 4, we need to comment out the CDATA-tag.
2309    $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
2310    $embed_suffix = "\n//--><!]]>\n";
2311  
2312    foreach ($javascript as $type => $data) {
2313  
2314      if (!$data) continue;
2315  
2316      switch ($type) {
2317        case 'setting':
2318          $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";
2319          break;
2320        case 'inline':
2321          foreach ($data as $info) {
2322            $output .= '<script type="text/javascript"' . ($info['defer'] ? ' defer="defer"' : '') . '>' . $embed_prefix . $info['code'] . $embed_suffix . "</script>\n";
2323          }
2324          break;
2325        default:
2326          // If JS preprocessing is off, we still need to output the scripts.
2327          // Additionally, go through any remaining scripts if JS preprocessing is on and output the non-cached ones.
2328          foreach ($data as $path => $info) {
2329            if (!$info['preprocess'] || !$is_writable || !$preprocess_js) {
2330              $no_preprocess[$type] .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. base_path() . $path . ($info['cache'] ? $query_string : '?'. time()) ."\"></script>\n";
2331            }
2332            else {
2333              $files[$path] = $info;
2334            }
2335          }
2336      }
2337    }
2338  
2339    // Aggregate any remaining JS files that haven't already been output.
2340    if ($is_writable && $preprocess_js && count($files) > 0) {
2341      // Prefix filename to prevent blocking by firewalls which reject files
2342      // starting with "ad*".
2343      $filename = 'js_'. md5(serialize($files) . $query_string) .'.js';
2344      $preprocess_file = drupal_build_js_cache($files, $filename);
2345      $preprocessed .= '<script type="text/javascript" src="'. base_path() . $preprocess_file .'"></script>'."\n";
2346    }
2347  
2348    // Keep the order of JS files consistent as some are preprocessed and others are not.
2349    // Make sure any inline or JS setting variables appear last after libraries have loaded.
2350    $output = $preprocessed . implode('', $no_preprocess) . $output;
2351  
2352    return $output;
2353  }
2354  
2355  /**
2356   * Assist in adding the tableDrag JavaScript behavior to a themed table.
2357   *
2358   * Draggable tables should be used wherever an outline or list of sortable items
2359   * needs to be arranged by an end-user. Draggable tables are very flexible and
2360   * can manipulate the value of form elements placed within individual columns.
2361   *
2362   * To set up a table to use drag and drop in place of weight select-lists or
2363   * in place of a form that contains parent relationships, the form must be
2364   * themed into a table. The table must have an id attribute set. If using
2365   * theme_table(), the id may be set as such:
2366   * @code
2367   * $output = theme('table', $header, $rows, array('id' => 'my-module-table'));
2368   * return $output;
2369   * @endcode
2370   *
2371   * In the theme function for the form, a special class must be added to each
2372   * form element within the same column, "grouping" them together.
2373   *
2374   * In a situation where a single weight column is being sorted in the table, the
2375   * classes could be added like this (in the theme function):
2376   * @code
2377   * $form['my_elements'][$delta]['weight']['#attributes']['class'] = "my-elements-weight";
2378   * @endcode
2379   *
2380   * Each row of the table must also have a class of "draggable" in order to enable the
2381   * drag handles:
2382   * @code
2383   * $row = array(...);
2384   * $rows[] = array(
2385   *   'data' => $row,
2386   *   'class' => 'draggable',
2387   * );
2388   * @endcode
2389   *
2390   * When tree relationships are present, the two additional classes
2391   * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior:
2392   * - Rows with the 'tabledrag-leaf' class cannot have child rows.
2393   * - Rows with the 'tabledrag-root' class cannot be nested under a parent row.
2394   *
2395   * Calling drupal_add_tabledrag() would then be written as such:
2396   * @code
2397   * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight');
2398   * @endcode
2399   *
2400   * In a more complex case where there are several groups in one column (such as
2401   * the block regions on the admin/build/block page), a separate subgroup class
2402   * must also be added to differentiate the groups.
2403   * @code
2404   * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = "my-elements-weight my-elements-weight-". $region;
2405   * @endcode
2406   *
2407   * $group is still 'my-element-weight', and the additional $subgroup variable
2408   * will be passed in as 'my-elements-weight-'. $region. This also means that
2409   * you'll need to call drupal_add_tabledrag() once for every region added.
2410   *
2411   * @code
2412   * foreach ($regions as $region) {
2413   *   drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight', 'my-elements-weight-'. $region);
2414   * }
2415   * @endcode
2416   *
2417   * In a situation where tree relationships are present, adding multiple
2418   * subgroups is not necessary, because the table will contain indentations that
2419   * provide enough information about the sibling and parent relationships.
2420   * See theme_menu_overview_form() for an example creating a table containing
2421   * parent relationships.
2422   *
2423   * Please note that this function should be called from the theme layer, such as
2424   * in a .tpl.php file, theme_ function, or in a template_preprocess function,
2425   * not in a form declartion. Though the same JavaScript could be added to the
2426   * page using drupal_add_js() directly, this function helps keep template files
2427   * clean and readable. It also prevents tabledrag.js from being added twice
2428   * accidentally.
2429   *
2430   * @param $table_id
2431   *   String containing the target table's id attribute. If the table does not
2432   *   have an id, one will need to be set, such as <table id="my-module-table">.
2433   * @param $action
2434   *   String describing the action to be done on the form item. Either 'match'
2435   *   'depth', or 'order'. Match is typically used for parent relationships.
2436   *   Order is typically used to set weights on other form elements with the same
2437   *   group. Depth updates the target element with the current indentation.
2438   * @param $relationship
2439   *   String describing where the $action variable should be performed. Either
2440   *   'parent', 'sibling', 'group', or 'self'. Parent will only look for fields
2441   *   up the tree. Sibling will look for fields in the same group in rows above
2442   *   and below it. Self affects the dragged row itself. Group affects the
2443   *   dragged row, plus any children below it (the entire dragged group).
2444   * @param $group
2445   *   A class name applied on all related form elements for this action.
2446   * @param $subgroup
2447   *   (optional) If the group has several subgroups within it, this string should
2448   *   contain the class name identifying fields in the same subgroup.
2449   * @param $source
2450   *   (optional) If the $action is 'match', this string should contain the class
2451   *   name identifying what field will be used as the source value when matching
2452   *   the value in $subgroup.
2453   * @param $hidden
2454   *   (optional) The column containing the field elements may be entirely hidden
2455   *   from view dynamically when the JavaScript is loaded. Set to FALSE if the
2456   *   column should not be hidden.
2457   * @param $limit
2458   *   (optional) Limit the maximum amount of parenting in this table.
2459   * @see block-admin-display-form.tpl.php
2460   * @see theme_menu_overview_form()
2461   */
2462  function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
2463    static $js_added = FALSE;
2464    if (!$js_added) {
2465      drupal_add_js('misc/tabledrag.js', 'core');
2466      $js_added = TRUE;
2467    }
2468  
2469    // If a subgroup or source isn't set, assume it is the same as the group.
2470    $target = isset($subgroup) ? $subgroup : $group;
2471    $source = isset($source) ? $source : $target;
2472    $settings['tableDrag'][$table_id][$group][] = array(
2473      'target' => $target,
2474      'source' => $source,
2475      'relationship' => $relationship,
2476      'action' => $action,
2477      'hidden' => $hidden,
2478      'limit' => $limit,
2479    );
2480    drupal_add_js($settings, 'setting');
2481  }
2482  
2483  /**
2484   * Aggregate JS files, putting them in the files directory.
2485   *
2486   * @param $files
2487   *   An array of JS files to aggregate and compress into one file.
2488   * @param $filename
2489   *   The name of the aggregate JS file.
2490   * @return
2491   *   The name of the JS file.
2492   */
2493  function drupal_build_js_cache($files, $filename) {
2494    $contents = '';
2495  
2496    // Create the js/ within the files folder.
2497    $jspath = file_create_path('js');
2498    file_check_directory($jspath, FILE_CREATE_DIRECTORY);
2499  
2500    if (!file_exists($jspath .'/'. $filename)) {
2501      // Build aggregate JS file.
2502      foreach ($files as $path => $info) {
2503        if ($info['preprocess']) {
2504          // Append a ';' and a newline after each JS file to prevent them from running together.
2505          $contents .= file_get_contents($path) .";\n";
2506        }
2507      }
2508  
2509      // Create the JS file.
2510      file_save_data($contents, $jspath .'/'. $filename, FILE_EXISTS_REPLACE);
2511    }
2512  
2513    return $jspath .'/'. $filename;
2514  }
2515  
2516  /**
2517   * Delete all cached JS files.
2518   */
2519  function drupal_clear_js_cache() {
2520    file_scan_directory(file_create_path('js'), '.*', array('.', '..', 'CVS'), 'file_delete', TRUE);
2521    variable_set('javascript_parsed', array());
2522  }
2523  
2524  /**
2525   * Converts a PHP variable into its Javascript equivalent.
2526   *
2527   * We use HTML-safe strings, i.e. with <, > and & escaped.
2528   */
2529  function drupal_to_js($var) {
2530    switch (gettype($var)) {
2531      case 'boolean':
2532        return $var ? 'true' : 'false'; // Lowercase necessary!
2533      case 'integer':
2534      case 'double':
2535        return $var;
2536      case 'resource':
2537      case 'string':
2538        return '"'. str_replace(array("\r", "\n", "<", ">", "&"),
2539                                array('\r', '\n', '\x3c', '\x3e', '\x26'),
2540                                addslashes($var)) .'"';
2541      case 'array':
2542        // Arrays in JSON can't be associative. If the array is empty or if it
2543        // has sequential whole number keys starting with 0, it's not associative
2544        // so we can go ahead and convert it as an array.
2545        if (empty ($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
2546          $output = array();
2547          foreach ($var as $v) {
2548            $output[] = drupal_to_js($v);
2549          }
2550          return '[ '. implode(', ', $output) .' ]';
2551        }
2552        // Otherwise, fall through to convert the array as an object.
2553      case 'object':
2554        $output = array();
2555        foreach ($var as $k => $v) {
2556          $output[] = drupal_to_js(strval($k)) .': '. drupal_to_js($v);
2557        }
2558        return '{ '. implode(', ', $output) .' }';
2559      default:
2560        return 'null';
2561    }
2562  }
2563  
2564  /**
2565   * Return data in JSON format.
2566   *
2567   * This function should be used for JavaScript callback functions returning
2568   * data in JSON format. It sets the header for JavaScript output.
2569   *
2570   * @param $var
2571   *   (optional) If set, the variable will be converted to JSON and output.
2572   */
2573  function drupal_json($var = NULL) {
2574    // We are returning JavaScript, so tell the browser.
2575    drupal_set_header('Content-Type: text/javascript; charset=utf-8');
2576  
2577    if (isset($var)) {
2578      echo drupal_to_js($var);
2579    }
2580  }
2581  
2582  /**
2583   * Wrapper around urlencode() which avoids Apache quirks.
2584   *
2585   * Should be used when placing arbitrary data in an URL. Note that Drupal paths
2586   * are urlencoded() when passed through url() and do not require urlencoding()
2587   * of individual components.
2588   *
2589   * Notes:
2590   * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature'
2591   *   in Apache where it 404s on any path containing '%2F'.
2592   * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean
2593   *   URLs are used, which are interpreted as delimiters by PHP. These
2594   *   characters are double escaped so PHP will still see the encoded version.
2595   * - With clean URLs, Apache changes '//' to '/', so every second slash is
2596   *   double escaped.
2597   * - This function should only be used on paths, not on query string arguments,
2598   *   otherwise unwanted double encoding will occur.
2599   *
2600   * @param $text
2601   *   String to encode
2602   */
2603  function drupal_urlencode($text) {
2604    if (variable_get('clean_url', '0')) {
2605      return str_replace(array('%2F', '%26', '%23', '//'),
2606                         array('/', '%2526', '%2523', '/%252F'),
2607                         rawurlencode($text));
2608    }
2609    else {
2610      return str_replace('%2F', '/', rawurlencode($text));
2611    }
2612  }
2613  
2614  /**
2615   * Ensure the private key variable used to generate tokens is set.
2616   *
2617   * @return
2618   *   The private key.
2619   */
2620  function drupal_get_private_key() {
2621    if (!($key = variable_get('drupal_private_key', 0))) {
2622      $key = md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true));
2623      variable_set('drupal_private_key', $key);
2624    }
2625    return $key;
2626  }
2627  
2628  /**
2629   * Generate a token based on $value, the current user session and private key.
2630   *
2631   * @param $value
2632   *   An additional value to base the token on.
2633   */
2634  function drupal_get_token($value = '') {
2635    $private_key = drupal_get_private_key();
2636    return md5(session_id() . $value . $private_key);
2637  }
2638  
2639  /**
2640   * Validate a token based on $value, the current user session and private key.
2641   *
2642   * @param $token
2643   *   The token to be validated.
2644   * @param $value
2645   *   An additional value to base the token on.
2646   * @param $skip_anonymous
2647   *   Set to true to skip token validation for anonymous users.
2648   * @return
2649   *   True for a valid token, false for an invalid token. When $skip_anonymous
2650   *   is true, the return value will always be true for anonymous users.
2651   */
2652  function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
2653    global $user;
2654    return (($skip_anonymous && $user->uid == 0) || ($token == md5(session_id() . $value . variable_get('drupal_private_key', ''))));
2655  }
2656  
2657  /**
2658   * Performs one or more XML-RPC request(s).
2659   *
2660   * @param $url
2661   *   An absolute URL of the XML-RPC endpoint.
2662   *     Example:
2663   *     http://www.example.com/xmlrpc.php
2664   * @param ...
2665   *   For one request:
2666   *     The method name followed by a variable number of arguments to the method.
2667   *   For multiple requests (system.multicall):
2668   *     An array of call arrays. Each call array follows the pattern of the single
2669   *     request: method name followed by the arguments to the method.
2670   * @return
2671   *   For one request:
2672   *     Either the return value of the method on success, or FALSE.
2673   *     If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg().
2674   *   For multiple requests:
2675   *     An array of results. Each result will either be the result
2676   *     returned by the method called, or an xmlrpc_error object if the call
2677   *     failed. See xmlrpc_error().
2678   */
2679  function xmlrpc($url) {
2680    require_once  './includes/xmlrpc.inc';
2681    $args = func_get_args();
2682    return call_user_func_array('_xmlrpc', $args);
2683  }
2684  
2685  function _drupal_bootstrap_full() {
2686    static $called;
2687  
2688    if ($called) {
2689      return;
2690    }
2691    $called = 1;
2692    require_once  './includes/theme.inc';
2693    require_once  './includes/pager.inc';
2694    require_once  './includes/menu.inc';
2695    require_once  './includes/tablesort.inc';
2696    require_once  './includes/file.inc';
2697    require_once  './includes/unicode.inc';
2698    require_once  './includes/image.inc';
2699    require_once  './includes/form.inc';
2700    require_once  './includes/mail.inc';
2701    require_once  './includes/actions.inc';
2702    // Set the Drupal custom error handler.
2703    set_error_handler('drupal_error_handler');
2704    // Emit the correct charset HTTP header.
2705    drupal_set_header('Content-Type: text/html; charset=utf-8');
2706    // Detect string handling method
2707    unicode_check();
2708    // Undo magic quotes
2709    fix_gpc_magic();
2710    // Load all enabled modules
2711    module_load_all();
2712    // Let all modules take action before menu system handles the request
2713    // We do not want this while running update.php.
2714    if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
2715      module_invoke_all('init');
2716    }
2717  }
2718  
2719  /**
2720   * Store the current page in the cache.
2721   *
2722   * If page_compression is enabled, a gzipped version of the page is stored in
2723   * the cache to avoid compressing the output on each request. The cache entry
2724   * is unzipped in the relatively rare event that the page is requested by a
2725   * client without gzip support.
2726   *
2727   * Page compression requires the PHP zlib extension
2728   * (http://php.net/manual/en/ref.zlib.php).
2729   *
2730   * @see drupal_page_header
2731   */
2732  function page_set_cache() {
2733    global $user, $base_root;
2734  
2735    if (!$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET' && page_get_cache(TRUE)) {
2736      // This will fail in some cases, see page_get_cache() for the explanation.
2737      if ($data = ob_get_contents()) {
2738        if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) {
2739          $data = gzencode($data, 9, FORCE_GZIP);
2740        }
2741        ob_end_flush();
2742        cache_set($base_root . request_uri(), $data, 'cache_page', CACHE_TEMPORARY, drupal_get_headers());
2743      }
2744    }
2745  }
2746  
2747  /**
2748   * Executes a cron run when called
2749   * @return
2750   * Returns TRUE if ran successfully
2751   */
2752  function drupal_cron_run() {
2753    // Try to allocate enough time to run all the hook_cron implementations.
2754    if (function_exists('set_time_limit')) {
2755      @set_time_limit(240);
2756    }
2757  
2758    // Fetch the cron semaphore
2759    $semaphore = variable_get('cron_semaphore', FALSE);
2760  
2761    if ($semaphore) {
2762      if (time() - $semaphore > 3600) {
2763        // Either cron has been running for more than an hour or the semaphore
2764        // was not reset due to a database error.
2765        watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR);
2766  
2767        // Release cron semaphore
2768        variable_del('cron_semaphore');
2769      }
2770      else {
2771        // Cron is still running normally.
2772        watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING);
2773      }
2774    }
2775    else {
2776      // Register shutdown callback
2777      register_shutdown_function('drupal_cron_cleanup');
2778  
2779      // Lock cron semaphore
2780      variable_set('cron_semaphore', time());
2781  
2782      // Iterate through the modules calling their cron handlers (if any):
2783      module_invoke_all('cron');
2784  
2785      // Record cron time
2786      variable_set('cron_last', time());
2787      watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE);
2788  
2789      // Release cron semaphore
2790      variable_del('cron_semaphore');
2791  
2792      // Return TRUE so other functions can check if it did run successfully
2793      return TRUE;
2794    }
2795  }
2796  
2797  /**
2798   * Shutdown function for cron cleanup.
2799   */
2800  function drupal_cron_cleanup() {
2801    // See if the semaphore is still locked.
2802    if (variable_get('cron_semaphore', FALSE)) {
2803      watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING);
2804  
2805      // Release cron semaphore
2806      variable_del('cron_semaphore');
2807    }
2808  }
2809  
2810  /**
2811   * Return an array of system file objects.
2812   *
2813   * Returns an array of file objects of the given type from the site-wide
2814   * directory (i.e. modules/), the all-sites directory (i.e.
2815   * sites/all/modules/), the profiles directory, and site-specific directory
2816   * (i.e. sites/somesite/modules/). The returned array will be keyed using the
2817   * key specified (name, basename, filename). Using name or basename will cause
2818   * site-specific files to be prioritized over similar files in the default
2819   * directories. That is, if a file with the same name appears in both the
2820   * site-wide directory and site-specific directory, only the site-specific
2821   * version will be included.
2822   *
2823   * @param $mask
2824   *   The regular expression of the files to find.
2825   * @param $directory
2826   *   The subdirectory name in which the files are found. For example,
2827   *   'modules' will search in both modules/ and
2828   *   sites/somesite/modules/.
2829   * @param $key
2830   *   The key to be passed to file_scan_directory().
2831   * @param $min_depth
2832   *   Minimum depth of directories to return files from.
2833   *
2834   * @return
2835   *   An array of file objects of the specified type.
2836   */
2837  function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) {
2838    global $profile;
2839    $config = conf_path();
2840  
2841    // When this function is called during Drupal's initial installation process,
2842    // the name of the profile that's about to be installed is stored in the global
2843    // $profile variable. At all other times, the standard Drupal systems variable
2844    // table contains the name of the current profile, and we can call variable_get()
2845    // to determine what one is active.
2846    if (!isset($profile)) {
2847      $profile = variable_get('install_profile', 'default');
2848    }
2849    $searchdir = array($directory);
2850    $files = array();
2851  
2852    // The 'profiles' directory contains pristine collections of modules and
2853    // themes as organized by a distribution.  It is pristine in the same way
2854    // that /modules is pristine for core; users should avoid changing anything
2855    // there in favor of sites/all or sites/<domain> directories.
2856    if (file_exists("profiles/$profile/$directory")) {
2857      $searchdir[] = "profiles/$profile/$directory";
2858    }
2859  
2860    // Always search sites/all/* as well as the global directories
2861    $searchdir[] = 'sites/all/'. $directory;
2862  
2863    if (file_exists("$config/$directory")) {
2864      $searchdir[] = "$config/$directory";
2865    }
2866  
2867    // Get current list of items
2868    foreach ($searchdir as $dir) {
2869      $files = array_merge($files, file_scan_directory($dir, $mask, array('.', '..', 'CVS'), 0, TRUE, $key, $min_depth));
2870    }
2871  
2872    return $files;
2873  }
2874  
2875  
2876  /**
2877   * Hands off alterable variables to type-specific *_alter implementations.
2878   *
2879   * This dispatch function hands off the passed in variables to type-specific
2880   * hook_TYPE_alter() implementations in modules. It ensures a consistent
2881   * interface for all altering operations.
2882   *
2883   * @param $type
2884   *   A string describing the type of the alterable $data (e.g. 'form',
2885   *   'profile').
2886   * @param $data
2887   *   The variable that will be passed to hook_TYPE_alter() implementations to
2888   *   be altered. The type of this variable depends on $type. For example, when
2889   *   altering a 'form', $data will be a structured array. When altering a
2890   *   'profile', $data will be an object. If you need to pass additional
2891   *   parameters by reference to the hook_TYPE_alter() functions, include them
2892   *   as an array in $data['__drupal_alter_by_ref']. They will be unpacked and
2893   *   passed to the hook_TYPE_alter() functions, before the additional
2894   *   ... parameters (see below).
2895   * @param ...
2896   *   Any additional parameters will be passed on to the hook_TYPE_alter()
2897   *   functions (not by reference), after any by-reference parameters included
2898   *   in $data (see above)
2899   */
2900  function drupal_alter($type, &$data) {
2901    // PHP's func_get_args() always returns copies of params, not references, so
2902    // drupal_alter() can only manipulate data that comes in via the required first
2903    // param. For the edge case functions that must pass in an arbitrary number of
2904    // alterable parameters (hook_form_alter() being the best example), an array of
2905    // those params can be placed in the __drupal_alter_by_ref key of the $data
2906    // array. This is somewhat ugly, but is an unavoidable consequence of a flexible
2907    // drupal_alter() function, and the limitations of func_get_args().
2908    // @todo: Remove this in Drupal 7.
2909    if (is_array($data) && isset($data['__drupal_alter_by_ref'])) {
2910      $by_ref_parameters = $data['__drupal_alter_by_ref'];
2911      unset($data['__drupal_alter_by_ref']);
2912    }
2913  
2914    // Hang onto a reference to the data array so that it isn't blown away later.
2915    // Also, merge in any parameters that need to be passed by reference.
2916    $args = array(&$data);
2917    if (isset($by_ref_parameters)) {
2918      $args = array_merge($args, $by_ref_parameters);
2919    }
2920  
2921    // Now, use func_get_args() to pull in any additional parameters passed into
2922    // the drupal_alter() call.
2923    $additional_args = func_get_args();
2924    array_shift($additional_args);
2925    array_shift($additional_args);
2926    $args = array_merge($args, $additional_args);
2927  
2928    foreach (module_implements($type .'_alter') as $module) {
2929      $function = $module .'_'. $type .'_alter';
2930      call_user_func_array($function, $args);
2931    }
2932  }
2933  
2934  
2935  /**
2936   * Renders HTML given a structured array tree.
2937   *
2938   * Recursively iterates over each of the array elements, generating HTML code.
2939   * This function is usually called from within another function, like
2940   * drupal_get_form() or node_view().
2941   *
2942   * drupal_render() flags each element with a '#printed' status to indicate that
2943   * the element has been rendered, which allows individual elements of a given
2944   * array to be rendered independently. This prevents elements from being
2945   * rendered more than once on subsequent calls to drupal_render() if, for example,
2946   * they are part of a larger array. If the same array or array element is passed
2947   * more than once to drupal_render(), it simply returns a NULL value.
2948   *
2949   * @param $elements
2950   *   The structured array describing the data to be rendered.
2951   * @return
2952   *   The rendered HTML.
2953   */
2954  function drupal_render(&$elements) {
2955    if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) {
2956      return NULL;
2957    }
2958  
2959    // If the default values for this element haven't been loaded yet, populate
2960    // them.
2961    if (!isset($elements['#defaults_loaded']) || !$elements['#defaults_loaded']) {
2962      if ((!empty($elements['#type'])) && ($info = _element_info($elements['#type']))) {
2963        $elements += $info;
2964      }
2965    }
2966  
2967    // Make any final changes to the element before it is rendered. This means
2968    // that the $element or the children can be altered or corrected before the
2969    // element is rendered into the final text.
2970    if (isset($elements['#pre_render'])) {
2971      foreach ($elements['#pre_render'] as $function) {
2972        if (function_exists($function)) {
2973          $elements = $function($elements);
2974        }
2975      }
2976    }
2977  
2978    $content = '';
2979    // Either the elements did not go through form_builder or one of the children
2980    // has a #weight.
2981    if (!isset($elements['#sorted'])) {
2982      uasort($elements, "element_sort");
2983    }
2984    $elements += array('#title' => NULL, '#description' => NULL);
2985    if (!isset($elements['#children'])) {
2986      $children = element_children($elements);
2987      // Render all the children that use a theme function.
2988      if (isset($elements['#theme']) && empty($elements['#theme_used'])) {
2989        $elements['#theme_used'] = TRUE;
2990  
2991        $previous = array();
2992        foreach (array('#value', '#type', '#prefix', '#suffix') as $key) {
2993          $previous[$key] = isset($elements[$key]) ? $elements[$key] : NULL;
2994        }
2995        // If we rendered a single element, then we will skip the renderer.
2996        if (empty($children)) {
2997          $elements['#printed'] = TRUE;
2998        }
2999        else {
3000          $elements['#value'] = '';
3001        }
3002        $elements['#type'] = 'markup';
3003  
3004        unset($elements['#prefix'], $elements['#suffix']);
3005        $content = theme($elements['#theme'], $elements);
3006  
3007        foreach (array('#value', '#type', '#prefix', '#suffix') as $key) {
3008          $elements[$key] = isset($previous[$key]) ? $previous[$key] : NULL;
3009        }
3010      }
3011      // Render each of the children using drupal_render and concatenate them.
3012      if (!isset($content) || $content === '') {
3013        foreach ($children as $key) {
3014          $content .= drupal_render($elements[$key]);
3015        }
3016      }
3017    }
3018    if (isset($content) && $content !== '') {
3019      $elements['#children'] = $content;
3020    }
3021  
3022    // Until now, we rendered the children, here we render the element itself
3023    if (!isset($elements['#printed'])) {
3024      $content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements);
3025      $elements['#printed'] = TRUE;
3026    }
3027  
3028    if (isset($content) && $content !== '') {
3029      // Filter the outputted content and make any last changes before the
3030      // content is sent to the browser. The changes are made on $content
3031      // which allows the output'ed text to be filtered.
3032      if (isset($elements['#post_render'])) {
3033        foreach ($elements['#post_render'] as $function) {
3034          if (function_exists($function)) {
3035            $content = $function($content, $elements);
3036          }
3037        }
3038      }
3039      $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
3040      $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
3041      return $prefix . $content . $suffix;
3042    }
3043  }
3044  
3045  /**
3046   * Function used by uasort to sort structured arrays by weight.
3047   */
3048  function element_sort($a, $b) {
3049    $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
3050    $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
3051    if ($a_weight == $b_weight) {
3052      return 0;
3053    }
3054    return ($a_weight < $b_weight) ? -1 : 1;
3055  }
3056  
3057  /**
3058   * Check if the key is a property.
3059   */
3060  function element_property($key) {
3061    return $key[0] == '#';
3062  }
3063  
3064  /**
3065   * Get properties of a structured array element. Properties begin with '#'.
3066   */
3067  function element_properties($element) {
3068    return array_filter(array_keys((array) $element), 'element_property');
3069  }
3070  
3071  /**
3072   * Check if the key is a child.
3073   */
3074  function element_child($key) {
3075    return !isset($key[0]) || $key[0] != '#';
3076  }
3077  
3078  /**
3079   * Get keys of a structured array tree element that are not properties (i.e., do not begin with '#').
3080   */
3081  function element_children($element) {
3082    return array_filter(array_keys((array) $element), 'element_child');
3083  }
3084  
3085  /**
3086   * Provide theme registration for themes across .inc files.
3087   */
3088  function drupal_common_theme() {
3089    return array(
3090      // theme.inc
3091      'placeholder' => array(
3092        'arguments' => array('text' => NULL)
3093      ),
3094      'page' => array(
3095        'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
3096        'template' => 'page',
3097      ),
3098      'maintenance_page' => array(
3099        'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
3100        'template' => 'maintenance-page',
3101      ),
3102      'update_page' => array(
3103        'arguments' => array('content' => NULL, 'show_messages' => TRUE),
3104      ),
3105      'install_page' => array(
3106        'arguments' => array('content' => NULL),
3107      ),
3108      'task_list' => array(
3109        'arguments' => array('items' => NULL, 'active' => NULL),
3110      ),
3111      'status_messages' => array(
3112        'arguments' => array('display' => NULL),
3113      ),
3114      'links' => array(
3115        'arguments' => array('links' => NULL, 'attributes' => array('class' => 'links')),
3116      ),
3117      'image' => array(
3118        'arguments' => array('path' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE),
3119      ),
3120      'breadcrumb' => array(
3121        'arguments' => array('breadcrumb' => NULL),
3122      ),
3123      'help' => array(
3124        'arguments' => array(),
3125      ),
3126      'submenu' => array(
3127        'arguments' => array('links' => NULL),
3128      ),
3129      'table' => array(
3130        'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL),
3131      ),
3132      'table_select_header_cell' => array(
3133        'arguments' => array(),
3134      ),
3135      'tablesort_indicator' => array(
3136        'arguments' => array('style' => NULL),
3137      ),
3138      'box' => array(
3139        'arguments' => array('title' => NULL, 'content' => NULL, 'region' => 'main'),
3140        'template' => 'box',
3141      ),
3142      'block' => array(
3143        'arguments' => array('block' => NULL),
3144        'template' => 'block',
3145      ),
3146      'mark' => array(
3147        'arguments' => array('type' => MARK_NEW),
3148      ),
3149      'item_list' => array(
3150        'arguments' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => NULL),
3151      ),
3152      'more_help_link' => array(
3153        'arguments' => array('url' => NULL),
3154      ),
3155      'xml_icon' => array(
3156        'arguments' => array('url' => NULL),
3157      ),
3158      'feed_icon' => array(
3159        'arguments' => array('url' => NULL, 'title' => NULL),
3160      ),
3161      'more_link' => array(
3162        'arguments' => array('url' => NULL, 'title' => NULL)
3163      ),
3164      'closure' => array(
3165        'arguments' => array('main' => 0),
3166      ),
3167      'blocks' => array(
3168        'arguments' => array('region' => NULL),
3169      ),
3170      'username' => array(
3171        'arguments' => array('object' => NULL),
3172      ),
3173      'progress_bar' => array(
3174        'arguments' => array('percent' => NULL, 'message' => NULL),
3175      ),
3176      'indentation' => array(
3177        'arguments' => array('size' => 1),
3178      ),
3179      // from pager.inc
3180      'pager' => array(
3181        'arguments' => array('tags' => array(), 'limit' => 10, 'element' => 0, 'parameters' => array()),
3182      ),
3183      'pager_first' => array(
3184        'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()),
3185      ),
3186      'pager_previous' => array(
3187        'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
3188      ),
3189      'pager_next' => array(
3190        'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
3191      ),
3192      'pager_last' => array(
3193        'arguments' => array('text' => NULL, 'limit' => NULL, 'element' => 0, 'parameters' => array()),
3194      ),
3195      'pager_link' => array(
3196        'arguments' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()),
3197      ),
3198      // from menu.inc
3199      'menu_item_link' => array(
3200        'arguments' => array('item' => NULL),
3201      ),
3202      'menu_tree' => array(
3203        'arguments' => array('tree' => NULL),
3204      ),
3205      'menu_item' => array(
3206        'arguments' => array('link' => NULL, 'has_children' => NULL, 'menu' => ''),
3207      ),
3208      'menu_local_task' => array(
3209        'arguments' => array('link' => NULL, 'active' => FALSE),
3210      ),
3211      'menu_local_tasks' => array(
3212        'arguments' => array(),
3213      ),
3214      // from form.inc
3215      'select' => array(
3216        'arguments' => array('element' => NULL),
3217      ),
3218      'fieldset' => array(
3219        'arguments' => array('element' => NULL),
3220      ),
3221      'radio' => array(
3222        'arguments' => array('element' => NULL),
3223      ),
3224      'radios' => array(
3225        'arguments' => array('element' => NULL),
3226      ),
3227      'password_confirm' => array(
3228        'arguments' => array('element' => NULL),
3229      ),
3230      'date' => array(
3231        'arguments' => array('element' => NULL),
3232      ),
3233      'item' => array(
3234        'arguments' => array('element' => NULL),
3235      ),
3236      'checkbox' => array(
3237        'arguments' => array('element' => NULL),
3238      ),
3239      'checkboxes' => array(
3240        'arguments' => array('element' => NULL),
3241      ),
3242      'submit' => array(
3243        'arguments' => array('element' => NULL),
3244      ),
3245      'button' => array(
3246        'arguments' => array('element' => NULL),
3247      ),
3248      'image_button' => array(
3249        'arguments' => array('element' => NULL),
3250      ),
3251      'hidden' => array(
3252        'arguments' => array('element' => NULL),
3253      ),
3254      'token' => array(
3255        'arguments' => array('element' => NULL),
3256      ),
3257      'textfield' => array(
3258        'arguments' => array('element' => NULL),
3259      ),
3260      'form' => array(
3261        'arguments' => array('element' => NULL),
3262      ),
3263      'textarea' => array(
3264        'arguments' => array('element' => NULL),
3265      ),
3266      'markup' => array(
3267        'arguments' => array('element' => NULL),
3268      ),
3269      'password' => array(
3270        'arguments' => array('element' => NULL),
3271      ),
3272      'file' => array(
3273        'arguments' => array('element' => NULL),
3274      ),
3275      'form_element' => array(
3276        'arguments' => array('element' => NULL, 'value' => NULL),
3277      ),
3278    );
3279  }
3280  
3281  /**
3282   * @ingroup schemaapi
3283   * @{
3284   */
3285  
3286  /**
3287   * Get the schema definition of a table, or the whole database schema.
3288   *
3289   * The returned schema will include any modifications made by any
3290   * module that implements hook_schema_alter().
3291   *
3292   * @param $table
3293   *   The name of the table. If not given, the schema of all tables is returned.
3294   * @param $rebuild
3295   *   If true, the schema will be rebuilt instead of retrieved from the cache.
3296   */
3297  function drupal_get_schema($table = NULL, $rebuild = FALSE) {
3298    static $schema = array();
3299  
3300    if (empty($schema) || $rebuild) {
3301      // Try to load the schema from cache.
3302      if (!$rebuild && $cached = cache_get('schema')) {
3303        $schema = $cached->data;
3304      }
3305      // Otherwise, rebuild the schema cache.
3306      else {
3307        $schema = array();
3308        // Load the .install files to get hook_schema.
3309        module_load_all_includes('install');
3310  
3311        // Invoke hook_schema for all modules.
3312        foreach (module_implements('schema') as $module) {
3313          // Cast the result of hook_schema() to an array, as a NULL return value
3314          // would cause array_merge() to set the $schema variable to NULL as well.
3315          // That would break modules which use $schema further down the line.
3316          $current = (array) module_invoke($module, 'schema');
3317          _drupal_initialize_schema($module, $current);
3318          $schema = array_merge($schema, $current);
3319        }
3320  
3321        drupal_alter('schema', $schema);
3322        cache_set('schema', $schema);
3323      }
3324    }
3325  
3326    if (!isset($table)) {
3327      return $schema;
3328    }
3329    elseif (isset($schema[$table])) {
3330      return $schema[$table];
3331    }
3332    else {
3333      return FALSE;
3334    }
3335  }
3336  
3337  /**
3338   * Create all tables that a module defines in its hook_schema().
3339   *
3340   * Note: This function does not pass the module's schema through
3341   * hook_schema_alter(). The module's tables will be created exactly as the
3342   * module defines them.
3343   *
3344   * @param $module
3345   *   The module for which the tables will be created.
3346   * @return
3347   *   An array of arrays with the following key/value pairs:
3348   *    - success: a boolean indicating whether the query succeeded.
3349   *    - query: the SQL query(s) executed, passed through check_plain().
3350   */
3351  function drupal_install_schema($module) {
3352    $schema = drupal_get_schema_unprocessed($module);
3353    _drupal_initialize_schema($module, $schema);
3354  
3355    $ret = array();
3356    foreach ($schema as $name => $table) {
3357      db_create_table($ret, $name, $table);
3358    }
3359    return $ret;
3360  }
3361  
3362  /**
3363   * Remove all tables that a module defines in its hook_schema().
3364   *
3365   * Note: This function does not pass the module's schema through
3366   * hook_schema_alter(). The module's tables will be created exactly as the
3367   * module defines them.
3368   *
3369   * @param $module
3370   *   The module for which the tables will be removed.
3371   * @return
3372   *   An array of arrays with the following key/value pairs:
3373   *    - success: a boolean indicating whether the query succeeded.
3374   *    - query: the SQL query(s) executed, passed through check_plain().
3375   */
3376  function drupal_uninstall_schema($module) {
3377    $schema = drupal_get_schema_unprocessed($module);
3378    _drupal_initialize_schema($module, $schema);
3379  
3380    $ret = array();
3381    foreach ($schema as $table) {
3382      db_drop_table($ret, $table['name']);
3383    }
3384    return $ret;
3385  }
3386  
3387  /**
3388   * Returns the unprocessed and unaltered version of a module's schema.
3389   *
3390   * Use this function only if you explicitly need the original
3391   * specification of a schema, as it was defined in a module's
3392   * hook_schema(). No additional default values will be set,
3393   * hook_schema_alter() is not invoked and these unprocessed
3394   * definitions won't be cached.
3395   *
3396   * This function can be used to retrieve a schema specification in
3397   * hook_schema(), so it allows you to derive your tables from existing
3398   * specifications.
3399   *
3400   * It is also used by drupal_install_schema() and
3401   * drupal_uninstall_schema() to ensure that a module's tables are
3402   * created exactly as specified without any changes introduced by a
3403   * module that implements hook_schema_alter().
3404   *
3405   * @param $module
3406   *   The module to which the table belongs.
3407   * @param $table
3408   *   The name of the table. If not given, the module's complete schema
3409   *   is returned.
3410   */
3411  function drupal_get_schema_unprocessed($module, $table = NULL) {
3412    // Load the .install file to get hook_schema.
3413    module_load_install($module);
3414    $schema = module_invoke($module, 'schema');
3415  
3416    if (!is_null($table) && isset($schema[$table])) {
3417      return $schema[$table];
3418    }
3419    elseif (!empty($schema)) {
3420      return $schema;
3421    }
3422  
3423    return array();
3424  }
3425  
3426  /**
3427   * Fill in required default values for table definitions returned by hook_schema().
3428   *
3429   * @param $module
3430   *   The module for which hook_schema() was invoked.
3431   * @param $schema
3432   *   The schema definition array as it was returned by the module's
3433   *   hook_schema().
3434   */
3435  function _drupal_initialize_schema($module, &$schema) {
3436    // Set the name and module key for all tables.
3437    foreach ($schema as $name => $table) {
3438      if (empty($table['module'])) {
3439        $schema[$name]['module'] = $module;
3440      }
3441      if (!isset($table['name'])) {
3442        $schema[$name]['name'] = $name;
3443      }
3444    }
3445  }
3446  
3447  /**
3448   * Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query.
3449   *
3450   * @param $table
3451   *   The name of the table from which to retrieve fields.
3452   * @param
3453   *   An optional prefix to to all fields.
3454   *
3455   * @return An array of fields.
3456   **/
3457  function drupal_schema_fields_sql($table, $prefix = NULL) {
3458    $schema = drupal_get_schema($table);
3459    $fields = array_keys($schema['fields']);
3460    if ($prefix) {
3461      $columns = array();
3462      foreach ($fields as $field) {
3463        $columns[] = "$prefix.$field";
3464      }
3465      return $columns;
3466    }
3467    else {
3468      return $fields;
3469    }
3470  }
3471  
3472  /**
3473   * Save a record to the database based upon the schema.
3474   *
3475   * Default values are filled in for missing items, and 'serial' (auto increment)
3476   * types are filled in with IDs.
3477   *
3478   * @param $table
3479   *   The name of the table; this must exist in schema API.
3480   * @param $object
3481   *   The object to write. This is a reference, as defaults according to
3482   *   the schema may be filled in on the object, as well as ID on the serial
3483   *   type(s). Both array an object types may be passed.
3484   * @param $update
3485   *   If this is an update, specify the primary keys' field names. It is the
3486   *   caller's responsibility to know if a record for this object already
3487   *   exists in the database. If there is only 1 key, you may pass a simple string.
3488   * @return
3489   *   Failure to write a record will return FALSE. Otherwise SAVED_NEW or
3490   *   SAVED_UPDATED is returned depending on the operation performed. The
3491   *   $object parameter contains values for any serial fields defined by
3492   *   the $table. For example, $object->nid will be populated after inserting
3493   *   a new node.
3494   */
3495  function drupal_write_record($table, &$object, $update = array()) {
3496    // Standardize $update to an array.
3497    if (is_string($update)) {
3498      $update = array($update);
3499    }
3500  
3501    $schema = drupal_get_schema($table);
3502    if (empty($schema)) {
3503      return FALSE;
3504    }
3505  
3506    // Convert to an object if needed.
3507    if (is_array($object)) {
3508      $object = (object) $object;
3509      $array = TRUE;
3510    }
3511    else {
3512      $array = FALSE;
3513    }
3514  
3515    $fields = $defs = $values = $serials = $placeholders = array();
3516  
3517    // Go through our schema, build SQL, and when inserting, fill in defaults for
3518    // fields that are not set.
3519    foreach ($schema['fields'] as $field => $info) {
3520      // Special case -- skip serial types if we are updating.
3521      if ($info['type'] == 'serial' && count($update)) {
3522        continue;
3523      }
3524  
3525      // For inserts, populate defaults from Schema if not already provided
3526      if (!isset($object->$field) && !count($update) && isset($info['default'])) {
3527        $object->$field = $info['default'];
3528      }
3529  
3530      // Track serial fields so we can helpfully populate them after the query.
3531      if ($info['type'] == 'serial') {
3532        $serials[] = $field;
3533        // Ignore values for serials when inserting data. Unsupported.
3534        unset($object->$field);
3535      }
3536  
3537      // Build arrays for the fields, placeholders, and values in our query.
3538      if (isset($object->$field)) {
3539        $fields[] = $field;
3540        $placeholders[] = db_type_placeholder($info['type']);
3541  
3542        if (empty($info['serialize'])) {
3543          $values[] = $object->$field;
3544        }
3545        else {
3546          $values[] = serialize($object->$field);
3547        }
3548      }
3549    }
3550  
3551    // Build the SQL.
3552    $query = '';
3553    if (!count($update)) {
3554      $query = "INSERT INTO {". $table ."} (". implode(', ', $fields) .') VALUES ('. implode(', ', $placeholders) .')';
3555      $return = SAVED_NEW;
3556    }
3557    else {
3558      $query = '';
3559      foreach ($fields as $id => $field) {
3560        if ($query) {
3561          $query .= ', ';
3562        }
3563        $query .= $field .' = '. $placeholders[$id];
3564      }
3565  
3566      foreach ($update as $key){
3567        $conditions[] = "$key = ". db_type_placeholder($schema['fields'][$key]['type']);
3568        $values[] = $object->$key;
3569      }
3570  
3571      $query = "UPDATE {". $table ."} SET $query WHERE ". implode(' AND ', $conditions);
3572      $return = SAVED_UPDATED;
3573    }
3574  
3575    // Execute the SQL.
3576    if (db_query($query, $values)) {
3577      if ($serials) {
3578        // Get last insert ids and fill them in.
3579        foreach ($serials as $field) {
3580          $object->$field = db_last_insert_id($table, $field);
3581        }
3582      }
3583    }
3584    else {
3585      $return = FALSE;
3586    }
3587  
3588    // If we began with an array, convert back so we don't surprise the caller.
3589    if ($array) {
3590      $object = (array) $object;
3591    }
3592  
3593    return $return;
3594  }
3595  
3596  /**
3597   * @} End of "ingroup schemaapi".
3598   */
3599  
3600  /**
3601   * Parse Drupal info file format.
3602   *
3603   * Files should use an ini-like format to specify values.
3604   * White-space generally doesn't matter, except inside values.
3605   * e.g.
3606   *
3607   * @code
3608   *   key = value
3609   *   key = "value"
3610   *   key = 'value'
3611   *   key = "multi-line
3612   *
3613   *   value"
3614   *   key = 'multi-line
3615   *
3616   *   value'
3617   *   key
3618   *   =
3619   *   'value'
3620   * @endcode
3621   *
3622   * Arrays are created using a GET-like syntax:
3623   *
3624   * @code
3625   *   key[] = "numeric array"
3626   *   key[index] = "associative array"
3627   *   key[index][] = "nested numeric array"
3628   *   key[index][index] = "nested associative array"
3629   * @endcode
3630   *
3631   * PHP constants are substituted in, but only when used as the entire value:
3632   *
3633   * Comments should start with a semi-colon at the beginning of a line.
3634   *
3635   * This function is NOT for placing arbitrary module-specific settings. Use
3636   * variable_get() and variable_set() for that.
3637   *
3638   * Information stored in the module.info file:
3639   * - name: The real name of the module for display purposes.
3640   * - description: A brief description of the module.
3641   * - dependencies: An array of shortnames of other modules this module depends on.
3642   * - package: The name of the package of modules this module belongs to.
3643   *
3644   * Example of .info file:
3645   * @code
3646   *   name = Forum
3647   *   description = Enables threaded discussions about general topics.
3648   *   dependencies[] = taxonomy
3649   *   dependencies[] = comment
3650   *   package = Core - optional
3651   *   version = VERSION
3652   * @endcode
3653   *
3654   * @param $filename
3655   *   The file we are parsing. Accepts file with relative or absolute path.
3656   * @return
3657   *   The info array.
3658   */
3659  function drupal_parse_info_file($filename) {
3660    $info = array();
3661    $constants = get_defined_constants();
3662  
3663    if (!file_exists($filename)) {
3664      return $info;
3665    }
3666  
3667    $data = file_get_contents($filename);
3668    if (preg_match_all('
3669      @^\s*                           # Start at the beginning of a line, ignoring leading whitespace
3670      ((?:
3671        [^=;\[\]]|                    # Key names cannot contain equal signs, semi-colons or square brackets,
3672        \[[^\[\]]*\]                  # unless they are balanced and not nested
3673      )+?)
3674      \s*=\s*                         # Key/value pairs are separated by equal signs (ignoring white-space)
3675      (?:
3676        ("(?:[^"]|(?<=\\\\)")*")|     # Double-quoted string, which may contain slash-escaped quotes/slashes
3677        (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
3678        ([^\r\n]*?)                   # Non-quoted string
3679      )\s*$                           # Stop at the next end of a line, ignoring trailing whitespace
3680      @msx', $data, $matches, PREG_SET_ORDER)) {
3681      foreach ($matches as $match) {
3682        // Fetch the key and value string
3683        $i = 0;
3684        foreach (array('key', 'value1', 'value2', 'value3') as $var) {
3685          $$var = isset($match[++$i]) ? $match[$i] : '';
3686        }
3687        $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
3688  
3689        // Parse array syntax
3690        $keys = preg_split('/\]?\[/', rtrim($key, ']'));
3691        $last = array_pop($keys);
3692        $parent = &$info;
3693  
3694        // Create nested arrays
3695        foreach ($keys as $key) {
3696          if ($key == '') {
3697            $key = count($parent);
3698          }
3699          if (!isset($parent[$key]) || !is_array($parent[$key])) {
3700            $parent[$key] = array();
3701          }
3702          $parent = &$parent[$key];
3703        }
3704  
3705        // Handle PHP constants.
3706        if (isset($constants[$value])) {
3707          $value = $constants[$value];
3708        }
3709  
3710        // Insert actual value
3711        if ($last == '') {
3712          $last = count($parent);
3713        }
3714        $parent[$last] = $value;
3715      }
3716    }
3717  
3718    return $info;
3719  }
3720  
3721  /**
3722   * @return
3723   *   Array of the possible severity levels for log messages.
3724   *
3725   * @see watchdog
3726   */
3727  function watchdog_severity_levels() {
3728    return array(
3729      WATCHDOG_EMERG    => t('emergency'),
3730      WATCHDOG_ALERT    => t('alert'),
3731      WATCHDOG_CRITICAL => t('critical'),
3732      WATCHDOG_ERROR    => t('error'),
3733      WATCHDOG_WARNING  => t('warning'),
3734      WATCHDOG_NOTICE   => t('notice'),
3735      WATCHDOG_INFO     => t('info'),
3736      WATCHDOG_DEBUG    => t('debug'),
3737    );
3738  }
3739  
3740  
3741  /**
3742   * Explode a string of given tags into an array.
3743   *
3744   * @see drupal_implode_tags()
3745   */
3746  function drupal_explode_tags($tags) {
3747    // This regexp allows the following types of user input:
3748    // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
3749    $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
3750    preg_match_all($regexp, $tags, $matches);
3751    $typed_tags = array_unique($matches[1]);
3752  
3753    $tags = array();
3754    foreach ($typed_tags as $tag) {
3755      // If a user has escaped a term (to demonstrate that it is a group,
3756      // or includes a comma or quote character), we remove the escape
3757      // formatting so to save the term into the database as the user intends.
3758      $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
3759      if ($tag != "") {
3760        $tags[] = $tag;
3761      }
3762    }
3763  
3764    return $tags;
3765  }
3766  
3767  /**
3768   * Implode an array of tags into a string.
3769   *
3770   * @see drupal_explode_tags()
3771   */
3772  function drupal_implode_tags($tags) {
3773    $encoded_tags = array();
3774    foreach ($tags as $tag) {
3775      // Commas and quotes in tag names are special cases, so encode them.
3776      if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
3777        $tag = '"'. str_replace('"', '""', $tag) .'"';
3778      }
3779  
3780      $encoded_tags[] = $tag;
3781    }
3782    return implode(', ', $encoded_tags);
3783  }
3784  
3785  /**
3786   * Flush all cached data on the site.
3787   *
3788   * Empties cache tables, rebuilds the menu cache and theme registries, and
3789   * invokes a hook so that other modules' cache data can be cleared as well.
3790   */
3791  function drupal_flush_all_caches() {
3792    // Change query-strings on css/js files to enforce reload for all users.
3793    _drupal_flush_css_js();
3794  
3795    drupal_clear_css_cache();
3796    drupal_clear_js_cache();
3797  
3798    // If invoked from update.php, we must not update the theme information in the
3799    // database, or this will result in all themes being disabled.
3800    if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
3801      _system_theme_data();
3802    }
3803    else {
3804      system_theme_data();
3805    }
3806  
3807    drupal_rebuild_theme_registry();
3808    menu_rebuild();
3809    node_types_rebuild();
3810    // Don't clear cache_form - in-progress form submissions may break.
3811    // Ordered so clearing the page cache will always be the last action.
3812    $core = array('cache', 'cache_block', 'cache_filter', 'cache_page');
3813    $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
3814    foreach ($cache_tables as $table) {
3815      cache_clear_all('*', $table, TRUE);
3816    }
3817  }
3818  
3819  /**
3820   * Helper function to change query-strings on css/js files.
3821   *
3822   * Changes the character added to all css/js files as dummy query-string,
3823   * so that all browsers are forced to reload fresh files. We keep
3824   * 20 characters history (FIFO) to avoid repeats, but only the first
3825   * (newest) character is actually used on urls, to keep them short.
3826   * This is also called from update.php.
3827   */
3828  function _drupal_flush_css_js() {
3829    $string_history = variable_get('css_js_query_string', '00000000000000000000');
3830    $new_character = $string_history[0];
3831    // Not including 'q' to allow certain JavaScripts to re-use query string.
3832    $characters = 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
3833    while (strpos($string_history, $new_character) !== FALSE) {
3834      $new_character = $characters[mt_rand(0, strlen($characters) - 1)];
3835    }
3836    variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
3837  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7