Simpletest Coverage - includes/common.inc

1 <?php
2 // $Id: common.inc,v 1.961 2009/08/15 06:20:20 webchick Exp $
3
4 /**
5 * @file
6 * Common functions that many Drupal modules will need to reference.
7 *
8 * The functions that are critical and need to be available even when serving
9 * a cached page are instead located in bootstrap.inc.
10 */
11
12 /**
13 * Error reporting level: display no errors.
14 */
15 define('ERROR_REPORTING_HIDE', 0);
16
17 /**
18 * Error reporting level: display errors and warnings.
19 */
20 define('ERROR_REPORTING_DISPLAY_SOME', 1);
21
22 /**
23 * Error reporting level: display all messages.
24 */
25 define('ERROR_REPORTING_DISPLAY_ALL', 2);
26
27 /**
28 * Return status for saving which involved creating a new item.
29 */
30 define('SAVED_NEW', 1);
31
32 /**
33 * Return status for saving which involved an update to an existing item.
34 */
35 define('SAVED_UPDATED', 2);
36
37 /**
38 * Return status for saving which deleted an existing item.
39 */
40 define('SAVED_DELETED', 3);
41
42 /**
43 * The default weight of system CSS files added to the page.
44 */
45 define('CSS_SYSTEM', -100);
46
47 /**
48 * The default weight of CSS files added to the page.
49 */
50 define('CSS_DEFAULT', 0);
51
52 /**
53 * The default weight of theme CSS files added to the page.
54 */
55 define('CSS_THEME', 100);
56
57 /**
58 * The weight of JavaScript libraries, settings or jQuery plugins being
59 * added to the page.
60 */
61 define('JS_LIBRARY', -100);
62
63 /**
64 * The default weight of JavaScript being added to the page.
65 */
66 define('JS_DEFAULT', 0);
67
68 /**
69 * The weight of theme JavaScript code being added to the page.
70 */
71 define('JS_THEME', 100);
72
73 /**
74 * Error code indicating that the request made by drupal_http_request() exceeded
75 * the specified timeout.
76 */
77 define('HTTP_REQUEST_TIMEOUT', 1);
78
79 /**
80 * Add content to a specified region.
81 *
82 * @param $region
83 * Page region the content is added to.
84 * @param $data
85 * Content to be added.
86 */
87 function drupal_add_region_content($region = NULL, $data = NULL) {
88 static $content = array();
89
90 if (!is_null($region) && !is_null($data)) {
91 $content[$region][] = $data;
92 }
93 return $content;
94 }
95
96 /**
97 * Get assigned content for a given region.
98 *
99 * @param $region
100 * A specified region to fetch content for. If NULL, all regions will be
101 * returned.
102 * @param $delimiter
103 * Content to be inserted between imploded array elements.
104 */
105 function drupal_get_region_content($region = NULL, $delimiter = ' ') {
106 $content = drupal_add_region_content();
107 if (isset($region)) {
108 if (isset($content[$region]) && is_array($content[$region])) {
109 return implode($delimiter, $content[$region]);
110 }
111 }
112 else {
113 foreach (array_keys($content) as $region) {
114 if (is_array($content[$region])) {
115 $content[$region] = implode($delimiter, $content[$region]);
116 }
117 }
118 return $content;
119 }
120 }
121
122 /**
123 * Set the breadcrumb trail for the current page.
124 *
125 * @param $breadcrumb
126 * Array of links, starting with "home" and proceeding up to but not including
127 * the current page.
128 */
129 function drupal_set_breadcrumb($breadcrumb = NULL) {
130 $stored_breadcrumb = &drupal_static(__FUNCTION__);
131
132 if (!is_null($breadcrumb)) {
133 $stored_breadcrumb = $breadcrumb;
134 }
135 return $stored_breadcrumb;
136 }
137
138 /**
139 * Get the breadcrumb trail for the current page.
140 */
141 function drupal_get_breadcrumb() {
142 $breadcrumb = drupal_set_breadcrumb();
143
144 if (is_null($breadcrumb)) {
145 $breadcrumb = menu_get_active_breadcrumb();
146 }
147
148 return $breadcrumb;
149 }
150
151 /**
152 * Return a string containing RDF namespaces for the <html> tag of an XHTML
153 * page.
154 */
155 function drupal_get_rdf_namespaces() {
156 // Serialize the RDF namespaces used in RDFa annotation.
157 $xml_rdf_namespaces = array();
158 foreach (module_invoke_all('rdf_namespaces') as $prefix => $uri) {
159 $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
160 }
161 return implode("\n ", $xml_rdf_namespaces);
162 }
163
164 /**
165 * Add output to the head tag of the HTML page.
166 *
167 * This function can be called as long the headers aren't sent.
168 */
169 function drupal_add_html_head($data = NULL) {
170 $stored_head = &drupal_static(__FUNCTION__, '');
171
172 if (!is_null($data)) {
173 $stored_head .= $data . "\n";
174 }
175 return $stored_head;
176 }
177
178 /**
179 * Retrieve output to be displayed in the head tag of the HTML page.
180 */
181 function drupal_get_html_head() {
182 $output = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n";
183 return $output . drupal_add_html_head();
184 }
185
186 /**
187 * Reset the static variable which holds the aliases mapped for this request.
188 */
189 function drupal_clear_path_cache() {
190 drupal_lookup_path('wipe');
191 }
192
193 /**
194 * Add a feed URL for the current page.
195 *
196 * This function can be called as long the HTML header hasn't been sent.
197 *
198 * @param $url
199 * A url for the feed.
200 * @param $title
201 * The title of the feed.
202 */
203 function drupal_add_feed($url = NULL, $title = '') {
204 $stored_feed_links = &drupal_static(__FUNCTION__, array());
205
206 if (!is_null($url) && !isset($stored_feed_links[$url])) {
207 $stored_feed_links[$url] = theme('feed_icon', $url, $title);
208
209 drupal_add_link(array('rel' => 'alternate',
210 'type' => 'application/rss+xml',
211 'title' => $title,
212 'href' => $url));
213 }
214 return $stored_feed_links;
215 }
216
217 /**
218 * Get the feed URLs for the current page.
219 *
220 * @param $delimiter
221 * A delimiter to split feeds by.
222 */
223 function drupal_get_feeds($delimiter = "\n") {
224 $feeds = drupal_add_feed();
225 return implode($feeds, $delimiter);
226 }
227
228 /**
229 * @name HTTP handling
230 * @{
231 * Functions to properly handle HTTP responses.
232 */
233
234 /**
235 * Parse an array into a valid urlencoded query string.
236 *
237 * @param $query
238 * The array to be processed e.g. $_GET.
239 * @param $exclude
240 * The array filled with keys to be excluded. Use parent[child] to exclude
241 * nested items.
242 * @param $parent
243 * Should not be passed, only used in recursive calls.
244 * @return
245 * An urlencoded string which can be appended to/as the URL query string.
246 */
247 function drupal_query_string_encode($query, $exclude = array(), $parent = '') {
248 $params = array();
249
250 foreach ($query as $key => $value) {
251 $key = rawurlencode($key);
252 if ($parent) {
253 $key = $parent . '[' . $key . ']';
254 }
255
256 if (in_array($key, $exclude)) {
257 continue;
258 }
259
260 if (is_array($value)) {
261 $params[] = drupal_query_string_encode($value, $exclude, $key);
262 }
263 else {
264 $params[] = $key . '=' . rawurlencode($value);
265 }
266 }
267
268 return implode('&', $params);
269 }
270
271 /**
272 * Prepare a destination query string for use in combination with drupal_goto().
273 *
274 * Used to direct the user back to the referring page after completing a form.
275 * By default the current URL is returned. If a destination exists in the
276 * previous request, that destination is returned. As such, a destination can
277 * persist across multiple pages.
278 *
279 * @see drupal_goto()
280 */
281 function drupal_get_destination() {
282 if (isset($_REQUEST['destination'])) {
283 return 'destination=' . urlencode($_REQUEST['destination']);
284 }
285 else {
286 // Use $_GET here to retrieve the original path in source form.
287 $path = isset($_GET['q']) ? $_GET['q'] : '';
288 $query = drupal_query_string_encode($_GET, array('q'));
289 if ($query != '') {
290 $path .= '?' . $query;
291 }
292 return 'destination=' . urlencode($path);
293 }
294 }
295
296 /**
297 * Send the user to a different Drupal page.
298 *
299 * This issues an on-site HTTP redirect. The function makes sure the redirected
300 * URL is formatted correctly.
301 *
302 * Usually the redirected URL is constructed from this function's input
303 * parameters. However you may override that behavior by setting a
304 * destination in either the $_REQUEST-array (i.e. by using
305 * the query string of an URI) This is used to direct the user back to
306 * the proper page after completing a form. For example, after editing
307 * a post on the 'admin/content'-page or after having logged on using the
308 * 'user login'-block in a sidebar. The function drupal_get_destination()
309 * can be used to help set the destination URL.
310 *
311 * Drupal will ensure that messages set by drupal_set_message() and other
312 * session data are written to the database before the user is redirected.
313 *
314 * This function ends the request; use it instead of a return in your menu callback.
315 *
316 * @param $path
317 * A Drupal path or a full URL.
318 * @param $query
319 * A query string component, if any.
320 * @param $fragment
321 * A destination fragment identifier (named anchor).
322 * @param $http_response_code
323 * Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
324 * - 301 Moved Permanently (the recommended value for most redirects)
325 * - 302 Found (default in Drupal and PHP, sometimes used for spamming search
326 * engines)
327 * - 303 See Other
328 * - 304 Not Modified
329 * - 305 Use Proxy
330 * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
331 * Note: Other values are defined by RFC 2616, but are rarely used and poorly
332 * supported.
333 * @see drupal_get_destination()
334 */
335 function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
336
337 if (isset($_REQUEST['destination'])) {
338 extract(parse_url(urldecode($_REQUEST['destination'])));
339 }
340
341 $url = url($path, array('query' => $query, 'fragment' => $fragment, 'absolute' => TRUE));
342
343 // Allow modules to react to the end of the page request before redirecting.
344 // We do not want this while running update.php.
345 if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
346 module_invoke_all('exit', $url);
347 }
348
349 // Commit the session, if necessary. We need all session data written to the
350 // database before redirecting.
351 drupal_session_commit();
352
353 header('Location: ' . $url, TRUE, $http_response_code);
354
355 // The "Location" header sends a redirect status code to the HTTP daemon. In
356 // some cases this can be wrong, so we make sure none of the code below the
357 // drupal_goto() call gets executed upon redirection.
358 exit();
359 }
360
361 /**
362 * Generates a site offline message.
363 */
364 function drupal_site_offline() {
365 drupal_maintenance_theme();
366 drupal_set_header('503 Service unavailable');
367 drupal_set_title(t('Site offline'));
368 print theme('maintenance_page', filter_xss_admin(variable_get('site_offline_message',
369 t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
370 }
371
372 /**
373 * Generates a 404 error if the request can not be handled.
374 */
375 function drupal_not_found() {
376 drupal_set_header('404 Not Found');
377
378 watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
379
380 // Keep old path for reference, and to allow forms to redirect to it.
381 if (!isset($_REQUEST['destination'])) {
382 $_REQUEST['destination'] = $_GET['q'];
383 }
384
385 $path = drupal_get_normal_path(variable_get('site_404', ''));
386 if ($path && $path != $_GET['q']) {
387 // Custom 404 handler. Set the active item in case there are tabs to
388 // display, or other dependencies on the path.
389 menu_set_active_item($path);
390 $return = menu_execute_active_handler($path);
391 }
392
393 if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
394 // Standard 404 handler.
395 drupal_set_title(t('Page not found'));
396 $return = t('The requested page could not be found.');
397 }
398
399 drupal_set_page_content($return);
400 $page = element_info('page');
401 // Optionally omit the blocks to conserve CPU and bandwidth.
402 $page['#show_blocks'] = variable_get('site_404_blocks', FALSE);
403
404 print drupal_render_page($page);
405 }
406
407 /**
408 * Generates a 403 error if the request is not allowed.
409 */
410 function drupal_access_denied() {
411 drupal_set_header('403 Forbidden');
412 watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
413
414 // Keep old path for reference, and to allow forms to redirect to it.
415 if (!isset($_REQUEST['destination'])) {
416 $_REQUEST['destination'] = $_GET['q'];
417 }
418
419 $path = drupal_get_normal_path(variable_get('site_403', ''));
420 if ($path && $path != $_GET['q']) {
421 // Custom 403 handler. Set the active item in case there are tabs to
422 // display or other dependencies on the path.
423 menu_set_active_item($path);
424 $return = menu_execute_active_handler($path);
425 }
426
427 if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
428 // Standard 403 handler.
429 drupal_set_title(t('Access denied'));
430 $return = t('You are not authorized to access this page.');
431 }
432
433 print drupal_render_page($return);
434 }
435
436 /**
437 * Perform an HTTP request.
438 *
439 * This is a flexible and powerful HTTP client implementation. Correctly
440 * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
441 *
442 * @param $url
443 * A string containing a fully qualified URI.
444 * @param $options
445 * (optional) An array which can have one or more of following keys:
446 * - headers
447 * An array containing request headers to send as name/value pairs.
448 * - method
449 * A string containing the request method. Defaults to 'GET'.
450 * - data
451 * A string containing the request body. Defaults to NULL.
452 * - max_redirects
453 * An integer representing how many times a redirect may be followed.
454 * Defaults to 3.
455 * - timeout
456 * A float representing the maximum number of seconds the function call
457 * may take. The default is 30 seconds. If a timeout occurs, the error
458 * code is set to the HTTP_REQUEST_TIMEOUT constant.
459 * @return
460 * An object which can have one or more of the following parameters:
461 * - request
462 * A string containing the request body that was sent.
463 * - code
464 * An integer containing the response status code, or the error code if
465 * an error occurred.
466 * - protocol
467 * The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
468 * - status_message
469 * The status message from the response, if a response was received.
470 * - redirect_code
471 * If redirected, an integer containing the initial response status code.
472 * - redirect_url
473 * If redirected, a string containing the redirection location.
474 * - error
475 * If an error occurred, the error message. Otherwise not set.
476 * - headers
477 * An array containing the response headers as name/value pairs.
478 * - data
479 * A string containing the response body that was received.
480 */
481 function drupal_http_request($url, array $options = array()) {
482 global $db_prefix;
483
484 $result = new stdClass();
485
486 // Parse the URL and make sure we can handle the schema.
487 $uri = @parse_url($url);
488
489 if ($uri == FALSE) {
490 $result->error = 'unable to parse URL';
491 return $result;
492 }
493
494 if (!isset($uri['scheme'])) {
495 $result->error = 'missing schema';
496 return $result;
497 }
498
499 timer_start(__FUNCTION__);
500
501 // Merge the default options.
502 $options += array(
503 'headers' => array(),
504 'method' => 'GET',
505 'data' => NULL,
506 'max_redirects' => 3,
507 'timeout' => 30,
508 );
509
510 switch ($uri['scheme']) {
511 case 'http':
512 $port = isset($uri['port']) ? $uri['port'] : 80;
513 $host = $uri['host'] . ($port != 80 ? ':' . $port : '');
514 $fp = @fsockopen($uri['host'], $port, $errno, $errstr, $options['timeout']);
515 break;
516 case 'https':
517 // Note: Only works when PHP is compiled with OpenSSL support.
518 $port = isset($uri['port']) ? $uri['port'] : 443;
519 $host = $uri['host'] . ($port != 443 ? ':' . $port : '');
520 $fp = @fsockopen('ssl://' . $uri['host'], $port, $errno, $errstr, $options['timeout']);
521 break;
522 default:
523 $result->error = 'invalid schema ' . $uri['scheme'];
524 return $result;
525 }
526
527 // Make sure the socket opened properly.
528 if (!$fp) {
529 // When a network error occurs, we use a negative number so it does not
530 // clash with the HTTP status codes.
531 $result->code = -$errno;
532 $result->error = trim($errstr);
533
534 // Mark that this request failed. This will trigger a check of the web
535 // server's ability to make outgoing HTTP requests the next time that
536 // requirements checking is performed.
537 // @see system_requirements()
538 variable_set('drupal_http_request_fails', TRUE);
539
540 return $result;
541 }
542
543 // Construct the path to act on.
544 $path = isset($uri['path']) ? $uri['path'] : '/';
545 if (isset($uri['query'])) {
546 $path .= '?' . $uri['query'];
547 }
548
549 // Merge the default headers.
550 $options['headers'] += array(
551 'User-Agent' => 'Drupal (+http://drupal.org/)',
552 );
553
554 // RFC 2616: "non-standard ports MUST, default ports MAY be included".
555 // We don't add the standard port to prevent from breaking rewrite rules
556 // checking the host that do not take into account the port number.
557 $options['headers']['Host'] = $host;
558
559 // Only add Content-Length if we actually have any content or if it is a POST
560 // or PUT request. Some non-standard servers get confused by Content-Length in
561 // at least HEAD/GET requests, and Squid always requires Content-Length in
562 // POST/PUT requests.
563 $content_length = strlen($options['data']);
564 if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
565 $options['headers']['Content-Length'] = $content_length;
566 }
567
568 // If the server URL has a user then attempt to use basic authentication.
569 if (isset($uri['user'])) {
570 $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
571 }
572
573 // If the database prefix is being used by SimpleTest to run the tests in a copied
574 // database then set the user-agent header to the database prefix so that any
575 // calls to other Drupal pages will run the SimpleTest prefixed database. The
576 // user-agent is used to ensure that multiple testing sessions running at the
577 // same time won't interfere with each other as they would if the database
578 // prefix were stored statically in a file or database variable.
579 if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) {
580 $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]);
581 }
582
583 $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
584 foreach ($options['headers'] as $name => $value) {
585 $request .= $name . ': ' . trim($value) . "\r\n";
586 }
587 $request .= "\r\n" . $options['data'];
588 $result->request = $request;
589
590 fwrite($fp, $request);
591
592 // Fetch response.
593 $response = '';
594 while (!feof($fp)) {
595 // Calculate how much time is left of the original timeout value.
596 $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
597 if ($timeout <= 0) {
598 $result->code = HTTP_REQUEST_TIMEOUT;
599 $result->error = 'request timed out';
600 return $result;
601 }
602 stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
603 $response .= fread($fp, 1024);
604 }
605 fclose($fp);
606
607 // Parse response headers from the response body.
608 list($response, $result->data) = explode("\r\n\r\n", $response, 2);
609 $response = preg_split("/\r\n|\n|\r/", $response);
610
611 // Parse the response status line.
612 list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
613 $result->protocol = $protocol;
614 $result->status_message = $status_message;
615
616 $result->headers = array();
617
618 // Parse the response headers.
619 while ($line = trim(array_shift($response))) {
620 list($header, $value) = explode(':', $line, 2);
621 if (isset($result->headers[$header]) && $header == 'Set-Cookie') {
622 // RFC 2109: the Set-Cookie response header comprises the token Set-
623 // Cookie:, followed by a comma-separated list of one or more cookies.
624 $result->headers[$header] .= ',' . trim($value);
625 }
626 else {
627 $result->headers[$header] = trim($value);
628 }
629 }
630
631 $responses = array(
632 100 => 'Continue',
633 101 => 'Switching Protocols',
634 200 => 'OK',
635 201 => 'Created',
636 202 => 'Accepted',
637 203 => 'Non-Authoritative Information',
638 204 => 'No Content',
639 205 => 'Reset Content',
640 206 => 'Partial Content',
641 300 => 'Multiple Choices',
642 301 => 'Moved Permanently',
643 302 => 'Found',
644 303 => 'See Other',
645 304 => 'Not Modified',
646 305 => 'Use Proxy',
647 307 => 'Temporary Redirect',
648 400 => 'Bad Request',
649 401 => 'Unauthorized',
650 402 => 'Payment Required',
651 403 => 'Forbidden',
652 404 => 'Not Found',
653 405 => 'Method Not Allowed',
654 406 => 'Not Acceptable',
655 407 => 'Proxy Authentication Required',
656 408 => 'Request Time-out',
657 409 => 'Conflict',
658 410 => 'Gone',
659 411 => 'Length Required',
660 412 => 'Precondition Failed',
661 413 => 'Request Entity Too Large',
662 414 => 'Request-URI Too Large',
663 415 => 'Unsupported Media Type',
664 416 => 'Requested range not satisfiable',
665 417 => 'Expectation Failed',
666 500 => 'Internal Server Error',
667 501 => 'Not Implemented',
668 502 => 'Bad Gateway',
669 503 => 'Service Unavailable',
670 504 => 'Gateway Time-out',
671 505 => 'HTTP Version not supported',
672 );
673 // RFC 2616 states that all unknown HTTP codes must be treated the same as the
674 // base code in their class.
675 if (!isset($responses[$code])) {
676 $code = floor($code / 100) * 100;
677 }
678 $result->code = $code;
679
680 switch ($code) {
681 case 200: // OK
682 case 304: // Not modified
683 break;
684 case 301: // Moved permanently
685 case 302: // Moved temporarily
686 case 307: // Moved temporarily
687 $location = $result->headers['Location'];
688 $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
689 if ($options['timeout'] <= 0) {
690 $result->code = HTTP_REQUEST_TIMEOUT;
691 $result->error = 'request timed out';
692 }
693 elseif ($options['max_redirects']) {
694 // Redirect to the new location.
695 $options['max_redirects']--;
696 $result = drupal_http_request($location, $options);
697 $result->redirect_code = $code;
698 }
699 $result->redirect_url = $location;
700 break;
701 default:
702 $result->error = $status_message;
703 }
704
705 return $result;
706 }
707 /**
708 * @} End of "HTTP handling".
709 */
710
711 /**
712 * Custom PHP error handler.
713 *
714 * @param $error_level
715 * The level of the error raised.
716 * @param $message
717 * The error message.
718 * @param $filename
719 * The filename that the error was raised in.
720 * @param $line
721 * The line number the error was raised at.
722 * @param $context
723 * An array that points to the active symbol table at the point the error occurred.
724 */
725 function _drupal_error_handler($error_level, $message, $filename, $line, $context) {
726 if ($error_level & error_reporting()) {
727 // All these constants are documented at http://php.net/manual/en/errorfunc.constants.php
728 $types = array(
729 E_ERROR => 'Error',
730 E_WARNING => 'Warning',
731 E_PARSE => 'Parse error',
732 E_NOTICE => 'Notice',
733 E_CORE_ERROR => 'Core error',
734 E_CORE_WARNING => 'Core warning',
735 E_COMPILE_ERROR => 'Compile error',
736 E_COMPILE_WARNING => 'Compile warning',
737 E_USER_ERROR => 'User error',
738 E_USER_WARNING => 'User warning',
739 E_USER_NOTICE => 'User notice',
740 E_STRICT => 'Strict warning',
741 E_RECOVERABLE_ERROR => 'Recoverable fatal error'
742 );
743 $caller = _drupal_get_last_caller(debug_backtrace());
744
745 // We treat recoverable errors as fatal.
746 _drupal_log_error(array(
747 '%type' => isset($types[$error_level]) ? $types[$error_level] : 'Unknown error',
748 '%message' => $message,
749 '%function' => $caller['function'],
750 '%file' => $caller['file'],
751 '%line' => $caller['line'],
752 ), $error_level == E_RECOVERABLE_ERROR);
753 }
754 }
755
756 /**
757 * Custom PHP exception handler.
758 *
759 * Uncaught exceptions are those not enclosed in a try/catch block. They are
760 * always fatal: the execution of the script will stop as soon as the exception
761 * handler exits.
762 *
763 * @param $exception
764 * The exception object that was thrown.
765 */
766 function _drupal_exception_handler($exception) {
767 // Log the message to the watchdog and return an error page to the user.
768 _drupal_log_error(_drupal_decode_exception($exception), TRUE);
769 }
770
771 /**
772 * Decode an exception, especially to retrive the correct caller.
773 *
774 * @param $exception
775 * The exception object that was thrown.
776 * @return An error in the format expected by _drupal_log_error().
777 */
778 function _drupal_decode_exception($exception) {
779 $message = $exception->getMessage();
780
781 $backtrace = $exception->getTrace();
782 // Add the line throwing the exception to the backtrace.
783 array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
784
785 // For PDOException errors, we try to return the initial caller,
786 // skipping internal functions of the database layer.
787 if ($exception instanceof PDOException) {
788 // The first element in the stack is the call, the second element gives us the caller.
789 // We skip calls that occurred in one of the classes of the database layer
790 // or in one of its global functions.
791 $db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql');
792 while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
793 ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
794 in_array($caller['function'], $db_functions))) {
795 // We remove that call.
796 array_shift($backtrace);
797 }
798 if (isset($exception->query_string, $exception->args)) {
799 $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
800 }
801 }
802 $caller = _drupal_get_last_caller($backtrace);
803
804 return array(
805 '%type' => get_class($exception),
806 '%message' => $message,
807 '%function' => $caller['function'],
808 '%file' => $caller['file'],
809 '%line' => $caller['line'],
810 );
811 }
812
813 /**
814 * Log a PHP error or exception, display an error page in fatal cases.
815 *
816 * @param $error
817 * An array with the following keys: %type, %message, %function, %file, %line.
818 * @param $fatal
819 * TRUE if the error is fatal.
820 */
821 function _drupal_log_error($error, $fatal = FALSE) {
822 // Initialize a maintenance theme if the boostrap was not complete.
823 // Do it early because drupal_set_message() triggers a drupal_theme_initialize().
824 if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
825 unset($GLOBALS['theme']);
826 if (!defined('MAINTENANCE_MODE')) {
827 define('MAINTENANCE_MODE', 'error');
828 }
829 drupal_maintenance_theme();
830 }
831
832 // When running inside the testing framework, we relay the errors
833 // to the tested site by the way of HTTP headers.
834 if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
835 // $number does not use drupal_static as it should not be reset
836 // as it uniquely identifies each PHP error.
837 static $number = 0;
838 $assertion = array(
839 $error['%message'],
840 $error['%type'],
841 array(
842 'function' => $error['%function'],
843 'file' => $error['%file'],
844 'line' => $error['%line'],
845 ),
846 );
847 header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
848 $number++;
849 }
850
851 try {
852 watchdog('php', '%type: %message in %function (line %line of %file).', $error, WATCHDOG_ERROR);
853 }
854 catch (Exception $e) {
855 // Ignore any additional watchdog exception, as that probably means
856 // that the database was not initialized correctly.
857 }
858
859 if ($fatal) {
860 drupal_set_header('500 Service unavailable (with message)');
861 }
862
863 if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
864 if ($fatal) {
865 // When called from JavaScript, simply output the error message.
866 print t('%type: %message in %function (line %line of %file).', $error);
867 exit;
868 }
869 }
870 else {
871 // Display the message if the current error reporting level allows this type
872 // of message to be displayed, and unconditionnaly in update.php.
873 $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL);
874 $display_error = $error_level == ERROR_REPORTING_DISPLAY_ALL || ($error_level == ERROR_REPORTING_DISPLAY_SOME && $error['%type'] != 'Notice');
875 if ($display_error || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) {
876 $class = 'error';
877
878 // If error type is 'User notice' then treat it as debug information
879 // instead of an error message, see dd().
880 if ($error['%type'] == 'User notice') {
881 $error['%type'] = 'Debug';
882 $class = 'status';
883 }
884
885 drupal_set_message(t('%type: %message in %function (line %line of %file).', $error), $class);
886 }
887
888 if ($fatal) {
889 drupal_set_title(t('Error'));
890 // We fallback to a maintenance page at this point, because the page generation
891 // itself can generate errors.
892 print theme('maintenance_page', t('The website encountered an unexpected error. Please try again later.'), FALSE);
893 exit;
894 }
895 }
896 }
897
898 /**
899 * Gets the last caller from a backtrace.
900 *
901 * @param $backtrace
902 * A standard PHP backtrace.
903 * @return
904 * An associative array with keys 'file', 'line' and 'function'.
905 */
906 function _drupal_get_last_caller($backtrace) {
907 // Errors that occur inside PHP internal functions do not generate
908 // information about file and line. Ignore black listed functions.
909 $blacklist = array('debug');
910 while (($backtrace && !isset($backtrace[0]['line'])) ||
911 (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) {
912 array_shift($backtrace);
913 }
914
915 // The first trace is the call itself.
916 // It gives us the line and the file of the last call.
917 $call = $backtrace[0];
918
919 // The second call give us the function where the call originated.
920 if (isset($backtrace[1])) {
921 if (isset($backtrace[1]['class'])) {
922 $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
923 }
924 else {
925 $call['function'] = $backtrace[1]['function'] . '()';
926 }
927 }
928 else {
929 $call['function'] = 'main()';
930 }
931 return $call;
932 }
933
934 function _fix_gpc_magic(&$item) {
935 if (is_array($item)) {
936 array_walk($item, '_fix_gpc_magic');
937 }
938 else {
939 $item = stripslashes($item);
940 }
941 }
942
943 /**
944 * Helper function to strip slashes from $_FILES skipping over the tmp_name keys
945 * since PHP generates single backslashes for file paths on Windows systems.
946 *
947 * tmp_name does not have backslashes added see
948 * http://php.net/manual/en/features.file-upload.php#42280
949 */
950 function _fix_gpc_magic_files(&$item, $key) {
951 if ($key != 'tmp_name') {
952 if (is_array($item)) {
953 array_walk($item, '_fix_gpc_magic_files');
954 }
955 else {
956 $item = stripslashes($item);
957 }
958 }
959 }
960
961 /**
962 * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
963 */
964 function fix_gpc_magic() {
965 $fixed = &drupal_static(__FUNCTION__, FALSE);
966 if (!$fixed && ini_get('magic_quotes_gpc')) {
967 array_walk($_GET, '_fix_gpc_magic');
968 array_walk($_POST, '_fix_gpc_magic');
969 array_walk($_COOKIE, '_fix_gpc_magic');
970 array_walk($_REQUEST, '_fix_gpc_magic');
971 array_walk($_FILES, '_fix_gpc_magic_files');
972 $fixed = TRUE;
973 }
974 }
975
976 /**
977 * Translate strings to the page language or a given language.
978 *
979 * Human-readable text that will be displayed somewhere within a page should
980 * be run through the t() function.
981 *
982 * Examples:
983 * @code
984 * if (!$info || !$info['extension']) {
985 * form_set_error('picture_upload', t('The uploaded file was not an image.'));
986 * }
987 *
988 * $form['submit'] = array(
989 * '#type' => 'submit',
990 * '#value' => t('Log in'),
991 * );
992 * @endcode
993 *
994 * Any text within t() can be extracted by translators and changed into
995 * the equivalent text in their native language.
996 *
997 * Special variables called "placeholders" are used to signal dynamic
998 * information in a string which should not be translated. Placeholders
999 * can also be used for text that may change from time to time (such as
1000 * link paths) to be changed without requiring updates to translations.
1001 *
1002 * For example:
1003 * @code
1004 * $output = t('There are currently %members and %visitors online.', array(
1005 * '%members' => format_plural($total_users, '1 user', '@count users'),
1006 * '%visitors' => format_plural($guests->count, '1 guest', '@count guests')));
1007 * @endcode
1008 *
1009 * There are three styles of placeholders:
1010 * - !variable, which indicates that the text should be inserted as-is. This is
1011 * useful for inserting variables into things like e-mail.
1012 * @code
1013 * $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))));
1014 * @endcode
1015 *
1016 * - @variable, which indicates that the text should be run through
1017 * check_plain, to escape HTML characters. Use this for any output that's
1018 * displayed within a Drupal page.
1019 * @code
1020 * drupal_set_title($title = t("@name's blog", array('@name' => $account->name)), PASS_THROUGH);
1021 * @endcode
1022 *
1023 * - %variable, which indicates that the string should be HTML escaped and
1024 * highlighted with theme_placeholder() which shows up by default as
1025 * <em>emphasized</em>.
1026 * @code
1027 * $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => $user->name, '%name-to' => $account->name));
1028 * @endcode
1029 *
1030 * When using t(), try to put entire sentences and strings in one t() call.
1031 * This makes it easier for translators, as it provides context as to what
1032 * each word refers to. HTML markup within translation strings is allowed, but
1033 * should be avoided if possible. The exception are embedded links; link
1034 * titles add a context for translators, so should be kept in the main string.
1035 *
1036 * Here is an example of incorrect usage of t():
1037 * @code
1038 * $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact')));
1039 * @endcode
1040 *
1041 * Here is an example of t() used correctly:
1042 * @code
1043 * $output .= '<p>' . t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) . '</p>';
1044 * @endcode
1045 *
1046 * Avoid escaping quotation marks wherever possible.
1047 *
1048 * Incorrect:
1049 * @code
1050 * $output .= t('Don\'t click me.');
1051 * @endcode
1052 *
1053 * Correct:
1054 * @code
1055 * $output .= t("Don't click me.");
1056 * @endcode
1057 *
1058 * Because t() is designed for handling code-based strings, in almost all
1059 * cases, the actual string and not a variable must be passed through t().
1060 *
1061 * Extraction of translations is done based on the strings contained in t()
1062 * calls. If a variable is passed through t(), the content of the variable
1063 * cannot be extracted from the file for translation.
1064 *
1065 * Incorrect:
1066 * @code
1067 * $message = 'An error occurred.';
1068 * drupal_set_message(t($message), 'error');
1069 * $output .= t($message);
1070 * @endcode
1071 *
1072 * Correct:
1073 * @code
1074 * $message = t('An error occurred.');
1075 * drupal_set_message($message, 'error');
1076 * $output .= $message;
1077 * @endcode
1078 *
1079 * The only case in which variables can be passed safely through t() is when
1080 * code-based versions of the same strings will be passed through t() (or
1081 * otherwise extracted) elsewhere.
1082 *
1083 * In some cases, modules may include strings in code that can't use t()
1084 * calls. For example, a module may use an external PHP application that
1085 * produces strings that are loaded into variables in Drupal for output.
1086 * In these cases, module authors may include a dummy file that passes the
1087 * relevant strings through t(). This approach will allow the strings to be
1088 * extracted.
1089 *
1090 * Sample external (non-Drupal) code:
1091 * @code
1092 * class Time {
1093 * public $yesterday = 'Yesterday';
1094 * public $today = 'Today';
1095 * public $tomorrow = 'Tomorrow';
1096 * }
1097 * @endcode
1098 *
1099 * Sample dummy file.
1100 * @code
1101 * // Dummy function included in example.potx.inc.
1102 * function example_potx() {
1103 * $strings = array(
1104 * t('Yesterday'),
1105 * t('Today'),
1106 * t('Tomorrow'),
1107 * );
1108 * // No return value needed, since this is a dummy function.
1109 * }
1110 * @endcode
1111 *
1112 * Having passed strings through t() in a dummy function, it is then
1113 * okay to pass variables through t().
1114 *
1115 * Correct (if a dummy file was used):
1116 * @code
1117 * $time = new Time();
1118 * $output .= t($time->today);
1119 * @endcode
1120 *
1121 * However tempting it is, custom data from user input or other non-code
1122 * sources should not be passed through t(). Doing so leads to the following
1123 * problems and errors:
1124 * - The t() system doesn't support updates to existing strings. When user
1125 * data is updated, the next time it's passed through t() a new record is
1126 * created instead of an update. The database bloats over time and any
1127 * existing translations are orphaned with each update.
1128 * - The t() system assumes any data it receives is in English. User data may
1129 * be in another language, producing translation errors.
1130 * - The "Built-in interface" text group in the locale system is used to
1131 * produce translations for storage in .po files. When non-code strings are
1132 * passed through t(), they are added to this text group, which is rendered
1133 * inaccurate since it is a mix of actual interface strings and various user
1134 * input strings of uncertain origin.
1135 *
1136 * Incorrect:
1137 * @code
1138 * $item = item_load();
1139 * $output .= check_plain(t($item['title']));
1140 * @endcode
1141 *
1142 * Instead, translation of these data can be done through the locale system,
1143 * either directly or through helper functions provided by contributed
1144 * modules.
1145 * @see hook_locale()
1146 *
1147 * During installation, st() is used in place of t(). Code that may be called
1148 * during installation or during normal operation should use the get_t()
1149 * helper function.
1150 * @see st()
1151 * @see get_t()
1152 *
1153 * @param $string
1154 * A string containing the English string to translate.
1155 * @param $args
1156 * An associative array of replacements to make after translation. Incidences
1157 * of any key in this array are replaced with the corresponding value. Based
1158 * on the first character of the key, the value is escaped and/or themed:
1159 * - !variable: inserted as is
1160 * - @variable: escape plain text to HTML (check_plain)
1161 * - %variable: escape text and theme as a placeholder for user-submitted
1162 * content (check_plain + theme_placeholder)
1163 * @param $options
1164 * An associative array of additional options, with the following keys:
1165 * - 'langcode' (default to the current language) The language code to
1166 * translate to a language other than what is used to display the page.
1167 * - 'context' (default to the empty context) The context the source string
1168 * belongs to.
1169 * @return
1170 * The translated string.
1171 */
1172 function t($string, array $args = array(), array $options = array()) {
1173 global $language;
1174 static $custom_strings;
1175
1176 // Merge in default.
1177 if (empty($options['langcode'])) {
1178 $options['langcode'] = isset($language->language) ? $language->language : 'en';
1179 }
1180 if (empty($options['context'])) {
1181 $options['context'] = '';
1182 }
1183
1184 // First, check for an array of customized strings. If present, use the array
1185 // *instead of* database lookups. This is a high performance way to provide a
1186 // handful of string replacements. See settings.php for examples.
1187 // Cache the $custom_strings variable to improve performance.
1188 if (!isset($custom_strings[$options['langcode']])) {
1189 $custom_strings[$options['langcode']] = variable_get('locale_custom_strings_' . $options['langcode'], array());
1190 }
1191 // Custom strings work for English too, even if locale module is disabled.
1192 if (isset($custom_strings[$options['langcode']][$options['context']][$string])) {
1193 $string = $custom_strings[$options['langcode']][$options['context']][$string];
1194 }
1195 // Translate with locale module if enabled.
1196 // We don't use drupal_function_exists() here, because it breaks the testing
1197 // framework if the locale module is enabled in the parent site (we cannot
1198 // unload functions in PHP).
1199 elseif (function_exists('locale') && $options['langcode'] != 'en') {
1200 $string = locale($string, $options['context'], $options['langcode']);
1201 }
1202 if (empty($args)) {
1203 return $string;
1204 }
1205 else {
1206 // Transform arguments before inserting them.
1207 foreach ($args as $key => $value) {
1208 switch ($key[0]) {
1209 case '@':
1210 // Escaped only.
1211 $args[$key] = check_plain($value);
1212 break;
1213
1214 case '%':
1215 default:
1216 // Escaped and placeholder.
1217 $args[$key] = theme('placeholder', $value);
1218 break;
1219
1220 case '!':
1221 // Pass-through.
1222 }
1223 }
1224 return strtr($string, $args);
1225 }
1226 }
1227
1228 /**
1229 * @defgroup validation Input validation
1230 * @{
1231 * Functions to validate user input.
1232 */
1233
1234 /**
1235 * Verify the syntax of the given e-mail address.
1236 *
1237 * Empty e-mail addresses are allowed. See RFC 2822 for details.
1238 *
1239 * @param $mail
1240 * A string containing an e-mail address.
1241 * @return
1242 * TRUE if the address is in a valid format.
1243 */
1244 function valid_email_address($mail) {
1245 return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
1246 }
1247
1248 /**
1249 * Verify the syntax of the given URL.
1250 *
1251 * This function should only be used on actual URLs. It should not be used for
1252 * Drupal menu paths, which can contain arbitrary characters.
1253 * Valid values per RFC 3986.
1254 * @param $url
1255 * The URL to verify.
1256 * @param $absolute
1257 * Whether the URL is absolute (beginning with a scheme such as "http:").
1258 * @return
1259 * TRUE if the URL is in a valid format.
1260 */
1261 function valid_url($url, $absolute = FALSE) {
1262 if ($absolute) {
1263 return (bool)preg_match("
1264 /^ # Start at the beginning of the text
1265 (?:ftp|https?):\/\/ # Look for ftp, http, or https schemes
1266 (?: # Userinfo (optional) which is typically
1267 (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
1268 (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
1269 )?
1270 (?:
1271 (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
1272 |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
1273 )
1274 (?::[0-9]+)? # Server port number (optional)
1275 (?:[\/|\?]
1276 (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
1277 *)?
1278 $/xi", $url);
1279 }
1280 else {
1281 return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
1282 }
1283 }
1284
1285 /**
1286 * @} End of "defgroup validation".
1287 */
1288
1289 /**
1290 * Register an event for the current visitor to the flood control mechanism.
1291 *
1292 * @param $name
1293 * The name of an event.
1294 * @param $identifier
1295 * Optional identifier (defaults to the current user's IP address).
1296 */
1297 function flood_register_event($name, $identifier = NULL) {
1298 if (!isset($identifier)) {
1299 $identifier = ip_address();
1300 }
1301 db_insert('flood')
1302 ->fields(array(
1303 'event' => $name,
1304 'identifier' => $identifier,
1305 'timestamp' => REQUEST_TIME,
1306 ))
1307 ->execute();
1308 }
1309
1310 /**
1311 * Make the flood control mechanism forget about an event for the current visitor.
1312 *
1313 * @param $name
1314 * The name of an event.
1315 * @param $identifier
1316 * Optional identifier (defaults to the current user's IP address).
1317 */
1318 function flood_clear_event($name, $identifier = NULL) {
1319 if (!isset($identifier)) {
1320 $identifier = ip_address();
1321 }
1322 db_delete('flood')
1323 ->condition('event', $name)
1324 ->condition('identifier', $identifier)
1325 ->execute();
1326 }
1327
1328 /**
1329 * Check if the current visitor is allowed to proceed with the specified event.
1330 *
1331 * The user is allowed to proceed if he did not trigger the specified event more
1332 * than $threshold times in the specified time window.
1333 *
1334 * @param $name
1335 * The name of the event.
1336 * @param $threshold
1337 * The maximum number of the specified event allowed per time window.
1338 * @param $window
1339 * Optional number of seconds over which to look for events. Defaults to
1340 * 3600 (1 hour).
1341 * @param $identifier
1342 * Optional identifier (defaults to the current user's IP address).
1343 * @return
1344 * True if the user did not exceed the hourly threshold. False otherwise.
1345 */
1346 function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) {
1347 if (!isset($identifier)) {
1348 $identifier = ip_address();
1349 }
1350 $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array(
1351 ':event' => $name,
1352 ':identifier' => $identifier,
1353 ':timestamp' => REQUEST_TIME - $window))
1354 ->fetchField();
1355 return ($number < $threshold);
1356 }
1357
1358 function check_file($filename) {
1359 return is_uploaded_file($filename);
1360 }
1361
1362 /**
1363 * @defgroup sanitization Sanitization functions
1364 * @{
1365 * Functions to sanitize values.
1366 */
1367
1368 /**
1369 * Prepare a URL for use in an HTML attribute. Strips harmful protocols.
1370 */
1371 function check_url($uri) {
1372 return filter_xss_bad_protocol($uri, FALSE);
1373 }
1374
1375 /**
1376 * Very permissive XSS/HTML filter for admin-only use.
1377 *
1378 * Use only for fields where it is impractical to use the
1379 * whole filter system, but where some (mainly inline) mark-up
1380 * is desired (so check_plain() is not acceptable).
1381 *
1382 * Allows all tags that can be used inside an HTML body, save
1383 * for scripts and styles.
1384 */
1385 function filter_xss_admin($string) {
1386 return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'));
1387 }
1388
1389 /**
1390 * Filter XSS.
1391 *
1392 * Based on kses by Ulf Harnhammar, see
1393 * http://sourceforge.net/projects/kses
1394 *
1395 * For examples of various XSS attacks, see:
1396 * http://ha.ckers.org/xss.html
1397 *
1398 * This code does four things:
1399 * - Removes characters and constructs that can trick browsers
1400 * - Makes sure all HTML entities are well-formed
1401 * - Makes sure all HTML tags and attributes are well-formed
1402 * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. javascript:)
1403 *
1404 * @param $string
1405 * The string with raw HTML in it. It will be stripped of everything that can cause
1406 * an XSS attack.
1407 * @param $allowed_tags
1408 * An array of allowed tags.
1409 */
1410 function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
1411 // Only operate on valid UTF-8 strings. This is necessary to prevent cross
1412 // site scripting issues on Internet Explorer 6.
1413 if (!drupal_validate_utf8($string)) {
1414 return '';
1415 }
1416 // Store the text format
1417 _filter_xss_split($allowed_tags, TRUE);
1418 // Remove NULL characters (ignored by some browsers)
1419 $string = str_replace(chr(0), '', $string);
1420 // Remove Netscape 4 JS entities
1421 $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
1422
1423 // Defuse all HTML entities
1424 $string = str_replace('&', '&amp;', $string);
1425 // Change back only well-formed entities in our whitelist
1426 // Decimal numeric entities
1427 $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
1428 // Hexadecimal numeric entities
1429 $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
1430 // Named entities
1431 $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
1432
1433 return preg_replace_callback('%
1434 (
1435 <(?=[^a-zA-Z!/]) # a lone <
1436 | # or
1437 <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
1438 | # or
1439 > # just a >
1440 )%x', '_filter_xss_split', $string);
1441 }
1442
1443 /**
1444 * Processes an HTML tag.
1445 *
1446 * @param $m
1447 * An array with various meaning depending on the value of $store.
1448 * If $store is TRUE then the array contains the allowed tags.
1449 * If $store is FALSE then the array has one element, the HTML tag to process.
1450 * @param $store
1451 * Whether to store $m.
1452 * @return
1453 * If the element isn't allowed, an empty string. Otherwise, the cleaned up
1454 * version of the HTML element.
1455 */
1456 function _filter_xss_split($m, $store = FALSE) {
1457 static $allowed_html;
1458
1459 if ($store) {
1460 $allowed_html = array_flip($m);
1461 return;
1462 }
1463
1464 $string = $m[1];
1465
1466 if (substr($string, 0, 1) != '<') {
1467 // We matched a lone ">" character
1468 return '&gt;';
1469 }
1470 elseif (strlen($string) == 1) {
1471 // We matched a lone "<" character
1472 return '&lt;';
1473 }
1474
1475 if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?$%', $string, $matches)) {
1476 // Seriously malformed
1477 return '';
1478 }
1479
1480 $slash = trim($matches[1]);
1481 $elem = &$matches[2];
1482 $attrlist = &$matches[3];
1483
1484 if (!isset($allowed_html[strtolower($elem)])) {
1485 // Disallowed HTML element
1486 return '';
1487 }
1488
1489 if ($slash != '') {
1490 return "</$elem>";
1491 }
1492
1493 // Is there a closing XHTML slash at the end of the attributes?
1494 $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
1495 $xhtml_slash = $count ? ' /' : '';
1496
1497 // Clean up attributes
1498 $attr2 = implode(' ', _filter_xss_attributes($attrlist));
1499 $attr2 = preg_replace('/[<>]/', '', $attr2);
1500 $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
1501
1502 return "<$elem$attr2$xhtml_slash>";
1503 }
1504
1505 /**
1506 * Processes a string of HTML attributes.
1507 *
1508 * @return
1509 * Cleaned up version of the HTML attributes.
1510 */
1511 function _filter_xss_attributes($attr) {
1512 $attrarr = array();
1513 $mode = 0;
1514 $attrname = '';
1515
1516 while (strlen($attr) != 0) {
1517 // Was the last operation successful?
1518 $working = 0;
1519
1520 switch ($mode) {
1521 case 0:
1522 // Attribute name, href for instance
1523 if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
1524 $attrname = strtolower($match[1]);
1525 $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
1526 $working = $mode = 1;
1527 $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
1528 }
1529 break;
1530
1531 case 1:
1532 // Equals sign or valueless ("selected")
1533 if (preg_match('/^\s*=\s*/', $attr)) {
1534 $working = 1; $mode = 2;
1535 $attr = preg_replace('/^\s*=\s*/', '', $attr);
1536 break;
1537 }
1538
1539 if (preg_match('/^\s+/', $attr)) {
1540 $working = 1; $mode = 0;
1541 if (!$skip) {
1542 $attrarr[] = $attrname;
1543 }
1544 $attr = preg_replace('/^\s+/', '', $attr);
1545 }
1546 break;
1547
1548 case 2:
1549 // Attribute value, a URL after href= for instance
1550 if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
1551 $thisval = filter_xss_bad_protocol($match[1]);
1552
1553 if (!$skip) {
1554 $attrarr[] = "$attrname=\"$thisval\"";
1555 }
1556 $working = 1;
1557 $mode = 0;
1558 $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
1559 break;
1560 }
1561
1562 if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
1563 $thisval = filter_xss_bad_protocol($match[1]);
1564
1565 if (!$skip) {
1566 $attrarr[] = "$attrname='$thisval'";
1567 }
1568 $working = 1; $mode = 0;
1569 $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
1570 break;
1571 }
1572
1573 if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
1574 $thisval = filter_xss_bad_protocol($match[1]);
1575
1576 if (!$skip) {
1577 $attrarr[] = "$attrname=\"$thisval\"";
1578 }
1579 $working = 1; $mode = 0;
1580 $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
1581 }
1582 break;
1583 }
1584
1585 if ($working == 0) {
1586 // not well formed, remove and try again
1587 $attr = preg_replace('/
1588 ^
1589 (
1590 "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
1591 | # or
1592 \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
1593 | # or
1594 \S # - a non-whitespace character
1595 )* # any number of the above three
1596 \s* # any number of whitespaces
1597 /x', '', $attr);
1598 $mode = 0;
1599 }
1600 }
1601
1602 // The attribute list ends with a valueless attribute like "selected".
1603 if ($mode == 1) {
1604 $attrarr[] = $attrname;
1605 }
1606 return $attrarr;
1607 }
1608
1609 /**
1610 * Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:).
1611 *
1612 * @param $string
1613 * The string with the attribute value.
1614 * @param $decode
1615 * Whether to decode entities in the $string. Set to FALSE if the $string
1616 * is in plain text, TRUE otherwise. Defaults to TRUE.
1617 * @return
1618 * Cleaned up and HTML-escaped version of $string.
1619 */
1620 function filter_xss_bad_protocol($string, $decode = TRUE) {
1621 static $allowed_protocols;
1622
1623 if (!isset($allowed_protocols)) {
1624 $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'telnet', 'webcal')));
1625 }
1626
1627 // Get the plain text representation of the attribute value (i.e. its meaning).
1628 if ($decode) {
1629 $string = decode_entities($string);
1630 }
1631
1632 // Iteratively remove any invalid protocol found.
1633 do {
1634 $before = $string;
1635 $colonpos = strpos($string, ':');
1636 if ($colonpos > 0) {
1637 // We found a colon, possibly a protocol. Verify.
1638 $protocol = substr($string, 0, $colonpos);
1639 // If a colon is preceded by a slash, question mark or hash, it cannot
1640 // possibly be part of the URL scheme. This must be a relative URL,
1641 // which inherits the (safe) protocol of the base document.
1642 if (preg_match('![/?#]!', $protocol)) {
1643 break;
1644 }
1645 // Per RFC2616, section 3.2.3 (URI Comparison) scheme comparison must be case-insensitive
1646 // Check if this is a disallowed protocol.
1647 if (!isset($allowed_protocols[strtolower($protocol)])) {
1648 $string = substr($string, $colonpos + 1);
1649 }
1650 }
1651 } while ($before != $string);
1652
1653 return check_plain($string);
1654 }
1655
1656 /**
1657 * @} End of "defgroup sanitization".
1658 */
1659
1660 /**
1661 * @defgroup format Formatting
1662 * @{
1663 * Functions to format numbers, strings, dates, etc.
1664 */
1665
1666 /**
1667 * Formats an RSS channel.
1668 *
1669 * Arbitrary elements may be added using the $args associative array.
1670 */
1671 function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
1672 global $language;
1673 $langcode = $langcode ? $langcode : $language->language;
1674
1675 $output = "<channel>\n";
1676 $output .= ' <title>' . check_plain($title) . "</title>\n";
1677 $output .= ' <link>' . check_url($link) . "</link>\n";
1678
1679 // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
1680 // We strip all HTML tags, but need to prevent double encoding from properly
1681 // escaped source data (such as &amp becoming &amp;amp;).
1682 $output .= ' <description>' . check_plain(decode_entities(strip_tags($description))) . "</description>\n";
1683 $output .= ' <language>' . check_plain($langcode) . "</language>\n";
1684 $output .= format_xml_elements($args);
1685 $output .= $items;
1686 $output .= "</channel>\n";
1687
1688 return $output;
1689 }
1690
1691 /**
1692 * Format a single RSS item.
1693 *
1694 * Arbitrary elements may be added using the $args associative array.
1695 */
1696 function format_rss_item($title, $link, $description, $args = array()) {
1697 $output = "<item>\n";
1698 $output .= ' <title>' . check_plain($title) . "</title>\n";
1699 $output .= ' <link>' . check_url($link) . "</link>\n";
1700 $output .= ' <description>' . check_plain($description) . "</description>\n";
1701 $output .= format_xml_elements($args);
1702 $output .= "</item>\n";
1703
1704 return $output;
1705 }
1706
1707 /**
1708 * Format XML elements.
1709 *
1710 * @param $array
1711 * An array where each item represent an element and is either a:
1712 * - (key => value) pair (<key>value</key>)
1713 * - Associative array with fields:
1714 * - 'key': element name
1715 * - 'value': element contents
1716 * - 'attributes': associative array of element attributes
1717 *
1718 * In both cases, 'value' can be a simple string, or it can be another array
1719 * with the same format as $array itself for nesting.
1720 */
1721 function format_xml_elements($array) {
1722 $output = '';
1723 foreach ($array as $key => $value) {
1724 if (is_numeric($key)) {
1725 if ($value['key']) {
1726 $output .= ' <' . $value['key'];
1727 if (isset($value['attributes']) && is_array($value['attributes'])) {
1728 $output .= drupal_attributes($value['attributes']);
1729 }
1730
1731 if (isset($value['value']) && $value['value'] != '') {
1732 $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '</' . $value['key'] . ">\n";
1733 }
1734 else {
1735 $output .= " />\n";
1736 }
1737 }
1738 }
1739 else {
1740 $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : check_plain($value)) . "</$key>\n";
1741 }
1742 }
1743 return $output;
1744 }
1745
1746 /**
1747 * Format a string containing a count of items.
1748 *
1749 * This function ensures that the string is pluralized correctly. Since t() is
1750 * called by this function, make sure not to pass already-localized strings to
1751 * it.
1752 *
1753 * For example:
1754 * @code
1755 * $output = format_plural($node->comment_count, '1 comment', '@count comments');
1756 * @endcode
1757 *
1758 * Example with additional replacements:
1759 * @code
1760 * $output = format_plural($update_count,
1761 * 'Changed the content type of 1 post from %old-type to %new-type.',
1762 * 'Changed the content type of @count posts from %old-type to %new-type.',
1763 * array('%old-type' => $info->old_type, '%new-type' => $info->new_type)));
1764 * @endcode
1765 *
1766 * @param $count
1767 * The item count to display.
1768 * @param $singular
1769 * The string for the singular case. Please make sure it is clear this is
1770 * singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
1771 * Do not use @count in the singular string.
1772 * @param $plural
1773 * The string for the plural case. Please make sure it is clear this is plural,
1774 * to ease translation. Use @count in place of the item count, as in "@count
1775 * new comments".
1776 * @param $args
1777 * An associative array of replacements to make after translation. Incidences
1778 * of any key in this array are replaced with the corresponding value.
1779 * Based on the first character of the key, the value is escaped and/or themed:
1780 * - !variable: inserted as is
1781 * - @variable: escape plain text to HTML (check_plain)
1782 * - %variable: escape text and theme as a placeholder for user-submitted
1783 * content (check_plain + theme_placeholder)
1784 * Note that you do not need to include @count in this array.
1785 * This replacement is done automatically for the plural case.
1786 * @param $options
1787 * An associative array of additional options, with the following keys:
1788 * - 'langcode' (default to the current language) The language code to
1789 * translate to a language other than what is used to display the page.
1790 * - 'context' (default to the empty context) The context the source string
1791 * belongs to.
1792 * @return
1793 * A translated string.
1794 */
1795 function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
1796 $args['@count'] = $count;
1797 if ($count == 1) {
1798 return t($singular, $args, $options);
1799 }
1800
1801 // Get the plural index through the gettext formula.
1802 $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1;
1803 // Backwards compatibility.
1804 if ($index < 0) {
1805 return t($plural, $args, $options);
1806 }
1807 else {
1808 switch ($index) {
1809 case "0":
1810 return t($singular, $args, $options);
1811 case "1":
1812 return t($plural, $args, $options);
1813 default:
1814 unset($args['@count']);
1815 $args['@count[' . $index . ']'] = $count;
1816 return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options);
1817 }
1818 }
1819 }
1820
1821 /**
1822 * Parse a given byte count.
1823 *
1824 * @param $size
1825 * A size expressed as a number of bytes with optional SI or IEC binary unit
1826 * prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
1827 * @return
1828 * An integer representation of the size in bytes.
1829 */
1830 function parse_size($size) {
1831 $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
1832 $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
1833 if ($unit) {
1834 // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
1835 return round($size * pow(DRUPAL_KILOBYTE, stripos('bkmgtpezy', $unit[0])));
1836 }
1837 else {
1838 return round($size);
1839 }
1840 }
1841
1842 /**
1843 * Generate a string representation for the given byte count.
1844 *
1845 * @param $size
1846 * A size in bytes.
1847 * @param $langcode
1848 * Optional language code to translate to a language other than what is used
1849 * to display the page.
1850 * @return
1851 * A translated string representation of the size.
1852 */
1853 function format_size($size, $langcode = NULL) {
1854 if ($size < DRUPAL_KILOBYTE) {
1855 return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
1856 }
1857 else {
1858 $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
1859 $units = array(
1860 t('@size KB', array(), array('langcode' => $langcode)),
1861 t('@size MB', array(), array('langcode' => $langcode)),
1862 t('@size GB', array(), array('langcode' => $langcode)),
1863 t('@size TB', array(), array('langcode' => $langcode)),
1864 t('@size PB', array(), array('langcode' => $langcode)),
1865 t('@size EB', array(), array('langcode' => $langcode)),
1866 t('@size ZB', array(), array('langcode' => $langcode)),
1867 t('@size YB', array(), array('langcode' => $langcode)),
1868 );
1869 foreach ($units as $unit) {
1870 if (round($size, 2) >= DRUPAL_KILOBYTE) {
1871 $size = $size / DRUPAL_KILOBYTE;
1872 }
1873 else {
1874 break;
1875 }
1876 }
1877 return str_replace('@size', round($size, 2), $unit);
1878 }
1879 }
1880
1881 /**
1882 * Format a time interval with the requested granularity.
1883 *
1884 * @param $timestamp
1885 * The length of the interval in seconds.
1886 * @param $granularity
1887 * How many different units to display in the string.
1888 * @param $langcode
1889 * Optional language code to translate to a language other than
1890 * what is used to display the page.
1891 * @return
1892 * A translated string representation of the interval.
1893 */
1894 function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
1895 $units = array(
1896 '1 year|@count years' => 31536000,
1897 '1 month|@count months' => 2592000,
1898 '1 week|@count weeks' => 604800,
1899 '1 day|@count days' => 86400,
1900 '1 hour|@count hours' => 3600,
1901 '1 min|@count min' => 60,
1902 '1 sec|@count sec' => 1
1903 );
1904 $output = '';
1905 foreach ($units as $key => $value) {
1906 $key = explode('|', $key);
1907 if ($timestamp >= $value) {
1908 $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
1909 $timestamp %= $value;
1910 $granularity--;
1911 }
1912
1913 if ($granularity == 0) {
1914 break;
1915 }
1916 }
1917 return $output ? $output : t('0 sec', array(), array('langcode' => $langcode));
1918 }
1919
1920 /**
1921 * Format a date with the given configured format or a custom format string.
1922 *
1923 * Drupal allows administrators to select formatting strings for 'small',
1924 * 'medium' and 'large' date formats. This function can handle these formats,
1925 * as well as any custom format.
1926 *
1927 * @param $timestamp
1928 * The exact date to format, as a UNIX timestamp.
1929 * @param $type
1930 * The format to use. Can be "small", "medium" or "large" for the preconfigured
1931 * date formats. If "custom" is specified, then $format is required as well.
1932 * @param $format
1933 * A PHP date format string as required by date(). A backslash should be used
1934 * before a character to avoid interpreting the character as part of a date
1935 * format.
1936 * @param $timezone
1937 * Time zone identifier; if omitted, the user's time zone is used.
1938 * @param $langcode
1939 * Optional language code to translate to a language other than what is used
1940 * to display the page.
1941 * @return
1942 * A translated date string in the requested format.
1943 */
1944 function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
1945 $timezones = &drupal_static(__FUNCTION__, array());
1946 if (!isset($timezone)) {
1947 global $user;
1948 if (variable_get('configurable_timezones', 1) && $user->uid && $user->timezone) {
1949 $timezone = $user->timezone;
1950 }
1951 else {
1952 $timezone = variable_get('date_default_timezone', 'UTC');
1953 }
1954 }
1955 // Store DateTimeZone objects in an array rather than repeatedly
1956 // contructing identical objects over the life of a request.
1957 if (!isset($timezones[$timezone])) {
1958 $timezones[$timezone] = timezone_open($timezone);
1959 }
1960
1961 // Use the default langcode if none is set.
1962 global $language;
1963 if (empty($langcode)) {
1964 $langcode = isset($language->language) ? $language->language : 'en';
1965 }
1966
1967 switch ($type) {
1968 case 'small':
1969 $format = variable_get('date_format_short', 'm/d/Y - H:i');
1970 break;
1971 case 'large':
1972 $format = variable_get('date_format_long', 'l, F j, Y - H:i');
1973 break;
1974 case 'custom':
1975 // No change to format.
1976 break;
1977 case 'medium':
1978 default:
1979 $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
1980 }
1981
1982 // Create a DateTime object from the timestamp.
1983 $date_time = date_create('@' . $timestamp);
1984 // Set the time zone for the DateTime object.
1985 date_timezone_set($date_time, $timezones[$timezone]);
1986
1987 // Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'.
1988 // xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the
1989 // input string.
1990 // Paired backslashes are isolated to prevent errors in read-ahead evaluation.
1991 // The read-ahead expression ensures that A matches, but not \A.
1992 $format = preg_replace(array('/\\\\\\\\/', '/(?<!\\\\)([AaeDlMTF])/'), array("\xEF\\\\\\\\\xFF", "\xEF\\\\\$1\$1\xFF"), $format);
1993
1994 // Call date_format().
1995 $format = date_format($date_time, $format);
1996
1997 // Pass the langcode to _format_date_callback().
1998 _format_date_callback(NULL, $langcode);
1999
2000 // Translate the marked sequences.
2001 return preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', '_format_date_callback', $format);
2002 }
2003
2004 /**
2005 * Callback function for preg_replace_callback().
2006 */
2007 function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
2008 // We cache translations to avoid redundant and rather costly calls to t().
2009 static $cache, $langcode;
2010
2011 if (!isset($matches)) {
2012 $langcode = $new_langcode;
2013 return;
2014 }
2015
2016 $code = $matches[1];
2017 $string = $matches[2];
2018
2019 if (!isset($cache[$langcode][$code][$string])) {
2020 $options = array(
2021 'langcode' => $langcode,
2022 );
2023
2024 if ($code == 'F') {
2025 $options['context'] = 'Long month name';
2026 }
2027
2028 if ($code == '') {
2029 $cache[$langcode][$code][$string] = $string;
2030 }
2031 else {
2032 $cache[$langcode][$code][$string] = t($string, array(), $options);
2033 }
2034 }
2035 return $cache[$langcode][$code][$string];
2036 }
2037
2038 /**
2039 * @} End of "defgroup format".
2040 */
2041
2042 /**
2043 * Generate a URL from a Drupal menu path. Will also pass-through existing URLs.
2044 *
2045 * @param $path
2046 * The Drupal path being linked to, such as "admin/content", or an
2047 * existing URL like "http://drupal.org/". The special path
2048 * '<front>' may also be given and will generate the site's base URL.
2049 * @param $options
2050 * An associative array of additional options, with the following keys:
2051 * - 'query'
2052 * A URL-encoded query string to append to the link, or an array of query
2053 * key/value-pairs without any URL-encoding.
2054 * - 'fragment'
2055 * A fragment identifier (or named anchor) to append to the link.
2056 * Do not include the '#' character.
2057 * - 'absolute' (default FALSE)
2058 * Whether to force the output to be an absolute link (beginning with
2059 * http:). Useful for links that will be displayed outside the site, such
2060 * as in an RSS feed.
2061 * - 'alias' (default FALSE)
2062 * Whether the given path is an alias already.
2063 * - 'external'
2064 * Whether the given path is an external URL.
2065 * - 'language'
2066 * An optional language object. Used to build the URL to link to and
2067 * look up the proper alias for the link.
2068 * - 'base_url'
2069 * Only used internally, to modify the base URL when a language dependent
2070 * URL requires so.
2071 * - 'prefix'
2072 * Only used internally, to modify the path when a language dependent URL
2073 * requires so.
2074 * @return
2075 * A string containing a URL to the given path.
2076 *
2077 * When creating links in modules, consider whether l() could be a better
2078 * alternative than url().
2079 */
2080 function url($path = NULL, array $options = array()) {
2081 // Merge in defaults.
2082 $options += array(
2083 'fragment' => '',
2084 'query' => '',
2085 'absolute' => FALSE,
2086 'alias' => FALSE,
2087 'prefix' => ''
2088 );
2089 if (!isset($options['external'])) {
2090 // Return an external link if $path contains an allowed absolute URL.
2091 // Only call the slow filter_xss_bad_protocol if $path contains a ':' before
2092 // any / ? or #.
2093 $colonpos = strpos($path, ':');
2094 $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path));
2095 }
2096
2097 // May need language dependent rewriting if language.inc is present.
2098 if (function_exists('language_url_rewrite')) {
2099 language_url_rewrite($path, $options);
2100 }
2101 if ($options['fragment']) {
2102 $options['fragment'] = '#' . $options['fragment'];
2103 }
2104 if (is_array($options['query'])) {
2105 $options['query'] = drupal_query_string_encode($options['query']);
2106 }
2107
2108 if ($options['external']) {
2109 // Split off the fragment.
2110 if (strpos($path, '#') !== FALSE) {
2111 list($path, $old_fragment) = explode('#', $path, 2);
2112 if (isset($old_fragment) && !$options['fragment']) {
2113 $options['fragment'] = '#' . $old_fragment;
2114 }
2115 }
2116 // Append the query.
2117 if ($options['query']) {
2118 $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query'];
2119 }
2120 // Reassemble.
2121 return $path . $options['fragment'];
2122 }
2123
2124 global $base_url;
2125 $script = &drupal_static(__FUNCTION__);
2126
2127 if (!isset($script)) {
2128 // On some web servers, such as IIS, we can't omit "index.php". So, we
2129 // generate "index.php?q=foo" instead of "?q=foo" on anything that is not
2130 // Apache.
2131 $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : '';
2132 }
2133
2134 if (!isset($options['base_url'])) {
2135 // The base_url might be rewritten from the language rewrite in domain mode.
2136 $options['base_url'] = $base_url;
2137 }
2138
2139 // Preserve the original path before aliasing.
2140 $original_path = $path;
2141
2142 // The special path '<front>' links to the default front page.
2143 if ($path == '<front>') {
2144 $path = '';
2145 }
2146 elseif (!empty($path) && !$options['alias']) {
2147 $path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : '');
2148 }
2149
2150 if (function_exists('custom_url_rewrite_outbound')) {
2151 // Modules may alter outbound links by reference.
2152 custom_url_rewrite_outbound($path, $options, $original_path);
2153 }
2154
2155 $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
2156 $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
2157 $path = drupal_encode_path($prefix . $path);
2158
2159 if (variable_get('clean_url', '0')) {
2160 // With Clean URLs.
2161 if ($options['query']) {
2162 return $base . $path . '?' . $options['query'] . $options['fragment'];
2163 }
2164 else {
2165 return $base . $path . $options['fragment'];
2166 }
2167 }
2168 else {
2169 // Without Clean URLs.
2170 $variables = array();
2171 if (!empty($path)) {
2172 $variables[] = 'q=' . $path;
2173 }
2174 if (!empty($options['query'])) {
2175 $variables[] = $options['query'];
2176 }
2177 if ($query = join('&', $variables)) {
2178 return $base . $script . '?' . $query . $options['fragment'];
2179 }
2180 else {
2181 return $base . $options['fragment'];
2182 }
2183 }
2184 }
2185
2186 /**
2187 * Format an attribute string to insert in a tag.
2188 *
2189 * Each array key and its value will be formatted into an HTML attribute string.
2190 * If a value is itself an array, then each array element is concatenated with a
2191 * space between each value (e.g. a multi-value class attribute).
2192 *
2193 * @param $attributes
2194 * An associative array of HTML attributes.
2195 * @return
2196 * An HTML string ready for insertion in a tag.
2197 */
2198 function drupal_attributes(array $attributes = array()) {
2199 foreach ($attributes as $attribute => &$data) {
2200 if (is_array($data)) {
2201 $data = implode(' ', $data);
2202 }
2203 $data = $attribute . '="' . check_plain($data) . '"';
2204 }
2205 return $attributes ? ' ' . implode(' ', $attributes) : '';
2206 }
2207
2208 /**
2209 * Format an internal Drupal link.
2210 *
2211 * This function correctly handles aliased paths, and allows themes to highlight
2212 * links to the current page correctly, so all internal links output by modules
2213 * should be generated by this function if possible.
2214 *
2215 * @param $text
2216 * The text to be enclosed with the anchor tag.
2217 * @param $path
2218 * The Drupal path being linked to, such as "admin/content". Can be an
2219 * external or internal URL.
2220 * - If you provide the full URL, it will be considered an external URL.
2221 * - If you provide only the path (e.g. "admin/content"), it is
2222 * considered an internal link. In this case, it must be a system URL
2223 * as the url() function will generate the alias.
2224 * - If you provide '<front>', it generates a link to the site's
2225 * base URL (again via the url() function).
2226 * - If you provide a path, and 'alias' is set to TRUE (see below), it is
2227 * used as is.
2228 * @param $options
2229 * An associative array of additional options, with the following keys:
2230 * - 'attributes'
2231 * An associative array of HTML attributes to apply to the anchor tag.
2232 * - 'query'
2233 * A query string to append to the link, or an array of query key/value
2234 * properties.
2235 * - 'fragment'
2236 * A fragment identifier (named anchor) to append to the link.
2237 * Do not include the '#' character.
2238 * - 'absolute' (default FALSE)
2239 * Whether to force the output to be an absolute link (beginning with
2240 * http:). Useful for links that will be displayed outside the site, such
2241 * as in an RSS feed.
2242 * - 'html' (default FALSE)
2243 * Whether $text is HTML, or just plain-text. For example for making
2244 * an image a link, this must be set to TRUE, or else you will see the
2245 * escaped HTML.
2246 * - 'alias' (default FALSE)
2247 * Whether the given path is an alias already.
2248 * @return
2249 * an HTML string containing a link to the given path.
2250 */
2251 function l($text, $path, array $options = array()) {
2252 global $language;
2253
2254 // Merge in defaults.
2255 $options += array(
2256 'attributes' => array(),
2257 'html' => FALSE,
2258 );
2259
2260 // Append active class.
2261 if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
2262 (empty($options['language']) || $options['language']->language == $language->language)) {
2263 if (isset($options['attributes']['class'])) {
2264 $options['attributes']['class'] .= ' active';
2265 }
2266 else {
2267 $options['attributes']['class'] = 'active';
2268 }
2269 }
2270
2271 // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
2272 // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
2273 if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
2274 $options['attributes']['title'] = strip_tags($options['attributes']['title']);
2275 }
2276
2277 return '<a href="' . check_plain(url($path, $options)) . '"' . drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text : check_plain($text)) . '</a>';
2278 }
2279
2280 /**
2281 * Perform end-of-request tasks.
2282 *
2283 * This function sets the page cache if appropriate, and allows modules to
2284 * react to the closing of the page by calling hook_exit().
2285 */
2286 function drupal_page_footer() {
2287 global $user;
2288
2289 module_invoke_all('exit');
2290
2291 // Commit the user session, if needed.
2292 drupal_session_commit();
2293
2294 if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED && ($cache = drupal_page_set_cache())) {
2295 drupal_serve_page_from_cache($cache);
2296 }
2297 else {
2298 ob_flush();
2299 }
2300
2301 module_implements(MODULE_IMPLEMENTS_WRITE_CACHE);
2302 _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
2303 drupal_cache_system_paths();
2304 }
2305
2306 /**
2307 * Form an associative array from a linear array.
2308 *
2309 * This function walks through the provided array and constructs an associative
2310 * array out of it. The keys of the resulting array will be the values of the
2311 * input array. The values will be the same as the keys unless a function is
2312 * specified, in which case the output of the function is used for the values
2313 * instead.
2314 *
2315 * @param $array
2316 * A linear array.
2317 * @param $function
2318 * A name of a function to apply to all values before output.
2319 * @result
2320 * An associative array.
2321 */
2322 function drupal_map_assoc($array, $function = NULL) {
2323 if (!isset($function)) {
2324 $result = array();
2325 foreach ($array as $value) {
2326 $result[$value] = $value;
2327 }
2328 return $result;
2329 }
2330 elseif (function_exists($function)) {
2331 $result = array();
2332 foreach ($array as $value) {
2333 $result[$value] = $function($value);
2334 }
2335 return $result;
2336 }
2337 }
2338
2339 /**
2340 * Attempts to set the PHP maximum execution time.
2341 *
2342 * This function is a wrapper around the PHP function set_time_limit().
2343 * When called, set_time_limit() restarts the timeout counter from zero.
2344 * In other words, if the timeout is the default 30 seconds, and 25 seconds
2345 * into script execution a call such as set_time_limit(20) is made, the
2346 * script will run for a total of 45 seconds before timing out.
2347 *
2348 * It also means that it is possible to decrease the total time limit if
2349 * the sum of the new time limit and the current time spent running the
2350 * script is inferior to the original time limit. It is inherent to the way
2351 * set_time_limit() works, it should rather be called with an appropriate
2352 * value every time you need to allocate a certain amount of time
2353 * to execute a task than only once at the beginning of the script.
2354 *
2355 * Before calling set_time_limit(), we check if this function is available
2356 * because it could be disabled by the server administrator. We also hide all
2357 * the errors that could occur when calling set_time_limit(), because it is
2358 * not possible to reliably ensure that PHP or a security extension will
2359 * not issue a warning/error if they prevent the use of this function.
2360 *
2361 * @param $time_limit
2362 * An integer specifying the new time limit, in seconds. A value of 0
2363 * indicates unlimited execution time.
2364 */
2365 function drupal_set_time_limit($time_limit) {
2366 if (function_exists('set_time_limit')) {
2367 @set_time_limit($time_limit);
2368 }
2369 }
2370
2371 /**
2372 * Returns the path to a system item (module, theme, etc.).
2373 *
2374 * @param $type
2375 * The type of the item (i.e. theme, theme_engine, module).
2376 * @param $name
2377 * The name of the item for which the path is requested.
2378 *
2379 * @return
2380 * The path to the requested item.
2381 */
2382 function drupal_get_path($type, $name) {
2383 return dirname(drupal_get_filename($type, $name));
2384 }
2385
2386 /**
2387 * Return the base URL path (i.e., directory) of the Drupal installation.
2388 *
2389 * base_path() prefixes and suffixes a "/" onto the returned path if the path is
2390 * not empty. At the very least, this will return "/".
2391 *
2392 * Examples:
2393 * - http://example.com returns "/" because the path is empty.
2394 * - http://example.com/drupal/folder returns "/drupal/folder/".
2395 */
2396 function base_path() {
2397 return $GLOBALS['base_path'];
2398 }
2399
2400 /**
2401 * Add a <link> tag to the page's HEAD.
2402 *
2403 * This function can be called as long the HTML header hasn't been sent.
2404 */
2405 function drupal_add_link($attributes) {
2406 drupal_add_html_head('<link' . drupal_attributes($attributes) . " />\n");
2407 }
2408
2409 /**
2410 * Adds a cascading stylesheet to the stylesheet queue.
2411 *
2412 * Calling drupal_static_reset('drupal_add_css') will clear all cascading
2413 * stylesheets added so far.
2414 *
2415 * @param $data
2416 * (optional) The stylesheet data to be added, depending on what is passed
2417 * through to the $options['type'] parameter:
2418 * - 'file': The path to the CSS file relative to the base_path(),
2419 * e.g., "modules/devel/devel.css".
2420 *
2421 * Modules should always prefix the names of their CSS files with the
2422 * module name, for example: system-menus.css rather than simply menus.css.
2423 * Themes can override module-supplied CSS files based on their filenames,
2424 * and this prefixing helps prevent confusing name collisions for theme
2425 * developers. See drupal_get_css where the overrides are performed.
2426 *
2427 * If the direction of the current language is right-to-left (Hebrew,
2428 * Arabic, etc.), the function will also look for an RTL CSS file and append
2429 * it to the list. The name of this file should have an '-rtl.css' suffix.
2430 * For example a CSS file called 'mymodule-name.css' will have a
2431 * 'mymodule-name-rtl.css' file added to the list, if exists in the same
2432 * directory. This CSS file should contain overrides for properties which
2433 * should be reversed or otherwise different in a right-to-left display.
2434 * - 'inline': A string of CSS that should be placed in the given scope. Note
2435 * that it is better practice to use 'file' stylesheets, rather than 'inline'
2436 * as the CSS would then be aggregated and cached.
2437 * - 'external': The absolute path to an external CSS file that is not hosted
2438 * on the local server. These files will not be aggregated if CSS aggregation
2439 * is enabled.
2440 *
2441 * @param $options
2442 * (optional) A string defining the 'type' of CSS that is being added in the
2443 * $data parameter ('file'/'inline'), or an array which can have any or all of
2444 * the following keys:
2445 * - 'type': The type of stylesheet being added. Available options are 'file',
2446 * 'inline' or 'external'. Defaults to 'file'.
2447 * - 'weight': The weight of the stylesheet specifies the order in which the
2448 * CSS will appear when presented on the page.
2449 *
2450 * Available constants are:
2451 * - CSS_SYSTEM: Any system-layer CSS.
2452 * - CSS_DEFAULT: Any module-layer CSS.
2453 * - CSS_THEME: Any theme-layer CSS.
2454 *
2455 * If you need to embed a CSS file before any other module's stylesheets,
2456 * for example, you would use CSS_DEFAULT - 1. Note that inline CSS is
2457 * simply appended to the end of the specified scope (region), so they
2458 * always come last.
2459 *
2460 * - 'media': The media type for the stylesheet, e.g., all, print, screen.
2461 * Defaults to 'all'.
2462 * - 'preprocess': Allows the CSS to be aggregated and compressed if the
2463 * Optimize CSS feature has been turned on under the performance section.
2464 * Defaults to TRUE.
2465 *
2466 * What does this actually mean?
2467 * CSS preprocessing is the process of aggregating a bunch of separate CSS
2468 * files into one file that is then compressed by removing all extraneous
2469 * white space. Note that preprocessed inline stylesheets will not be
2470 * aggregated into this single file, instead it will just be compressed
2471 * when being output on the page. External stylesheets will not be
2472 * aggregated.
2473 *
2474 * The reason for merging the CSS files is outlined quite thoroughly here:
2475 * http://www.die.net/musings/page_load_time/
2476 * "Load fewer external objects. Due to request overhead, one bigger file
2477 * just loads faster than two smaller ones half its size."
2478 *
2479 * However, you should *not* preprocess every file as this can lead to
2480 * redundant caches. You should set $preprocess = FALSE when your styles
2481 * are only used rarely on the site. This could be a special admin page,
2482 * the homepage, or a handful of pages that does not represent the
2483 * majority of the pages on your site.
2484 *
2485 * Typical candidates for caching are for example styles for nodes across
2486 * the site, or used in the theme.
2487 * @return
2488 * An array of queued cascading stylesheets.
2489 */
2490 function drupal_add_css($data = NULL, $options = NULL) {
2491 $css = &drupal_static(__FUNCTION__, array());
2492
2493 // Construct the options, taking the defaults into consideration.
2494 if (isset($options)) {
2495 if (!is_array($options)) {
2496 $options = array('type' => $options);
2497 }
2498 }
2499 else {
2500 $options = array();
2501 }
2502
2503 // Create an array of CSS files for each media type first, since each type needs to be served
2504 // to the browser differently.
2505 if (isset($data)) {
2506 $options += array(
2507 'type' => 'file',
2508 'weight' => CSS_DEFAULT,
2509 'media' => 'all',
2510 'preprocess' => TRUE,
2511 'data' => $data,
2512 );
2513
2514 // Always add a tiny value to the weight, to conserve the insertion order.
2515 $options['weight'] += count($css) / 1000;
2516
2517 // Add the data to the CSS array depending on the type.
2518 switch ($options['type']) {
2519 case 'inline':
2520 // For inline stylesheets, we don't want to use the $data as the array
2521 // key as $data could be a very long string of CSS.
2522 $css[] = $options;
2523 break;
2524 default:
2525 // Local and external files must keep their name as the associative key
2526 // so the same CSS file is not be added twice.
2527 $css[$data] = $options;
2528 }
2529 }
2530
2531 return $css;
2532 }
2533
2534 /**
2535 * Returns a themed representation of all stylesheets that should be attached to the page.
2536 *
2537 * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
2538 * This ensures proper cascading of styles so themes can easily override
2539 * module styles through CSS selectors.
2540 *
2541 * Themes may replace module-defined CSS files by adding a stylesheet with the
2542 * same filename. For example, themes/garland/system-menus.css would replace
2543 * modules/system/system-menus.css. This allows themes to override complete
2544 * CSS files, rather than specific selectors, when necessary.
2545 *
2546 * If the original CSS file is being overridden by a theme, the theme is
2547 * responsible for supplying an accompanying RTL CSS file to replace the
2548 * module's.
2549 *
2550 * @param $css
2551 * (optional) An array of CSS files. If no array is provided, the default
2552 * stylesheets array is used instead.
2553 * @return
2554 * A string of XHTML CSS tags.
2555 */
2556 function drupal_get_css($css = NULL) {
2557 $output = '';
2558 if (!isset($css)) {
2559 $css = drupal_add_css();
2560 }
2561
2562 $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
2563 $directory = file_directory_path();
2564 $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
2565
2566 // A dummy query-string is added to filenames, to gain control over
2567 // browser-caching. The string changes on every update or full cache
2568 // flush, forcing browsers to load a new copy of the files, as the
2569 // URL changed.
2570 $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1);
2571
2572 // Allow modules to alter the css items.
2573 drupal_alter('css', $css);
2574
2575 // Sort css items according to their weights.
2576 uasort($css, 'drupal_sort_weight');
2577
2578 // Remove the overriden CSS files. Later CSS files override former ones.
2579 $previous_item = array();
2580 foreach ($css as $key => $item) {
2581 if ($item['type'] == 'file') {
2582 $basename = basename($item['data']);
2583 if (isset($previous_item[$basename])) {
2584 // Remove the previous item that shared the same base name.
2585 unset($css[$previous_item[$basename]]);
2586 }
2587 $previous_item[$basename] = $key;
2588 }
2589 }
2590
2591 // If CSS preprocessing is off, we still need to output the styles.
2592 // Additionally, go through any remaining styles if CSS preprocessing is on and output the non-cached ones.
2593 $rendered_css = array();
2594 $inline_css = '';
2595 $external_css = '';
2596 $preprocess_items = array();
2597 foreach ($css as $data => $item) {
2598 // Loop through each of the stylesheets, including them appropriately based
2599 // on their type.
2600 switch ($item['type']) {
2601 case 'file':
2602 // Depending on whether aggregation is desired, include the file.
2603 if (!$item['preprocess'] || !($is_writable && $preprocess_css)) {
2604 $rendered_css[] = '<link type="text/css" rel="stylesheet" media="' . $item['media'] . '" href="' . base_path() . $item['data'] . $query_string . '" />';
2605 }
2606 else {
2607 $preprocess_items[$item['media']][] = $item;
2608 // Mark the position of the preprocess element,
2609 // it should be at the position of the first preprocessed file.
2610 $rendered_css['preprocess'] = '';
2611 }
2612 break;
2613 case 'inline':
2614 // Include inline stylesheets.
2615 $inline_css .= drupal_load_stylesheet_content($item['data'], $item['preprocess']);
2616 break;
2617 case 'external':
2618 // Preprocessing for external CSS files is ignored.
2619 $external_css .= '<link type="text/css" rel="stylesheet" media="' . $item['media'] . '" href="' . $item['data'] . '" />' . "\n";
2620 break;
2621 }
2622 }
2623
2624 if (!empty($preprocess_items)) {
2625 foreach ($preprocess_items as $media => $items) {
2626 // Prefix filename to prevent blocking by firewalls which reject files
2627 // starting with "ad*".
2628 $filename = 'css_' . md5(serialize($items) . $query_string) . '.css';
2629 $preprocess_file = drupal_build_css_cache($items, $filename);
2630 $rendered_css['preprocess'] .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . base_path() . $preprocess_file . '" />' . "\n";
2631 }
2632 }
2633 // Enclose the inline CSS with the style tag if required.
2634 if (!empty($inline_css)) {
2635 $inline_css = "\n" . '<style type="text/css">' . $inline_css .'</style>';
2636 }
2637
2638 // Output all the CSS files with the inline stylesheets showing up last.
2639 return implode("\n", $rendered_css) . $external_css . $inline_css;
2640 }
2641
2642 /**
2643 * Aggregate and optimize CSS files, putting them in the files directory.
2644 *
2645 * @param $css
2646 * An array of CSS files to aggregate and compress into one file.
2647 * @param $filename
2648 * The name of the aggregate CSS file.
2649 * @return
2650 * The name of the CSS file.
2651 */
2652 function drupal_build_css_cache($css, $filename) {
2653 $data = '';
2654
2655 // Create the css/ within the files folder.
2656 $csspath = file_create_path('css');
2657 file_check_directory($csspath, FILE_CREATE_DIRECTORY);
2658
2659 if (!file_exists($csspath . '/' . $filename)) {
2660 // Build aggregate CSS file.
2661 foreach ($css as $stylesheet) {
2662 // Only 'file' stylesheets can be aggregated.
2663 if ($stylesheet['type'] == 'file') {
2664 $contents = drupal_load_stylesheet($stylesheet['data'], TRUE);
2665 // Return the path to where this CSS file originated from.
2666 $base = base_path() . dirname($stylesheet['data']) . '/';
2667 _drupal_build_css_path(NULL, $base);
2668 // Prefix all paths within this CSS file, ignoring external and absolute paths.
2669 $data .= preg_replace_callback('/url\([\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\)/i', '_drupal_build_css_path', $contents);
2670 }
2671 }
2672
2673 // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
2674 // @import rules must proceed any other style, so we move those to the top.
2675 $regexp = '/@import[^;]+;/i';
2676 preg_match_all($regexp, $data, $matches);
2677 $data = preg_replace($regexp, '', $data);
2678 $data = implode('', $matches[0]) . $data;
2679
2680 // Create the CSS file.
2681 file_unmanaged_save_data($data, $csspath . '/' . $filename, FILE_EXISTS_REPLACE);
2682 }
2683 return $csspath . '/' . $filename;
2684 }
2685
2686 /**
2687 * Helper function for drupal_build_css_cache().
2688 *
2689 * This function will prefix all paths within a CSS file.
2690 */
2691 function _drupal_build_css_path($matches, $base = NULL) {
2692 $_base = &drupal_static(__FUNCTION__);
2693 // Store base path for preg_replace_callback.
2694 if (isset($base)) {
2695 $_base = $base;
2696 }
2697
2698 // Prefix with base and remove '../' segments where possible.
2699 $path = $_base . $matches[1];
2700 $last = '';
2701 while ($path != $last) {
2702 $last = $path;
2703 $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
2704 }
2705 return 'url(' . $path . ')';
2706 }
2707
2708 /**
2709 * Loads the stylesheet and resolves all @import commands.
2710 *
2711 * Loads a stylesheet and replaces @import commands with the contents of the
2712 * imported file. Use this instead of file_get_contents when processing
2713 * stylesheets.
2714 *
2715 * The returned contents are compressed removing white space and comments only
2716 * when CSS aggregation is enabled. This optimization will not apply for
2717 * color.module enabled themes with CSS aggregation turned off.
2718 *
2719 * @param $file
2720 * Name of the stylesheet to be processed.
2721 * @param $optimize
2722 * Defines if CSS contents should be compressed or not.
2723 * @return
2724 * Contents of the stylesheet, including any resolved @import commands.
2725 */
2726 function drupal_load_stylesheet($file, $optimize = NULL) {
2727 // $_optimize does not use drupal_static as it is set by $optimize.
2728 static $_optimize;
2729 // Store optimization parameter for preg_replace_callback with nested @import loops.
2730 if (isset($optimize)) {
2731 $_optimize = $optimize;
2732 }
2733
2734 $contents = '';
2735 if (file_exists($file)) {
2736 // Load the local CSS stylesheet.
2737 $contents = file_get_contents($file);
2738
2739 // Change to the current stylesheet's directory.
2740 $cwd = getcwd();
2741 chdir(dirname($file));
2742
2743 // Process the stylesheet.
2744 $contents = drupal_load_stylesheet_content($contents, $_optimize);
2745
2746 // Change back directory.
2747 chdir($cwd);
2748 }
2749
2750 return $contents;
2751 }
2752
2753 /**
2754 * Process the contents of a stylesheet for aggregation.
2755 *
2756 * @param $contents
2757 * The contents of the stylesheet.
2758 * @param $optimize
2759 * (optional) Boolean whether CSS contents should be minified. Defaults to
2760 * FALSE.
2761 * @return
2762 * Contents of the stylesheet including the imported stylesheets.
2763 */
2764 function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
2765 // Replaces @import commands with the actual stylesheet content.
2766 // This happens recursively but omits external files.
2767 $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents);
2768 // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
2769 $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
2770
2771 if ($optimize) {
2772 // Perform some safe CSS optimizations.
2773 $contents = preg_replace('<
2774 \s*([@{}:;,]|\)\s|\s\()\s* | # Remove whitespace around separators, but keep space around parentheses.
2775 /\*([^*\\\\]|\*(?!/))+\*/ | # Remove comments that are not CSS hacks.
2776 [\n\r] # Remove line breaks.
2777 >x', '\1', $contents);
2778 }
2779 return $contents;
2780 }
2781
2782 /**
2783 * Loads stylesheets recursively and returns contents with corrected paths.
2784 *
2785 * This function is used for recursive loading of stylesheets and
2786 * returns the stylesheet content with all url() paths corrected.
2787 */
2788 function _drupal_load_stylesheet($matches) {
2789 $filename = $matches[1];
2790 // Load the imported stylesheet and replace @import commands in there as well.
2791 $file = drupal_load_stylesheet($filename);
2792 // Alter all url() paths, but not external.
2793 return preg_replace('/url\(([\'"]?)(?![a-z]+:)([^\'")]+)[\'"]?\)?;/i', 'url(\1' . dirname($filename) . '/', $file);
2794 }
2795
2796 /**
2797 * Delete all cached CSS files.
2798 */
2799 function drupal_clear_css_cache() {
2800 file_scan_directory(file_create_path('css'), '/.*/', array('callback' => 'file_unmanaged_delete'));
2801 }
2802
2803 /**
2804 * Add a JavaScript file, setting or inline code to the page.
2805 *
2806 * The behavior of this function depends on the parameters it is called with.
2807 * Generally, it handles the addition of JavaScript to the page, either as
2808 * reference to an existing file or as inline code. The following actions can be
2809 * performed using this function:
2810 *
2811 * - Add a file ('file'):
2812 * Adds a reference to a JavaScript file to the page.
2813 *
2814 * - Add inline JavaScript code ('inline'):
2815 * Executes a piece of JavaScript code on the current page by placing the code
2816 * directly in the page. This can, for example, be useful to tell the user that
2817 * a new message arrived, by opening a pop up, alert box etc. This should only
2818 * be used for JavaScript which cannot be placed and executed from a file.
2819 * When adding inline code, make sure that you are not relying on $ being jQuery.
2820 * Wrap your code in (function ($) { ... })(jQuery); or use jQuery instead of $.
2821 *
2822 * - Add external JavaScript ('external'):
2823 * Allows the inclusion of external JavaScript files that are not hosted on the
2824 * local server. Note that these external JavaScript references do not get
2825 * aggregated when preprocessing is on.
2826 *
2827 * - Add settings ('setting'):
2828 * Adds a setting to Drupal's global storage of JavaScript settings. Per-page
2829 * settings are required by some modules to function properly. All settings
2830 * will be accessible at Drupal.settings.
2831 *
2832 * Examples:
2833 * @code
2834 * drupal_add_js('misc/collapse.js');
2835 * drupal_add_js('misc/collapse.js', 'file');
2836 * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline');
2837 * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });',
2838 * array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)
2839 * );
2840 * drupal_add_js('http://example.com/example.js', 'external');
2841 * @endcode
2842 *
2843 * Calling drupal_static_reset('drupal_add_js') will clear all JavaScript added
2844 * so far.
2845 *
2846 * @param $data
2847 * (optional) If given, the value depends on the $options parameter:
2848 * - 'file': Path to the file relative to base_path().
2849 * - 'inline': The JavaScript code that should be placed in the given scope.
2850 * - 'external': The absolute path to an external JavaScript file that is not
2851 * hosted on the local server. These files will not be aggregated if
2852 * JavaScript aggregation is enabled.
2853 * - 'setting': An array with configuration options as associative array. The
2854 * array is directly placed in Drupal.settings. All modules should wrap
2855 * their actual configuration settings in another variable to prevent
2856 * the pollution of the Drupal.settings namespace.
2857 * @param $options
2858 * (optional) A string defining the type of JavaScript that is being added
2859 * in the $data parameter ('file'/'setting'/'inline'), or an array which
2860 * can have any or all of the following keys. JavaScript settings should
2861 * always pass the string 'setting' only.
2862 * - type
2863 * The type of JavaScript that is to be added to the page. Allowed
2864 * values are 'file', 'inline', 'external' or 'setting'. Defaults
2865 * to 'file'.
2866 * - scope
2867 * The location in which you want to place the script. Possible values
2868 * are 'header' or 'footer'. If your theme implements different regions,
2869 * however, you can also use these. Defaults to 'header'.
2870 * - weight
2871 * A number defining the order in which the JavaScript is added to the
2872 * page. In some cases, the order in which the JavaScript is presented
2873 * on the page is very important. jQuery, for example, must be added to
2874 * to the page before any jQuery code is run, so jquery.js uses a weight
2875 * of JS_LIBRARY - 2, drupal.js uses a weight of JS_LIBRARY - 1, and all
2876 * following scripts depending on jQuery and Drupal behaviors are simply
2877 * added using the default weight of JS_DEFAULT.
2878 *
2879 * Available constants are:
2880 * - JS_LIBRARY: Any libraries, settings, or jQuery plugins.
2881 * - JS_DEFAULT: Any module-layer JavaScript.
2882 * - JS_THEME: Any theme-layer JavaScript.
2883 *
2884 * If you need to invoke a JavaScript file before any other module's
2885 * JavaScript, for example, you would use JS_DEFAULT - 1.
2886 * Note that inline JavaScripts are simply appended to the end of the
2887 * specified scope (region), so they always come last.
2888 * - defer
2889 * If set to TRUE, the defer attribute is set on the &lt;script&gt; tag.
2890 * Defaults to FALSE.
2891 * - cache
2892 * If set to FALSE, the JavaScript file is loaded anew on every page
2893 * call, that means, it is not cached. Used only when 'type' references
2894 * a JavaScript file. Defaults to TRUE.
2895 * - preprocess
2896 * Aggregate the JavaScript if the JavaScript optimization setting has
2897 * been toggled in admin/config/development/performance. Note that
2898 * JavaScript of type 'external' is not aggregated. Defaults to TRUE.
2899 * @return
2900 * The contructed array of JavaScript files.
2901 * @see drupal_get_js()
2902 */
2903 function drupal_add_js($data = NULL, $options = NULL) {
2904 $javascript = &drupal_static(__FUNCTION__, array());
2905
2906 // Construct the options, taking the defaults into consideration.
2907 if (isset($options)) {
2908 if (!is_array($options)) {
2909 $options = array('type' => $options);
2910 }
2911 }
2912 else {
2913 $options = array();
2914 }
2915 $options += drupal_js_defaults($data);
2916
2917 // Preprocess can only be set if caching is enabled.
2918 $options['preprocess'] = $options['cache'] ? $options['preprocess'] : FALSE;
2919
2920 // Tweak the weight so that files of the same weight are included in the
2921 // order of the calls to drupal_add_js().
2922 $options['weight'] += count($javascript) / 1000;
2923
2924 if (isset($data)) {
2925 // Add jquery.js and drupal.js, as well as the basePath setting, the
2926 // first time a Javascript file is added.
2927 if (empty($javascript)) {
2928 $javascript = array(
2929 'settings' => array(
2930 'data' => array(
2931 array('basePath' => base_path()),
2932 ),
2933 'type' => 'setting',
2934 'scope' => 'header',
2935 'weight' => JS_LIBRARY,
2936 ),
2937 'misc/drupal.js' => array(
2938 'data' => 'misc/drupal.js',
2939 'type' => 'file',
2940 'scope' => 'header',
2941 'weight' => JS_LIBRARY - 1,
2942 'cache' => TRUE,
2943 'defer' => FALSE,
2944 'preprocess' => TRUE,
2945 ),
2946 );
2947 // jQuery itself is registered as a library.
2948 drupal_add_library('system', 'jquery');
2949 }
2950
2951 switch ($options['type']) {
2952 case 'setting':
2953 // All JavaScript settings are placed in the header of the page with
2954 // the library weight so that inline scripts appear afterwards.
2955 $javascript['settings']['data'][] = $data;
2956 break;
2957
2958 case 'inline':
2959 $javascript[] = $options;
2960 break;
2961
2962 default: // 'file' and 'external'
2963 // Local and external files must keep their name as the associative key
2964 // so the same JavaScript file is not be added twice.
2965 $javascript[$options['data']] = $options;
2966 }
2967 }
2968 return $javascript;
2969 }
2970
2971 /**
2972 * Constructs an array of the defaults that are used for JavaScript items.
2973 *
2974 * @param $data
2975 * (optional) The default data parameter for the JavaScript item array.
2976 * @see drupal_get_js()
2977 * @see drupal_add_js()
2978 */
2979 function drupal_js_defaults($data = NULL) {
2980 return array(
2981 'type' => 'file',
2982 'weight' => JS_DEFAULT,
2983 'scope' => 'header',
2984 'cache' => TRUE,
2985 'defer' => FALSE,
2986 'preprocess' => TRUE,
2987 'data' => $data,
2988 );
2989 }
2990
2991 /**
2992 * Returns a themed presentation of all JavaScript code for the current page.
2993 *
2994 * References to JavaScript files are placed in a certain order: first, all
2995 * 'core' files, then all 'module' and finally all 'theme' JavaScript files
2996 * are added to the page. Then, all settings are output, followed by 'inline'
2997 * JavaScript code. If running update.php, all preprocessing is disabled.
2998 *
2999 * Note that hook_js_alter(&$javascript) is called during this function call
3000 * to allow alterations of the JavaScript during its presentation. Calls to
3001 * drupal_add_js() from hook_js_alter() will not be added to the output
3002 * presentation. The correct way to add JavaScript during hook_js_alter()
3003 * is to add another element to the $javascript array, deriving from
3004 * drupal_js_defaults(). See locale_js_alter() for an example of this.
3005 *
3006 * @param $scope
3007 * (optional) The scope for which the JavaScript rules should be returned.
3008 * Defaults to 'header'.
3009 * @param $javascript
3010 * (optional) An array with all JavaScript code. Defaults to the default
3011 * JavaScript array for the given scope.
3012 * @return
3013 * All JavaScript code segments and includes for the scope as HTML tags.
3014 * @see drupal_add_js()
3015 * @see locale_js_alter()
3016 * @see drupal_js_defaults()
3017 */
3018 function drupal_get_js($scope = 'header', $javascript = NULL) {
3019 if (!isset($javascript)) {
3020 $javascript = drupal_add_js();
3021 }
3022 if (empty($javascript)) {
3023 return '';
3024 }
3025
3026 // Allow modules to alter the JavaScript.
3027 drupal_alter('js', $javascript);
3028
3029 // Filter out elements of the given scope.
3030 $items = array();
3031 foreach ($javascript as $item) {
3032 if ($item['scope'] == $scope) {
3033 $items[] = $item;
3034 }
3035 }
3036
3037 $output = '';
3038 $preprocessed = '';
3039 $no_preprocess = '';
3040 $files = array();
3041 $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
3042 $directory = file_directory_path();
3043 $is_writable = is_dir($directory) && is_writable($directory) && (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC);
3044
3045 // A dummy query-string is added to filenames, to gain control over
3046 // browser-caching. The string changes on every update or full cache
3047 // flush, forcing browsers to load a new copy of the files, as the
3048 // URL changed. Files that should not be cached (see drupal_add_js())
3049 // get REQUEST_TIME as query-string instead, to enforce reload on every
3050 // page request.
3051 $query_string = '?' . substr(variable_get('css_js_query_string', '0'), 0, 1);
3052
3053 // For inline Javascript to validate as XHTML, all Javascript containing
3054 // XHTML needs to be wrapped in CDATA. To make that backwards compatible
3055 // with HTML 4, we need to comment out the CDATA-tag.
3056 $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
3057 $embed_suffix = "\n//--><!]]>\n";
3058
3059 // Sort the JavaScript by weight so that it appears in the correct order.
3060 uasort($items, 'drupal_sort_weight');
3061
3062 // Loop through the JavaScript to construct the rendered output.
3063 foreach ($items as $item) {
3064 switch ($item['type']) {
3065 case 'setting':
3066 $output .= '<script type="text/javascript">' . $embed_prefix . 'jQuery.extend(Drupal.settings, ' . drupal_to_js(call_user_func_array('array_merge_recursive', $item['data'])) . ");" . $embed_suffix . "</script>\n";
3067 break;
3068
3069 case 'inline':
3070 $output .= '<script type="text/javascript"' . ($item['defer'] ? ' defer="defer"' : '') . '>' . $embed_prefix . $item['data'] . $embed_suffix . "</script>\n";
3071 break;
3072
3073 case 'file':
3074 if (!$item['preprocess'] || !$is_writable || !$preprocess_js) {
3075 $no_preprocess .= '<script type="text/javascript"' . ($item['defer'] ? ' defer="defer"' : '') . ' src="' . base_path() . $item['data'] . ($item['cache'] ? $query_string : '?' . REQUEST_TIME) . "\"></script>\n";
3076 }
3077 else {
3078 $files[$item['data']] = $item;
3079 }
3080 break;
3081
3082 case 'external':
3083 // Preprocessing for external JavaScript files is ignored.
3084 $output .= '<script type="text/javascript"' . ($item['defer'] ? ' defer="defer"' : '') . ' src="' . $item['data'] . "\"></script>\n";
3085 break;
3086 }
3087 }
3088
3089 // Aggregate any remaining JS files that haven't already been output.
3090 if ($is_writable && $preprocess_js && count($files) > 0) {
3091 // Prefix filename to prevent blocking by firewalls which reject files
3092 // starting with "ad*".
3093 $filename = 'js_' . md5(serialize($files) . $query_string) . '.js';
3094 $preprocess_file = drupal_build_js_cache($files, $filename);
3095 $preprocessed .= '<script type="text/javascript" src="' . base_path() . $preprocess_file . '"></script>' . "\n";
3096 }
3097
3098 // Keep the order of JS files consistent as some are preprocessed and others are not.
3099 // Make sure any inline or JS setting variables appear last after libraries have loaded.
3100 return $preprocessed . $no_preprocess . $output;
3101 }
3102
3103 /**
3104 * Adds multiple JavaScript or CSS files at the same time.
3105 *
3106 * A library defines a set of JavaScript and/or CSS files, optionally using
3107 * settings, and optionally requiring another library. For example, a library
3108 * can be a jQuery plugin, a JavaScript framework, or a CSS framework. This
3109 * function allows modules to load a library defined/shipped by itself or a
3110 * depending module; without having to add all files of the library separately.
3111 * Each library is only loaded once.
3112 *
3113 * @param $module
3114 * The name of the module that registered the library.
3115 * @param $name
3116 * The name of the library to add.
3117 * @return
3118 * TRUE when the library was successfully added or FALSE if the library or one
3119 * of its dependencies could not be added.
3120 *
3121 * @see drupal_get_library()
3122 * @see hook_library()
3123 * @see hook_library_alter()
3124 */
3125 function drupal_add_library($module, $name) {
3126 $added = &drupal_static(__FUNCTION__, array());
3127
3128 // Only process the library if it exists and it was not added already.
3129 if (!isset($added[$module][$name]) && $library = drupal_get_library($module, $name)) {
3130 // Prevent repeated/recursive processing.
3131 $added[$module][$name] = TRUE;
3132
3133 // Ensure dependencies first.
3134 foreach ($library['dependencies'] as $dependency) {
3135 if (drupal_add_library($dependency[0], $dependency[1]) === FALSE) {
3136 // If any dependent library could not be added, this library will break;
3137 // stop here.
3138 $added[$module][$name] = FALSE;
3139 return FALSE;
3140 }
3141 }
3142
3143 // Add defined JavaScript.
3144 foreach ($library['js'] as $data => $options) {
3145 // For JS settings we need to transform $options['data'] into $data.
3146 if (isset($options['type'], $options['data']) && $options['type'] == 'setting') {
3147 $data = $options['data'];
3148 unset($options['data']);
3149 }
3150 // If not specified, assign a default weight of JS_LIBRARY.
3151 elseif (!isset($options['weight'])) {
3152 $options['weight'] = JS_LIBRARY;
3153 }
3154 drupal_add_js($data, $options);
3155 }
3156
3157 // Add defined stylesheets.
3158 foreach ($library['css'] as $data => $options) {
3159 drupal_add_css($data, $options);
3160 }
3161 }
3162 // Requested library does not exist.
3163 else {
3164 $added[$module][$name] = FALSE;
3165 }
3166
3167 return $added[$module][$name];
3168 }
3169
3170 /**
3171 * Retrieves information for a JavaScript/CSS library.
3172 *
3173 * Library information is statically cached. Libraries are keyed by module for
3174 * several reasons:
3175 * - Libraries are not unique. Multiple modules might ship with the same library
3176 * in a different version or variant. This registry cannot (and does not
3177 * attempt to) prevent library conflicts.
3178 * - Modules implementing and thereby depending on a library that is registered
3179 * by another module can only rely on that module's library.
3180 * - Two (or more) modules can still register the same library and use it
3181 * without conflicts in case the libraries are loaded on certain pages only.
3182 *
3183 * @param $module
3184 * The name of a module that registered a library.
3185 * @param $library
3186 * The name of a registered library.
3187 * @return
3188 * The definition of the requested library, if existent, or FALSE.
3189 *
3190 * @see drupal_add_library()
3191 * @see hook_library()
3192 * @see hook_library_alter()
3193 *
3194 * @todo The purpose of drupal_get_*() is completely different to other page
3195 * requisite API functions; find and use a different name.
3196 */
3197 function drupal_get_library($module, $name) {
3198 $libraries = &drupal_static(__FUNCTION__, array());
3199
3200 if (!array_key_exists($module, $libraries)) {
3201 // Retrieve all libraries associated with the module.
3202 $module_libraries = module_invoke($module, 'library');
3203
3204 // Allow modules to alter the module's registered libraries.
3205 if (!empty($module_libraries)) {
3206 drupal_alter('library', $module_libraries, $module);
3207 }
3208 $libraries[$module] = $module_libraries;
3209 }
3210 if (!empty($libraries[$module][$name]) && is_array($libraries[$module][$name])) {
3211 // Add default elements to allow for easier processing.
3212 $libraries[$module][$name] += array('dependencies' => array(), 'js' => array(), 'css' => array());
3213 }
3214 else {
3215 $libraries[$module][$name] = FALSE;
3216 }
3217
3218 return $libraries[$module][$name];
3219 }
3220
3221 /**
3222 * Assist in adding the tableDrag JavaScript behavior to a themed table.
3223 *
3224 * Draggable tables should be used wherever an outline or list of sortable items
3225 * needs to be arranged by an end-user. Draggable tables are very flexible and
3226 * can manipulate the value of form elements placed within individual columns.
3227 *
3228 * To set up a table to use drag and drop in place of weight select-lists or
3229 * in place of a form that contains parent relationships, the form must be
3230 * themed into a table. The table must have an id attribute set. If using
3231 * theme_table(), the id may be set as such:
3232 * @code
3233 * $output = theme('table', $header, $rows, array('id' => 'my-module-table'));
3234 * return $output;
3235 * @endcode
3236 *
3237 * In the theme function for the form, a special class must be added to each
3238 * form element within the same column, "grouping" them together.
3239 *
3240 * In a situation where a single weight column is being sorted in the table, the
3241 * classes could be added like this (in the theme function):
3242 * @code
3243 * $form['my_elements'][$delta]['weight']['#attributes']['class'] = "my-elements-weight";
3244 * @endcode
3245 *
3246 * Each row of the table must also have a class of "draggable" in order to enable the
3247 * drag handles:
3248 * @code
3249 * $row = array(...);
3250 * $rows[] = array(
3251 * 'data' => $row,
3252 * 'class' => 'draggable',
3253 * );
3254 * @endcode
3255 *
3256 * When tree relationships are present, the two additional classes
3257 * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior:
3258 * - Rows with the 'tabledrag-leaf' class cannot have child rows.
3259 * - Rows with the 'tabledrag-root' class cannot be nested under a parent row.
3260 *
3261 * Calling drupal_add_tabledrag() would then be written as such:
3262 * @code
3263 * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight');
3264 * @endcode
3265 *
3266 * In a more complex case where there are several groups in one column (such as
3267 * the block regions on the admin/structure/block page), a separate subgroup class
3268 * must also be added to differentiate the groups.
3269 * @code
3270 * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = "my-elements-weight my-elements-weight-" . $region;
3271 * @endcode
3272 *
3273 * $group is still 'my-element-weight', and the additional $subgroup variable
3274 * will be passed in as 'my-elements-weight-' . $region. This also means that
3275 * you'll need to call drupal_add_tabledrag() once for every region added.
3276 *
3277 * @code
3278 * foreach ($regions as $region) {
3279 * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight', 'my-elements-weight-' . $region);
3280 * }
3281 * @endcode
3282 *
3283 * In a situation where tree relationships are present, adding multiple
3284 * subgroups is not necessary, because the table will contain indentations that
3285 * provide enough information about the sibling and parent relationships.
3286 * See theme_menu_overview_form() for an example creating a table containing
3287 * parent relationships.
3288 *
3289 * Please note that this function should be called from the theme layer, such as
3290 * in a .tpl.php file, theme_ function, or in a template_preprocess function,
3291 * not in a form declaration. Though the same JavaScript could be added to the
3292 * page using drupal_add_js() directly, this function helps keep template files
3293 * clean and readable. It also prevents tabledrag.js from being added twice
3294 * accidentally.
3295 *
3296 * @param $table_id
3297 * String containing the target table's id attribute. If the table does not
3298 * have an id, one will need to be set, such as <table id="my-module-table">.
3299 * @param $action
3300 * String describing the action to be done on the form item. Either 'match'
3301 * 'depth', or 'order'. Match is typically used for parent relationships.
3302 * Order is typically used to set weights on other form elements with the same
3303 * group. Depth updates the target element with the current indentation.
3304 * @param $relationship
3305 * String describing where the $action variable should be performed. Either
3306 * 'parent', 'sibling', 'group', or 'self'. Parent will only look for fields
3307 * up the tree. Sibling will look for fields in the same group in rows above
3308 * and below it. Self affects the dragged row itself. Group affects the
3309 * dragged row, plus any children below it (the entire dragged group).
3310 * @param $group
3311 * A class name applied on all related form elements for this action.
3312 * @param $subgroup
3313 * (optional) If the group has several subgroups within it, this string should
3314 * contain the class name identifying fields in the same subgroup.
3315 * @param $source
3316 * (optional) If the $action is 'match', this string should contain the class
3317 * name identifying what field will be used as the source value when matching
3318 * the value in $subgroup.
3319 * @param $hidden
3320 * (optional) The column containing the field elements may be entirely hidden
3321 * from view dynamically when the JavaScript is loaded. Set to FALSE if the
3322 * column should not be hidden.
3323 * @param $limit
3324 * (optional) Limit the maximum amount of parenting in this table.
3325 * @see block-admin-display-form.tpl.php
3326 * @see theme_menu_overview_form()
3327 */
3328 function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
3329 $js_added = &drupal_static(__FUNCTION__, FALSE);
3330 if (!$js_added) {
3331 // Add the table drag JavaScript to the page before the module JavaScript
3332 // to ensure that table drag behaviors are registered before any module
3333 // uses it.
3334 drupal_add_js('misc/tabledrag.js', array('weight' => JS_DEFAULT - 1));
3335 $js_added = TRUE;
3336 }
3337
3338 // If a subgroup or source isn't set, assume it is the same as the group.
3339 $target = isset($subgroup) ? $subgroup : $group;
3340 $source = isset($source) ? $source : $target;
3341 $settings['tableDrag'][$table_id][$group][] = array(
3342 'target' => $target,
3343 'source' => $source,
3344 'relationship' => $relationship,
3345 'action' => $action,
3346 'hidden' => $hidden,
3347 'limit' => $limit,
3348 );
3349 drupal_add_js($settings, 'setting');
3350 }
3351
3352 /**
3353 * Aggregate JS files, putting them in the files directory.
3354 *
3355 * @param $files
3356 * An array of JS files to aggregate and compress into one file.
3357 * @param $filename
3358 * The name of the aggregate JS file.
3359 * @return
3360 * The name of the JS file.
3361 */
3362 function drupal_build_js_cache($files, $filename) {
3363 $contents = '';
3364
3365 // Create the js/ within the files folder.
3366 $jspath = file_create_path('js');
3367 file_check_directory($jspath, FILE_CREATE_DIRECTORY);
3368
3369 if (!file_exists($jspath . '/' . $filename)) {
3370 // Build aggregate JS file.
3371 foreach ($files as $path => $info) {
3372 if ($info['preprocess']) {
3373 // Append a ';' after each JS file to prevent them from running together.
3374 $contents .= file_get_contents($path) . ';';
3375 }
3376 }
3377
3378 // Create the JS file.
3379 file_unmanaged_save_data($contents, $jspath . '/' . $filename, FILE_EXISTS_REPLACE);
3380 }
3381
3382 return $jspath . '/' . $filename;
3383 }
3384
3385 /**
3386 * Delete all cached JS files.
3387 */
3388 function drupal_clear_js_cache() {
3389 file_scan_directory(file_create_path('js'), '/.*/', array('callback' => 'file_unmanaged_delete'));
3390 variable_set('javascript_parsed', array());
3391 }
3392
3393 /**
3394 * Converts a PHP variable into its Javascript equivalent.
3395 *
3396 * We use HTML-safe strings, i.e. with <, > and & escaped.
3397 */
3398 function drupal_to_js($var) {
3399 // json_encode() does not escape <, > and &, so we do it with str_replace()
3400 return str_replace(array("<", ">", "&"), array('\x3c', '\x3e', '\x26'), json_encode($var));
3401 }
3402
3403 /**
3404 * Return data in JSON format.
3405 *
3406 * This function should be used for JavaScript callback functions returning
3407 * data in JSON format. It sets the header for JavaScript output.
3408 *
3409 * @param $var
3410 * (optional) If set, the variable will be converted to JSON and output.
3411 */
3412 function drupal_json($var = NULL) {
3413 // We are returning JavaScript, so tell the browser.
3414 drupal_set_header('Content-Type', 'text/javascript; charset=utf-8');
3415
3416 if (isset($var)) {
3417 echo drupal_to_js($var);
3418 }
3419 }
3420
3421 /**
3422 * Wrapper around urlencode() which avoids Apache quirks.
3423 *
3424 * Should be used when placing arbitrary data in an URL. Note that Drupal paths
3425 * are urlencoded() when passed through url() and do not require urlencoding()
3426 * of individual components.
3427 *
3428 * Notes:
3429 * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature'
3430 * in Apache where it 404s on any path containing '%2F'.
3431 * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean
3432 * URLs are used, which are interpreted as delimiters by PHP. These
3433 * characters are double escaped so PHP will still see the encoded version.
3434 * - With clean URLs, Apache changes '//' to '/', so every second slash is
3435 * double escaped.
3436 * - This function should only be used on paths, not on query string arguments,
3437 * otherwise unwanted double encoding will occur.
3438 *
3439 * @param $text
3440 * String to encode
3441 */
3442 function drupal_encode_path($text) {
3443 if (variable_get('clean_url', '0')) {
3444 return str_replace(array('%2F', '%26', '%23', '//'),
3445 array('/', '%2526', '%2523', '/%252F'),
3446 rawurlencode($text));
3447 }
3448 else {
3449 return str_replace('%2F', '/', rawurlencode($text));
3450 }
3451 }
3452
3453 /**
3454 * Returns a string of highly randomized bytes (over the full 8-bit range).
3455 *
3456 * This function is better than simply calling mt_rand() or any other built-in
3457 * PHP function because it can return a long string of bytes (compared to < 4
3458 * bytes normally from mt_rand()) and uses the best available pseudo-random source.
3459 *
3460 * @param $count
3461 * The number of characters (bytes) to return in the string.
3462 */
3463 function drupal_random_bytes($count) {
3464 // $random_state does not use drupal_static as it stores random bytes.
3465 static $random_state;
3466 // We initialize with the somewhat random PHP process ID on the first call.
3467 if (empty($random_state)) {
3468 $random_state = getmypid();
3469 }
3470 $output = '';
3471 // /dev/urandom is available on many *nix systems and is considered the best
3472 // commonly available pseudo-random source.
3473 if ($fh = @fopen('/dev/urandom', 'rb')) {
3474 $output = fread($fh, $count);
3475 fclose($fh);
3476 }
3477 // If /dev/urandom is not available or returns no bytes, this loop will
3478 // generate a good set of pseudo-random bytes on any system.
3479 // Note that it may be important that our $random_state is passed
3480 // through md5() prior to being rolled into $output, that the two md5()
3481 // invocations are different, and that the extra input into the first one -
3482 // the microtime() - is prepended rather than appended. This is to avoid
3483 // directly leaking $random_state via the $output stream, which could
3484 // allow for trivial prediction of further "random" numbers.
3485 while (strlen($output) < $count) {
3486 $random_state = md5(microtime() . mt_rand() . $random_state);
3487 $output .= md5(mt_rand() . $random_state, TRUE);
3488 }
3489 return substr($output, 0, $count);
3490 }
3491
3492 /**
3493 * Ensure the private key variable used to generate tokens is set.
3494 *
3495 * @return
3496 * The private key.
3497 */
3498 function drupal_get_private_key() {
3499 if (!($key = variable_get('drupal_private_key', 0))) {
3500 $key = md5(drupal_random_bytes(64));
3501 variable_set('drupal_private_key', $key);
3502 }
3503 return $key;
3504 }
3505
3506 /**
3507 * Generate a token based on $value, the current user session and private key.
3508 *
3509 * @param $value
3510 * An additional value to base the token on.
3511 */
3512 function drupal_get_token($value = '') {
3513 $private_key = drupal_get_private_key();
3514 return md5(session_id() . $value . $private_key);
3515 }
3516
3517 /**
3518 * Validate a token based on $value, the current user session and private key.
3519 *
3520 * @param $token
3521 * The token to be validated.
3522 * @param $value
3523 * An additional value to base the token on.
3524 * @param $skip_anonymous
3525 * Set to true to skip token validation for anonymous users.
3526 * @return
3527 * True for a valid token, false for an invalid token. When $skip_anonymous
3528 * is true, the return value will always be true for anonymous users.
3529 */
3530 function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
3531 global $user;
3532 return (($skip_anonymous && $user->uid == 0) || ($token == md5(session_id() . $value . variable_get('drupal_private_key', ''))));
3533 }
3534
3535 function _drupal_bootstrap_full() {
3536 $called = &drupal_static(__FUNCTION__);
3537
3538 if ($called) {
3539 return;
3540 }
3541 $called = 1;
3542 require_once DRUPAL_ROOT . '/includes/theme.inc';
3543 require_once DRUPAL_ROOT . '/includes/pager.inc';
3544 require_once DRUPAL_ROOT . '/includes/menu.inc';
3545 require_once DRUPAL_ROOT . '/includes/tablesort.inc';
3546 require_once DRUPAL_ROOT . '/includes/file.inc';
3547 require_once DRUPAL_ROOT . '/includes/unicode.inc';
3548 require_once DRUPAL_ROOT . '/includes/image.inc';
3549 require_once DRUPAL_ROOT . '/includes/form.inc';
3550 require_once DRUPAL_ROOT . '/includes/mail.inc';
3551 require_once DRUPAL_ROOT . '/includes/actions.inc';
3552 // Set the Drupal custom error handler.
3553 set_error_handler('_drupal_error_handler');
3554 set_exception_handler('_drupal_exception_handler');
3555
3556 if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'simpletest') !== FALSE) {
3557 // Valid SimpleTest user-agent, log fatal errors to test specific file
3558 // directory. The user-agent is validated in DRUPAL_BOOTSTRAP_DATABASE
3559 // phase so as long as it is a SimpleTest user-agent it is valid.
3560 ini_set('log_errors', 1);
3561 ini_set('error_log', file_directory_path() . '/error.log');
3562 }
3563
3564 // Emit the correct charset HTTP header.
3565 drupal_set_header('Content-Type', 'text/html; charset=utf-8');
3566 // Detect string handling method
3567 unicode_check();
3568 // Undo magic quotes
3569 fix_gpc_magic();
3570 // Load all enabled modules
3571 module_load_all();
3572 // Make sure all stream wrappers are registered.
3573 file_get_stream_wrappers();
3574 // Let all modules take action before menu system handles the request
3575 // We do not want this while running update.php.
3576 if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
3577 module_invoke_all('init');
3578 }
3579 }
3580
3581 /**
3582 * Store the current page in the cache.
3583 *
3584 * We try to store a gzipped version of the cache. This requires the
3585 * PHP zlib extension (http://php.net/manual/en/ref.zlib.php).
3586 * Presence of the extension is checked by testing for the function
3587 * gzencode. There are two compression algorithms: gzip and deflate.
3588 * The majority of all modern browsers support gzip or both of them.
3589 * We thus only deal with the gzip variant and unzip the cache in case
3590 * the browser does not accept gzip encoding.
3591 *
3592 * @see drupal_page_header
3593 */
3594 function drupal_page_set_cache() {
3595 global $base_root;
3596
3597 if (drupal_page_is_cacheable()) {
3598 $cache_page = TRUE;
3599
3600 $cache = (object) array(
3601 'cid' => $base_root . request_uri(),
3602 'data' => ob_get_clean(),
3603 'expire' => CACHE_TEMPORARY,
3604 'created' => REQUEST_TIME,
3605 'headers' => array(),
3606 );
3607
3608 // Restore preferred header names based on the lower-case names returned
3609 // by drupal_get_header().
3610 $header_names = _drupal_set_preferred_header_name();
3611 foreach (drupal_get_header() as $name_lower => $value) {
3612 $cache->headers[$header_names[$name_lower]] = $value;
3613 }
3614
3615 if (variable_get('page_compression', TRUE) && function_exists('gzencode')) {
3616 // We do not store the data in case the zlib mode is deflate. This should
3617 // be rarely happening.
3618 if (zlib_get_coding_type() == 'deflate') {
3619 $cache_page = FALSE;
3620 }
3621 elseif (zlib_get_coding_type() == FALSE) {
3622 $cache->data = gzencode($cache->data, 9, FORCE_GZIP);
3623 }
3624 // The remaining case is 'gzip' which means the data is already
3625 // compressed and nothing left to do but to store it.
3626 }
3627 if ($cache_page && $cache->data) {
3628 cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire, $cache->headers);
3629 }
3630 return $cache;
3631 }
3632 }
3633
3634 /**
3635 * Executes a cron run when called
3636 * @return
3637 * Returns TRUE if ran successfully
3638 */
3639 function drupal_cron_run() {
3640 // Allow execution to continue even if the request gets canceled.
3641 @ignore_user_abort(TRUE);
3642
3643 // Try to allocate enough time to run all the hook_cron implementations.
3644 drupal_set_time_limit(240);
3645
3646 // Fetch the cron semaphore
3647 $semaphore = variable_get('cron_semaphore', FALSE);
3648
3649 if ($semaphore) {
3650 if (REQUEST_TIME - $semaphore > 3600) {
3651 // Either cron has been running for more than an hour or the semaphore
3652 // was not reset due to a database error.
3653 watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR);
3654
3655 // Release cron semaphore
3656 variable_del('cron_semaphore');
3657 }
3658 else {
3659 // Cron is still running normally.
3660 watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING);
3661 }
3662 }
3663 else {
3664 // Register shutdown callback
3665 register_shutdown_function('drupal_cron_cleanup');
3666
3667 // Lock cron semaphore
3668 variable_set('cron_semaphore', REQUEST_TIME);
3669
3670 // Iterate through the modules calling their cron handlers (if any):
3671 module_invoke_all('cron');
3672
3673 // Record cron time
3674 variable_set('cron_last', REQUEST_TIME);
3675 watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE);
3676
3677 // Release cron semaphore
3678 variable_del('cron_semaphore');
3679
3680 // Return TRUE so other functions can check if it did run successfully
3681 return TRUE;
3682 }
3683 }
3684
3685 /**
3686 * Shutdown function for cron cleanup.
3687 */
3688 function drupal_cron_cleanup() {
3689 // See if the semaphore is still locked.
3690 if (variable_get('cron_semaphore', FALSE)) {
3691 watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING);
3692
3693 // Release cron semaphore
3694 variable_del('cron_semaphore');
3695 }
3696 }
3697
3698 /**
3699 * Return an array of system file objects.
3700 *
3701 * Returns an array of file objects of the given type from the site-wide
3702 * directory (i.e. modules/), the all-sites directory (i.e.
3703 * sites/all/modules/), the profiles directory, and site-specific directory
3704 * (i.e. sites/somesite/modules/). The returned array will be keyed using the
3705 * key specified (name, basename, filename). Using name or basename will cause
3706 * site-specific files to be prioritized over similar files in the default
3707 * directories. That is, if a file with the same name appears in both the
3708 * site-wide directory and site-specific directory, only the site-specific
3709 * version will be included.
3710 *
3711 * @param $mask
3712 * The preg_match() regular expression of the files to find.
3713 * @param $directory
3714 * The subdirectory name in which the files are found. For example,
3715 * 'modules' will search in both modules/ and
3716 * sites/somesite/modules/.
3717 * @param $key
3718 * The key to be passed to file_scan_directory().
3719 * @param $min_depth
3720 * Minimum depth of directories to return files from.
3721 *
3722 * @return
3723 * An array of file objects of the specified type.
3724 */
3725 function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) {
3726 global $install_state;
3727 $config = conf_path();
3728
3729 // When this function is called during Drupal's initial installation process,
3730 // the name of the profile that's about to be installed is stored in the global
3731 // installation state. At all other times, the standard Drupal systems variable
3732 // table contains the name of the current profile, and we can call variable_get()
3733 // to determine what one is active.
3734 if (isset($install_state['parameters']['profile'])) {
3735 $profile = $install_state['parameters']['profile'];
3736 }
3737 else {
3738 $profile = variable_get('install_profile', 'default');
3739 }
3740 $searchdir = array($directory);
3741 $files = array();
3742
3743 // The 'profiles' directory contains pristine collections of modules and
3744 // themes as organized by a distribution. It is pristine in the same way
3745 // that /modules is pristine for core; users should avoid changing anything
3746 // there in favor of sites/all or sites/<domain> directories.
3747 if (file_exists("profiles/$profile/$directory")) {
3748 $searchdir[] = "profiles/$profile/$directory";
3749 }
3750
3751 // Always search sites/all/* as well as the global directories
3752 $searchdir[] = 'sites/all/' . $directory;
3753
3754 if (file_exists("$config/$directory")) {
3755 $searchdir[] = "$config/$directory";
3756 }
3757
3758 // If the database is not available, we can't use drupal_function_exists(), so
3759 // we load the file_scan_directory function definition manually.
3760 if (!function_exists('file_scan_directory')) {
3761 require_once DRUPAL_ROOT . '/includes/file.inc';
3762 }
3763
3764 // Get current list of items
3765 foreach ($searchdir as $dir) {
3766 $files = array_merge($files, file_scan_directory($dir, $mask, array('key' => $key, 'min_depth' => $min_depth)));
3767 }
3768
3769 return $files;
3770 }
3771
3772 /**
3773 * Hands off structured Drupal arrays to type-specific *_alter implementations.
3774 *
3775 * This dispatch function hands off structured Drupal arrays to type-specific
3776 * *_alter implementations. It ensures a consistent interface for all altering
3777 * operations.
3778 *
3779 * @param $type
3780 * The data type of the structured array. 'form', 'links',
3781 * 'node_content', and so on are several examples.
3782 * @param $data
3783 * The structured array to be altered.
3784 * @param ...
3785 * Any additional params will be passed on to the called
3786 * hook_$type_alter functions.
3787 */
3788 function drupal_alter($type, &$data) {
3789 // PHP's func_get_args() always returns copies of params, not references, so
3790 // drupal_alter() can only manipulate data that comes in via the required first
3791 // param. For the edge case functions that must pass in an arbitrary number of
3792 // alterable parameters (hook_form_alter() being the best example), an array of
3793 // those params can be placed in the __drupal_alter_by_ref key of the $data
3794 // array. This is somewhat ugly, but is an unavoidable consequence of a flexible
3795 // drupal_alter() function, and the limitations of func_get_args().
3796 // @todo: Remove this in Drupal 7.
3797 if (is_array($data) && isset($data['__drupal_alter_by_ref'])) {
3798 $by_ref_parameters = $data['__drupal_alter_by_ref'];
3799 unset($data['__drupal_alter_by_ref']);
3800 }
3801
3802 // Hang onto a reference to the data array so that it isn't blown away later.
3803 // Also, merge in any parameters that need to be passed by reference.
3804 $args = array(&$data);
3805 if (isset($by_ref_parameters)) {
3806 $args = array_merge($args, $by_ref_parameters);
3807 }
3808
3809 // Now, use func_get_args() to pull in any additional parameters passed into
3810 // the drupal_alter() call.
3811 $additional_args = func_get_args();
3812 array_shift($additional_args);
3813 array_shift($additional_args);
3814 $args = array_merge($args, $additional_args);
3815
3816 foreach (module_implements($type . '_alter') as $module) {
3817 $function = $module . '_' . $type . '_alter';
3818 call_user_func_array($function, $args);
3819 }
3820 }
3821
3822 /**
3823 * Set the main page content value for later use.
3824 *
3825 * Given the nature of the Drupal page handling, this will be called once with
3826 * a string or array. We store that and return it later as the block is being
3827 * displayed.
3828 *
3829 * @param $content
3830 * A string or renderable array representing the body of the page.
3831 * @return
3832 * A renderable array representing the body of the page.
3833 */
3834 function drupal_set_page_content($content = NULL) {
3835 $content_block = &drupal_static(__FUNCTION__, NULL);
3836 if (!empty($content)) {
3837 $content_block = (is_array($content) ? $content : array('main' => array('#markup' => $content)));
3838 }
3839 else {
3840 return $content_block;
3841 }
3842 }
3843
3844 /**
3845 * Renders the page, including all theming.
3846 *
3847 * @param $page
3848 * A string or array representing the content of a page. The array consists of
3849 * the following keys:
3850 * - #type: Value is always 'page'. This pushes the theming through page.tpl.php (required).
3851 * - #show_blocks: A marker which suppresses sidebar regions if FALSE (optional).
3852 * - #show_messages: Suppress drupal_get_message() items. Used by Batch API (optional).
3853 *
3854 * @see hook_page_alter()
3855 * @see element_info('page')
3856 */
3857 function drupal_render_page($page) {
3858 // Allow menu callbacks to return strings or arbitrary arrays to render.
3859 // If the array returned is not of #type page directly, we need to fill
3860 // in the page with defaults.
3861 if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) {
3862 drupal_set_page_content($page);
3863 $page = element_info('page');
3864 }
3865 // Modules alter the $page as needed. Blocks are populated into regions like
3866 // 'sidebar_first', 'footer', etc.
3867 drupal_alter('page', $page);
3868
3869 return drupal_render($page);
3870 }
3871
3872 /**
3873 * Renders HTML given a structured array tree.
3874 *
3875 * Recursively iterates over each of the array elements, generating HTML code.
3876 *
3877 * HTML generation is controlled by two properties containing theme functions,
3878 * #theme and #theme_wrappers.
3879 *
3880 * #theme is the theme function called first. If it is set and the element has
3881 * any children, they have to be rendered there. For elements that are not
3882 * allowed to have any children, e.g. buttons or textfields, it can be used to
3883 * render the element itself. If #theme is not present and the element has
3884 * children, they are rendered and concatenated into a string by
3885 * drupal_render_children().
3886 *
3887 * The #theme_wrappers property contains an array of theme functions which will
3888 * be called, in order, after #theme has run. These can be used to add further
3889 * markup around the rendered children; e.g., fieldsets add the required markup
3890 * for a fieldset around their rendered child elements. All wrapper theme
3891 * functions have to include the element's #children property in their output,
3892 * as it contains the output of the previous theme functions and the rendered
3893 * children.
3894 *
3895 * For example, for the form element type, by default only the #theme_wrappers
3896 * property is set, which adds the form markup around the rendered child
3897 * elements of the form. This allows you to set the #theme property on a
3898 * specific form to a custom theme function, giving you complete control over
3899 * the placement of the form's children while not at all having to deal with
3900 * the form markup itself.
3901 *
3902 * This function is usually called from within another function, like
3903 * drupal_get_form() or a theme function. Elements are sorted internally
3904 * using uasort(). Since this is expensive, when passing already sorted
3905 * elements to drupal_render(), for example from a database query, set
3906 * $elements['#sorted'] = TRUE to avoid sorting them a second time.
3907 *
3908 * @param $elements
3909 * The structured array describing the data to be rendered.
3910 * @return
3911 * The rendered HTML.
3912 */
3913 function drupal_render(&$elements) {
3914 static $defaults;
3915 // Early-return nothing if user does not have access.
3916 if (!isset($elements) || (isset($elements['#access']) && !$elements['#access'])) {
3917 return;
3918 }
3919
3920 // Do not print elements twice.
3921 if (isset($elements['#printed']) && $elements['#printed']) {
3922 return;
3923 }
3924
3925 // If the default values for this element have not been loaded yet, populate
3926 // them.
3927 if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
3928 $elements += element_info($elements['#type']);
3929 }
3930 else {
3931 if (!isset($defaults)) {
3932 $defaults = element_basic_defaults();
3933 }
3934 $elements += $defaults;
3935 }
3936
3937 // If #markup is not empty and no theme function is set, use theme_markup.
3938 // This allows to specify just #markup on an element without setting the #type.
3939 if (!empty($elements['#markup']) && empty($elements['#theme'])) {
3940 $elements['#theme'] = 'markup';
3941 }
3942
3943 // Make any final changes to the element before it is rendered. This means
3944 // that the $element or the children can be altered or corrected before the
3945 // element is rendered into the final text.
3946 if (isset($elements['#pre_render'])) {
3947 foreach ($elements['#pre_render'] as $function) {
3948 if (drupal_function_exists($function)) {
3949 $elements = $function($elements);
3950 }
3951 }
3952 }
3953
3954 // Get the children of the element, sorted by weight.
3955 $children = element_children($elements, TRUE);
3956
3957 $elements['#children'] = '';
3958 // Call the element's #theme function if it is set. Then any children of the
3959 // element have to be rendered there.
3960 if (isset($elements['#theme'])) {
3961 $elements['#children'] = theme($elements['#theme'], $elements);
3962 }
3963 // If #theme was not set and the element has children, render them now
3964 // using drupal_render_children().
3965 if ($elements['#children'] == '') {
3966 $elements['#children'] = drupal_render_children($elements, $children);
3967 }
3968
3969 // Let the theme functions in #theme_wrappers add markup around the rendered
3970 // children.
3971 if (isset($elements['#theme_wrappers'])) {
3972 foreach ($elements['#theme_wrappers'] as $theme_wrapper) {
3973 $elements['#children'] = theme($theme_wrapper, $elements);
3974 }
3975 }
3976
3977 // Filter the outputted content and make any last changes before the
3978 // content is sent to the browser. The changes are made on $content
3979 // which allows the output'ed text to be filtered.
3980 if (isset($elements['#post_render'])) {
3981 foreach ($elements['#post_render'] as $function) {
3982 if (drupal_function_exists($function)) {
3983 $elements['#children'] = $function($elements['#children'], $elements);
3984 }
3985 }
3986 }
3987
3988 // Add additional CSS and JavaScript files associated with this element.
3989 foreach (array('css', 'js') as $kind) {
3990 if (!empty($elements['#attached_' . $kind]) && is_array($elements['#attached_' . $kind])) {
3991 foreach ($elements['#attached_' . $kind] as $data => $options) {
3992 // If the value is not an array, it's a filename and passed as first
3993 // (and only) argument.
3994 if (!is_array($options)) {
3995 $data = $options;
3996 $options = NULL;
3997 }
3998 // When drupal_add_js with 'type' => 'setting' is called, the first
3999 // parameter ($data) is an array. Arrays can't be keys in PHP, so we
4000 // have to get $data from the value array.
4001 if (is_numeric($data)) {
4002 $data = $options['data'];
4003 unset($options['data']);
4004 }
4005 call_user_func('drupal_add_' . $kind, $data, $options);
4006 }
4007 }
4008 }
4009
4010 $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
4011 $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
4012
4013 $elements['#printed'] = TRUE;
4014 return $prefix . $elements['#children'] . $suffix;
4015 }
4016
4017 /**
4018 * Render children of an element and concatenate them.
4019 *
4020 * This renders all children of an element using drupal_render() and then
4021 * joins them together into a single string.
4022 *
4023 * @param $element
4024 * The structured array whose children shall be rendered.
4025 * @param $children_keys
4026 * If the keys of the element's children are already known, they can be passed
4027 * in to save another run of element_children().
4028 */
4029 function drupal_render_children(&$element, $children_keys = NULL) {
4030 if ($children_keys === NULL) {
4031 $children_keys = element_children($element);
4032 }
4033 $output = '';
4034 foreach ($children_keys as $key) {
4035 $output .= drupal_render($element[$key]);
4036 }
4037 return $output;
4038 }
4039
4040 /**
4041 * Render and print an element.
4042 *
4043 * This function renders an element using drupal_render(). The top level
4044 * element is always rendered even if hide() had been previously used on it.
4045 *
4046 * Any nested elements are only rendered if they haven't been rendered before
4047 * or if they have been re-enabled with show().
4048 *
4049 * @see drupal_render()
4050 * @see show()
4051 * @see hide()
4052 */
4053 function render(&$element) {
4054 if (is_array($element)) {
4055 show($element);
4056 return drupal_render($element);
4057 }
4058 else {
4059 // Safe-guard for inappropriate use of render() on flat variables: return
4060 // the variable as-is.
4061 return $element;
4062 }
4063 }
4064
4065 /**
4066 * Hide an element from later rendering.
4067 *
4068 * @see render()
4069 * @see show()
4070 */
4071 function hide(&$element) {
4072 $element['#printed'] = TRUE;
4073 return $element;
4074 }
4075
4076 /**
4077 * Show a hidden or already printed element from later rendering.
4078 *
4079 * Alternatively, render($element) could be used which automatically shows the
4080 * element while rendering it.
4081 *
4082 * @see render()
4083 * @see hide()
4084 */
4085 function show(&$element) {
4086 $element['#printed'] = FALSE;
4087 return $element;
4088 }
4089
4090 /**
4091 * Function used by uasort to sort structured arrays by weight.
4092 */
4093 function element_sort($a, $b) {
4094 $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
4095 $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
4096 if ($a_weight == $b_weight) {
4097 return 0;
4098 }
4099 return ($a_weight < $b_weight) ? -1 : 1;
4100 }
4101
4102 /**
4103 * Retrieve the default properties for the defined element type.
4104 */
4105 function element_info($type) {
4106 $cache = &drupal_static(__FUNCTION__);
4107
4108 if (!isset($cache)) {
4109 $basic_defaults = element_basic_defaults();
4110 $cache = array();
4111 foreach (module_implements('elements') as $module) {
4112 $elements = module_invoke($module, 'elements');
4113 if (isset($elements) && is_array($elements)) {
4114 $cache = array_merge_recursive($cache, $elements);
4115 }
4116 }
4117 if (!empty($cache)) {
4118 foreach ($cache as $element_type => $info) {
4119 $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
4120 $cache[$element_type]['#type'] = $element_type;
4121 }
4122 }
4123 // Allow modules to alter the element type defaults.
4124 drupal_alter('element_info', $cache);
4125 }
4126
4127 return $cache[$type];
4128 }
4129
4130 /**
4131 * Retrieve the basic default properties that are common to all elements.
4132 */
4133 function element_basic_defaults() {
4134 return array(
4135 '#description' => '',
4136 '#title' => '',
4137 '#attributes' => array(),
4138 '#required' => FALSE,
4139 );
4140 }
4141
4142 /**
4143 * Function used by uasort to sort structured arrays by weight, without the property weight prefix.
4144 */
4145 function drupal_sort_weight($a, $b) {
4146 $a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0;
4147 $b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0;
4148 if ($a_weight == $b_weight) {
4149 return 0;
4150 }
4151 return ($a_weight < $b_weight) ? -1 : 1;
4152 }
4153
4154 /**
4155 * Check if the key is a property.
4156 */
4157 function element_property($key) {
4158 return $key[0] == '#';
4159 }
4160
4161 /**
4162 * Get properties of a structured array element. Properties begin with '#'.
4163 */
4164 function element_properties($element) {
4165 return array_filter(array_keys((array) $element), 'element_property');
4166 }
4167
4168 /**
4169 * Check if the key is a child.
4170 */
4171 function element_child($key) {
4172 return !isset($key[0]) || $key[0] != '#';
4173 }
4174
4175 /**
4176 * Return the children of an element, optionally sorted by weight.
4177 *
4178 * @param $elements
4179 * The element to be sorted.
4180 * @param $sort
4181 * Boolean to indicate whether the children should be sorted by weight.
4182 * @return
4183 * The array keys of the element's children.
4184 */
4185 function element_children(&$elements, $sort = FALSE) {
4186 // Do not attempt to sort elements which have already been sorted.
4187 $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;
4188
4189 // Filter out properties from the element, leaving only children.
4190 $children = array();
4191 $sortable = FALSE;
4192 foreach ($elements as $key => $value) {
4193 if ($key[0] !== '#') {
4194 $children[$key] = $value;
4195 if (is_array($value) && isset($value['#weight'])) {
4196 $sortable = TRUE;
4197 }
4198 }
4199 }
4200 // Sort the children if necessary.
4201 if ($sort && $sortable) {
4202 uasort($children, 'element_sort');
4203 // Put the sorted children back into $elements in the correct order, to
4204 // preserve sorting if the same element is passed through
4205 // element_children() twice.
4206 foreach ($children as $key => $child) {
4207 unset($elements[$key]);
4208 $elements[$key] = $child;
4209 }
4210 $elements['#sorted'] = TRUE;
4211 }
4212
4213 return array_keys($children);
4214 }
4215
4216 /**
4217 * Provide theme registration for themes across .inc files.
4218 */
4219 function drupal_common_theme() {
4220 return array(
4221 // theme.inc
4222 'placeholder' => array(
4223 'arguments' => array('text' => NULL)
4224 ),
4225 'page' => array(
4226 'arguments' => array('page' => NULL),
4227 'template' => 'page',
4228 ),
4229 'maintenance_page' => array(
4230 'arguments' => array('content' => NULL, 'show_blocks' => TRUE, 'show_messages' => TRUE),
4231 'template' => 'maintenance-page',
4232 'path' => 'includes',
4233 'file' => 'theme.maintenance.inc',
4234 ),
4235 'update_page' => array(
4236 'arguments' => array('content' => NULL, 'show_messages' => TRUE),
4237 ),
4238 'install_page' => array(
4239 'arguments' => array('content' => NULL),
4240 ),
4241 'task_list' => array(
4242 'arguments' => array('items' => NULL, 'active' => NULL),
4243 ),
4244 'status_messages' => array(
4245 'arguments' => array('display' => NULL),
4246 ),
4247 'links' => array(
4248 'arguments' => array('links' => NULL, 'attributes' => array('class' => 'links')),
4249 ),
4250 'image' => array(
4251 'arguments' => array('path' => NULL, 'alt' => '', 'title' => '', 'attributes' => array(), 'getsize' => TRUE),
4252 ),
4253 'breadcrumb' => array(
4254 'arguments' => array('breadcrumb' => NULL),
4255 ),
4256 'help' => array(
4257 'arguments' => array(),
4258 ),
4259 'submenu' => array(
4260 'arguments' => array('links' => NULL),
4261 ),
4262 'table' => array(
4263 'arguments' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE),
4264 ),
4265 'table_select_header_cell' => array(
4266 'arguments' => array(),
4267 ),
4268 'tablesort_indicator' => array(
4269 'arguments' => array('style' => NULL),
4270 ),
4271 'mark' => array(
4272 'arguments' => array('type' => MARK_NEW),
4273 ),
4274 'item_list' => array(
4275 'arguments' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => array()),
4276 ),
4277 'more_help_link' => array(
4278 'arguments' => array('url' => NULL),
4279 ),
4280 'feed_icon' => array(
4281 'arguments' => array('url' => NULL, 'title' => NULL),
4282 ),
4283 'more_link' => array(
4284 'arguments' => array('url' => NULL, 'title' => NULL)
4285 ),
4286 'blocks' => array(
4287 'arguments' => array('region' => NULL),
4288 ),
4289 'username' => array(
4290 'arguments' => array('object' => NULL),
4291 ),
4292 'progress_bar' => array(
4293 'arguments' => array('percent' => NULL, 'message' => NULL),
4294 ),
4295 'indentation' => array(
4296 'arguments' => array('size' => 1),
4297 ),
4298 // from pager.inc
4299 'pager' => array(
4300 'arguments' => array('tags' => array(), 'element' => 0, 'parameters' => array(), 'quantity' => 9),
4301 ),
4302 'pager_first' => array(
4303 'arguments' => array('text' => NULL, 'element' => 0, 'parameters' => array()),
4304 ),
4305 'pager_previous' => array(
4306 'arguments' => array('text' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
4307 ),
4308 'pager_next' => array(
4309 'arguments' => array('text' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
4310 ),
4311 'pager_last' => array(
4312 'arguments' => array('text' => NULL, 'element' => 0, 'parameters' => array()),
4313 ),
4314 'pager_link' => array(
4315 'arguments' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()),
4316 ),
4317 // from locale.inc
4318 'locale_admin_manage_screen' => array(
4319 'arguments' => array('form' => NULL),
4320 ),
4321 // from menu.inc
4322 'menu_item_link' => array(
4323 'arguments' => array('item' => NULL),
4324 ),
4325 'menu_tree' => array(
4326 'arguments' => array('tree' => NULL),
4327 ),
4328 'menu_item' => array(
4329 'arguments' => array('link' => NULL, 'has_children' => NULL, 'menu' => ''),
4330 ),
4331 'menu_local_task' => array(
4332 'arguments' => array('link' => NULL, 'active' => FALSE),
4333 ),
4334 'menu_local_tasks' => array(
4335 'arguments' => array(),
4336 ),
4337 // from form.inc
4338 'select' => array(
4339 'arguments' => array('element' => NULL),
4340 ),
4341 'fieldset' => array(
4342 'arguments' => array('element' => NULL),
4343 ),
4344 'radio' => array(
4345 'arguments' => array('element' => NULL),
4346 ),
4347 'radios' => array(
4348 'arguments' => array('element' => NULL),
4349 ),
4350 'date' => array(
4351 'arguments' => array('element' => NULL),
4352 ),
4353 'checkbox' => array(
4354 'arguments' => array('element' => NULL),
4355 ),
4356 'checkboxes' => array(
4357 'arguments' => array('element' => NULL),
4358 ),
4359 'button' => array(
4360 'arguments' => array('element' => NULL),
4361 ),
4362 'image_button' => array(
4363 'arguments' => array('element' => NULL),
4364 ),
4365 'hidden' => array(
4366 'arguments' => array('element' => NULL),
4367 ),
4368 'textfield' => array(
4369 'arguments' => array('element' => NULL),
4370 ),
4371 'form' => array(
4372 'arguments' => array('element' => NULL),
4373 ),
4374 'textarea' => array(
4375 'arguments' => array('element' => NULL),
4376 ),
4377 'markup' => array(
4378 'arguments' => array('element' => NULL),
4379 ),
4380 'password' => array(
4381 'arguments' => array('element' => NULL),
4382 ),
4383 'file' => array(
4384 'arguments' => array('element' => NULL),
4385 ),
4386 'tableselect' => array(
4387 'arguments' => array('element' => NULL),
4388 ),
4389 'form_element' => array(
4390 'arguments' => array('element' => NULL),
4391 ),
4392 'text_format_wrapper' => array(
4393 'arguments' => array('element' => NULL),
4394 ),
4395 'vertical_tabs' => array(
4396 'arguments' => array('element' => NULL),
4397 ),
4398 );
4399 }
4400
4401 /**
4402 * @ingroup schemaapi
4403 * @{
4404 */
4405
4406 /**
4407 * Create all tables that a module defines in its hook_schema().
4408 *
4409 * Note: This function does not pass the module's schema through
4410 * hook_schema_alter(). The module's tables will be created exactly as the
4411 * module defines them.
4412 *
4413 * @param $module
4414 * The module for which the tables will be created.
4415 * @return
4416 * An array of arrays with the following key/value pairs:
4417 * - success: a boolean indicating whether the query succeeded.
4418 * - query: the SQL query(s) executed, passed through check_plain().
4419 */
4420 function drupal_install_schema($module) {
4421 $schema = drupal_get_schema_unprocessed($module);
4422 _drupal_schema_initialize($module, $schema);
4423
4424 $ret = array();
4425 foreach ($schema as $name => $table) {
4426 db_create_table($ret, $name, $table);
4427 }
4428 return $ret;
4429 }
4430
4431 /**
4432 * Remove all tables that a module defines in its hook_schema().
4433 *
4434 * Note: This function does not pass the module's schema through
4435 * hook_schema_alter(). The module's tables will be created exactly as the
4436 * module defines them.
4437 *
4438 * @param $module
4439 * The module for which the tables will be removed.
4440 * @return
4441 * An array of arrays with the following key/value pairs:
4442 * - success: a boolean indicating whether the query succeeded.
4443 * - query: the SQL query(s) executed, passed through check_plain().
4444 */
4445 function drupal_uninstall_schema($module) {
4446 $schema = drupal_get_schema_unprocessed($module);
4447 _drupal_schema_initialize($module, $schema);
4448
4449 $ret = array();
4450 foreach ($schema as $table) {
4451 if (db_table_exists($table['name'])) {
4452 db_drop_table($ret, $table['name']);
4453 }
4454 }
4455 return $ret;
4456 }
4457
4458 /**
4459 * Returns the unprocessed and unaltered version of a module's schema.
4460 *
4461 * Use this function only if you explicitly need the original
4462 * specification of a schema, as it was defined in a module's
4463 * hook_schema(). No additional default values will be set,
4464 * hook_schema_alter() is not invoked and these unprocessed
4465 * definitions won't be cached.
4466 *
4467 * This function can be used to retrieve a schema specification in
4468 * hook_schema(), so it allows you to derive your tables from existing
4469 * specifications.
4470 *
4471 * It is also used by drupal_install_schema() and
4472 * drupal_uninstall_schema() to ensure that a module's tables are
4473 * created exactly as specified without any changes introduced by a
4474 * module that implements hook_schema_alter().
4475 *
4476 * @param $module
4477 * The module to which the table belongs.
4478 * @param $table
4479 * The name of the table. If not given, the module's complete schema
4480 * is returned.
4481 */
4482 function drupal_get_schema_unprocessed($module, $table = NULL) {
4483 // Load the .install file to get hook_schema.
4484 module_load_install($module);
4485 $schema = module_invoke($module, 'schema');
4486
4487 if (!is_null($table) && isset($schema[$table])) {
4488 return $schema[$table];
4489 }
4490 else {
4491 return $schema;
4492 }
4493 }
4494
4495 /**
4496 * Fill in required default values for table definitions returned by hook_schema().
4497 *
4498 * @param $module
4499 * The module for which hook_schema() was invoked.
4500 * @param $schema
4501 * The schema definition array as it was returned by the module's
4502 * hook_schema().
4503 */
4504 function _drupal_schema_initialize($module, &$schema) {
4505 // Set the name and module key for all tables.
4506 foreach ($schema as $name => $table) {
4507 if (empty($table['module'])) {
4508 $schema[$name]['module'] = $module;
4509 }
4510 if (!isset($table['name'])) {
4511 $schema[$name]['name'] = $name;
4512 }
4513 }
4514 }
4515
4516 /**
4517 * Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query.
4518 *
4519 * @param $table
4520 * The name of the table from which to retrieve fields.
4521 * @param
4522 * An optional prefix to to all fields.
4523 *
4524 * @return An array of fields.
4525 **/
4526 function drupal_schema_fields_sql($table, $prefix = NULL) {
4527 $schema = drupal_get_schema($table);
4528 $fields = array_keys($schema['fields']);
4529 if ($prefix) {
4530 $columns = array();
4531 foreach ($fields as $field) {
4532 $columns[] = "$prefix.$field";
4533 }
4534 return $columns;
4535 }
4536 else {
4537 return $fields;
4538 }
4539 }
4540
4541 /**
4542 * Save a record to the database based upon the schema.
4543 *
4544 * Default values are filled in for missing items, and 'serial' (auto increment)
4545 * types are filled in with IDs.
4546 *
4547 * @param $table
4548 * The name of the table; this must exist in schema API.
4549 * @param $object
4550 * The object to write. This is a reference, as defaults according to
4551 * the schema may be filled in on the object, as well as ID on the serial
4552 * type(s). Both array an object types may be passed.
4553 * @param $primary_keys
4554 * If this is an update, specify the primary keys' field names. It is the
4555 * caller's responsibility to know if a record for this object already
4556 * exists in the database. If there is only 1 key, you may pass a simple string.
4557 * @return
4558 * Failure to write a record will return FALSE. Otherwise SAVED_NEW or
4559 * SAVED_UPDATED is returned depending on the operation performed. The
4560 * $object parameter contains values for any serial fields defined by
4561 * the $table. For example, $object->nid will be populated after inserting
4562 * a new node.
4563 */
4564 function drupal_write_record($table, &$object, $primary_keys = array()) {
4565 // Standardize $primary_keys to an array.
4566 if (is_string($primary_keys)) {
4567 $primary_keys = array($primary_keys);
4568 }
4569
4570 $schema = drupal_get_schema($table);
4571 if (empty($schema)) {
4572 return FALSE;
4573 }
4574
4575 // Convert to an object if needed.
4576 if (is_array($object)) {
4577 $object = (object) $object;
4578 $array = TRUE;
4579 }
4580 else {
4581 $array = FALSE;
4582 }
4583
4584 $fields = array();
4585
4586 // Go through our schema, build SQL, and when inserting, fill in defaults for
4587 // fields that are not set.
4588 foreach ($schema['fields'] as $field => $info) {
4589 // Special case -- skip serial types if we are updating.
4590 if ($info['type'] == 'serial' && !empty($primary_keys)) {
4591 continue;
4592 }
4593
4594 // For inserts, populate defaults from schema if not already provided.
4595 if (!isset($object->$field) && empty($primary_keys) && isset($info['default'])) {
4596 $object->$field = $info['default'];
4597 }
4598
4599 // Track serial field so we can helpfully populate them after the query.
4600 // NOTE: Each table should come with one serial field only.
4601 if ($info['type'] == 'serial') {
4602 $serial = $field;
4603 }
4604
4605 // Build arrays for the fields and values in our query.
4606 if (isset($object->$field)) {
4607 if (empty($info['serialize'])) {
4608 $fields[$field] = $object->$field;
4609 }
4610 elseif (!empty($object->$field)) {
4611 $fields[$field] = serialize($object->$field);
4612 }
4613 else {
4614 $fields[$field] = '';
4615 }
4616 }
4617
4618 // We don't need to care about type casting if value does not exist.
4619 if (!isset($fields[$field])) {
4620 continue;
4621 }
4622
4623 // Special case -- skip null value if field allows null.
4624 if ($fields[$field] == NULL && $info['not null'] == FALSE) {
4625 continue;
4626 }
4627
4628 // Type cast if field does not allow null. Required by DB API.
4629 if ($info['type'] == 'int' || $info['type'] == 'serial') {
4630 $fields[$field] = (int) $fields[$field];
4631 }
4632 elseif ($info['type'] == 'float') {
4633 $fields[$field] = (float) $fields[$field];
4634 }
4635 else {
4636 $fields[$field] = (string) $fields[$field];
4637 }
4638 }
4639
4640 if (empty($fields)) {
4641 // No changes requested.
4642 // If we began with an array, convert back so we don't surprise the caller.
4643 if ($array) {
4644 $object = (array) $object;
4645 }
4646 return;
4647 }
4648
4649 // Build the SQL.
4650 if (empty($primary_keys)) {
4651 $options = array('return' => Database::RETURN_INSERT_ID);
4652 if (isset($serial) && isset($fields[$serial])) {
4653 // If the serial column has been explicitly set with an ID, then we don't
4654 // require the database to return the last insert id.
4655 if ($fields[$serial]) {
4656 $options['return'] = Database::RETURN_AFFECTED;
4657 }
4658 // If a serial column does exist with no value (i.e. 0) then remove it as
4659 // the database will insert the correct value for us.
4660 else {
4661 unset($fields[$serial]);
4662 }
4663 }
4664 $query = db_insert($table, $options)->fields($fields);
4665 $return = SAVED_NEW;
4666 }
4667 else {
4668 $query = db_update($table)->fields($fields);
4669 foreach ($primary_keys as $key) {
4670 $query->condition($key, $object->$key);
4671 }
4672 $return = SAVED_UPDATED;
4673 }
4674
4675 // Execute the SQL.
4676 if ($last_insert_id = $query->execute()) {
4677 if (isset($serial)) {
4678 // If the database was not told to return the last insert id, it will be
4679 // because we already know it.
4680 if (isset($options) && $options['return'] != Database::RETURN_INSERT_ID) {
4681 $object->$serial = $fields[$serial];
4682 }
4683 else {
4684 $object->$serial = $last_insert_id;
4685 }
4686 }
4687 }
4688 // If we have a single-field primary key but got no insert ID, the
4689 // query failed.
4690 elseif (count($primary_keys) == 1) {
4691 $return = FALSE;
4692 }
4693
4694 // If we began with an array, convert back so we don't surprise the caller.
4695 if ($array) {
4696 $object = (array) $object;
4697 }
4698
4699 return $return;
4700 }
4701
4702 /**
4703 * @} End of "ingroup schemaapi".
4704 */
4705
4706 /**
4707 * Parse Drupal info file format.
4708 *
4709 * Files should use an ini-like format to specify values.
4710 * White-space generally doesn't matter, except inside values.
4711 * e.g.
4712 *
4713 * @verbatim
4714 * key = value
4715 * key = "value"
4716 * key = 'value'
4717 * key = "multi-line
4718 *
4719 * value"
4720 * key = 'multi-line
4721 *
4722 * value'
4723 * key
4724 * =
4725 * 'value'
4726 * @endverbatim
4727 *
4728 * Arrays are created using a GET-like syntax:
4729 *
4730 * @verbatim
4731 * key[] = "numeric array"
4732 * key[index] = "associative array"
4733 * key[index][] = "nested numeric array"
4734 * key[index][index] = "nested associative array"
4735 * @endverbatim
4736 *
4737 * PHP constants are substituted in, but only when used as the entire value:
4738 *
4739 * Comments should start with a semi-colon at the beginning of a line.
4740 *
4741 * This function is NOT for placing arbitrary module-specific settings. Use
4742 * variable_get() and variable_set() for that.
4743 *
4744 * Information stored in the module.info file:
4745 * - name: The real name of the module for display purposes.
4746 * - description: A brief description of the module.
4747 * - dependencies: An array of shortnames of other modules this module requires.
4748 * - package: The name of the package of modules this module belongs to.
4749 *
4750 * Example of .info file:
4751 * @verbatim
4752 * name = Forum
4753 * description = Enables threaded discussions about general topics.
4754 * dependencies[] = taxonomy
4755 * dependencies[] = comment
4756 * package = Core
4757 * version = VERSION
4758 * @endverbatim
4759 *
4760 * @param $filename
4761 * The file we are parsing. Accepts file with relative or absolute path.
4762 * @return
4763 * The info array.
4764 */
4765 function drupal_parse_info_file($filename) {
4766 $info = array();
4767
4768 if (!file_exists($filename)) {
4769 return $info;
4770 }
4771
4772 $data = file_get_contents($filename);
4773 if (preg_match_all('
4774 @^\s* # Start at the beginning of a line, ignoring leading whitespace
4775 ((?:
4776 [^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets,
4777 \[[^\[\]]*\] # unless they are balanced and not nested
4778 )+?)
4779 \s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space)
4780 (?:
4781 ("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes
4782 (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
4783 ([^\r\n]*?) # Non-quoted string
4784 )\s*$ # Stop at the next end of a line, ignoring trailing whitespace
4785 @msx', $data, $matches, PREG_SET_ORDER)) {
4786 foreach ($matches as $match) {
4787 // Fetch the key and value string
4788 $i = 0;
4789 foreach (array('key', 'value1', 'value2', 'value3') as $var) {
4790 $$var = isset($match[++$i]) ? $match[$i] : '';
4791 }
4792 $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
4793
4794 // Parse array syntax
4795 $keys = preg_split('/\]?\[/', rtrim($key, ']'));
4796 $last = array_pop($keys);
4797 $parent = &$info;
4798
4799 // Create nested arrays
4800 foreach ($keys as $key) {
4801 if ($key == '') {
4802 $key = count($parent);
4803 }
4804 if (!isset($parent[$key]) || !is_array($parent[$key])) {
4805 $parent[$key] = array();
4806 }
4807 $parent = &$parent[$key];
4808 }
4809
4810 // Handle PHP constants
4811 if (defined($value)) {
4812 $value = constant($value);
4813 }
4814
4815 // Insert actual value
4816 if ($last == '') {
4817 $last = count($parent);
4818 }
4819 $parent[$last] = $value;
4820 }
4821 }
4822
4823 return $info;
4824 }
4825
4826 /**
4827 * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
4828 *
4829 * @return
4830 * Array of the possible severity levels for log messages.
4831 *
4832 * @see watchdog()
4833 */
4834 function watchdog_severity_levels() {
4835 return array(
4836 WATCHDOG_EMERG => t('emergency'),
4837 WATCHDOG_ALERT => t('alert'),
4838 WATCHDOG_CRITICAL => t('critical'),
4839 WATCHDOG_ERROR => t('error'),
4840 WATCHDOG_WARNING => t('warning'),
4841 WATCHDOG_NOTICE => t('notice'),
4842 WATCHDOG_INFO => t('info'),
4843 WATCHDOG_DEBUG => t('debug'),
4844 );
4845 }
4846
4847
4848 /**
4849 * Explode a string of given tags into an array.
4850 */
4851 function drupal_explode_tags($tags) {
4852 // This regexp allows the following types of user input:
4853 // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
4854 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
4855 preg_match_all($regexp, $tags, $matches);
4856 $typed_tags = array_unique($matches[1]);
4857
4858 $tags = array();
4859 foreach ($typed_tags as $tag) {
4860 // If a user has escaped a term (to demonstrate that it is a group,
4861 // or includes a comma or quote character), we remove the escape
4862 // formatting so to save the term into the database as the user intends.
4863 $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
4864 if ($tag != "") {
4865 $tags[] = $tag;
4866 }
4867 }
4868
4869 return $tags;
4870 }
4871
4872 /**
4873 * Implode an array of tags into a string.
4874 */
4875 function drupal_implode_tags($tags) {
4876 $encoded_tags = array();
4877 foreach ($tags as $tag) {
4878 // Commas and quotes in tag names are special cases, so encode them.
4879 if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
4880 $tag = '"' . str_replace('"', '""', $tag) . '"';
4881 }
4882
4883 $encoded_tags[] = $tag;
4884 }
4885 return implode(', ', $encoded_tags);
4886 }
4887
4888 /**
4889 * Flush all cached data on the site.
4890 *
4891 * Empties cache tables, rebuilds the menu cache and theme registries, and
4892 * invokes a hook so that other modules' cache data can be cleared as well.
4893 */
4894 function drupal_flush_all_caches() {
4895 // Change query-strings on css/js files to enforce reload for all users.
4896 _drupal_flush_css_js();
4897
4898 registry_rebuild();
4899 drupal_clear_css_cache();
4900 drupal_clear_js_cache();
4901
4902 // If invoked from update.php, we must not update the theme information in the
4903 // database, or this will result in all themes being disabled.
4904 if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
4905 _system_get_theme_data();
4906 }
4907 else {
4908 system_get_theme_data();
4909 }
4910
4911 drupal_theme_rebuild();
4912 // Rebuild content types, menu will be rebuilt as well.
4913 node_types_rebuild();
4914 // Don't clear cache_form - in-progress form submissions may break.
4915 // Ordered so clearing the page cache will always be the last action.
4916 $core = array('cache', 'cache_filter', 'cache_registry', 'cache_page');
4917 $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
4918 foreach ($cache_tables as $table) {
4919 cache_clear_all('*', $table, TRUE);
4920 }
4921 }
4922
4923 /**
4924 * Helper function to change query-strings on css/js files.
4925 *
4926 * Changes the character added to all css/js files as dummy query-string,
4927 * so that all browsers are forced to reload fresh files. We keep
4928 * 20 characters history (FIFO) to avoid repeats, but only the first
4929 * (newest) character is actually used on urls, to keep them short.
4930 * This is also called from update.php.
4931 */
4932 function _drupal_flush_css_js() {
4933 $string_history = variable_get('css_js_query_string', '00000000000000000000');
4934 $new_character = $string_history[0];
4935 // Not including 'q' to allow certain JavaScripts to re-use query string.
4936 $characters = 'abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
4937 while (strpos($string_history, $new_character) !== FALSE) {
4938 $new_character = $characters[mt_rand(0, strlen($characters) - 1)];
4939 }
4940 variable_set('css_js_query_string', $new_character . substr($string_history, 0, 19));
4941 }
4942
4943 /**
4944 * Debug function used for outputting debug information.
4945 *
4946 * The debug information is passed on to trigger_error() after being converted
4947 * to a string using _drupal_debug_message().
4948 *
4949 * @param $data
4950 * Data to be output.
4951 * @param $label
4952 * Label to prefix the data.
4953 * @param $print_r
4954 * Flag to switch between print_r() and var_export() for data conversion to
4955 * string. Set $print_r to TRUE when dealing with a recursive data structure
4956 * as var_export() will generate an error.
4957 */
4958 function debug($data, $label = NULL, $print_r = FALSE) {
4959 // Print $data contents to string.
4960 $string = $print_r ? print_r($data, TRUE) : var_export($data, TRUE);
4961 trigger_error(trim($label ? "$label: $string" : $string));
4962 }
4963
4964 /**
4965 * Parse a dependency for comparison by drupal_check_incompatibility().
4966 *
4967 * @param $dependency
4968 * A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'.
4969 * @return
4970 * An associative array with three keys:
4971 * - 'name' includes the name of the thing to depend on (e.g. 'foo').
4972 * - 'original_version' contains the original version string (which can be
4973 * used in the UI for reporting incompatibilities).
4974 * - 'versions' is a list of associative arrays, each containing the keys
4975 * 'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<',
4976 * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
4977 * Callers should pass this structure to drupal_check_incompatibility().
4978 *
4979 * @see drupal_check_incompatibility()
4980 */
4981 function drupal_parse_dependency($dependency) {
4982 // We use named subpatterns and support every op that version_compare
4983 // supports. Also, op is optional and defaults to equals.
4984 $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
4985 // Core version is always optional: 7.x-2.x and 2.x is treated the same.
4986 $p_core = '(?:' . preg_quote(DRUPAL_CORE_COMPATIBILITY) . '-)?';
4987 $p_major = '(?P<major>\d+)';
4988 // By setting the minor version to x, branches can be matched.
4989 $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
4990 $value = array();
4991 $parts = explode('(', $dependency, 2);
4992 $value['name'] = trim($parts[0]);
4993 if (isset($parts[1])) {
4994 $value['original_version'] = ' (' . $parts[1];
4995 foreach (explode(',', $parts[1]) as $version) {
4996 if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
4997 $op = !empty($matches['operation']) ? $matches['operation'] : '=';
4998 if ($matches['minor'] == 'x') {
4999 // Drupal considers "2.x" to mean any version that begins with
5000 // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
5001 // on the other hand, treats "x" as a string; so to
5002 // version_compare(), "2.x" is considered less than 2.0. This
5003 // means that >=2.x and <2.x are handled by version_compare()
5004 // as we need, but > and <= are not.
5005 if ($op == '>' || $op == '<=') {
5006 $matches['major']++;
5007 }
5008 // Equivalence can be checked by adding two restrictions.
5009 if ($op == '=' || $op == '==') {
5010 $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
5011 $op = '>=';
5012 }
5013 }
5014 $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
5015 }
5016 }
5017 }
5018 return $value;
5019 }
5020
5021 /**
5022 * Check whether a version is compatible with a given dependency.
5023 *
5024 * @param $v
5025 * The parsed dependency structure from drupal_parse_dependency().
5026 * @param $current_version
5027 * The version to check against (like 4.2).
5028 * @return
5029 * NULL if compatible, otherwise the original dependency version string that
5030 * caused the incompatiblity.
5031 *
5032 * @see drupal_parse_dependency()
5033 */
5034 function drupal_check_incompatibility($v, $current_version) {
5035 if (!empty($v['versions'])) {
5036 foreach ($v['versions'] as $required_version) {
5037 if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op']))) {
5038 return $v['original_version'];
5039 }
5040 }
5041 }
5042 }
5043

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.