2001-12-01 15:20:48 +00:00
< ? php
2004-07-13 07:21:14 +00:00
/**
* @ file
* Common functions that many Drupal modules will need to reference .
*
* The functions that are critical and need to be available even when serving
* a cached page are instead located in bootstrap . inc .
*/
2009-09-28 22:22:54 +00:00
/**
* @ defgroup php_wrappers PHP wrapper functions
* @ {
* Functions that are wrappers or custom implementations of PHP functions .
*
* Certain PHP functions should not be used in Drupal . Instead , Drupal ' s
* replacement functions should be used .
*
* For example , for improved or more secure UTF8 - handling , or RFC - compliant
* handling of URLs in Drupal .
*
* For ease of use and memorizing , all these wrapper functions use the same name
* as the original PHP function , but prefixed with " drupal_ " . Beware , however ,
* that not all wrapper functions support the same arguments as the original
* functions .
*
* You should always use these wrapper functions in your code .
*
* Wrong :
* @ code
* $my_substring = substr ( $original_string , 0 , 5 );
* @ endcode
*
* Correct :
* @ code
* $my_substring = drupal_substr ( $original_string , 0 , 5 );
* @ endcode
*
2010-12-15 03:00:40 +00:00
* @ }
2009-09-28 22:22:54 +00:00
*/
2005-05-07 01:48:06 +00:00
/**
* Return status for saving which involved creating a new item .
*/
2011-11-29 09:56:53 +00:00
const SAVED_NEW = 1 ;
2005-05-07 01:48:06 +00:00
/**
* Return status for saving which involved an update to an existing item .
*/
2011-11-29 09:56:53 +00:00
const SAVED_UPDATED = 2 ;
2005-05-07 01:48:06 +00:00
/**
* Return status for saving which deleted an existing item .
*/
2011-11-29 09:56:53 +00:00
const SAVED_DELETED = 3 ;
2005-05-07 01:48:06 +00:00
2009-07-30 19:57:10 +00:00
/**
2010-10-05 19:59:10 +00:00
* The default group for system CSS files added to the page .
2009-07-30 19:57:10 +00:00
*/
2011-11-29 09:56:53 +00:00
const CSS_SYSTEM = - 100 ;
2009-07-30 19:57:10 +00:00
/**
2010-10-05 19:59:10 +00:00
* The default group for module CSS files added to the page .
2009-07-30 19:57:10 +00:00
*/
2011-11-29 09:56:53 +00:00
const CSS_DEFAULT = 0 ;
2009-07-30 19:57:10 +00:00
/**
2010-10-05 19:59:10 +00:00
* The default group for theme CSS files added to the page .
2009-07-30 19:57:10 +00:00
*/
2011-11-29 09:56:53 +00:00
const CSS_THEME = 100 ;
2009-07-30 19:57:10 +00:00
2008-11-10 05:23:01 +00:00
/**
2011-11-03 11:00:04 +00:00
* The default group for JavaScript libraries or jQuery plugins added
2010-10-05 19:59:10 +00:00
* to the page .
2008-11-10 05:23:01 +00:00
*/
2011-11-29 09:56:53 +00:00
const JS_LIBRARY = - 100 ;
2008-11-10 05:23:01 +00:00
/**
2010-10-05 19:59:10 +00:00
* The default group for module JavaScript code added to the page .
2008-11-10 05:23:01 +00:00
*/
2011-11-29 09:56:53 +00:00
const JS_DEFAULT = 0 ;
2008-11-10 05:23:01 +00:00
/**
2010-10-05 19:59:10 +00:00
* The default group for theme JavaScript code added to the page .
2008-11-10 05:23:01 +00:00
*/
2011-11-29 09:56:53 +00:00
const JS_THEME = 100 ;
2008-11-10 05:23:01 +00:00
2011-11-03 11:00:04 +00:00
/**
* The default group for JavaScript settings added to the page .
*/
2011-11-29 09:56:53 +00:00
const JS_SETTING = 200 ;
2011-11-03 11:00:04 +00:00
2009-06-06 15:43:05 +00:00
/**
* Error code indicating that the request made by drupal_http_request () exceeded
* the specified timeout .
*/
2011-11-29 09:56:53 +00:00
const HTTP_REQUEST_TIMEOUT = - 1 ;
2009-06-06 15:43:05 +00:00
2009-08-31 17:06:10 +00:00
/**
* Constants defining cache granularity for blocks and renderable arrays .
*
* Modules specify the caching patterns for their blocks using binary
* combinations of these constants in their hook_block_info () :
* $block [ delta ][ 'cache' ] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE ;
* DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is
* specified . Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
* implement
*
* The block cache is cleared in cache_clear_all (), and uses the same clearing
* policy than page cache ( node , comment , user , taxonomy added or updated ... ) .
* Blocks requiring more fine - grained clearing might consider disabling the
* built - in block cache ( DRUPAL_NO_CACHE ) and roll their own .
*
* Note that user 1 is excluded from block caching .
*/
/**
* The block should not get cached . This setting should be used :
* - for simple blocks ( notably those that do not perform any db query ),
* where querying the db cache would be more expensive than directly generating
* the content .
* - for blocks that change too frequently .
*/
2011-11-29 09:56:53 +00:00
const DRUPAL_NO_CACHE = - 1 ;
2009-08-31 17:06:10 +00:00
/**
* The block is handling its own caching in its hook_block_view () . From the
* perspective of the block cache system , this is equivalent to DRUPAL_NO_CACHE .
* Useful when time based expiration is needed or a site uses a node access
* which invalidates standard block cache .
*/
2011-11-29 09:56:53 +00:00
const DRUPAL_CACHE_CUSTOM = - 2 ;
2009-08-31 17:06:10 +00:00
/**
* The block or element can change depending on the roles the user viewing the
* page belongs to . This is the default setting for blocks , used when the block
* does not specify anything .
*/
2011-11-29 09:56:53 +00:00
const DRUPAL_CACHE_PER_ROLE = 0x0001 ;
2009-08-31 17:06:10 +00:00
/**
* The block or element can change depending on the user viewing the page .
* This setting can be resource - consuming for sites with large number of users ,
* and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient .
*/
2011-11-29 09:56:53 +00:00
const DRUPAL_CACHE_PER_USER = 0x0002 ;
2009-08-31 17:06:10 +00:00
/**
* The block or element can change depending on the page being viewed .
*/
2011-11-29 09:56:53 +00:00
const DRUPAL_CACHE_PER_PAGE = 0x0004 ;
2009-08-31 17:06:10 +00:00
/**
* The block or element is the same for every user on every page where it is visible .
*/
2011-11-29 09:56:53 +00:00
const DRUPAL_CACHE_GLOBAL = 0x0008 ;
2009-08-31 17:06:10 +00:00
2005-08-16 18:06:18 +00:00
/**
2009-05-21 21:12:25 +00:00
* Add content to a specified region .
2005-08-16 18:06:18 +00:00
*
* @ param $region
2009-05-21 21:12:25 +00:00
* Page region the content is added to .
2005-08-16 18:06:18 +00:00
* @ param $data
2009-05-21 21:12:25 +00:00
* Content to be added .
2005-08-16 18:06:18 +00:00
*/
2009-05-21 21:12:25 +00:00
function drupal_add_region_content ( $region = NULL , $data = NULL ) {
2005-08-16 18:06:18 +00:00
static $content = array ();
2010-09-24 02:48:35 +00:00
if ( isset ( $region ) && isset ( $data )) {
2005-08-16 18:06:18 +00:00
$content [ $region ][] = $data ;
}
return $content ;
}
/**
2009-05-21 21:12:25 +00:00
* Get assigned content for a given region .
2005-08-16 18:06:18 +00:00
*
* @ param $region
2007-10-08 14:08:19 +00:00
* A specified region to fetch content for . If NULL , all regions will be
* returned .
2005-08-16 18:06:18 +00:00
* @ param $delimiter
2009-07-30 10:54:41 +00:00
* Content to be inserted between imploded array elements .
2005-08-16 18:06:18 +00:00
*/
2009-05-21 21:12:25 +00:00
function drupal_get_region_content ( $region = NULL , $delimiter = ' ' ) {
$content = drupal_add_region_content ();
2005-10-23 09:47:53 +00:00
if ( isset ( $region )) {
if ( isset ( $content [ $region ]) && is_array ( $content [ $region ])) {
2005-12-17 10:35:59 +00:00
return implode ( $delimiter , $content [ $region ]);
2005-10-23 09:47:53 +00:00
}
2005-08-16 18:06:18 +00:00
}
else {
foreach ( array_keys ( $content ) as $region ) {
if ( is_array ( $content [ $region ])) {
2005-12-17 10:35:59 +00:00
$content [ $region ] = implode ( $delimiter , $content [ $region ]);
2005-08-16 18:06:18 +00:00
}
}
return $content ;
}
}
2009-08-21 07:50:08 +00:00
/**
* Get the name of the currently active install profile .
*
* When this function is called during Drupal ' s initial installation process ,
* the name of the profile that ' s about to be installed is stored in the global
* installation state . At all other times , the standard Drupal systems variable
* table contains the name of the current profile , and we can call variable_get ()
* to determine what one is active .
*
* @ return $profile
* The name of the install profile .
*/
function drupal_get_profile () {
global $install_state ;
if ( isset ( $install_state [ 'parameters' ][ 'profile' ])) {
$profile = $install_state [ 'parameters' ][ 'profile' ];
}
else {
2010-01-04 23:08:34 +00:00
$profile = variable_get ( 'install_profile' , 'standard' );
2009-08-21 07:50:08 +00:00
}
return $profile ;
}
2003-11-23 10:41:04 +00:00
/**
2004-09-09 05:51:08 +00:00
* Set the breadcrumb trail for the current page .
2003-12-08 06:32:19 +00:00
*
2004-09-09 05:51:08 +00:00
* @ param $breadcrumb
* Array of links , starting with " home " and proceeding up to but not including
* the current page .
2004-01-06 19:52:14 +00:00
*/
2003-11-23 10:41:04 +00:00
function drupal_set_breadcrumb ( $breadcrumb = NULL ) {
2009-05-31 07:00:12 +00:00
$stored_breadcrumb = & drupal_static ( __FUNCTION__ );
2003-11-23 10:41:04 +00:00
2010-09-24 00:37:45 +00:00
if ( isset ( $breadcrumb )) {
2003-11-23 10:41:04 +00:00
$stored_breadcrumb = $breadcrumb ;
}
return $stored_breadcrumb ;
}
2004-09-09 05:51:08 +00:00
/**
* Get the breadcrumb trail for the current page .
*/
2003-11-23 10:41:04 +00:00
function drupal_get_breadcrumb () {
$breadcrumb = drupal_set_breadcrumb ();
2010-09-24 00:37:45 +00:00
if ( ! isset ( $breadcrumb )) {
2003-11-23 10:41:04 +00:00
$breadcrumb = menu_get_active_breadcrumb ();
}
return $breadcrumb ;
}
2004-01-14 22:30:09 +00:00
/**
2004-09-09 05:51:08 +00:00
* Add output to the head tag of the HTML page .
2007-10-12 14:10:18 +00:00
*
2011-10-27 17:34:00 +00:00
* This function can be called as long as the headers aren ' t sent . Pass no
2009-11-03 06:47:23 +00:00
* arguments ( or NULL for both ) to retrieve the currently stored elements .
*
* @ param $data
* A renderable array . If the '#type' key is not set then 'html_tag' will be
* added as the default '#type' .
* @ param $key
* A unique string key to allow implementations of hook_html_head_alter () to
* identify the element in $data . Required if $data is not NULL .
*
* @ return
* An array of all stored HEAD elements .
*
* @ see theme_html_tag ()
2004-01-14 22:30:09 +00:00
*/
2009-11-03 06:47:23 +00:00
function drupal_add_html_head ( $data = NULL , $key = NULL ) {
$stored_head = & drupal_static ( __FUNCTION__ );
2004-01-14 22:30:09 +00:00
2009-11-03 06:47:23 +00:00
if ( ! isset ( $stored_head )) {
// Make sure the defaults, including Content-Type, come first.
$stored_head = _drupal_default_html_head ();
}
if ( isset ( $data ) && isset ( $key )) {
if ( ! isset ( $data [ '#type' ])) {
$data [ '#type' ] = 'html_tag' ;
}
$stored_head [ $key ] = $data ;
2004-01-14 22:30:09 +00:00
}
return $stored_head ;
}
2004-09-09 05:51:08 +00:00
/**
2009-11-03 06:47:23 +00:00
* Returns elements that are always displayed in the HEAD tag of the HTML page .
*/
function _drupal_default_html_head () {
// Add default elements. Make sure the Content-Type comes first because the
// IE browser may be vulnerable to XSS via encoding attacks from any content
// that comes before this META tag, such as a TITLE tag.
$elements [ 'system_meta_content_type' ] = array (
'#type' => 'html_tag' ,
'#tag' => 'meta' ,
'#attributes' => array (
2011-11-18 15:06:47 +00:00
'charset' => 'utf-8' ,
2009-11-03 06:47:23 +00:00
),
// Security: This always has to be output first.
'#weight' => - 1000 ,
);
// Show Drupal and the major version number in the META GENERATOR tag.
// Get the major version.
list ( $version , ) = explode ( '.' , VERSION );
$elements [ 'system_meta_generator' ] = array (
'#type' => 'html_tag' ,
'#tag' => 'meta' ,
'#attributes' => array (
'name' => 'Generator' ,
'content' => 'Drupal ' . $version . ' (http://drupal.org)' ,
),
);
// Also send the generator in the HTTP header.
$elements [ 'system_meta_generator' ][ '#attached' ][ 'drupal_add_http_header' ][] = array ( 'X-Generator' , $elements [ 'system_meta_generator' ][ '#attributes' ][ 'content' ]);
return $elements ;
}
/**
* Retrieve output to be displayed in the HEAD tag of the HTML page .
2004-09-09 05:51:08 +00:00
*/
2004-01-14 22:30:09 +00:00
function drupal_get_html_head () {
2009-11-03 06:47:23 +00:00
$elements = drupal_add_html_head ();
drupal_alter ( 'html_head' , $elements );
return drupal_render ( $elements );
2004-01-14 22:30:09 +00:00
}
2006-12-05 05:45:05 +00:00
/**
2006-08-23 05:55:38 +00:00
* Add a feed URL for the current page .
*
2008-09-18 10:40:28 +00:00
* This function can be called as long the HTML header hasn ' t been sent .
*
2006-08-23 05:55:38 +00:00
* @ param $url
2010-11-29 04:53:32 +00:00
* An internal system path or a fully qualified external URL of the feed .
2006-08-23 07:23:09 +00:00
* @ param $title
2007-10-08 14:08:19 +00:00
* The title of the feed .
2006-08-23 05:55:38 +00:00
*/
2006-08-23 07:23:09 +00:00
function drupal_add_feed ( $url = NULL , $title = '' ) {
2009-05-31 07:00:12 +00:00
$stored_feed_links = & drupal_static ( __FUNCTION__ , array ());
2006-08-23 05:55:38 +00:00
2009-11-03 06:47:23 +00:00
if ( isset ( $url )) {
2009-10-09 01:00:08 +00:00
$stored_feed_links [ $url ] = theme ( 'feed_icon' , array ( 'url' => $url , 'title' => $title ));
2006-08-23 07:23:09 +00:00
2010-11-29 04:53:32 +00:00
drupal_add_html_head_link ( array (
'rel' => 'alternate' ,
'type' => 'application/rss+xml' ,
'title' => $title ,
// Force the URL to be absolute, for consistency with other <link> tags
// output by Drupal.
'href' => url ( $url , array ( 'absolute' => TRUE )),
));
2006-08-23 05:55:38 +00:00
}
return $stored_feed_links ;
}
/**
* Get the feed URLs for the current page .
*
* @ param $delimiter
2007-10-08 14:08:19 +00:00
* A delimiter to split feeds by .
2006-08-23 05:55:38 +00:00
*/
function drupal_get_feeds ( $delimiter = " \n " ) {
$feeds = drupal_add_feed ();
return implode ( $feeds , $delimiter );
}
2004-02-08 17:12:44 +00:00
/**
2010-10-08 05:07:53 +00:00
* @ defgroup http_handling HTTP handling
2004-02-08 17:12:44 +00:00
* @ {
2004-09-09 05:51:08 +00:00
* Functions to properly handle HTTP responses .
2004-02-08 17:12:44 +00:00
*/
2006-04-13 08:25:27 +00:00
/**
2009-09-29 15:31:17 +00:00
* Process a URL query parameter array to remove unwanted elements .
2006-04-13 08:25:27 +00:00
*
* @ param $query
2009-09-29 15:31:17 +00:00
* ( optional ) An array to be processed . Defaults to $_GET .
2006-04-13 08:25:27 +00:00
* @ param $exclude
2009-09-29 15:31:17 +00:00
* ( optional ) A list of $query array keys to remove . Use " parent[child] " to
* exclude nested items . Defaults to array ( 'q' ) .
2006-04-13 08:25:27 +00:00
* @ param $parent
2009-09-29 15:31:17 +00:00
* Internal use only . Used to build the $query array key for nested items .
*
2006-04-13 08:25:27 +00:00
* @ return
2009-09-29 15:31:17 +00:00
* An array containing query parameters , which can be used for url () .
2006-04-13 08:25:27 +00:00
*/
2009-09-29 15:31:17 +00:00
function drupal_get_query_parameters ( array $query = NULL , array $exclude = array ( 'q' ), $parent = '' ) {
// Set defaults, if none given.
if ( ! isset ( $query )) {
$query = $_GET ;
}
// If $exclude is empty, there is nothing to filter.
if ( empty ( $exclude )) {
return $query ;
}
elseif ( ! $parent ) {
$exclude = array_flip ( $exclude );
}
2006-04-13 08:25:27 +00:00
2009-09-29 15:31:17 +00:00
$params = array ();
2006-04-13 08:25:27 +00:00
foreach ( $query as $key => $value ) {
2009-09-29 15:31:17 +00:00
$string_key = ( $parent ? $parent . '[' . $key . ']' : $key );
if ( isset ( $exclude [ $string_key ])) {
continue ;
2006-04-13 08:25:27 +00:00
}
2009-09-29 15:31:17 +00:00
if ( is_array ( $value )) {
$params [ $key ] = drupal_get_query_parameters ( $value , $exclude , $string_key );
}
else {
$params [ $key ] = $value ;
2006-04-13 08:25:27 +00:00
}
2009-09-29 15:31:17 +00:00
}
return $params ;
}
2009-10-09 16:33:14 +00:00
/**
* Split an URL - encoded query string into an array .
*
* @ param $query
* The query string to split .
*
* @ return
* An array of url decoded couples $param_name => $value .
*/
function drupal_get_query_array ( $query ) {
$result = array ();
if ( ! empty ( $query )) {
foreach ( explode ( '&' , $query ) as $param ) {
$param = explode ( '=' , $param );
$result [ $param [ 0 ]] = isset ( $param [ 1 ]) ? rawurldecode ( $param [ 1 ]) : '' ;
}
}
return $result ;
}
2009-09-29 15:31:17 +00:00
/**
* Parse an array into a valid , rawurlencoded query string .
*
* This differs from http_build_query () as we need to rawurlencode () ( instead of
* urlencode ()) all query parameters .
*
* @ param $query
* The query parameter array to be processed , e . g . $_GET .
* @ param $parent
* Internal use only . Used to build the $query array key for nested items .
*
* @ return
* A rawurlencoded string which can be used as or appended to the URL query
* string .
*
* @ see drupal_get_query_parameters ()
* @ ingroup php_wrappers
*/
function drupal_http_build_query ( array $query , $parent = '' ) {
$params = array ();
foreach ( $query as $key => $value ) {
$key = ( $parent ? $parent . '[' . rawurlencode ( $key ) . ']' : rawurlencode ( $key ));
2006-04-13 08:25:27 +00:00
2009-09-29 15:31:17 +00:00
// Recurse into children.
2006-04-13 08:25:27 +00:00
if ( is_array ( $value )) {
2009-09-29 15:31:17 +00:00
$params [] = drupal_http_build_query ( $value , $key );
}
// If a query parameter value is NULL, only append its key.
elseif ( ! isset ( $value )) {
$params [] = $key ;
2006-04-13 08:25:27 +00:00
}
else {
2009-09-29 15:31:17 +00:00
// For better readability of paths in query strings, we decode slashes.
$params [] = $key . '=' . str_replace ( '%2F' , '/' , rawurlencode ( $value ));
2006-04-13 08:25:27 +00:00
}
}
return implode ( '&' , $params );
}
2005-02-01 19:45:58 +00:00
/**
2009-09-29 15:31:17 +00:00
* Prepare a 'destination' URL query parameter for use in combination with drupal_goto () .
2007-10-12 14:10:18 +00:00
*
2007-10-08 14:08:19 +00:00
* Used to direct the user back to the referring page after completing a form .
* By default the current URL is returned . If a destination exists in the
* previous request , that destination is returned . As such , a destination can
* persist across multiple pages .
2005-02-01 19:45:58 +00:00
*
* @ see drupal_goto ()
*/
function drupal_get_destination () {
2009-09-29 15:31:17 +00:00
$destination = & drupal_static ( __FUNCTION__ );
if ( isset ( $destination )) {
return $destination ;
}
2009-09-21 06:44:14 +00:00
if ( isset ( $_GET [ 'destination' ])) {
2009-09-29 15:31:17 +00:00
$destination = array ( 'destination' => $_GET [ 'destination' ]);
2005-07-20 10:48:20 +00:00
}
else {
2009-09-29 15:31:17 +00:00
$path = $_GET [ 'q' ];
$query = drupal_http_build_query ( drupal_get_query_parameters ());
2006-04-13 08:25:27 +00:00
if ( $query != '' ) {
2008-04-14 17:48:46 +00:00
$path .= '?' . $query ;
2006-02-27 14:06:09 +00:00
}
2009-09-29 15:31:17 +00:00
$destination = array ( 'destination' => $path );
}
return $destination ;
}
/**
2009-10-11 02:14:43 +00:00
* Wrapper around parse_url () to parse a system URL string into an associative array , suitable for url () .
*
* This function should only be used for URLs that have been generated by the
* system , resp . url () . It should not be used for URLs that come from external
* sources , or URLs that link to external resources .
2009-09-29 15:31:17 +00:00
*
* The returned array contains a 'path' that may be passed separately to url () .
* For example :
* @ code
* $options = drupal_parse_url ( $_GET [ 'destination' ]);
* $my_url = url ( $options [ 'path' ], $options );
* $my_link = l ( 'Example link' , $options [ 'path' ], $options );
* @ endcode
*
* This is required , because url () does not support relative URLs containing a
* query string or fragment in its $path argument . Instead , any query string
* needs to be parsed into an associative query parameter array in
* $options [ 'query' ] and the fragment into $options [ 'fragment' ] .
*
* @ param $url
* The URL string to parse , f . e . $_GET [ 'destination' ] .
*
* @ return
* An associative array containing the keys :
* - 'path' : The path of the URL . If the given $url is external , this includes
* the scheme and host .
* - 'query' : An array of query parameters of $url , if existent .
* - 'fragment' : The fragment of $url , if existent .
*
* @ see url ()
* @ see drupal_goto ()
* @ ingroup php_wrappers
*/
function drupal_parse_url ( $url ) {
$options = array (
'path' => NULL ,
'query' => array (),
'fragment' => '' ,
);
// External URLs: not using parse_url() here, so we do not have to rebuild
// the scheme, host, and path without having any use for it.
if ( strpos ( $url , '://' ) !== FALSE ) {
// Split off everything before the query string into 'path'.
$parts = explode ( '?' , $url );
$options [ 'path' ] = $parts [ 0 ];
// If there is a query string, transform it into keyed query parameters.
if ( isset ( $parts [ 1 ])) {
$query_parts = explode ( '#' , $parts [ 1 ]);
parse_str ( $query_parts [ 0 ], $options [ 'query' ]);
// Take over the fragment, if there is any.
if ( isset ( $query_parts [ 1 ])) {
$options [ 'fragment' ] = $query_parts [ 1 ];
}
}
}
// Internal URLs.
else {
2009-10-01 19:04:26 +00:00
// parse_url() does not support relative URLs, so make it absolute. E.g. the
// relative URL "foo/bar:1" isn't properly parsed.
$parts = parse_url ( 'http://example.com/' . $url );
// Strip the leading slash that was just added.
$options [ 'path' ] = substr ( $parts [ 'path' ], 1 );
2009-09-29 15:31:17 +00:00
if ( isset ( $parts [ 'query' ])) {
parse_str ( $parts [ 'query' ], $options [ 'query' ]);
}
if ( isset ( $parts [ 'fragment' ])) {
$options [ 'fragment' ] = $parts [ 'fragment' ];
}
}
2009-10-11 02:14:43 +00:00
// The 'q' parameter contains the path of the current page if clean URLs are
// disabled. It overrides the 'path' of the URL when present, even if clean
// URLs are enabled, due to how Apache rewriting rules work.
if ( isset ( $options [ 'query' ][ 'q' ])) {
$options [ 'path' ] = $options [ 'query' ][ 'q' ];
unset ( $options [ 'query' ][ 'q' ]);
}
2009-09-29 15:31:17 +00:00
return $options ;
}
/**
2010-01-29 22:40:41 +00:00
* Encodes a Drupal path for use in a URL .
2009-09-29 15:31:17 +00:00
*
2010-01-29 22:40:41 +00:00
* For aesthetic reasons slashes are not escaped .
2009-09-29 15:31:17 +00:00
*
2010-01-29 22:40:41 +00:00
* Note that url () takes care of calling this function , so a path passed to that
* function should not be encoded in advance .
2009-09-29 15:31:17 +00:00
*
* @ param $path
2010-01-29 22:40:41 +00:00
* The Drupal path to encode .
2009-09-29 15:31:17 +00:00
*/
function drupal_encode_path ( $path ) {
2010-01-29 22:40:41 +00:00
return str_replace ( '%2F' , '/' , rawurlencode ( $path ));
2005-02-01 19:45:58 +00:00
}
2004-01-06 19:52:14 +00:00
/**
2004-07-11 07:31:11 +00:00
* Send the user to a different Drupal page .
2004-01-06 19:52:14 +00:00
*
2004-07-11 07:31:11 +00:00
* This issues an on - site HTTP redirect . The function makes sure the redirected
* URL is formatted correctly .
2004-01-06 19:52:14 +00:00
*
2011-03-21 00:32:21 +00:00
* If a destination was specified in the current request ' s URI ( i . e . ,
* $_GET [ 'destination' ]) then it will override the $path and $options values
* passed to this function . This provides the flexibility to build a link to
* user / login and override the default redirection so that the user is
* redirected to a specific path after logging in :
* @ code
2011-06-01 08:47:29 +00:00
* $query = array ( 'destination' => " node/ $node->nid " );
* $link = l ( t ( 'Log in' ), 'user/login' , array ( 'query' => $query ));
2011-03-21 00:32:21 +00:00
* @ endcode
2005-02-01 19:45:58 +00:00
*
2007-10-17 21:47:14 +00:00
* Drupal will ensure that messages set by drupal_set_message () and other
* session data are written to the database before the user is redirected .
2004-07-11 07:31:11 +00:00
*
2009-10-15 16:18:46 +00:00
* This function ends the request ; use it instead of a return in your menu
* callback .
2004-07-11 07:31:11 +00:00
*
* @ param $path
2006-08-26 00:23:12 +00:00
* A Drupal path or a full URL .
2009-10-15 16:18:46 +00:00
* @ param $options
* An associative array of additional URL options to pass to url () .
2006-11-30 08:13:31 +00:00
* @ param $http_response_code
* Valid values for an actual " goto " as per RFC 2616 section 10.3 are :
* - 301 Moved Permanently ( the recommended value for most redirects )
* - 302 Found ( default in Drupal and PHP , sometimes used for spamming search
* engines )
* - 303 See Other
* - 304 Not Modified
* - 305 Use Proxy
2007-10-08 14:08:19 +00:00
* - 307 Temporary Redirect ( alternative to " 503 Site Down for Maintenance " )
2006-11-30 08:13:31 +00:00
* Note : Other values are defined by RFC 2616 , but are rarely used and poorly
2007-10-08 14:08:19 +00:00
* supported .
2009-10-15 16:18:46 +00:00
*
2005-02-01 19:45:58 +00:00
* @ see drupal_get_destination ()
2009-10-15 16:18:46 +00:00
* @ see url ()
2004-01-06 19:52:14 +00:00
*/
2009-10-15 16:18:46 +00:00
function drupal_goto ( $path = '' , array $options = array (), $http_response_code = 302 ) {
// A destination in $_GET always overrides the function arguments.
2010-06-21 14:59:14 +00:00
// We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
if ( isset ( $_GET [ 'destination' ]) && ! url_is_external ( $_GET [ 'destination' ])) {
2010-05-18 06:59:46 +00:00
$destination = drupal_parse_url ( $_GET [ 'destination' ]);
2009-10-15 16:18:46 +00:00
$path = $destination [ 'path' ];
$options [ 'query' ] = $destination [ 'query' ];
$options [ 'fragment' ] = $destination [ 'fragment' ];
2005-02-01 19:45:58 +00:00
}
2009-10-15 16:18:46 +00:00
drupal_alter ( 'drupal_goto' , $path , $options , $http_response_code );
// The 'Location' HTTP header must be absolute.
$options [ 'absolute' ] = TRUE ;
2009-09-05 13:05:31 +00:00
2009-10-15 16:18:46 +00:00
$url = url ( $path , $options );
2004-01-06 19:52:14 +00:00
2008-04-14 17:48:46 +00:00
header ( 'Location: ' . $url , TRUE , $http_response_code );
2007-10-06 15:30:41 +00:00
// The "Location" header sends a redirect status code to the HTTP daemon. In
2007-10-08 14:08:19 +00:00
// some cases this can be wrong, so we make sure none of the code below the
// drupal_goto() call gets executed upon redirection.
2009-10-13 21:16:44 +00:00
drupal_exit ( $url );
2004-01-06 19:52:14 +00:00
}
2005-10-08 12:38:20 +00:00
/**
2009-10-15 14:07:30 +00:00
* Deliver a " site is under maintenance " message to the browser .
*
* Page callback functions wanting to report a " site offline " message should
* return MENU_SITE_OFFLINE instead of calling drupal_site_offline () . However ,
* functions that are invoked in contexts where that return value might not
* bubble up to menu_execute_active_handler () should call drupal_site_offline () .
2005-10-08 12:38:20 +00:00
*/
function drupal_site_offline () {
2009-10-15 14:07:30 +00:00
drupal_deliver_page ( MENU_SITE_OFFLINE );
2005-10-08 12:38:20 +00:00
}
2004-01-06 19:52:14 +00:00
/**
2009-10-15 14:07:30 +00:00
* Deliver a " page not found " error to the browser .
*
* Page callback functions wanting to report a " page not found " message should
* return MENU_NOT_FOUND instead of calling drupal_not_found () . However ,
* functions that are invoked in contexts where that return value might not
* bubble up to menu_execute_active_handler () should call drupal_not_found () .
2004-01-06 19:52:14 +00:00
*/
2003-12-16 21:06:34 +00:00
function drupal_not_found () {
2009-10-15 14:07:30 +00:00
drupal_deliver_page ( MENU_NOT_FOUND );
2003-12-16 21:06:34 +00:00
}
2004-01-07 19:52:10 +00:00
2004-04-21 13:56:38 +00:00
/**
2009-10-15 14:07:30 +00:00
* Deliver a " access denied " error to the browser .
*
* Page callback functions wanting to report an " access denied " message should
* return MENU_ACCESS_DENIED instead of calling drupal_access_denied () . However ,
* functions that are invoked in contexts where that return value might not
* bubble up to menu_execute_active_handler () should call drupal_access_denied () .
2004-04-21 13:56:38 +00:00
*/
function drupal_access_denied () {
2009-10-15 14:07:30 +00:00
drupal_deliver_page ( MENU_ACCESS_DENIED );
2004-04-21 13:56:38 +00:00
}
2004-01-07 19:52:10 +00:00
/**
2004-07-13 07:21:14 +00:00
* Perform an HTTP request .
2004-01-07 19:52:10 +00:00
*
2008-12-26 21:01:57 +00:00
* This is a flexible and powerful HTTP client implementation . Correctly
* handles GET , POST , PUT or any other HTTP requests . Handles redirects .
2004-07-13 07:21:14 +00:00
*
* @ param $url
* A string containing a fully qualified URI .
2010-10-26 15:17:50 +00:00
* @ param array $options
* ( optional ) An array that can have one or more of the following elements :
* - headers : An array containing request headers to send as name / value pairs .
* - method : A string containing the request method . Defaults to 'GET' .
* - data : A string containing the request body , formatted as
* 'param=value¶m=value&...' . Defaults to NULL .
* - max_redirects : An integer representing how many times a redirect
* may be followed . Defaults to 3.
* - timeout : A float representing the maximum number of seconds the function
* call may take . The default is 30 seconds . If a timeout occurs , the error
* code is set to the HTTP_REQUEST_TIMEOUT constant .
* - context : A context resource created with stream_context_create () .
2010-11-20 08:27:52 +00:00
*
2010-10-26 15:17:50 +00:00
* @ return object
* An object that can have one or more of the following components :
* - request : A string containing the request body that was sent .
* - code : An integer containing the response status code , or the error code
* if an error occurred .
* - protocol : The response protocol ( e . g . HTTP / 1.1 or HTTP / 1.0 ) .
* - status_message : The status message from the response , if a response was
* received .
* - redirect_code : If redirected , an integer containing the initial response
* status code .
2011-06-22 06:09:30 +00:00
* - redirect_url : If redirected , a string containing the URL of the redirect
* target .
2010-10-26 15:17:50 +00:00
* - error : If an error occurred , the error message . Otherwise not set .
* - headers : An array containing the response headers as name / value pairs .
* HTTP header names are case - insensitive ( RFC 2616 , section 4.2 ), so for
* easy access the array keys are returned in lower case .
* - data : A string containing the response body that was received .
2004-01-07 19:52:10 +00:00
*/
2008-12-26 21:01:57 +00:00
function drupal_http_request ( $url , array $options = array ()) {
2007-01-05 05:32:23 +00:00
$result = new stdClass ();
2005-01-22 11:15:24 +00:00
2009-12-15 05:22:05 +00:00
// Parse the URL and make sure we can handle the schema.
$uri = @ parse_url ( $url );
if ( $uri == FALSE ) {
2008-08-13 07:11:18 +00:00
$result -> error = 'unable to parse URL' ;
2009-09-16 15:28:00 +00:00
$result -> code = - 1001 ;
2008-08-12 08:36:38 +00:00
return $result ;
}
2009-12-15 05:22:05 +00:00
if ( ! isset ( $uri [ 'scheme' ])) {
$result -> error = 'missing schema' ;
$result -> code = - 1002 ;
return $result ;
}
2008-08-12 08:36:38 +00:00
2009-06-06 15:43:05 +00:00
timer_start ( __FUNCTION__ );
// Merge the default options.
$options += array (
'headers' => array (),
'method' => 'GET' ,
'data' => NULL ,
'max_redirects' => 3 ,
2010-08-17 16:03:30 +00:00
'timeout' => 30.0 ,
'context' => NULL ,
2009-06-06 15:43:05 +00:00
);
2010-08-17 16:03:30 +00:00
// stream_socket_client() requires timeout to be a float.
$options [ 'timeout' ] = ( float ) $options [ 'timeout' ];
2009-06-06 15:43:05 +00:00
2004-01-07 19:52:10 +00:00
switch ( $uri [ 'scheme' ]) {
case 'http' :
2010-04-06 06:43:19 +00:00
case 'feed' :
2005-12-11 12:31:17 +00:00
$port = isset ( $uri [ 'port' ]) ? $uri [ 'port' ] : 80 ;
2010-08-17 16:03:30 +00:00
$socket = 'tcp://' . $uri [ 'host' ] . ':' . $port ;
// RFC 2616: "non-standard ports MUST, default ports MAY be included".
// We don't add the standard port to prevent from breaking rewrite rules
// checking the host that do not take into account the port number.
$options [ 'headers' ][ 'Host' ] = $uri [ 'host' ] . ( $port != 80 ? ':' . $port : '' );
2004-01-07 19:52:10 +00:00
break ;
case 'https' :
2008-11-07 17:21:54 +00:00
// Note: Only works when PHP is compiled with OpenSSL support.
2005-12-11 12:31:17 +00:00
$port = isset ( $uri [ 'port' ]) ? $uri [ 'port' ] : 443 ;
2010-08-17 16:03:30 +00:00
$socket = 'ssl://' . $uri [ 'host' ] . ':' . $port ;
$options [ 'headers' ][ 'Host' ] = $uri [ 'host' ] . ( $port != 443 ? ':' . $port : '' );
2004-01-07 19:52:10 +00:00
break ;
default :
2008-04-14 17:48:46 +00:00
$result -> error = 'invalid schema ' . $uri [ 'scheme' ];
2009-09-16 15:28:00 +00:00
$result -> code = - 1003 ;
2004-01-07 19:52:10 +00:00
return $result ;
}
2010-08-17 16:03:30 +00:00
if ( empty ( $options [ 'context' ])) {
$fp = @ stream_socket_client ( $socket , $errno , $errstr , $options [ 'timeout' ]);
}
else {
// Create a stream with context. Allows verification of a SSL certificate.
$fp = @ stream_socket_client ( $socket , $errno , $errstr , $options [ 'timeout' ], STREAM_CLIENT_CONNECT , $options [ 'context' ]);
}
2004-07-13 07:21:14 +00:00
// Make sure the socket opened properly.
2004-01-07 19:52:10 +00:00
if ( ! $fp ) {
2007-12-20 08:57:55 +00:00
// When a network error occurs, we use a negative number so it does not
// clash with the HTTP status codes.
2007-04-18 20:35:45 +00:00
$result -> code = - $errno ;
2010-08-17 16:03:30 +00:00
$result -> error = trim ( $errstr ) ? trim ( $errstr ) : t ( 'Error opening socket @socket' , array ( '@socket' => $socket ));
2009-01-14 21:13:42 +00:00
// Mark that this request failed. This will trigger a check of the web
// server's ability to make outgoing HTTP requests the next time that
// requirements checking is performed.
2011-11-01 05:04:16 +00:00
// See system_requirements().
2009-01-14 21:13:42 +00:00
variable_set ( 'drupal_http_request_fails' , TRUE );
2004-01-07 19:52:10 +00:00
return $result ;
}
2004-07-13 07:21:14 +00:00
// Construct the path to act on.
2005-12-11 12:31:17 +00:00
$path = isset ( $uri [ 'path' ]) ? $uri [ 'path' ] : '/' ;
if ( isset ( $uri [ 'query' ])) {
2008-04-14 17:48:46 +00:00
$path .= '?' . $uri [ 'query' ];
2004-01-07 19:52:10 +00:00
}
2008-12-26 21:01:57 +00:00
// Merge the default headers.
$options [ 'headers' ] += array (
'User-Agent' => 'Drupal (+http://drupal.org/)' ,
2004-01-07 19:52:10 +00:00
);
2009-05-09 22:16:37 +00:00
// Only add Content-Length if we actually have any content or if it is a POST
// or PUT request. Some non-standard servers get confused by Content-Length in
// at least HEAD/GET requests, and Squid always requires Content-Length in
// POST/PUT requests.
2009-06-17 11:08:46 +00:00
$content_length = strlen ( $options [ 'data' ]);
if ( $content_length > 0 || $options [ 'method' ] == 'POST' || $options [ 'method' ] == 'PUT' ) {
$options [ 'headers' ][ 'Content-Length' ] = $content_length ;
2009-05-09 22:16:37 +00:00
}
// If the server URL has a user then attempt to use basic authentication.
2007-10-11 09:47:23 +00:00
if ( isset ( $uri [ 'user' ])) {
2011-09-25 08:25:31 +00:00
$options [ 'headers' ][ 'Authorization' ] = 'Basic ' . base64_encode ( $uri [ 'user' ] . ( isset ( $uri [ 'pass' ]) ? ':' . $uri [ 'pass' ] : '' ));
2007-10-11 09:47:23 +00:00
}
2008-06-24 22:09:52 +00:00
// If the database prefix is being used by SimpleTest to run the tests in a copied
// database then set the user-agent header to the database prefix so that any
// calls to other Drupal pages will run the SimpleTest prefixed database. The
// user-agent is used to ensure that multiple testing sessions running at the
// same time won't interfere with each other as they would if the database
// prefix were stored statically in a file or database variable.
2010-06-28 19:57:34 +00:00
$test_info = & $GLOBALS [ 'drupal_test_info' ];
if ( ! empty ( $test_info [ 'test_run_id' ])) {
$options [ 'headers' ][ 'User-Agent' ] = drupal_generate_test_ua ( $test_info [ 'test_run_id' ]);
2008-06-24 22:09:52 +00:00
}
2009-05-08 22:19:04 +00:00
$request = $options [ 'method' ] . ' ' . $path . " HTTP/1.0 \r \n " ;
2008-12-26 21:01:57 +00:00
foreach ( $options [ 'headers' ] as $name => $value ) {
2009-05-08 22:19:04 +00:00
$request .= $name . ': ' . trim ( $value ) . " \r \n " ;
2004-01-07 19:52:10 +00:00
}
2009-05-08 22:19:04 +00:00
$request .= " \r \n " . $options [ 'data' ];
2004-01-07 19:52:10 +00:00
$result -> request = $request ;
2010-08-17 16:03:30 +00:00
// Calculate how much time is left of the original timeout value.
$timeout = $options [ 'timeout' ] - timer_read ( __FUNCTION__ ) / 1000 ;
if ( $timeout > 0 ) {
stream_set_timeout ( $fp , floor ( $timeout ), floor ( 1000000 * fmod ( $timeout , 1 )));
fwrite ( $fp , $request );
}
2004-01-07 19:52:10 +00:00
2010-04-27 10:24:24 +00:00
// Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
// and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
// instead must invoke stream_get_meta_data() each iteration.
$info = stream_get_meta_data ( $fp );
$alive = ! $info [ 'eof' ] && ! $info [ 'timed_out' ];
2004-04-27 18:17:17 +00:00
$response = '' ;
2010-04-27 10:24:24 +00:00
while ( $alive ) {
2009-06-06 15:43:05 +00:00
// Calculate how much time is left of the original timeout value.
$timeout = $options [ 'timeout' ] - timer_read ( __FUNCTION__ ) / 1000 ;
if ( $timeout <= 0 ) {
2010-04-27 10:24:24 +00:00
$info [ 'timed_out' ] = TRUE ;
break ;
2009-06-06 15:43:05 +00:00
}
stream_set_timeout ( $fp , floor ( $timeout ), floor ( 1000000 * fmod ( $timeout , 1 )));
2010-04-27 10:24:24 +00:00
$chunk = fread ( $fp , 1024 );
$response .= $chunk ;
$info = stream_get_meta_data ( $fp );
$alive = ! $info [ 'eof' ] && ! $info [ 'timed_out' ] && $chunk ;
2004-01-07 19:52:10 +00:00
}
fclose ( $fp );
2010-04-27 10:24:24 +00:00
if ( $info [ 'timed_out' ]) {
$result -> code = HTTP_REQUEST_TIMEOUT ;
$result -> error = 'request timed out' ;
return $result ;
}
2008-12-26 21:01:57 +00:00
// Parse response headers from the response body.
2011-04-23 20:34:13 +00:00
// Be tolerant of malformed HTTP responses that separate header and body with
// \n\n or \r\r instead of \r\n\r\n.
list ( $response , $result -> data ) = preg_split ( " / \r \n \r \n | \n \n | \r \r / " , $response , 2 );
2008-12-26 21:01:57 +00:00
$response = preg_split ( " / \r \n | \n | \r / " , $response );
2004-06-21 20:14:41 +00:00
2008-12-26 21:01:57 +00:00
// Parse the response status line.
2009-04-25 13:56:06 +00:00
list ( $protocol , $code , $status_message ) = explode ( ' ' , trim ( array_shift ( $response )), 3 );
$result -> protocol = $protocol ;
$result -> status_message = $status_message ;
2004-01-07 19:52:10 +00:00
$result -> headers = array ();
2008-12-26 21:01:57 +00:00
// Parse the response headers.
while ( $line = trim ( array_shift ( $response ))) {
2010-05-20 08:51:24 +00:00
list ( $name , $value ) = explode ( ':' , $line , 2 );
$name = strtolower ( $name );
if ( isset ( $result -> headers [ $name ]) && $name == 'set-cookie' ) {
2005-08-22 20:24:53 +00:00
// RFC 2109: the Set-Cookie response header comprises the token Set-
// Cookie:, followed by a comma-separated list of one or more cookies.
2010-05-20 08:51:24 +00:00
$result -> headers [ $name ] .= ',' . trim ( $value );
2005-08-22 20:24:53 +00:00
}
else {
2010-05-20 08:51:24 +00:00
$result -> headers [ $name ] = trim ( $value );
2005-08-22 20:24:53 +00:00
}
2004-01-07 19:52:10 +00:00
}
$responses = array (
2008-12-26 21:01:57 +00:00
100 => 'Continue' ,
101 => 'Switching Protocols' ,
200 => 'OK' ,
201 => 'Created' ,
202 => 'Accepted' ,
203 => 'Non-Authoritative Information' ,
204 => 'No Content' ,
205 => 'Reset Content' ,
206 => 'Partial Content' ,
300 => 'Multiple Choices' ,
301 => 'Moved Permanently' ,
302 => 'Found' ,
303 => 'See Other' ,
304 => 'Not Modified' ,
305 => 'Use Proxy' ,
307 => 'Temporary Redirect' ,
400 => 'Bad Request' ,
401 => 'Unauthorized' ,
402 => 'Payment Required' ,
403 => 'Forbidden' ,
404 => 'Not Found' ,
405 => 'Method Not Allowed' ,
406 => 'Not Acceptable' ,
407 => 'Proxy Authentication Required' ,
408 => 'Request Time-out' ,
409 => 'Conflict' ,
410 => 'Gone' ,
411 => 'Length Required' ,
412 => 'Precondition Failed' ,
413 => 'Request Entity Too Large' ,
414 => 'Request-URI Too Large' ,
415 => 'Unsupported Media Type' ,
416 => 'Requested range not satisfiable' ,
417 => 'Expectation Failed' ,
500 => 'Internal Server Error' ,
501 => 'Not Implemented' ,
502 => 'Bad Gateway' ,
503 => 'Service Unavailable' ,
504 => 'Gateway Time-out' ,
505 => 'HTTP Version not supported' ,
2004-01-07 19:52:10 +00:00
);
2007-10-08 14:08:19 +00:00
// RFC 2616 states that all unknown HTTP codes must be treated the same as the
// base code in their class.
2004-01-07 19:52:10 +00:00
if ( ! isset ( $responses [ $code ])) {
$code = floor ( $code / 100 ) * 100 ;
}
2008-12-26 21:01:57 +00:00
$result -> code = $code ;
2004-01-07 19:52:10 +00:00
switch ( $code ) {
case 200 : // OK
case 304 : // Not modified
break ;
case 301 : // Moved permanently
case 302 : // Moved temporarily
case 307 : // Moved temporarily
2010-05-20 08:51:24 +00:00
$location = $result -> headers [ 'location' ];
2009-06-06 15:43:05 +00:00
$options [ 'timeout' ] -= timer_read ( __FUNCTION__ ) / 1000 ;
if ( $options [ 'timeout' ] <= 0 ) {
$result -> code = HTTP_REQUEST_TIMEOUT ;
$result -> error = 'request timed out' ;
}
elseif ( $options [ 'max_redirects' ]) {
2008-12-26 21:01:57 +00:00
// Redirect to the new location.
$options [ 'max_redirects' ] -- ;
$result = drupal_http_request ( $location , $options );
2008-09-06 15:06:10 +00:00
$result -> redirect_code = $code ;
2004-01-07 19:52:10 +00:00
}
2011-06-22 06:09:30 +00:00
if ( ! isset ( $result -> redirect_url )) {
$result -> redirect_url = $location ;
}
2004-01-07 19:52:10 +00:00
break ;
default :
2009-04-25 13:56:06 +00:00
$result -> error = $status_message ;
2004-01-07 19:52:10 +00:00
}
return $result ;
}
2004-09-09 05:51:08 +00:00
/**
* @ } End of " HTTP handling " .
*/
2003-12-16 21:06:34 +00:00
2005-03-21 19:26:47 +00:00
function _fix_gpc_magic ( & $item ) {
2003-12-13 14:59:55 +00:00
if ( is_array ( $item )) {
2003-12-19 10:52:37 +00:00
array_walk ( $item , '_fix_gpc_magic' );
}
else {
2003-12-19 13:44:08 +00:00
$item = stripslashes ( $item );
2002-12-26 12:16:09 +00:00
}
}
2006-11-16 08:54:04 +00:00
/**
* Helper function to strip slashes from $_FILES skipping over the tmp_name keys
* since PHP generates single backslashes for file paths on Windows systems .
*
* tmp_name does not have backslashes added see
* http :// php . net / manual / en / features . file - upload . php #42280
*/
function _fix_gpc_magic_files ( & $item , $key ) {
if ( $key != 'tmp_name' ) {
if ( is_array ( $item )) {
array_walk ( $item , '_fix_gpc_magic_files' );
}
else {
$item = stripslashes ( $item );
}
}
}
2004-07-13 07:21:14 +00:00
/**
2007-10-08 14:08:19 +00:00
* Fix double - escaping problems caused by " magic quotes " in some PHP installations .
2004-07-13 07:21:14 +00:00
*/
2003-10-31 19:34:03 +00:00
function fix_gpc_magic () {
2011-02-19 13:19:26 +00:00
static $fixed = FALSE ;
2004-07-13 07:21:14 +00:00
if ( ! $fixed && ini_get ( 'magic_quotes_gpc' )) {
2003-12-13 14:59:55 +00:00
array_walk ( $_GET , '_fix_gpc_magic' );
array_walk ( $_POST , '_fix_gpc_magic' );
array_walk ( $_COOKIE , '_fix_gpc_magic' );
array_walk ( $_REQUEST , '_fix_gpc_magic' );
2006-11-16 08:54:04 +00:00
array_walk ( $_FILES , '_fix_gpc_magic_files' );
2003-12-13 14:59:55 +00:00
}
2011-02-19 13:19:26 +00:00
$fixed = TRUE ;
2003-10-31 19:34:03 +00:00
}
2004-01-06 19:52:14 +00:00
/**
2004-09-09 05:51:08 +00:00
* @ defgroup validation Input validation
2004-02-08 17:12:44 +00:00
* @ {
2004-09-09 05:51:08 +00:00
* Functions to validate user input .
2004-01-06 19:52:14 +00:00
*/
2003-03-28 10:55:27 +00:00
/**
2004-07-13 07:21:14 +00:00
* Verify the syntax of the given e - mail address .
*
* Empty e - mail addresses are allowed . See RFC 2822 for details .
2003-03-28 10:55:27 +00:00
*
2004-07-13 07:21:14 +00:00
* @ param $mail
2006-01-13 07:33:13 +00:00
* A string containing an e - mail address .
2004-01-07 19:52:10 +00:00
* @ return
2004-07-13 07:21:14 +00:00
* TRUE if the address is in a valid format .
2003-03-28 10:55:27 +00:00
*/
2003-04-13 13:42:51 +00:00
function valid_email_address ( $mail ) {
2008-09-16 17:50:02 +00:00
return ( bool ) filter_var ( $mail , FILTER_VALIDATE_EMAIL );
2003-03-28 10:55:27 +00:00
}
2003-07-16 20:14:26 +00:00
/**
* Verify the syntax of the given URL .
*
2006-11-21 19:40:09 +00:00
* This function should only be used on actual URLs . It should not be used for
* Drupal menu paths , which can contain arbitrary characters .
2009-01-08 19:09:49 +00:00
* Valid values per RFC 3986.
2004-04-12 08:27:57 +00:00
* @ param $url
2004-07-13 07:21:14 +00:00
* The URL to verify .
2004-04-12 08:27:57 +00:00
* @ param $absolute
2004-09-09 05:51:08 +00:00
* Whether the URL is absolute ( beginning with a scheme such as " http: " ) .
2004-04-12 08:27:57 +00:00
* @ return
2004-07-13 07:21:14 +00:00
* TRUE if the URL is in a valid format .
2003-07-16 20:14:26 +00:00
*/
2004-04-12 08:27:57 +00:00
function valid_url ( $url , $absolute = FALSE ) {
2004-03-21 10:28:10 +00:00
if ( $absolute ) {
2009-01-08 19:09:49 +00:00
return ( bool ) preg_match ( "
2009-01-11 08:39:08 +00:00
/^ # Start at the beginning of the text
2010-04-06 06:43:19 +00:00
( ? : ftp | https ? | feed ) : \ / \ / # Look for ftp, http, https or feed schemes
2009-01-11 08:39:08 +00:00
( ? : # Userinfo (optional) which is typically
( ? : ( ? : [ \w\ . \ - \ +! $ & ' \ ( \ ) * \ + ,; = ] |% [ 0 - 9 a - f ]{ 2 }) +: ) * # a username or a username and password
( ? : [ \w\ . \ - \ +%! $ & ' \ ( \ ) * \ + ,; = ] |% [ 0 - 9 a - f ]{ 2 }) +@ # combination
2009-01-08 19:09:49 +00:00
) ?
2009-01-11 08:39:08 +00:00
( ? :
( ? : [ a - z0 - 9 \ - \ . ] |% [ 0 - 9 a - f ]{ 2 }) + # A domain name or a IPv4 address
| ( ? : \ [( ? : [ 0 - 9 a - f ]{ 0 , 4 } : ) * ( ? : [ 0 - 9 a - f ]{ 0 , 4 }) \ ]) # or a well formed IPv6 address
)
( ? :: [ 0 - 9 ] + ) ? # Server port number (optional)
( ? : [ \ /| \ ? ]
2009-01-13 22:19:28 +00:00
( ? : [ \w #!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
2009-01-11 08:39:08 +00:00
* ) ?
2009-01-08 19:09:49 +00:00
$ / xi " , $url );
2004-03-21 10:28:10 +00:00
}
else {
2009-01-13 22:19:28 +00:00
return ( bool ) preg_match ( " /^(?:[ \ w#!: \ . \ ? \ +=&@ $ '~*,; \ / \ ( \ ) \ [ \ ] \ -]|%[0-9a-f] { 2})+ $ /i " , $url );
2004-03-21 10:28:10 +00:00
}
2003-07-16 20:14:26 +00:00
}
2008-01-28 16:05:17 +00:00
/**
* @ } End of " defgroup validation " .
*/
2004-11-15 21:17:25 +00:00
/**
2009-08-08 20:52:33 +00:00
* Register an event for the current visitor to the flood control mechanism .
2004-11-15 21:17:25 +00:00
*
* @ param $name
2007-10-08 14:08:19 +00:00
* The name of an event .
2009-10-18 11:34:45 +00:00
* @ param $window
* Optional number of seconds before this event expires . Defaults to 3600 ( 1
* hour ) . Typically uses the same value as the flood_is_allowed () $window
* parameter . Expired events are purged on cron run to prevent the flood table
* from growing indefinitely .
2009-08-08 20:52:33 +00:00
* @ param $identifier
* Optional identifier ( defaults to the current user ' s IP address ) .
2004-11-15 21:17:25 +00:00
*/
2009-10-18 11:34:45 +00:00
function flood_register_event ( $name , $window = 3600 , $identifier = NULL ) {
2009-08-08 20:52:33 +00:00
if ( ! isset ( $identifier )) {
$identifier = ip_address ();
}
2008-10-29 10:06:06 +00:00
db_insert ( 'flood' )
-> fields ( array (
'event' => $name ,
2009-08-08 20:52:33 +00:00
'identifier' => $identifier ,
2008-10-29 10:06:06 +00:00
'timestamp' => REQUEST_TIME ,
2009-10-18 11:34:45 +00:00
'expiration' => REQUEST_TIME + $window ,
2008-10-29 10:06:06 +00:00
))
-> execute ();
2004-11-15 21:17:25 +00:00
}
/**
2009-08-08 20:52:33 +00:00
* Make the flood control mechanism forget about an event for the current visitor .
*
* @ param $name
* The name of an event .
* @ param $identifier
* Optional identifier ( defaults to the current user ' s IP address ) .
*/
function flood_clear_event ( $name , $identifier = NULL ) {
if ( ! isset ( $identifier )) {
$identifier = ip_address ();
}
db_delete ( 'flood' )
-> condition ( 'event' , $name )
-> condition ( 'identifier' , $identifier )
-> execute ();
}
/**
2010-02-17 04:51:51 +00:00
* Checks whether user is allowed to proceed with the specified event .
2007-10-08 14:08:19 +00:00
*
2010-02-17 04:51:51 +00:00
* Events can have thresholds saying that each user can only do that event
* a certain number of times in a time window . This function verifies that the
* current user has not exceeded this threshold .
2004-11-15 21:17:25 +00:00
*
* @ param $name
2010-02-17 04:51:51 +00:00
* The unique name of the event .
2009-06-18 16:03:30 +00:00
* @ param $threshold
2010-02-17 04:51:51 +00:00
* The maximum number of times each user can do this event per time window .
2009-08-08 20:52:33 +00:00
* @ param $window
2010-02-17 04:51:51 +00:00
* Number of seconds in the time window for this event ( default is 3600
* seconds , or 1 hour ) .
2009-08-08 20:52:33 +00:00
* @ param $identifier
2010-02-17 04:51:51 +00:00
* Unique identifier of the current user . Defaults to their IP address .
*
2004-11-15 21:17:25 +00:00
* @ return
2010-02-17 04:51:51 +00:00
* TRUE if the user is allowed to proceed . FALSE if they have exceeded the
* threshold and should not be allowed to proceed .
2004-11-15 21:17:25 +00:00
*/
2009-08-08 20:52:33 +00:00
function flood_is_allowed ( $name , $threshold , $window = 3600 , $identifier = NULL ) {
if ( ! isset ( $identifier )) {
$identifier = ip_address ();
}
$number = db_query ( " SELECT COUNT(*) FROM { flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp " , array (
2008-10-29 10:06:06 +00:00
':event' => $name ,
2009-08-08 20:52:33 +00:00
':identifier' => $identifier ,
':timestamp' => REQUEST_TIME - $window ))
2008-10-29 10:06:06 +00:00
-> fetchField ();
return ( $number < $threshold );
2004-11-15 21:17:25 +00:00
}
2009-05-24 07:17:14 +00:00
/**
* @ defgroup sanitization Sanitization functions
* @ {
* Functions to sanitize values .
2010-04-24 14:53:59 +00:00
*
* See http :// drupal . org / writing - secure - code for information
* on writing secure code .
2009-05-24 07:17:14 +00:00
*/
2005-11-30 10:27:13 +00:00
/**
2010-07-07 17:00:43 +00:00
* Strips dangerous protocols ( e . g . 'javascript:' ) from a URI .
*
* This function must be called for all URIs within user - entered input prior
* to being output to an HTML attribute value . It is often called as part of
* check_url () or filter_xss (), but those functions return an HTML - encoded
* string , so this function can be called independently when the output needs to
* be a plain - text string for passing to t (), l (), drupal_attributes (), or
* another function that will call check_plain () separately .
*
* @ param $uri
* A plain - text URI that might contain dangerous protocols .
*
* @ return
* A plain - text URI stripped of dangerous protocols . As with all plain - text
* strings , this return value must not be output to an HTML page without
* check_plain () being called on it . However , it can be passed to functions
* expecting plain - text strings .
*
* @ see check_url ()
*/
function drupal_strip_dangerous_protocols ( $uri ) {
static $allowed_protocols ;
if ( ! isset ( $allowed_protocols )) {
2010-10-21 11:58:19 +00:00
$allowed_protocols = array_flip ( variable_get ( 'filter_allowed_protocols' , array ( 'ftp' , 'http' , 'https' , 'irc' , 'mailto' , 'news' , 'nntp' , 'rtsp' , 'sftp' , 'ssh' , 'tel' , 'telnet' , 'webcal' )));
2010-07-07 17:00:43 +00:00
}
// Iteratively remove any invalid protocol found.
do {
$before = $uri ;
$colonpos = strpos ( $uri , ':' );
if ( $colonpos > 0 ) {
// We found a colon, possibly a protocol. Verify.
$protocol = substr ( $uri , 0 , $colonpos );
// If a colon is preceded by a slash, question mark or hash, it cannot
// possibly be part of the URL scheme. This must be a relative URL, which
// inherits the (safe) protocol of the base document.
if ( preg_match ( '![/?#]!' , $protocol )) {
break ;
}
// Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
// (URI Comparison) scheme comparison must be case-insensitive.
if ( ! isset ( $allowed_protocols [ strtolower ( $protocol )])) {
$uri = substr ( $uri , $colonpos + 1 );
}
}
} while ( $before != $uri );
return $uri ;
}
/**
* Strips dangerous protocols ( e . g . 'javascript:' ) from a URI and encodes it for output to an HTML attribute value .
*
* @ param $uri
* A plain - text URI that might contain dangerous protocols .
*
* @ return
* A URI stripped of dangerous protocols and encoded for output to an HTML
* attribute value . Because it is already encoded , it should not be set as a
* value within a $attributes array passed to drupal_attributes (), because
* drupal_attributes () expects those values to be plain - text strings . To pass
* a filtered URI to drupal_attributes (), call
* drupal_strip_dangerous_protocols () instead .
*
* @ see drupal_strip_dangerous_protocols ()
2005-11-30 10:27:13 +00:00
*/
function check_url ( $uri ) {
2010-07-07 17:00:43 +00:00
return check_plain ( drupal_strip_dangerous_protocols ( $uri ));
2005-11-30 10:27:13 +00:00
}
2009-05-24 07:17:14 +00:00
/**
* Very permissive XSS / HTML filter for admin - only use .
*
* Use only for fields where it is impractical to use the
* whole filter system , but where some ( mainly inline ) mark - up
* is desired ( so check_plain () is not acceptable ) .
*
* Allows all tags that can be used inside an HTML body , save
* for scripts and styles .
*/
function filter_xss_admin ( $string ) {
2011-08-12 16:13:28 +00:00
return filter_xss ( $string , array ( 'a' , 'abbr' , 'acronym' , 'address' , 'article' , 'aside' , 'b' , 'bdi' , 'bdo' , 'big' , 'blockquote' , 'br' , 'caption' , 'cite' , 'code' , 'col' , 'colgroup' , 'command' , 'dd' , 'del' , 'details' , 'dfn' , 'div' , 'dl' , 'dt' , 'em' , 'figcaption' , 'figure' , 'footer' , 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' , 'header' , 'hgroup' , 'hr' , 'i' , 'img' , 'ins' , 'kbd' , 'li' , 'mark' , 'menu' , 'meter' , 'nav' , 'ol' , 'output' , 'p' , 'pre' , 'progress' , 'q' , 'rp' , 'rt' , 'ruby' , 's' , 'samp' , 'section' , 'small' , 'span' , 'strong' , 'sub' , 'summary' , 'sup' , 'table' , 'tbody' , 'td' , 'tfoot' , 'th' , 'thead' , 'time' , 'tr' , 'tt' , 'u' , 'ul' , 'var' , 'wbr' ));
2009-05-24 07:17:14 +00:00
}
/**
2010-06-24 18:51:33 +00:00
* Filters an HTML string to prevent cross - site - scripting ( XSS ) vulnerabilities .
2009-05-24 07:17:14 +00:00
*
2010-06-24 18:51:33 +00:00
* Based on kses by Ulf Harnhammar , see http :// sourceforge . net / projects / kses .
* For examples of various XSS attacks , see : http :// ha . ckers . org / xss . html .
2009-05-24 07:17:14 +00:00
*
* This code does four things :
2010-06-24 18:51:33 +00:00
* - Removes characters and constructs that can trick browsers .
* - Makes sure all HTML entities are well - formed .
* - Makes sure all HTML tags and attributes are well - formed .
* - Makes sure no HTML tags contain URLs with a disallowed protocol ( e . g .
* javascript : ) .
2009-05-24 07:17:14 +00:00
*
* @ param $string
2010-06-24 18:51:33 +00:00
* The string with raw HTML in it . It will be stripped of everything that can
* cause an XSS attack .
2009-05-24 07:17:14 +00:00
* @ param $allowed_tags
* An array of allowed tags .
2010-06-24 18:51:33 +00:00
*
* @ return
* An XSS safe version of $string , or an empty string if $string is not
* valid UTF - 8.
*
* @ see drupal_validate_utf8 ()
* @ ingroup sanitization
2009-05-24 07:17:14 +00:00
*/
function filter_xss ( $string , $allowed_tags = array ( 'a' , 'em' , 'strong' , 'cite' , 'blockquote' , 'code' , 'ul' , 'ol' , 'li' , 'dl' , 'dt' , 'dd' )) {
// Only operate on valid UTF-8 strings. This is necessary to prevent cross
// site scripting issues on Internet Explorer 6.
if ( ! drupal_validate_utf8 ( $string )) {
return '' ;
}
2011-11-01 05:04:16 +00:00
// Store the text format.
2009-05-24 07:17:14 +00:00
_filter_xss_split ( $allowed_tags , TRUE );
2011-11-01 05:04:16 +00:00
// Remove NULL characters (ignored by some browsers).
2009-05-24 07:17:14 +00:00
$string = str_replace ( chr ( 0 ), '' , $string );
2011-11-01 05:04:16 +00:00
// Remove Netscape 4 JS entities.
2009-05-24 07:17:14 +00:00
$string = preg_replace ( '%&\s*\{[^}]*(\}\s*;?|$)%' , '' , $string );
2011-11-01 05:04:16 +00:00
// Defuse all HTML entities.
2009-05-24 07:17:14 +00:00
$string = str_replace ( '&' , '&' , $string );
2011-11-01 05:04:16 +00:00
// Change back only well-formed entities in our whitelist:
// Decimal numeric entities.
2009-05-24 07:17:14 +00:00
$string = preg_replace ( '/&#([0-9]+;)/' , '&#\1' , $string );
2011-11-01 05:04:16 +00:00
// Hexadecimal numeric entities.
2009-05-24 07:17:14 +00:00
$string = preg_replace ( '/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/' , '&#x\1' , $string );
2011-11-01 05:04:16 +00:00
// Named entities.
2009-07-03 18:26:35 +00:00
$string = preg_replace ( '/&([A-Za-z][A-Za-z0-9]*;)/' , '&\1' , $string );
2009-05-24 07:17:14 +00:00
return preg_replace_callback ( ' %
(
< ( ? = [ ^ a - zA - Z !/ ]) # a lone <
| # or
2010-09-04 17:55:43 +00:00
<!--.* ? --> # a comment
| # or
2009-05-24 07:17:14 +00:00
< [ ^> ] * ( >| $ ) # a string that starts with a <, up until the > or the end of the string
| # or
> # just a >
) % x ', ' _filter_xss_split ' , $string );
}
/**
* Processes an HTML tag .
*
* @ param $m
* An array with various meaning depending on the value of $store .
* If $store is TRUE then the array contains the allowed tags .
* If $store is FALSE then the array has one element , the HTML tag to process .
* @ param $store
* Whether to store $m .
* @ return
* If the element isn ' t allowed , an empty string . Otherwise , the cleaned up
* version of the HTML element .
*/
function _filter_xss_split ( $m , $store = FALSE ) {
static $allowed_html ;
if ( $store ) {
$allowed_html = array_flip ( $m );
return ;
}
$string = $m [ 1 ];
if ( substr ( $string , 0 , 1 ) != '<' ) {
2011-11-01 05:04:16 +00:00
// We matched a lone ">" character.
2009-05-24 07:17:14 +00:00
return '>' ;
}
elseif ( strlen ( $string ) == 1 ) {
2011-11-01 05:04:16 +00:00
// We matched a lone "<" character.
2009-05-24 07:17:14 +00:00
return '<' ;
}
2010-09-04 17:55:43 +00:00
if ( ! preg_match ( '%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%' , $string , $matches )) {
2011-11-01 05:04:16 +00:00
// Seriously malformed.
2009-05-24 07:17:14 +00:00
return '' ;
}
$slash = trim ( $matches [ 1 ]);
$elem = & $matches [ 2 ];
$attrlist = & $matches [ 3 ];
2010-09-04 17:55:43 +00:00
$comment = & $matches [ 4 ];
if ( $comment ) {
$elem = '!--' ;
}
2009-05-24 07:17:14 +00:00
if ( ! isset ( $allowed_html [ strtolower ( $elem )])) {
2011-11-01 05:04:16 +00:00
// Disallowed HTML element.
2009-05-24 07:17:14 +00:00
return '' ;
}
2010-09-04 17:55:43 +00:00
if ( $comment ) {
return $comment ;
}
2009-05-24 07:17:14 +00:00
if ( $slash != '' ) {
return " </ $elem > " ;
}
// Is there a closing XHTML slash at the end of the attributes?
$attrlist = preg_replace ( '%(\s?)/\s*$%' , '\1' , $attrlist , - 1 , $count );
$xhtml_slash = $count ? ' /' : '' ;
2011-11-01 05:04:16 +00:00
// Clean up attributes.
2009-05-24 07:17:14 +00:00
$attr2 = implode ( ' ' , _filter_xss_attributes ( $attrlist ));
$attr2 = preg_replace ( '/[<>]/' , '' , $attr2 );
$attr2 = strlen ( $attr2 ) ? ' ' . $attr2 : '' ;
return " < $elem $attr2 $xhtml_slash > " ;
}
/**
* Processes a string of HTML attributes .
*
* @ return
* Cleaned up version of the HTML attributes .
*/
function _filter_xss_attributes ( $attr ) {
$attrarr = array ();
$mode = 0 ;
$attrname = '' ;
while ( strlen ( $attr ) != 0 ) {
// Was the last operation successful?
$working = 0 ;
switch ( $mode ) {
case 0 :
2011-11-01 05:04:16 +00:00
// Attribute name, href for instance.
2009-05-24 07:17:14 +00:00
if ( preg_match ( '/^([-a-zA-Z]+)/' , $attr , $match )) {
$attrname = strtolower ( $match [ 1 ]);
$skip = ( $attrname == 'style' || substr ( $attrname , 0 , 2 ) == 'on' );
$working = $mode = 1 ;
$attr = preg_replace ( '/^[-a-zA-Z]+/' , '' , $attr );
}
break ;
case 1 :
2011-11-01 05:04:16 +00:00
// Equals sign or valueless ("selected").
2009-05-24 07:17:14 +00:00
if ( preg_match ( '/^\s*=\s*/' , $attr )) {
$working = 1 ; $mode = 2 ;
$attr = preg_replace ( '/^\s*=\s*/' , '' , $attr );
break ;
}
if ( preg_match ( '/^\s+/' , $attr )) {
$working = 1 ; $mode = 0 ;
if ( ! $skip ) {
$attrarr [] = $attrname ;
}
$attr = preg_replace ( '/^\s+/' , '' , $attr );
}
break ;
case 2 :
2011-11-01 05:04:16 +00:00
// Attribute value, a URL after href= for instance.
2009-05-24 07:17:14 +00:00
if ( preg_match ( '/^"([^"]*)"(\s+|$)/' , $attr , $match )) {
$thisval = filter_xss_bad_protocol ( $match [ 1 ]);
if ( ! $skip ) {
$attrarr [] = " $attrname = \" $thisval\ " " ;
}
$working = 1 ;
$mode = 0 ;
$attr = preg_replace ( '/^"[^"]*"(\s+|$)/' , '' , $attr );
break ;
}
if ( preg_match ( " /^'([^']*)'( \ s+| $ )/ " , $attr , $match )) {
$thisval = filter_xss_bad_protocol ( $match [ 1 ]);
if ( ! $skip ) {
$attrarr [] = " $attrname =' $thisval ' " ;
}
$working = 1 ; $mode = 0 ;
$attr = preg_replace ( " /^'[^']*'( \ s+| $ )/ " , '' , $attr );
break ;
}
if ( preg_match ( " %^([^ \ s \" ']+)( \ s+| $ )% " , $attr , $match )) {
$thisval = filter_xss_bad_protocol ( $match [ 1 ]);
if ( ! $skip ) {
$attrarr [] = " $attrname = \" $thisval\ " " ;
}
$working = 1 ; $mode = 0 ;
$attr = preg_replace ( " %^[^ \ s \" ']+( \ s+| $ )% " , '' , $attr );
}
break ;
}
if ( $working == 0 ) {
2011-11-01 05:04:16 +00:00
// Not well formed; remove and try again.
2009-05-24 07:17:14 +00:00
$attr = preg_replace ( ' /
^
(
" [^ " ] * ( " | $ ) # - a string that starts with a double quote, up until the next double quote or the end of the string
| # or
\ ' [ ^ \ ' ] * ( \ ' | $ ) | # - a string that starts with a quote, up until the next quote or the end of the string
| # or
\S # - a non-whitespace character
) * # any number of the above three
\s * # any number of whitespaces
/ x ', ' ' , $attr );
$mode = 0 ;
}
}
2009-05-26 09:12:29 +00:00
// The attribute list ends with a valueless attribute like "selected".
2010-07-01 19:41:18 +00:00
if ( $mode == 1 && ! $skip ) {
2009-05-24 07:17:14 +00:00
$attrarr [] = $attrname ;
}
return $attrarr ;
}
/**
* Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol ( e . g . javascript : ) .
*
* @ param $string
* The string with the attribute value .
* @ param $decode
2010-07-07 17:00:43 +00:00
* ( Deprecated ) Whether to decode entities in the $string . Set to FALSE if the
* $string is in plain text , TRUE otherwise . Defaults to TRUE . This parameter
* is deprecated and will be removed in Drupal 8. To process a plain - text URI ,
* call drupal_strip_dangerous_protocols () or check_url () instead .
2009-05-24 07:17:14 +00:00
* @ return
* Cleaned up and HTML - escaped version of $string .
*/
function filter_xss_bad_protocol ( $string , $decode = TRUE ) {
// Get the plain text representation of the attribute value (i.e. its meaning).
2010-07-07 17:00:43 +00:00
// @todo Remove the $decode parameter in Drupal 8, and always assume an HTML
// string that needs decoding.
2009-05-24 07:17:14 +00:00
if ( $decode ) {
2011-01-01 22:35:28 +00:00
if ( ! function_exists ( 'decode_entities' )) {
2011-10-31 04:05:57 +00:00
require_once DRUPAL_ROOT . '/core/includes/unicode.inc' ;
2011-01-01 22:35:28 +00:00
}
2009-05-24 07:17:14 +00:00
$string = decode_entities ( $string );
}
2010-07-07 17:00:43 +00:00
return check_plain ( drupal_strip_dangerous_protocols ( $string ));
2009-05-24 07:17:14 +00:00
}
/**
* @ } End of " defgroup sanitization " .
*/
2004-02-08 17:12:44 +00:00
/**
2004-09-09 05:51:08 +00:00
* @ defgroup format Formatting
2004-02-08 17:12:44 +00:00
* @ {
2004-09-09 05:51:08 +00:00
* Functions to format numbers , strings , dates , etc .
2004-02-08 17:12:44 +00:00
*/
2004-07-13 07:21:14 +00:00
/**
* Formats an RSS channel .
*
* Arbitrary elements may be added using the $args associative array .
*/
2007-05-29 14:37:49 +00:00
function format_rss_channel ( $title , $link , $description , $items , $langcode = NULL , $args = array ()) {
2010-07-16 02:37:06 +00:00
global $language_content ;
$langcode = $langcode ? $langcode : $language_content -> language ;
2002-04-27 13:19:37 +00:00
2003-12-13 14:59:55 +00:00
$output = " <channel> \n " ;
2008-04-14 17:48:46 +00:00
$output .= ' <title>' . check_plain ( $title ) . " </title> \n " ;
$output .= ' <link>' . check_url ( $link ) . " </link> \n " ;
2006-03-01 21:30:17 +00:00
// The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
// We strip all HTML tags, but need to prevent double encoding from properly
// escaped source data (such as & becoming &amp;).
2008-04-14 17:48:46 +00:00
$output .= ' <description>' . check_plain ( decode_entities ( strip_tags ( $description ))) . " </description> \n " ;
$output .= ' <language>' . check_plain ( $langcode ) . " </language> \n " ;
2006-07-17 15:42:55 +00:00
$output .= format_xml_elements ( $args );
2001-12-01 15:20:48 +00:00
$output .= $items ;
$output .= " </channel> \n " ;
return $output ;
}
2004-07-13 07:21:14 +00:00
/**
* Format a single RSS item .
*
* Arbitrary elements may be added using the $args associative array .
*/
2002-04-27 13:19:37 +00:00
function format_rss_item ( $title , $link , $description , $args = array ()) {
2003-12-13 14:59:55 +00:00
$output = " <item> \n " ;
2008-04-14 17:48:46 +00:00
$output .= ' <title>' . check_plain ( $title ) . " </title> \n " ;
$output .= ' <link>' . check_url ( $link ) . " </link> \n " ;
$output .= ' <description>' . check_plain ( $description ) . " </description> \n " ;
2006-07-17 15:42:55 +00:00
$output .= format_xml_elements ( $args );
$output .= " </item> \n " ;
return $output ;
}
/**
* Format XML elements .
*
* @ param $array
2010-09-26 23:31:36 +00:00
* An array where each item represents an element and is either a :
2006-07-17 15:42:55 +00:00
* - ( key => value ) pair ( < key > value </ key > )
* - Associative array with fields :
* - 'key' : element name
* - 'value' : element contents
* - 'attributes' : associative array of element attributes
*
* In both cases , 'value' can be a simple string , or it can be another array
* with the same format as $array itself for nesting .
*/
function format_xml_elements ( $array ) {
2007-03-27 05:13:55 +00:00
$output = '' ;
2006-07-17 15:42:55 +00:00
foreach ( $array as $key => $value ) {
if ( is_numeric ( $key )) {
2005-02-01 14:09:31 +00:00
if ( $value [ 'key' ]) {
2008-04-14 17:48:46 +00:00
$output .= ' <' . $value [ 'key' ];
2005-12-14 20:10:45 +00:00
if ( isset ( $value [ 'attributes' ]) && is_array ( $value [ 'attributes' ])) {
2005-02-01 14:09:31 +00:00
$output .= drupal_attributes ( $value [ 'attributes' ]);
}
2008-07-19 10:38:13 +00:00
if ( isset ( $value [ 'value' ]) && $value [ 'value' ] != '' ) {
2008-04-14 17:48:46 +00:00
$output .= '>' . ( is_array ( $value [ 'value' ]) ? format_xml_elements ( $value [ 'value' ]) : check_plain ( $value [ 'value' ])) . '</' . $value [ 'key' ] . " > \n " ;
2005-02-01 14:09:31 +00:00
}
else {
$output .= " /> \n " ;
}
}
}
else {
2008-04-14 17:48:46 +00:00
$output .= ' <' . $key . '>' . ( is_array ( $value ) ? format_xml_elements ( $value ) : check_plain ( $value )) . " </ $key > \n " ;
2005-02-01 14:09:31 +00:00
}
2002-04-27 13:19:37 +00:00
}
2001-12-01 15:20:48 +00:00
return $output ;
}
2003-01-21 22:44:25 +00:00
/**
2004-07-13 07:21:14 +00:00
* Format a string containing a count of items .
2003-01-21 22:44:25 +00:00
*
2004-07-13 07:21:14 +00:00
* This function ensures that the string is pluralized correctly . Since t () is
2007-10-08 14:08:19 +00:00
* called by this function , make sure not to pass already - localized strings to
* it .
2004-07-13 07:21:14 +00:00
*
2007-03-07 13:08:04 +00:00
* For example :
* @ code
* $output = format_plural ( $node -> comment_count , '1 comment' , '@count comments' );
* @ endcode
*
* Example with additional replacements :
* @ code
* $output = format_plural ( $update_count ,
* 'Changed the content type of 1 post from %old-type to %new-type.' ,
* 'Changed the content type of @count posts from %old-type to %new-type.' ,
* array ( '%old-type' => $info -> old_type , '%new-type' => $info -> new_type )));
* @ endcode
*
2004-07-13 07:21:14 +00:00
* @ param $count
* The item count to display .
* @ param $singular
* The string for the singular case . Please make sure it is clear this is
* singular , to ease translation ( e . g . use " 1 new comment " instead of " 1 new " ) .
2007-07-02 14:41:37 +00:00
* Do not use @ count in the singular string .
2004-07-13 07:21:14 +00:00
* @ param $plural
* The string for the plural case . Please make sure it is clear this is plural ,
2006-08-18 12:17:00 +00:00
* to ease translation . Use @ count in place of the item count , as in " @count
2004-07-13 07:21:14 +00:00
* new comments " .
2007-03-07 13:08:04 +00:00
* @ param $args
* An associative array of replacements to make after translation . Incidences
* of any key in this array are replaced with the corresponding value .
* Based on the first character of the key , the value is escaped and / or themed :
* - ! variable : inserted as is
* - @ variable : escape plain text to HTML ( check_plain )
* - % variable : escape text and theme as a placeholder for user - submitted
2010-11-22 04:33:02 +00:00
* content ( check_plain + drupal_placeholder )
2007-03-07 13:08:04 +00:00
* Note that you do not need to include @ count in this array .
* This replacement is done automatically for the plural case .
2009-06-08 05:00:12 +00:00
* @ param $options
* An associative array of additional options , with the following keys :
* - 'langcode' ( default to the current language ) The language code to
* translate to a language other than what is used to display the page .
* - 'context' ( default to the empty context ) The context the source string
* belongs to .
2004-07-13 07:21:14 +00:00
* @ return
* A translated string .
2003-01-21 22:44:25 +00:00
*/
2009-06-08 05:00:12 +00:00
function format_plural ( $count , $singular , $plural , array $args = array (), array $options = array ()) {
2007-08-29 17:28:02 +00:00
$args [ '@count' ] = $count ;
2007-03-07 13:08:04 +00:00
if ( $count == 1 ) {
2009-06-08 05:00:12 +00:00
return t ( $singular , $args , $options );
2007-03-07 13:08:04 +00:00
}
2004-08-11 11:26:20 +00:00
2007-10-08 14:08:19 +00:00
// Get the plural index through the gettext formula.
2009-06-08 05:00:12 +00:00
$index = ( function_exists ( 'locale_get_plural' )) ? locale_get_plural ( $count , isset ( $options [ 'langcode' ]) ? $options [ 'langcode' ] : NULL ) : - 1 ;
2007-10-08 14:08:19 +00:00
// Backwards compatibility.
if ( $index < 0 ) {
2009-06-08 05:00:12 +00:00
return t ( $plural , $args , $options );
2004-08-11 11:26:20 +00:00
}
else {
switch ( $index ) {
case " 0 " :
2009-06-08 05:00:12 +00:00
return t ( $singular , $args , $options );
2004-08-11 11:26:20 +00:00
case " 1 " :
2009-06-08 05:00:12 +00:00
return t ( $plural , $args , $options );
2004-08-11 11:26:20 +00:00
default :
2007-03-07 13:08:04 +00:00
unset ( $args [ '@count' ]);
2008-04-14 17:48:46 +00:00
$args [ '@count[' . $index . ']' ] = $count ;
2009-06-08 05:00:12 +00:00
return t ( strtr ( $plural , array ( '@count' => '@count[' . $index . ']' )), $args , $options );
2004-08-11 11:26:20 +00:00
}
}
2001-12-01 15:20:48 +00:00
}
2006-12-07 17:02:25 +00:00
/**
* Parse a given byte count .
*
* @ param $size
2008-11-16 15:19:34 +00:00
* A size expressed as a number of bytes with optional SI or IEC binary unit
* prefix ( e . g . 2 , 3 K , 5 MB , 10 G , 6 GiB , 8 bytes , 9 mbytes ) .
2006-12-07 17:02:25 +00:00
* @ return
2008-11-16 15:19:34 +00:00
* An integer representation of the size in bytes .
2006-12-07 17:02:25 +00:00
*/
function parse_size ( $size ) {
2008-11-16 15:19:34 +00:00
$unit = preg_replace ( '/[^bkmgtpezy]/i' , '' , $size ); // Remove the non-unit characters from the size.
$size = preg_replace ( '/[^0-9\.]/' , '' , $size ); // Remove the non-numeric characters from the size.
if ( $unit ) {
// Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
return round ( $size * pow ( DRUPAL_KILOBYTE , stripos ( 'bkmgtpezy' , $unit [ 0 ])));
}
else {
return round ( $size );
2006-12-07 17:02:25 +00:00
}
}
2004-02-08 17:12:44 +00:00
/**
2004-07-13 07:21:14 +00:00
* Generate a string representation for the given byte count .
2004-02-08 17:12:44 +00:00
*
2004-07-13 07:21:14 +00:00
* @ param $size
2007-10-08 14:08:19 +00:00
* A size in bytes .
2007-05-29 14:37:49 +00:00
* @ param $langcode
2007-10-08 14:08:19 +00:00
* Optional language code to translate to a language other than what is used
* to display the page .
2004-07-13 07:21:14 +00:00
* @ return
* A translated string representation of the size .
2004-02-08 17:12:44 +00:00
*/
2007-05-29 14:37:49 +00:00
function format_size ( $size , $langcode = NULL ) {
2008-11-16 15:19:34 +00:00
if ( $size < DRUPAL_KILOBYTE ) {
2009-06-08 05:00:12 +00:00
return format_plural ( $size , '1 byte' , '@count bytes' , array (), array ( 'langcode' => $langcode ));
2001-12-01 15:20:48 +00:00
}
2006-12-30 21:33:04 +00:00
else {
2008-11-16 15:19:34 +00:00
$size = $size / DRUPAL_KILOBYTE ; // Convert bytes to kilobytes.
2008-08-14 09:21:49 +00:00
$units = array (
2009-06-08 05:00:12 +00:00
t ( '@size KB' , array (), array ( 'langcode' => $langcode )),
t ( '@size MB' , array (), array ( 'langcode' => $langcode )),
t ( '@size GB' , array (), array ( 'langcode' => $langcode )),
t ( '@size TB' , array (), array ( 'langcode' => $langcode )),
t ( '@size PB' , array (), array ( 'langcode' => $langcode )),
t ( '@size EB' , array (), array ( 'langcode' => $langcode )),
t ( '@size ZB' , array (), array ( 'langcode' => $langcode )),
t ( '@size YB' , array (), array ( 'langcode' => $langcode )),
2008-08-14 09:21:49 +00:00
);
foreach ( $units as $unit ) {
2008-11-16 15:19:34 +00:00
if ( round ( $size , 2 ) >= DRUPAL_KILOBYTE ) {
$size = $size / DRUPAL_KILOBYTE ;
2008-06-09 08:11:45 +00:00
}
else {
break ;
}
2006-12-30 21:33:04 +00:00
}
2008-08-14 09:21:49 +00:00
return str_replace ( '@size' , round ( $size , 2 ), $unit );
2001-12-01 15:20:48 +00:00
}
}
2004-02-08 17:12:44 +00:00
/**
2004-07-13 07:21:14 +00:00
* Format a time interval with the requested granularity .
2004-02-08 17:12:44 +00:00
*
2004-07-13 07:21:14 +00:00
* @ param $timestamp
* The length of the interval in seconds .
* @ param $granularity
* How many different units to display in the string .
2007-05-29 14:37:49 +00:00
* @ param $langcode
* Optional language code to translate to a language other than
* what is used to display the page .
2004-07-13 07:21:14 +00:00
* @ return
* A translated string representation of the interval .
2004-02-08 17:12:44 +00:00
*/
2007-05-29 14:37:49 +00:00
function format_interval ( $timestamp , $granularity = 2 , $langcode = NULL ) {
2009-04-26 16:09:57 +00:00
$units = array (
'1 year|@count years' => 31536000 ,
'1 month|@count months' => 2592000 ,
'1 week|@count weeks' => 604800 ,
'1 day|@count days' => 86400 ,
'1 hour|@count hours' => 3600 ,
'1 min|@count min' => 60 ,
'1 sec|@count sec' => 1
);
2004-04-12 08:27:57 +00:00
$output = '' ;
2003-12-24 12:40:28 +00:00
foreach ( $units as $key => $value ) {
2004-07-13 07:21:14 +00:00
$key = explode ( '|' , $key );
2001-12-01 15:20:48 +00:00
if ( $timestamp >= $value ) {
2009-06-08 05:00:12 +00:00
$output .= ( $output ? ' ' : '' ) . format_plural ( floor ( $timestamp / $value ), $key [ 0 ], $key [ 1 ], array (), array ( 'langcode' => $langcode ));
2001-12-01 15:20:48 +00:00
$timestamp %= $value ;
2004-01-21 06:40:57 +00:00
$granularity -- ;
}
if ( $granularity == 0 ) {
break ;
2001-12-01 15:20:48 +00:00
}
}
2009-06-08 05:00:12 +00:00
return $output ? $output : t ( '0 sec' , array (), array ( 'langcode' => $langcode ));
2001-12-01 15:20:48 +00:00
}
2004-02-08 17:12:44 +00:00
/**
2010-11-30 06:30:21 +00:00
* Formats a date , using a date type or a custom date format string .
2004-02-08 17:12:44 +00:00
*
2004-07-13 07:21:14 +00:00
* @ param $timestamp
2010-11-30 06:30:21 +00:00
* A UNIX timestamp to format .
2004-07-13 07:21:14 +00:00
* @ param $type
2010-11-30 06:30:21 +00:00
* ( optional ) The format to use , one of :
* - 'short' , 'medium' , or 'long' ( the corresponding built - in date formats ) .
2011-01-03 06:51:00 +00:00
* - The name of a date type defined by a module in hook_date_format_types (),
* if it ' s been assigned a format .
2010-11-30 06:30:21 +00:00
* - The machine name of an administrator - defined date format .
* - 'custom' , to use $format .
* Defaults to 'medium' .
2004-07-13 07:21:14 +00:00
* @ param $format
2010-11-30 06:30:21 +00:00
* ( optional ) If $type is 'custom' , a PHP date format string suitable for
* input to date () . Use a backslash to escape ordinary text , so it does not
* get interpreted as date format characters .
2004-07-13 07:21:14 +00:00
* @ param $timezone
2010-11-30 06:30:21 +00:00
* ( optional ) Time zone identifier , as described at
* http :// php . net / manual / en / timezones . php Defaults to the time zone used to
* display the page .
2007-05-29 14:37:49 +00:00
* @ param $langcode
2010-11-30 06:30:21 +00:00
* ( optional ) Language code to translate to . Defaults to the language used to
* display the page .
*
2004-07-13 07:21:14 +00:00
* @ return
* A translated date string in the requested format .
2004-02-08 17:12:44 +00:00
*/
2007-05-29 14:37:49 +00:00
function format_date ( $timestamp , $type = 'medium' , $format = '' , $timezone = NULL , $langcode = NULL ) {
2009-11-20 06:12:45 +00:00
// Use the advanced drupal_static() pattern, since this is called very often.
2010-01-07 04:54:18 +00:00
static $drupal_static_fast ;
if ( ! isset ( $drupal_static_fast )) {
$drupal_static_fast [ 'timezones' ] = & drupal_static ( __FUNCTION__ );
}
$timezones = & $drupal_static_fast [ 'timezones' ];
2009-11-20 06:12:45 +00:00
2006-01-17 15:39:07 +00:00
if ( ! isset ( $timezone )) {
2010-04-11 18:33:44 +00:00
$timezone = date_default_timezone_get ();
2004-02-08 21:42:59 +00:00
}
2008-11-20 06:56:17 +00:00
// Store DateTimeZone objects in an array rather than repeatedly
2010-01-25 10:38:35 +00:00
// constructing identical objects over the life of a request.
2008-11-20 06:56:17 +00:00
if ( ! isset ( $timezones [ $timezone ])) {
$timezones [ $timezone ] = timezone_open ( $timezone );
}
2001-12-01 15:20:48 +00:00
2009-07-08 20:40:21 +00:00
// Use the default langcode if none is set.
global $language ;
if ( empty ( $langcode )) {
2011-10-27 03:55:02 +00:00
$langcode = isset ( $language -> language ) ? $language -> language : LANGUAGE_SYSTEM ;
2009-07-08 20:40:21 +00:00
}
2001-12-01 15:20:48 +00:00
switch ( $type ) {
2009-08-25 10:27:15 +00:00
case 'short' :
2004-02-08 21:42:59 +00:00
$format = variable_get ( 'date_format_short' , 'm/d/Y - H:i' );
2001-12-01 15:20:48 +00:00
break ;
2010-11-30 06:30:21 +00:00
2009-08-25 10:27:15 +00:00
case 'long' :
2004-02-08 21:42:59 +00:00
$format = variable_get ( 'date_format_long' , 'l, F j, Y - H:i' );
2001-12-01 15:20:48 +00:00
break ;
2010-11-30 06:30:21 +00:00
2004-02-08 21:42:59 +00:00
case 'custom' :
2007-10-08 14:08:19 +00:00
// No change to format.
2001-12-01 15:20:48 +00:00
break ;
2010-11-30 06:30:21 +00:00
2004-02-08 21:42:59 +00:00
case 'medium' :
2001-12-01 15:20:48 +00:00
default :
2010-11-30 06:30:21 +00:00
// Retrieve the format of the custom $type passed.
if ( $type != 'medium' ) {
$format = variable_get ( 'date_format_' . $type , '' );
}
// Fall back to 'medium'.
if ( $format === '' ) {
$format = variable_get ( 'date_format_medium' , 'D, m/d/Y - H:i' );
}
break ;
2003-09-29 18:20:38 +00:00
}
2008-11-20 06:56:17 +00:00
// Create a DateTime object from the timestamp.
$date_time = date_create ( '@' . $timestamp );
// Set the time zone for the DateTime object.
date_timezone_set ( $date_time , $timezones [ $timezone ]);
2009-07-08 20:40:21 +00:00
// Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'.
// xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the
// input string.
// Paired backslashes are isolated to prevent errors in read-ahead evaluation.
// The read-ahead expression ensures that A matches, but not \A.
$format = preg_replace ( array ( '/\\\\\\\\/' , '/(?<!\\\\)([AaeDlMTF])/' ), array ( " \xEF \\ \\ \\ \\ \xFF " , " \xEF \\ \\ \$ 1 \$ 1 \xFF " ), $format );
// Call date_format().
$format = date_format ( $date_time , $format );
// Pass the langcode to _format_date_callback().
_format_date_callback ( NULL , $langcode );
// Translate the marked sequences.
return preg_replace_callback ( '/\xEF([AaeDlMTF]?)(.*?)\xFF/' , '_format_date_callback' , $format );
}
2009-10-18 18:32:27 +00:00
/**
* Returns an ISO8601 formatted date based on the given date .
*
* Can be used as a callback for RDF mappings .
*
* @ param $date
* A UNIX timestamp .
* @ return string
* An ISO8601 formatted date .
*/
function date_iso8601 ( $date ) {
// The DATE_ISO8601 constant cannot be used here because it does not match
// date('c') and produces invalid RDF markup.
return date ( 'c' , $date );
}
2009-07-08 20:40:21 +00:00
/**
* Callback function for preg_replace_callback () .
*/
function _format_date_callback ( array $matches = NULL , $new_langcode = NULL ) {
// We cache translations to avoid redundant and rather costly calls to t().
static $cache , $langcode ;
if ( ! isset ( $matches )) {
$langcode = $new_langcode ;
return ;
}
$code = $matches [ 1 ];
$string = $matches [ 2 ];
if ( ! isset ( $cache [ $langcode ][ $code ][ $string ])) {
$options = array (
'langcode' => $langcode ,
);
if ( $code == 'F' ) {
$options [ 'context' ] = 'Long month name' ;
2003-09-29 18:20:38 +00:00
}
2009-07-08 20:40:21 +00:00
if ( $code == '' ) {
$cache [ $langcode ][ $code ][ $string ] = $string ;
2004-12-03 20:38:22 +00:00
}
2003-09-29 18:20:38 +00:00
else {
2009-07-08 20:40:21 +00:00
$cache [ $langcode ][ $code ][ $string ] = t ( $string , array (), $options );
2003-09-29 18:20:38 +00:00
}
2001-12-01 15:20:48 +00:00
}
2009-07-08 20:40:21 +00:00
return $cache [ $langcode ][ $code ][ $string ];
2001-12-01 15:20:48 +00:00
}
2004-09-09 05:51:08 +00:00
/**
* @ } End of " defgroup format " .
*/
2001-12-01 15:20:48 +00:00
2004-07-13 07:21:14 +00:00
/**
2010-04-06 16:41:08 +00:00
* Generates an internal or external URL .
*
* When creating links in modules , consider whether l () could be a better
* alternative than url () .
2004-07-13 07:21:14 +00:00
*
* @ param $path
2010-04-06 16:41:08 +00:00
* The internal path or external URL being linked to , such as " node/34 " or
* " http://example.com/foo " . A few notes :
* - If you provide a full URL , it will be considered an external URL .
* - If you provide only the path ( e . g . " node/34 " ), it will be
* considered an internal link . In this case , it should be a system URL ,
* and it will be replaced with the alias , if one exists . Additional query
* arguments for internal paths must be supplied in $options [ 'query' ], not
* included in $path .
* - If you provide an internal path and $options [ 'alias' ] is set to TRUE , the
* path is assumed already to be the correct path alias , and the alias is
* not looked up .
* - The special string '<front>' generates a link to the site ' s base URL .
* - If your external URL contains a query ( e . g . http :// example . com / foo ? a = b ),
* then you can either URL encode the query keys and values yourself and
* include them in $path , or use $options [ 'query' ] to let this function
* URL encode them .
2007-02-15 11:40:19 +00:00
* @ param $options
2010-04-06 16:41:08 +00:00
* An associative array of additional options , with the following elements :
2009-09-29 15:31:17 +00:00
* - 'query' : An array of query key / value - pairs ( without any URL - encoding ) to
2010-04-06 16:41:08 +00:00
* append to the URL .
* - 'fragment' : A fragment identifier ( named anchor ) to append to the URL .
* Do not include the leading '#' character .
2009-09-29 15:31:17 +00:00
* - 'absolute' : Defaults to FALSE . Whether to force the output to be an
* absolute link ( beginning with http : ) . Useful for links that will be
2010-04-06 16:41:08 +00:00
* displayed outside the site , such as in an RSS feed .
2009-09-29 15:31:17 +00:00
* - 'alias' : Defaults to FALSE . Whether the given path is a URL alias
* already .
* - 'external' : Whether the given path is an external URL .
2010-03-18 19:15:02 +00:00
* - 'language' : An optional language object . If the path being linked to is
* internal to the site , $options [ 'language' ] is used to look up the alias
2010-07-30 01:52:54 +00:00
* for the URL . If $options [ 'language' ] is omitted , the global $language_url
* will be used .
2009-09-29 15:31:17 +00:00
* - 'https' : Whether this URL should point to a secure location . If not
2009-11-01 23:02:13 +00:00
* defined , the current scheme is used , so the user stays on http or https
2009-09-29 15:31:17 +00:00
* respectively . TRUE enforces HTTPS and FALSE enforces HTTP , but HTTPS can
* only be enforced when the variable 'https' is set to TRUE .
* - 'base_url' : Only used internally , to modify the base URL when a language
* dependent URL requires so .
* - 'prefix' : Only used internally , to modify the path when a language
* dependent URL requires so .
2010-05-26 10:52:28 +00:00
* - 'script' : The script filename in Drupal ' s root directory to use when
* clean URLs are disabled , such as 'index.php' . Defaults to an empty
* string , as most modern web servers automatically find 'index.php' . If
* clean URLs are disabled , the value of $path is appended as query
* parameter 'q' to $options [ 'script' ] in the returned URL . When deploying
* Drupal on a web server that cannot be configured to automatically find
* index . php , then hook_url_outbound_alter () can be implemented to force
* this value to 'index.php' .
2010-06-28 18:21:46 +00:00
* - 'entity_type' : The entity type of the object that called url () . Only set if
* url () is invoked by entity_uri () .
* - 'entity' : The entity object ( such as a node ) for which the URL is being
* generated . Only set if url () is invoked by entity_uri () .
2009-09-29 15:31:17 +00:00
*
2004-07-13 07:21:14 +00:00
* @ return
2007-10-08 14:08:19 +00:00
* A string containing a URL to the given path .
2004-07-13 07:21:14 +00:00
*/
2008-10-06 11:07:14 +00:00
function url ( $path = NULL , array $options = array ()) {
2007-10-08 14:08:19 +00:00
// Merge in defaults.
2007-02-15 11:40:19 +00:00
$options += array (
2007-10-02 16:03:17 +00:00
'fragment' => '' ,
2009-09-29 15:31:17 +00:00
'query' => array (),
2007-10-02 16:03:17 +00:00
'absolute' => FALSE ,
'alias' => FALSE ,
2007-12-05 16:34:07 +00:00
'prefix' => ''
2007-10-02 16:03:17 +00:00
);
2009-10-24 05:13:44 +00:00
2007-10-02 16:03:17 +00:00
if ( ! isset ( $options [ 'external' ])) {
2010-07-07 17:00:43 +00:00
// Return an external link if $path contains an allowed absolute URL. Only
// call the slow drupal_strip_dangerous_protocols() if $path contains a ':'
// before any / ? or #. Note: we could use url_is_external($path) here, but
// that would require another function call, and performance inside url() is
// critical.
2007-10-02 16:03:17 +00:00
$colonpos = strpos ( $path , ':' );
2010-07-07 17:00:43 +00:00
$options [ 'external' ] = ( $colonpos !== FALSE && ! preg_match ( '![/?#]!' , substr ( $path , 0 , $colonpos )) && drupal_strip_dangerous_protocols ( $path ) == $path );
2007-10-02 16:03:17 +00:00
}
2007-03-26 01:32:22 +00:00
2009-10-24 05:13:44 +00:00
// Preserve the original path before altering or aliasing.
$original_path = $path ;
2010-02-06 16:21:08 +00:00
// Allow other modules to alter the outbound URL and options.
2010-02-06 15:43:42 +00:00
drupal_alter ( 'url_outbound' , $path , $options , $original_path );
2009-10-24 05:13:44 +00:00
2010-08-10 01:00:42 +00:00
if ( isset ( $options [ 'fragment' ]) && $options [ 'fragment' ] !== '' ) {
2008-04-14 17:48:46 +00:00
$options [ 'fragment' ] = '#' . $options [ 'fragment' ];
2007-02-15 11:40:19 +00:00
}
2005-12-29 05:28:53 +00:00
2007-10-02 16:03:17 +00:00
if ( $options [ 'external' ]) {
2007-10-08 14:08:19 +00:00
// Split off the fragment.
2006-10-15 20:00:19 +00:00
if ( strpos ( $path , '#' ) !== FALSE ) {
2005-12-29 05:28:53 +00:00
list ( $path , $old_fragment ) = explode ( '#' , $path , 2 );
2009-09-29 15:31:17 +00:00
// If $options contains no fragment, take it over from the path.
2007-02-15 11:40:19 +00:00
if ( isset ( $old_fragment ) && ! $options [ 'fragment' ]) {
2008-04-14 17:48:46 +00:00
$options [ 'fragment' ] = '#' . $old_fragment ;
2005-12-29 05:28:53 +00:00
}
}
2007-10-08 14:08:19 +00:00
// Append the query.
2007-02-15 11:40:19 +00:00
if ( $options [ 'query' ]) {
2009-09-29 15:31:17 +00:00
$path .= ( strpos ( $path , '?' ) !== FALSE ? '&' : '?' ) . drupal_http_build_query ( $options [ 'query' ]);
2005-12-29 05:28:53 +00:00
}
2009-11-01 23:02:13 +00:00
if ( isset ( $options [ 'https' ]) && variable_get ( 'https' , FALSE )) {
if ( $options [ 'https' ] === TRUE ) {
$path = str_replace ( 'http://' , 'https://' , $path );
}
elseif ( $options [ 'https' ] === FALSE ) {
$path = str_replace ( 'https://' , 'http://' , $path );
}
}
2007-10-08 14:08:19 +00:00
// Reassemble.
2007-02-15 11:40:19 +00:00
return $path . $options [ 'fragment' ];
2005-12-29 05:28:53 +00:00
}
2004-01-17 23:19:02 +00:00
2009-09-05 13:05:31 +00:00
global $base_url , $base_secure_url , $base_insecure_url ;
2005-10-07 06:51:43 +00:00
2009-09-05 13:05:31 +00:00
// The base_url might be rewritten from the language rewrite in domain mode.
2007-11-29 14:42:31 +00:00
if ( ! isset ( $options [ 'base_url' ])) {
2009-09-05 13:05:31 +00:00
if ( isset ( $options [ 'https' ]) && variable_get ( 'https' , FALSE )) {
if ( $options [ 'https' ] === TRUE ) {
$options [ 'base_url' ] = $base_secure_url ;
$options [ 'absolute' ] = TRUE ;
}
elseif ( $options [ 'https' ] === FALSE ) {
$options [ 'base_url' ] = $base_insecure_url ;
$options [ 'absolute' ] = TRUE ;
}
}
else {
$options [ 'base_url' ] = $base_url ;
}
2007-11-29 14:42:31 +00:00
}
2004-02-11 19:21:14 +00:00
2005-12-27 10:36:16 +00:00
// The special path '<front>' links to the default front page.
2008-01-27 19:57:36 +00:00
if ( $path == '<front>' ) {
$path = '' ;
2007-12-05 16:34:07 +00:00
}
2008-01-27 19:57:36 +00:00
elseif ( ! empty ( $path ) && ! $options [ 'alias' ]) {
2009-10-09 16:33:14 +00:00
$language = isset ( $options [ 'language' ]) && isset ( $options [ 'language' ] -> language ) ? $options [ 'language' ] -> language : '' ;
2009-10-24 05:13:44 +00:00
$alias = drupal_get_path_alias ( $original_path , $language );
if ( $alias != $original_path ) {
$path = $alias ;
}
2008-01-27 19:57:36 +00:00
}
2008-04-14 17:48:46 +00:00
$base = $options [ 'absolute' ] ? $options [ 'base_url' ] . '/' : base_path ();
2008-01-27 19:57:36 +00:00
$prefix = empty ( $path ) ? rtrim ( $options [ 'prefix' ], '/' ) : $options [ 'prefix' ];
2009-09-29 15:31:17 +00:00
// With Clean URLs.
if ( ! empty ( $GLOBALS [ 'conf' ][ 'clean_url' ])) {
$path = drupal_encode_path ( $prefix . $path );
2007-12-05 16:34:07 +00:00
if ( $options [ 'query' ]) {
2009-09-29 15:31:17 +00:00
return $base . $path . '?' . drupal_http_build_query ( $options [ 'query' ]) . $options [ 'fragment' ];
2003-01-06 21:24:21 +00:00
}
else {
2007-12-05 16:34:07 +00:00
return $base . $path . $options [ 'fragment' ];
2003-01-06 21:24:21 +00:00
}
}
2009-09-29 15:31:17 +00:00
// Without Clean URLs.
2003-01-06 21:24:21 +00:00
else {
2009-09-29 15:31:17 +00:00
$path = $prefix . $path ;
$query = array ();
2007-12-05 16:34:07 +00:00
if ( ! empty ( $path )) {
2009-09-29 15:31:17 +00:00
$query [ 'q' ] = $path ;
2007-12-05 16:34:07 +00:00
}
2009-09-29 15:31:17 +00:00
if ( $options [ 'query' ]) {
// We do not use array_merge() here to prevent overriding $path via query
// parameters.
$query += $options [ 'query' ];
2007-12-08 14:06:23 +00:00
}
2010-05-26 10:52:28 +00:00
$query = $query ? ( '?' . drupal_http_build_query ( $query )) : '' ;
$script = isset ( $options [ 'script' ]) ? $options [ 'script' ] : '' ;
return $base . $script . $query . $options [ 'fragment' ];
2003-01-06 21:24:21 +00:00
}
2002-04-20 11:52:50 +00:00
}
2009-11-01 23:02:13 +00:00
/**
2010-06-09 14:55:30 +00:00
* Return TRUE if a path is external to Drupal ( e . g . http :// example . com ) .
*
* If a path cannot be assessed by Drupal ' s menu handler , then we must
* treat it as potentially insecure .
*
* @ param $path
* The internal path or external URL being linked to , such as " node/34 " or
* " http://example.com/foo " .
2010-08-01 19:54:31 +00:00
* @ return
2010-06-09 14:55:30 +00:00
* Boolean TRUE or FALSE , where TRUE indicates an external path .
2009-11-01 23:02:13 +00:00
*/
function url_is_external ( $path ) {
$colonpos = strpos ( $path , ':' );
2011-09-27 17:58:09 +00:00
// Avoid calling drupal_strip_dangerous_protocols() if there is any
// slash (/), hash (#) or question_mark (?) before the colon (:)
// occurrence - if any - as this would clearly mean it is not a URL.
2010-07-07 17:00:43 +00:00
return $colonpos !== FALSE && ! preg_match ( '![/?#]!' , substr ( $path , 0 , $colonpos )) && drupal_strip_dangerous_protocols ( $path ) == $path ;
2009-11-01 23:02:13 +00:00
}
2009-11-03 06:47:23 +00:00
/**
* Format an attribute string for a HTTP header .
*
* @ param $attributes
* An associative array of attributes such as 'rel' .
*
* @ return
* A ; separated string ready for insertion in a HTTP header . No escaping is
* performed for HTML entities , so this string is not safe to be printed .
*
* @ see drupal_add_http_header ()
*/
function drupal_http_header_attributes ( array $attributes = array ()) {
foreach ( $attributes as $attribute => & $data ) {
if ( is_array ( $data )) {
$data = implode ( ' ' , $data );
}
$data = $attribute . '="' . $data . '"' ;
}
return $attributes ? ' ' . implode ( '; ' , $attributes ) : '' ;
}
2004-07-13 07:21:14 +00:00
/**
2010-12-14 19:25:45 +00:00
* Converts an associative array to an attribute string for use in XML / HTML tags .
2004-07-13 07:21:14 +00:00
*
2010-12-14 19:25:45 +00:00
* Each array key and its value will be formatted into an attribute string .
* If a value is itself an array , then its elements are concatenated to a single
* space - delimited string ( for example , a class attribute with multiple values ) .
*
* Attribute values are sanitized by running them through check_plain () .
* Attribute names are not automatically sanitized . When using user - supplied
* attribute names , it is strongly recommended to allow only white - listed names ,
* since certain attributes carry security risks and can be abused .
*
* Examples of security aspects when using drupal_attributes :
* @ code
* // By running the value in the following statement through check_plain,
* // the malicious script is neutralized.
* drupal_attributes ( array ( 'title' => t ( '<script>steal_cookie();</script>' )));
*
* // The statement below demonstrates dangerous use of drupal_attributes, and
2011-02-19 00:09:11 +00:00
* // will return an onmouseout attribute with JavaScript code that, when used
2010-12-14 19:25:45 +00:00
* // as attribute in a tag, will cause users to be redirected to another site.
* //
* // In this case, the 'onmouseout' attribute should not be whitelisted --
* // you don't want users to have the ability to add this attribute or others
* // that take JavaScript commands.
* drupal_attributes ( array ( 'onmouseout' => 'window.location="http://malicious.com/";' )));
* @ endcode
2009-07-15 17:40:18 +00:00
*
2004-07-13 07:21:14 +00:00
* @ param $attributes
2010-12-14 19:25:45 +00:00
* An associative array of key - value pairs to be converted to attributes .
*
2004-07-13 07:21:14 +00:00
* @ return
2011-07-10 18:14:02 +00:00
* A string ready for insertion in a tag ( starts with a space ) .
2010-12-14 19:25:45 +00:00
*
* @ ingroup sanitization
2004-07-13 07:21:14 +00:00
*/
2009-07-28 19:22:18 +00:00
function drupal_attributes ( array $attributes = array ()) {
foreach ( $attributes as $attribute => & $data ) {
2010-11-05 01:29:20 +00:00
$data = implode ( ' ' , ( array ) $data );
2009-07-28 19:22:18 +00:00
$data = $attribute . '="' . check_plain ( $data ) . '"' ;
2002-04-27 13:19:37 +00:00
}
2009-07-28 19:22:18 +00:00
return $attributes ? ' ' . implode ( ' ' , $attributes ) : '' ;
2003-08-12 20:37:16 +00:00
}
2003-01-06 19:51:01 +00:00
2004-07-13 07:21:14 +00:00
/**
2010-04-06 16:41:08 +00:00
* Formats an internal or external URL link as an HTML anchor tag .
2004-07-13 07:21:14 +00:00
*
2010-04-06 16:41:08 +00:00
* This function correctly handles aliased paths , and adds an 'active' class
* attribute to links that point to the current page ( for theming ), so all
* internal links output by modules should be generated by this function if
* possible .
2004-07-13 07:21:14 +00:00
*
* @ param $text
2010-04-06 16:41:08 +00:00
* The link text for the anchor tag .
2004-07-13 07:21:14 +00:00
* @ param $path
2010-04-06 16:41:08 +00:00
* The internal path or external URL being linked to , such as " node/34 " or
* " http://example.com/foo " . After the url () function is called to construct
* the URL from $path and $options , the resulting URL is passed through
* check_plain () before it is inserted into the HTML anchor tag , to ensure
* well - formed HTML . See url () for more information and notes .
* @ param array $options
* An associative array of additional options , with the following elements :
* - 'attributes' : An associative array of HTML attributes to apply to the
2010-11-21 10:05:27 +00:00
* anchor tag . If element 'class' is included , it must be an array ; 'title'
* must be a string ; other elements are more flexible , as they just need
* to work in a call to drupal_attributes ( $options [ 'attributes' ]) .
2010-04-06 16:41:08 +00:00
* - 'html' ( default FALSE ) : Whether $text is HTML or just plain - text . For
* example , to make an image tag into a link , this must be set to TRUE , or
2011-07-03 18:00:28 +00:00
* you will see the escaped HTML image tag . $text is not sanitized if
* 'html' is TRUE . The calling function must ensure that $text is already
* safe .
2010-04-06 16:41:08 +00:00
* - 'language' : An optional language object . If the path being linked to is
* internal to the site , $options [ 'language' ] is used to determine whether
* the link is " active " , or pointing to the current page ( the language as
* well as the path must match ) . This element is also used by url () .
* - Additional $options elements used by the url () function .
2010-03-18 19:15:02 +00:00
*
2004-07-13 07:21:14 +00:00
* @ return
2010-04-06 16:41:08 +00:00
* An HTML string containing a link to the given path .
2004-07-13 07:21:14 +00:00
*/
2008-10-06 11:07:14 +00:00
function l ( $text , $path , array $options = array ()) {
2009-10-09 16:33:14 +00:00
global $language_url ;
2009-11-08 12:43:41 +00:00
static $use_theme = NULL ;
2009-01-22 03:18:30 +00:00
2007-10-08 14:08:19 +00:00
// Merge in defaults.
2007-02-15 11:40:19 +00:00
$options += array (
2010-09-24 00:37:45 +00:00
'attributes' => array (),
'html' => FALSE ,
);
2007-02-15 11:40:19 +00:00
2007-10-08 14:08:19 +00:00
// Append active class.
2009-01-22 03:18:30 +00:00
if (( $path == $_GET [ 'q' ] || ( $path == '<front>' && drupal_is_front_page ())) &&
2009-10-09 16:33:14 +00:00
( empty ( $options [ 'language' ]) || $options [ 'language' ] -> language == $language_url -> language )) {
2009-08-22 14:34:23 +00:00
$options [ 'attributes' ][ 'class' ][] = 'active' ;
2004-01-19 21:57:42 +00:00
}
2007-11-04 14:18:56 +00:00
// Remove all HTML and PHP tags from a tooltip. For best performance, we act only
// if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
if ( isset ( $options [ 'attributes' ][ 'title' ]) && strpos ( $options [ 'attributes' ][ 'title' ], '<' ) !== FALSE ) {
$options [ 'attributes' ][ 'title' ] = strip_tags ( $options [ 'attributes' ][ 'title' ]);
}
2009-11-08 12:43:41 +00:00
// Determine if rendering of the link is to be done with a theme function
// or the inline default. Inline is faster, but if the theme system has been
// loaded and a module or theme implements a preprocess or process function
// or overrides the theme_link() function, then invoke theme(). Preliminary
// benchmarks indicate that invoking theme() can slow down the l() function
// by 20% or more, and that some of the link-heavy Drupal pages spend more
// than 10% of the total page request time in the l() function.
if ( ! isset ( $use_theme ) && function_exists ( 'theme' )) {
// Allow edge cases to prevent theme initialization and force inline link
// rendering.
if ( variable_get ( 'theme_link' , TRUE )) {
drupal_theme_initialize ();
$registry = theme_get_registry ();
// We don't want to duplicate functionality that's in theme(), so any
// hint of a module or theme doing anything at all special with the 'link'
// theme hook should simply result in theme() being called. This includes
// the overriding of theme_link() with an alternate function or template,
// the presence of preprocess or process functions, or the presence of
// include files.
$use_theme = ! isset ( $registry [ 'link' ][ 'function' ]) || ( $registry [ 'link' ][ 'function' ] != 'theme_link' );
$use_theme = $use_theme || ! empty ( $registry [ 'link' ][ 'preprocess functions' ]) || ! empty ( $registry [ 'link' ][ 'process functions' ]) || ! empty ( $registry [ 'link' ][ 'includes' ]);
}
else {
$use_theme = FALSE ;
}
}
if ( $use_theme ) {
return theme ( 'link' , array ( 'text' => $text , 'path' => $path , 'options' => $options ));
}
2010-02-01 07:06:14 +00:00
// The result of url() is a plain-text URL. Because we are using it here
// in an HTML argument context, we need to encode it properly.
2009-03-17 22:35:07 +00:00
return '<a href="' . check_plain ( url ( $path , $options )) . '"' . drupal_attributes ( $options [ 'attributes' ]) . '>' . ( $options [ 'html' ] ? $text : check_plain ( $text )) . '</a>' ;
2002-04-20 11:52:50 +00:00
}
2009-10-15 14:07:30 +00:00
/**
2010-08-05 07:56:14 +00:00
* Delivers a page callback result to the browser in the appropriate format .
2009-10-15 14:07:30 +00:00
*
* This function is most commonly called by menu_execute_active_handler (), but
* can also be called by error conditions such as drupal_not_found (),
* drupal_access_denied (), and drupal_site_offline () .
*
2010-08-05 07:56:14 +00:00
* When a user requests a page , index . php calls menu_execute_active_handler (),
2009-10-15 14:07:30 +00:00
* which calls the 'page callback' function registered in hook_menu () . The page
* callback function can return one of :
2010-08-05 07:56:14 +00:00
* - NULL : to indicate no content .
* - An integer menu status constant : to indicate an error condition .
* - A string of HTML content .
* - A renderable array of content .
2009-10-15 14:07:30 +00:00
* Returning a renderable array rather than a string of HTML is preferred ,
* because that provides modules with more flexibility in customizing the final
* result .
*
* When the page callback returns its constructed content to
2010-08-05 07:56:14 +00:00
* menu_execute_active_handler (), this function gets called . The purpose of
2009-10-15 14:07:30 +00:00
* this function is to determine the most appropriate 'delivery callback'
* function to route the content to . The delivery callback function then
* sends the content to the browser in the needed format . The default delivery
2010-08-05 07:56:14 +00:00
* callback is drupal_deliver_html_page (), which delivers the content as an HTML
2009-10-15 14:07:30 +00:00
* page , complete with blocks in addition to the content . This default can be
2010-07-01 00:46:57 +00:00
* overridden on a per menu router item basis by setting 'delivery callback' in
* hook_menu () or hook_menu_alter (), and can also be overridden on a per request
* basis in hook_page_delivery_callback_alter () .
2009-10-15 14:07:30 +00:00
*
* For example , the same page callback function can be used for an HTML
2011-02-19 00:09:11 +00:00
* version of the page and an Ajax version of the page . The page callback
2009-10-15 14:07:30 +00:00
* function just needs to decide what content is to be returned and the
2011-02-19 00:09:11 +00:00
* delivery callback function will send it as an HTML page or an Ajax
2009-10-15 14:07:30 +00:00
* response , as appropriate .
*
* In order for page callbacks to be reusable in different delivery formats ,
* they should not issue any " print " or " echo " statements , but instead just
2009-10-16 13:18:32 +00:00
* return content .
2009-10-15 14:07:30 +00:00
*
2010-08-05 07:56:14 +00:00
* Also note that this function does not perform access checks . The delivery
* callback function specified in hook_menu (), hook_menu_alter (), or
* hook_page_delivery_callback_alter () will be called even if the router item
* access checks fail . This is intentional ( it is needed for JSON and other
* purposes ), but it has security implications . Do not call this function
* directly unless you understand the security implications , and be careful in
* writing delivery callbacks , so that they do not violate security . See
* drupal_deliver_html_page () for an example of a delivery callback that
* respects security .
*
2009-10-15 14:07:30 +00:00
* @ param $page_callback_result
* The result of a page callback . Can be one of :
* - NULL : to indicate no content .
* - An integer menu status constant : to indicate an error condition .
* - A string of HTML content .
* - A renderable array of content .
* @ param $default_delivery_callback
* ( Optional ) If given , it is the name of a delivery function most likely
* to be appropriate for the page request as determined by the calling
* function ( e . g . , menu_execute_active_handler ()) . If not given , it is
2010-07-01 00:46:57 +00:00
* determined from the menu router information of the current page .
2009-10-15 14:07:30 +00:00
*
* @ see menu_execute_active_handler ()
* @ see hook_menu ()
* @ see hook_menu_alter ()
* @ see hook_page_delivery_callback_alter ()
*/
function drupal_deliver_page ( $page_callback_result , $default_delivery_callback = NULL ) {
if ( ! isset ( $default_delivery_callback ) && ( $router_item = menu_get_item ())) {
$default_delivery_callback = $router_item [ 'delivery_callback' ];
}
$delivery_callback = ! empty ( $default_delivery_callback ) ? $default_delivery_callback : 'drupal_deliver_html_page' ;
2010-07-01 00:46:57 +00:00
// Give modules a chance to alter the delivery callback used, based on
// request-time context (e.g., HTTP request headers).
2009-10-15 14:07:30 +00:00
drupal_alter ( 'page_delivery_callback' , $delivery_callback );
if ( function_exists ( $delivery_callback )) {
$delivery_callback ( $page_callback_result );
}
else {
// If a delivery callback is specified, but doesn't exist as a function,
// something is wrong, but don't print anything, since it's not known
// what format the response needs to be in.
2011-07-04 16:58:33 +00:00
watchdog ( 'delivery callback not found' , 'callback %callback not found: %q.' , array ( '%callback' => $delivery_callback , '%q' => $_GET [ 'q' ]), WATCHDOG_ERROR );
2009-10-15 14:07:30 +00:00
}
}
/**
2010-01-27 11:41:20 +00:00
* Package and send the result of a page callback to the browser as HTML .
2009-10-15 14:07:30 +00:00
*
* @ param $page_callback_result
* The result of a page callback . Can be one of :
* - NULL : to indicate no content .
* - An integer menu status constant : to indicate an error condition .
* - A string of HTML content .
* - A renderable array of content .
*
2010-01-27 11:41:20 +00:00
* @ see drupal_deliver_page ()
2009-10-15 14:07:30 +00:00
*/
function drupal_deliver_html_page ( $page_callback_result ) {
2009-12-05 14:49:55 +00:00
// Emit the correct charset HTTP header, but not if the page callback
// result is NULL, since that likely indicates that it printed something
// in which case, no further headers may be sent, and not if code running
// for this page request has already set the content type header.
if ( isset ( $page_callback_result ) && is_null ( drupal_get_http_header ( 'Content-Type' ))) {
drupal_add_http_header ( 'Content-Type' , 'text/html; charset=utf-8' );
}
2011-10-27 15:16:55 +00:00
// Send appropriate HTTP-Header for browsers and search engines.
global $language ;
drupal_add_http_header ( 'Content-Language' , $language -> language );
2009-10-15 14:07:30 +00:00
// Menu status constants are integers; page content is a string or array.
if ( is_int ( $page_callback_result )) {
// @todo: Break these up into separate functions?
2009-10-16 13:18:32 +00:00
switch ( $page_callback_result ) {
2009-10-15 14:07:30 +00:00
case MENU_NOT_FOUND :
// Print a 404 page.
2010-03-06 06:31:24 +00:00
drupal_add_http_header ( 'Status' , '404 Not Found' );
2009-10-15 14:07:30 +00:00
2011-07-04 16:58:33 +00:00
watchdog ( 'page not found' , check_plain ( $_GET [ 'q' ]), NULL , WATCHDOG_WARNING );
2009-10-15 14:07:30 +00:00
2011-09-14 20:24:19 +00:00
// Check for and return a fast 404 page if configured.
drupal_fast_404 ();
2009-10-15 14:07:30 +00:00
// Keep old path for reference, and to allow forms to redirect to it.
if ( ! isset ( $_GET [ 'destination' ])) {
$_GET [ 'destination' ] = $_GET [ 'q' ];
}
$path = drupal_get_normal_path ( variable_get ( 'site_404' , '' ));
if ( $path && $path != $_GET [ 'q' ]) {
// Custom 404 handler. Set the active item in case there are tabs to
// display, or other dependencies on the path.
menu_set_active_item ( $path );
$return = menu_execute_active_handler ( $path , FALSE );
}
if ( empty ( $return ) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED ) {
// Standard 404 handler.
drupal_set_title ( t ( 'Page not found' ));
2011-09-14 20:24:19 +00:00
$return = t ( 'The requested page "@path" could not be found.' , array ( '@path' => request_uri ()));
2009-10-15 14:07:30 +00:00
}
drupal_set_page_content ( $return );
$page = element_info ( 'page' );
print drupal_render_page ( $page );
break ;
case MENU_ACCESS_DENIED :
// Print a 403 page.
2010-03-06 06:31:24 +00:00
drupal_add_http_header ( 'Status' , '403 Forbidden' );
2011-07-04 16:58:33 +00:00
watchdog ( 'access denied' , check_plain ( $_GET [ 'q' ]), NULL , WATCHDOG_WARNING );
2009-10-15 14:07:30 +00:00
// Keep old path for reference, and to allow forms to redirect to it.
if ( ! isset ( $_GET [ 'destination' ])) {
$_GET [ 'destination' ] = $_GET [ 'q' ];
}
$path = drupal_get_normal_path ( variable_get ( 'site_403' , '' ));
if ( $path && $path != $_GET [ 'q' ]) {
// Custom 403 handler. Set the active item in case there are tabs to
// display or other dependencies on the path.
menu_set_active_item ( $path );
$return = menu_execute_active_handler ( $path , FALSE );
}
if ( empty ( $return ) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED ) {
// Standard 403 handler.
drupal_set_title ( t ( 'Access denied' ));
$return = t ( 'You are not authorized to access this page.' );
}
print drupal_render_page ( $return );
break ;
case MENU_SITE_OFFLINE :
// Print a 503 page.
drupal_maintenance_theme ();
2010-03-06 06:31:24 +00:00
drupal_add_http_header ( 'Status' , '503 Service unavailable' );
2009-10-15 14:07:30 +00:00
drupal_set_title ( t ( 'Site under maintenance' ));
print theme ( 'maintenance_page' , array ( 'content' => filter_xss_admin ( variable_get ( 'maintenance_mode_message' ,
2009-10-16 13:18:32 +00:00
t ( '@site is currently under maintenance. We should be back shortly. Thank you for your patience.' , array ( '@site' => variable_get ( 'site_name' , 'Drupal' )))))));
2009-10-15 14:07:30 +00:00
break ;
}
}
elseif ( isset ( $page_callback_result )) {
// Print anything besides a menu constant, assuming it's not NULL or
// undefined.
print drupal_render_page ( $page_callback_result );
}
// Perform end-of-request tasks.
drupal_page_footer ();
}
2004-07-13 07:21:14 +00:00
/**
* Perform end - of - request tasks .
*
* This function sets the page cache if appropriate , and allows modules to
* react to the closing of the page by calling hook_exit () .
*/
2003-05-18 09:48:49 +00:00
function drupal_page_footer () {
2009-01-19 10:46:52 +00:00
global $user ;
2009-06-02 06:58:17 +00:00
module_invoke_all ( 'exit' );
2008-05-06 12:18:54 +00:00
2009-06-02 06:58:17 +00:00
// Commit the user session, if needed.
drupal_session_commit ();
2003-01-26 13:22:02 +00:00
2010-05-12 08:26:15 +00:00
if ( variable_get ( 'cache' , 0 ) && ( $cache = drupal_page_set_cache ())) {
2009-06-02 06:58:17 +00:00
drupal_serve_page_from_cache ( $cache );
}
else {
ob_flush ();
}
2008-05-06 12:18:54 +00:00
2008-11-11 22:39:59 +00:00
_registry_check_code ( REGISTRY_WRITE_LOOKUP_CACHE );
2009-05-16 19:07:02 +00:00
drupal_cache_system_paths ();
- Patch #557542 by CorniI, catch, fago, Crell, sun | pwolanin, chx, webchick, mattyoung, alexanderpas, justinrandell, dropcube, moshe weitzman, Damien Tournoud, Rob Loach, Dries: cache module_implements() for better performance and scalability.
2009-09-27 11:08:45 +00:00
module_implements_write_cache ();
2010-02-18 06:28:08 +00:00
system_run_automated_cron ();
2001-12-01 15:20:48 +00:00
}
2009-10-13 21:16:44 +00:00
/**
* Perform end - of - request tasks .
*
* In some cases page requests need to end without calling drupal_page_footer () .
* In these cases , call drupal_exit () instead . There should rarely be a reason
* to call exit instead of drupal_exit ();
*
* @ param $destination
* If this function is called from drupal_goto (), then this argument
* will be a fully - qualified URL that is the destination of the redirect .
* This should be passed along to hook_exit () implementations .
*/
function drupal_exit ( $destination = NULL ) {
if ( drupal_get_bootstrap_phase () == DRUPAL_BOOTSTRAP_FULL ) {
if ( ! defined ( 'MAINTENANCE_MODE' ) || MAINTENANCE_MODE != 'update' ) {
module_invoke_all ( 'exit' , $destination );
}
drupal_session_commit ();
}
exit ;
}
2004-02-12 19:37:04 +00:00
/**
2004-07-13 07:21:14 +00:00
* Form an associative array from a linear array .
2004-02-12 19:37:04 +00:00
*
2004-07-13 07:21:14 +00:00
* This function walks through the provided array and constructs an associative
* array out of it . The keys of the resulting array will be the values of the
* input array . The values will be the same as the keys unless a function is
* specified , in which case the output of the function is used for the values
* instead .
*
* @ param $array
* A linear array .
* @ param $function
2007-10-08 14:08:19 +00:00
* A name of a function to apply to all values before output .
2011-03-10 07:17:43 +00:00
*
* @ return
2004-07-13 07:21:14 +00:00
* An associative array .
2004-02-12 19:37:04 +00:00
*/
function drupal_map_assoc ( $array , $function = NULL ) {
2010-06-05 13:18:09 +00:00
// array_combine() fails with empty arrays:
// http://bugs.php.net/bug.php?id=34857.
$array = ! empty ( $array ) ? array_combine ( $array , $array ) : array ();
2010-01-06 21:39:07 +00:00
if ( is_callable ( $function )) {
$array = array_map ( $function , $array );
2004-02-12 19:37:04 +00:00
}
2010-01-06 21:39:07 +00:00
return $array ;
2004-02-12 19:37:04 +00:00
}
2009-08-05 15:58:35 +00:00
/**
* Attempts to set the PHP maximum execution time .
*
* This function is a wrapper around the PHP function set_time_limit () .
* When called , set_time_limit () restarts the timeout counter from zero .
* In other words , if the timeout is the default 30 seconds , and 25 seconds
* into script execution a call such as set_time_limit ( 20 ) is made , the
* script will run for a total of 45 seconds before timing out .
2009-08-15 06:20:20 +00:00
*
2009-08-05 15:58:35 +00:00
* It also means that it is possible to decrease the total time limit if
* the sum of the new time limit and the current time spent running the
* script is inferior to the original time limit . It is inherent to the way
* set_time_limit () works , it should rather be called with an appropriate
* value every time you need to allocate a certain amount of time
* to execute a task than only once at the beginning of the script .
2009-08-15 06:20:20 +00:00
*
2009-08-05 15:58:35 +00:00
* Before calling set_time_limit (), we check if this function is available
* because it could be disabled by the server administrator . We also hide all
* the errors that could occur when calling set_time_limit (), because it is
* not possible to reliably ensure that PHP or a security extension will
* not issue a warning / error if they prevent the use of this function .
*
* @ param $time_limit
* An integer specifying the new time limit , in seconds . A value of 0
* indicates unlimited execution time .
2009-09-28 22:22:54 +00:00
*
* @ ingroup php_wrappers
2009-08-05 15:58:35 +00:00
*/
function drupal_set_time_limit ( $time_limit ) {
if ( function_exists ( 'set_time_limit' )) {
@ set_time_limit ( $time_limit );
}
}
2004-11-24 22:44:01 +00:00
/**
* Returns the path to a system item ( module , theme , etc . ) .
*
* @ param $type
2010-02-17 03:55:45 +00:00
* The type of the item ( i . e . theme , theme_engine , module , profile ) .
2004-11-24 22:44:01 +00:00
* @ param $name
* The name of the item for which the path is requested .
*
* @ return
* The path to the requested item .
*/
function drupal_get_path ( $type , $name ) {
return dirname ( drupal_get_filename ( $type , $name ));
}
2006-02-02 12:44:57 +00:00
/**
2009-05-01 16:45:52 +00:00
* Return the base URL path ( i . e . , directory ) of the Drupal installation .
2009-04-26 07:50:51 +00:00
*
2009-05-24 17:39:35 +00:00
* base_path () prefixes and suffixes a " / " onto the returned path if the path is
2009-05-01 16:45:52 +00:00
* not empty . At the very least , this will return " / " .
2009-04-26 07:50:51 +00:00
*
2009-05-01 16:45:52 +00:00
* Examples :
* - http :// example . com returns " / " because the path is empty .
* - http :// example . com / drupal / folder returns " /drupal/folder/ " .
2006-02-02 12:44:57 +00:00
*/
function base_path () {
return $GLOBALS [ 'base_path' ];
}
2005-05-31 21:14:27 +00:00
/**
2009-11-03 06:47:23 +00:00
* Add a LINK tag with a distinct 'rel' attribute to the page ' s HEAD .
2008-09-18 10:40:28 +00:00
*
2009-11-03 06:47:23 +00:00
* This function can be called as long the HTML header hasn ' t been sent ,
* which on normal pages is up through the preprocess step of theme ( 'html' ) .
* Adding a link will overwrite a prior link with the exact same 'rel' and
* 'href' attributes .
2009-12-15 08:45:32 +00:00
*
2009-11-03 06:47:23 +00:00
* @ param $attributes
* Associative array of element attributes including 'href' and 'rel' .
* @ param $header
* Optional flag to determine if a HTTP 'Link:' header should be sent .
*/
function drupal_add_html_head_link ( $attributes , $header = FALSE ) {
$element = array (
'#tag' => 'link' ,
'#attributes' => $attributes ,
);
$href = $attributes [ 'href' ];
if ( $header ) {
// Also add a HTTP header "Link:".
$href = '<' . check_plain ( $attributes [ 'href' ]) . '>;' ;
unset ( $attributes [ 'href' ]);
$element [ '#attached' ][ 'drupal_add_http_header' ][] = array ( 'Link' , $href . drupal_http_header_attributes ( $attributes ), TRUE );
}
drupal_add_html_head ( $element , 'drupal_add_html_head_link:' . $attributes [ 'rel' ] . ':' . $href );
2005-05-31 21:14:27 +00:00
}
2006-08-03 07:06:36 +00:00
/**
2009-03-30 05:13:45 +00:00
* Adds a cascading stylesheet to the stylesheet queue .
*
2009-05-16 13:26:31 +00:00
* Calling drupal_static_reset ( 'drupal_add_css' ) will clear all cascading
* stylesheets added so far .
*
2010-07-30 02:47:28 +00:00
* If CSS aggregation / compression is enabled , all cascading style sheets added
* with $options [ 'preprocess' ] set to TRUE will be merged into one aggregate
* file and compressed by removing all extraneous white space .
* Preprocessed inline stylesheets will not be aggregated into this single file ;
* instead , they are just compressed upon output on the page . Externally hosted
* stylesheets are never aggregated or compressed .
*
* The reason for aggregating the files is outlined quite thoroughly here :
2009-12-05 22:16:44 +00:00
* http :// www . die . net / musings / page_load_time / " Load fewer external objects. Due
* to request overhead , one bigger file just loads faster than two smaller ones
* half its size . "
*
2010-07-30 02:47:28 +00:00
* $options [ 'preprocess' ] should be only set to TRUE when a file is required for
* all typical visitors and most pages of a site . It is critical that all
* preprocessed files are added unconditionally on every page , even if the
* files do not happen to be needed on a page . This is normally done by calling
* drupal_add_css () in a hook_init () implementation .
2009-12-05 22:16:44 +00:00
*
2010-07-30 02:47:28 +00:00
* Non - preprocessed files should only be added to the page when they are
* actually needed .
2009-12-05 22:16:44 +00:00
*
2009-03-30 05:13:45 +00:00
* @ param $data
* ( optional ) The stylesheet data to be added , depending on what is passed
* through to the $options [ 'type' ] parameter :
2011-07-28 19:32:16 +00:00
* - 'file' : The path to the CSS file relative to the base_path (), or a
* stream wrapper URI . For example : " modules/devel/devel.css " or
* " public://generated_css/stylesheet_1.css " . Note that Modules should
* always prefix the names of their CSS files with the module name ; for
* example , system - menus . css rather than simply menus . css . Themes can
* override module - supplied CSS files based on their filenames , and this
* prefixing helps prevent confusing name collisions for theme developers .
* See drupal_get_css () where the overrides are performed . Also , if the
2009-12-05 22:16:44 +00:00
* direction of the current language is right - to - left ( Hebrew , Arabic ,
* etc . ), the function will also look for an RTL CSS file and append it to
* the list . The name of this file should have an '-rtl.css' suffix . For
* example a CSS file called 'mymodule-name.css' will have a
2009-03-30 05:13:45 +00:00
* 'mymodule-name-rtl.css' file added to the list , if exists in the same
* directory . This CSS file should contain overrides for properties which
* should be reversed or otherwise different in a right - to - left display .
* - 'inline' : A string of CSS that should be placed in the given scope . Note
2009-12-05 22:16:44 +00:00
* that it is better practice to use 'file' stylesheets , rather than
* 'inline' , as the CSS would then be aggregated and cached .
2009-08-14 16:15:38 +00:00
* - 'external' : The absolute path to an external CSS file that is not hosted
2009-12-05 22:16:44 +00:00
* on the local server . These files will not be aggregated if CSS
* aggregation is enabled .
2008-10-26 18:06:39 +00:00
* @ param $options
2009-03-30 05:13:45 +00:00
* ( optional ) A string defining the 'type' of CSS that is being added in the
2009-12-05 22:16:44 +00:00
* $data parameter ( 'file' , 'inline' , or 'external' ), or an array which can
* have any or all of the following keys :
2009-08-14 16:15:38 +00:00
* - 'type' : The type of stylesheet being added . Available options are 'file' ,
* 'inline' or 'external' . Defaults to 'file' .
2010-03-21 21:14:30 +00:00
* - 'basename' : Force a basename for the file being added . Modules are
* expected to use stylesheets with unique filenames , but integration of
* external libraries may make this impossible . The basename of
2011-10-31 04:05:57 +00:00
* 'core/modules/node/node.css' is 'node.css' . If the external library
* " node.js " ships with a 'node.css' , then a different , unique basename
* would be 'node.js.css' .
2010-10-05 19:59:10 +00:00
* - 'group' : A number identifying the group in which to add the stylesheet .
* Available constants are :
2009-12-05 22:16:44 +00:00
* - CSS_SYSTEM : Any system - layer CSS .
* - CSS_DEFAULT : Any module - layer CSS .
* - CSS_THEME : Any theme - layer CSS .
2010-10-05 19:59:10 +00:00
* The group number serves as a weight : the markup for loading a stylesheet
* within a lower weight group is output to the page before the markup for
* loading a stylesheet within a higher weight group , so CSS within higher
* weight groups take precendence over CSS within lower weight groups .
* - 'every_page' : For optimal front - end performance when aggregation is
* enabled , this should be set to TRUE if the stylesheet is present on every
* page of the website for users for whom it is present at all . This
* defaults to FALSE . It is set to TRUE for stylesheets added via module and
* theme . info files . Modules that add stylesheets within hook_init ()
* implementations , or from other code that ensures that the stylesheet is
* added to all website pages , should also set this flag to TRUE . All
* stylesheets within the same group that have the 'every_page' flag set to
* TRUE and do not have 'preprocess' set to FALSE are aggregated together
* into a single aggregate file , and that aggregate file can be reused
* across a user ' s entire site visit , leading to faster navigation between
* pages . However , stylesheets that are only needed on pages less frequently
* visited , can be added by code that only runs for those particular pages ,
* and that code should not set the 'every_page' flag . This minimizes the
* size of the aggregate file that the user needs to download when first
* visiting the website . Stylesheets without the 'every_page' flag are
* aggregated into a separate aggregate file . This other aggregate file is
* likely to change from page to page , and each new aggregate file needs to
* be downloaded when first encountered , so it should be kept relatively
* small by ensuring that most commonly needed stylesheets are added to
* every page .
* - 'weight' : The weight of the stylesheet specifies the order in which the
* CSS will appear relative to other stylesheets with the same group and
* 'every_page' flag . The exact ordering of stylesheets is as follows :
* - First by group .
* - Then by the 'every_page' flag , with TRUE coming before FALSE .
* - Then by weight .
* - Then by the order in which the CSS was added . For example , all else
* being the same , a stylesheet added by a call to drupal_add_css () that
* happened later in the page request gets added to the page after one for
* which drupal_add_css () happened earlier in the page request .
2009-03-30 05:13:45 +00:00
* - 'media' : The media type for the stylesheet , e . g . , all , print , screen .
* Defaults to 'all' .
2010-07-30 02:47:28 +00:00
* - 'preprocess' : If TRUE and CSS aggregation / compression is enabled , the
2010-10-05 19:59:10 +00:00
* styles will be aggregated and compressed . Defaults to TRUE .
2010-02-25 20:57:39 +00:00
* - 'browsers' : An array containing information specifying which browsers
* should load the CSS item . See drupal_pre_render_conditional_comments ()
* for details .
2009-12-05 22:16:44 +00:00
*
2006-08-03 07:06:36 +00:00
* @ return
2009-03-30 05:13:45 +00:00
* An array of queued cascading stylesheets .
2011-05-30 05:41:06 +00:00
*
* @ see drupal_get_css ()
2006-08-03 07:06:36 +00:00
*/
2009-03-30 05:13:45 +00:00
function drupal_add_css ( $data = NULL , $options = NULL ) {
2009-05-16 13:26:31 +00:00
$css = & drupal_static ( __FUNCTION__ , array ());
2006-12-10 09:54:35 +00:00
2009-01-22 03:56:12 +00:00
// Construct the options, taking the defaults into consideration.
if ( isset ( $options )) {
if ( ! is_array ( $options )) {
$options = array ( 'type' => $options );
}
}
else {
$options = array ();
}
2006-12-10 09:54:35 +00:00
// Create an array of CSS files for each media type first, since each type needs to be served
// to the browser differently.
2009-03-30 05:13:45 +00:00
if ( isset ( $data )) {
2008-10-26 18:06:39 +00:00
$options += array (
2009-07-30 19:57:10 +00:00
'type' => 'file' ,
2010-10-05 19:59:10 +00:00
'group' => CSS_DEFAULT ,
'weight' => 0 ,
'every_page' => FALSE ,
2008-10-26 18:06:39 +00:00
'media' => 'all' ,
2010-10-05 19:59:10 +00:00
'preprocess' => TRUE ,
2009-07-30 19:57:10 +00:00
'data' => $data ,
2010-02-25 20:57:39 +00:00
'browsers' => array (),
);
$options [ 'browsers' ] += array (
'IE' => TRUE ,
'!IE' => TRUE ,
2008-10-29 10:06:06 +00:00
);
2008-10-26 18:06:39 +00:00
2010-10-05 19:59:10 +00:00
// Files with a query string cannot be preprocessed.
if ( $options [ 'type' ] === 'file' && $options [ 'preprocess' ] && strpos ( $options [ 'data' ], '?' ) !== FALSE ) {
$options [ 'preprocess' ] = FALSE ;
}
2009-07-30 19:57:10 +00:00
// Always add a tiny value to the weight, to conserve the insertion order.
$options [ 'weight' ] += count ( $css ) / 1000 ;
2006-08-03 07:06:36 +00:00
2009-07-30 19:57:10 +00:00
// Add the data to the CSS array depending on the type.
switch ( $options [ 'type' ]) {
case 'inline' :
// For inline stylesheets, we don't want to use the $data as the array
// key as $data could be a very long string of CSS.
$css [] = $options ;
break ;
2009-08-14 16:15:38 +00:00
default :
// Local and external files must keep their name as the associative key
// so the same CSS file is not be added twice.
$css [ $data ] = $options ;
2007-05-27 17:57:48 +00:00
}
}
2007-05-27 20:31:13 +00:00
2006-08-03 07:06:36 +00:00
return $css ;
}
/**
* Returns a themed representation of all stylesheets that should be attached to the page .
2007-10-08 14:08:19 +00:00
*
2008-02-20 13:38:32 +00:00
* It loads the CSS in order , with 'module' first , then 'theme' afterwards .
* This ensures proper cascading of styles so themes can easily override
* module styles through CSS selectors .
*
* Themes may replace module - defined CSS files by adding a stylesheet with the
2010-08-08 19:35:49 +00:00
* same filename . For example , themes / bartik / system - menus . css would replace
2008-02-20 13:38:32 +00:00
* modules / system / system - menus . css . This allows themes to override complete
* CSS files , rather than specific selectors , when necessary .
*
* If the original CSS file is being overridden by a theme , the theme is
* responsible for supplying an accompanying RTL CSS file to replace the
* module ' s .
2006-08-03 07:06:36 +00:00
*
* @ param $css
2007-10-08 14:08:19 +00:00
* ( optional ) An array of CSS files . If no array is provided , the default
* stylesheets array is used instead .
2010-10-04 17:46:01 +00:00
* @ param $skip_alter
* ( optional ) If set to TRUE , this function skips calling drupal_alter () on
* $css , useful when the calling function passes a $css array that has already
* been altered .
2011-05-30 05:41:06 +00:00
*
2006-08-03 07:06:36 +00:00
* @ return
* A string of XHTML CSS tags .
2011-05-30 05:41:06 +00:00
*
* @ see drupal_add_css ()
2006-08-03 07:06:36 +00:00
*/
2010-10-04 17:46:01 +00:00
function drupal_get_css ( $css = NULL , $skip_alter = FALSE ) {
2006-12-10 09:54:35 +00:00
if ( ! isset ( $css )) {
2006-08-03 07:06:36 +00:00
$css = drupal_add_css ();
}
2010-06-25 18:56:11 +00:00
// Allow modules and themes to alter the CSS items.
2010-10-04 17:46:01 +00:00
if ( ! $skip_alter ) {
drupal_alter ( 'css' , $css );
}
2009-07-30 19:57:10 +00:00
2010-10-05 19:59:10 +00:00
// Sort CSS items, so that they appear in the correct order.
uasort ( $css , 'drupal_sort_css_js' );
2009-07-30 19:57:10 +00:00
2010-01-25 10:38:35 +00:00
// Remove the overridden CSS files. Later CSS files override former ones.
2009-07-30 19:57:10 +00:00
$previous_item = array ();
foreach ( $css as $key => $item ) {
if ( $item [ 'type' ] == 'file' ) {
2010-03-21 21:14:30 +00:00
// If defined, force a unique basename for this file.
$basename = isset ( $item [ 'basename' ]) ? $item [ 'basename' ] : basename ( $item [ 'data' ]);
2009-07-30 19:57:10 +00:00
if ( isset ( $previous_item [ $basename ])) {
// Remove the previous item that shared the same base name.
unset ( $css [ $previous_item [ $basename ]]);
2008-02-20 13:38:32 +00:00
}
2009-07-30 19:57:10 +00:00
$previous_item [ $basename ] = $key ;
}
}
2010-02-25 20:57:39 +00:00
// Render the HTML needed to load the CSS.
$styles = array (
'#type' => 'styles' ,
'#items' => $css ,
);
2010-10-04 17:46:01 +00:00
// Provide the page with information about the individual CSS files used,
// information not otherwise available when CSS aggregation is enabled.
$setting [ 'ajaxPageState' ][ 'css' ] = array_fill_keys ( array_keys ( $css ), 1 );
$styles [ '#attached' ][ 'js' ][] = array ( 'type' => 'setting' , 'data' => $setting );
2010-02-25 20:57:39 +00:00
return drupal_render ( $styles );
}
2010-10-05 19:59:10 +00:00
/**
* Function used by uasort to sort the array structures returned by drupal_add_css () and drupal_add_js () .
*
* This sort order helps optimize front - end performance while providing modules
* and themes with the necessary control for ordering the CSS and JavaScript
* appearing on a page .
*/
function drupal_sort_css_js ( $a , $b ) {
// First order by group, so that, for example, all items in the CSS_SYSTEM
2010-12-21 19:16:31 +00:00
// group appear before items in the CSS_DEFAULT group, which appear before
2010-10-05 19:59:10 +00:00
// all items in the CSS_THEME group. Modules may create additional groups by
// defining their own constants.
if ( $a [ 'group' ] < $b [ 'group' ]) {
return - 1 ;
}
elseif ( $a [ 'group' ] > $b [ 'group' ]) {
return 1 ;
}
// Within a group, order all infrequently needed, page-specific files after
// common files needed throughout the website. Separating this way allows for
// the aggregate file generated for all of the common files to be reused
// across a site visit without being cut by a page using a less common file.
elseif ( $a [ 'every_page' ] && ! $b [ 'every_page' ]) {
return - 1 ;
}
elseif ( ! $a [ 'every_page' ] && $b [ 'every_page' ]) {
return 1 ;
}
// Finally, order by weight.
elseif ( $a [ 'weight' ] < $b [ 'weight' ]) {
return - 1 ;
}
elseif ( $a [ 'weight' ] > $b [ 'weight' ]) {
return 1 ;
}
else {
return 0 ;
}
}
2010-02-25 20:57:39 +00:00
/**
* Default callback to group CSS items .
*
* This function arranges the CSS items that are in the #items property of the
* styles element into groups . Arranging the CSS items into groups serves two
* purposes . When aggregation is enabled , files within a group are aggregated
* into a single file , significantly improving page loading performance by
* minimizing network traffic overhead . When aggregation is disabled , grouping
* allows multiple files to be loaded from a single STYLE tag , enabling sites
* with many modules enabled or a complex theme being used to stay within IE ' s
* 31 CSS inclusion tag limit : http :// drupal . org / node / 228818.
*
* This function puts multiple items into the same group if they are groupable
* and if they are for the same 'media' and 'browsers' . Items of the 'file' type
* are groupable if their 'preprocess' flag is TRUE , items of the 'inline' type
* are always groupable , and items of the 'external' type are never groupable .
* This function also ensures that the process of grouping items does not change
* their relative order . This requirement may result in multiple groups for the
2011-09-24 20:44:06 +00:00
* same type , media , and browsers , if needed to accommodate other items in
2010-02-25 20:57:39 +00:00
* between .
*
* @ param $css
* An array of CSS items , as returned by drupal_add_css (), but after
* alteration performed by drupal_get_css () .
*
* @ return
* An array of CSS groups . Each group contains the same keys ( e . g . , 'media' ,
* 'data' , etc . ) as a CSS item from the $css parameter , with the value of
* each key applying to the group as a whole . Each group also contains an
* 'items' key , which is the subset of items from $css that are in the group .
*
* @ see drupal_pre_render_styles ()
*/
function drupal_group_css ( $css ) {
$groups = array ();
// If a group can contain multiple items, we track the information that must
// be the same for each item in the group, so that when we iterate the next
// item, we can determine if it can be put into the current group, or if a
// new group needs to be made for it.
$current_group_keys = NULL ;
// When creating a new group, we pre-increment $i, so by initializing it to
// -1, the first group will have index 0.
$i = - 1 ;
foreach ( $css as $item ) {
// The browsers for which the CSS item needs to be loaded is part of the
// information that determines when a new group is needed, but the order of
// keys in the array doesn't matter, and we don't want a new group if all
// that's different is that order.
ksort ( $item [ 'browsers' ]);
// If the item can be grouped with other items, set $group_keys to an array
// of information that must be the same for all items in its group. If the
// item can't be grouped with other items, set $group_keys to FALSE. We
// put items into a group that can be aggregated together: whether they will
// be aggregated is up to the _drupal_css_aggregate() function or an
// override of that function specified in hook_css_alter(), but regardless
// of the details of that function, a group represents items that can be
// aggregated. Since a group may be rendered with a single HTML tag, all
// items in the group must share the same information that would need to be
// part of that HTML tag.
switch ( $item [ 'type' ]) {
case 'file' :
// Group file items if their 'preprocess' flag is TRUE.
2010-10-05 19:59:10 +00:00
// Help ensure maximum reuse of aggregate files by only grouping
// together items that share the same 'group' value and 'every_page'
// flag. See drupal_add_css() for details about that.
$group_keys = $item [ 'preprocess' ] ? array ( $item [ 'type' ], $item [ 'group' ], $item [ 'every_page' ], $item [ 'media' ], $item [ 'browsers' ]) : FALSE ;
2010-02-25 20:57:39 +00:00
break ;
case 'inline' :
// Always group inline items.
$group_keys = array ( $item [ 'type' ], $item [ 'media' ], $item [ 'browsers' ]);
break ;
case 'external' :
// Do not group external items.
$group_keys = FALSE ;
break ;
}
// If the group keys don't match the most recent group we're working with,
// then a new group must be made.
if ( $group_keys !== $current_group_keys ) {
$i ++ ;
// Initialize the new group with the same properties as the first item
// being placed into it. The item's 'data' and 'weight' properties are
// unique to the item and should not be carried over to the group.
$groups [ $i ] = $item ;
unset ( $groups [ $i ][ 'data' ], $groups [ $i ][ 'weight' ]);
$groups [ $i ][ 'items' ] = array ();
$current_group_keys = $group_keys ? $group_keys : NULL ;
}
// Add the item to the current group.
$groups [ $i ][ 'items' ][] = $item ;
}
return $groups ;
}
/**
* Default callback to aggregate CSS files and inline content .
*
* Having the browser load fewer CSS files results in much faster page loads
* than when it loads many small files . This function aggregates files within
* the same group into a single file unless the site - wide setting to do so is
* disabled ( commonly the case during site development ) . To optimize download ,
* it also compresses the aggregate files by removing comments , whitespace , and
* other unnecessary content . Additionally , this functions aggregates inline
* content together , regardless of the site - wide aggregation setting .
*
* @ param $css_groups
* An array of CSS groups as returned by drupal_group_css () . This function
* modifies the group 's ' data ' property for each group that is aggregated .
*
* @ see drupal_group_css ()
* @ see drupal_pre_render_styles ()
*/
function drupal_aggregate_css ( & $css_groups ) {
$preprocess_css = ( variable_get ( 'preprocess_css' , FALSE ) && ( ! defined ( 'MAINTENANCE_MODE' ) || MAINTENANCE_MODE != 'update' ));
// For each group that needs aggregation, aggregate its items.
foreach ( $css_groups as $key => $group ) {
switch ( $group [ 'type' ]) {
// If a file group can be aggregated into a single file, do so, and set
// the group's data property to the file path of the aggregate file.
case 'file' :
2010-03-31 15:35:29 +00:00
if ( $group [ 'preprocess' ] && $preprocess_css ) {
2010-05-09 19:44:25 +00:00
$css_groups [ $key ][ 'data' ] = drupal_build_css_cache ( $group [ 'items' ]);
2010-02-25 20:57:39 +00:00
}
break ;
// Aggregate all inline CSS content into the group's data property.
case 'inline' :
$css_groups [ $key ][ 'data' ] = '' ;
foreach ( $group [ 'items' ] as $item ) {
$css_groups [ $key ][ 'data' ] .= drupal_load_stylesheet_content ( $item [ 'data' ], $item [ 'preprocess' ]);
}
break ;
}
}
}
/**
* #pre_render callback to add the elements needed for CSS tags to be rendered.
*
* For production websites , LINK tags are preferable to STYLE tags with @ import
* statements , because :
* - They are the standard tag intended for linking to a resource .
* - On Firefox 2 and perhaps other browsers , CSS files included with @ import
* statements don ' t get saved when saving the complete web page for offline
* use : http :// drupal . org / node / 145218.
* - On IE , if only LINK tags and no @ import statements are used , all the CSS
* files are downloaded in parallel , resulting in faster page load , but if
* @ import statements are used and span across multiple STYLE tags , all the
* ones from one STYLE tag must be downloaded before downloading begins for
* the next STYLE tag . Furthermore , IE7 does not support media declaration on
* the @ import statement , so multiple STYLE tags must be used when different
* files are for different media types . Non - IE browsers always download in
* parallel , so this is an IE - specific performance quirk :
* http :// www . stevesouders . com / blog / 2009 / 04 / 09 / dont - use - import /.
*
* However , IE has an annoying limit of 31 total CSS inclusion tags
* ( http :// drupal . org / node / 228818 ) and LINK tags are limited to one file per
* tag , whereas STYLE tags can contain multiple @ import statements allowing
* multiple files to be loaded per tag . When CSS aggregation is disabled , a
* Drupal site can easily have more than 31 CSS files that need to be loaded , so
* using LINK tags exclusively would result in a site that would display
* incorrectly in IE . Depending on different needs , different strategies can be
* employed to decide when to use LINK tags and when to use STYLE tags .
*
* The strategy employed by this function is to use LINK tags for all aggregate
* files and for all files that cannot be aggregated ( e . g . , if 'preprocess' is
* set to FALSE or the type is 'external' ), and to use STYLE tags for groups
* of files that could be aggregated together but aren ' t ( e . g . , if the site - wide
* aggregation setting is disabled ) . This results in all LINK tags when
* aggregation is enabled , a guarantee that as many or only slightly more tags
* are used with aggregation disabled than enabled ( so that if the limit were to
* be crossed with aggregation enabled , the site developer would also notice the
* problem while aggregation is disabled ), and an easy way for a developer to
* view HTML source while aggregation is disabled and know what files will be
* aggregated together when aggregation becomes enabled .
*
* This function evaluates the aggregation enabled / disabled condition on a group
* by group basis by testing whether an aggregate file has been made for the
* group rather than by testing the site - wide aggregation setting . This allows
* this function to work correctly even if modules have implemented custom
* logic for grouping and aggregating files .
*
* @ param $element
* A render array containing :
* - '#items' : The CSS items as returned by drupal_add_css () and altered by
* drupal_get_css () .
* - '#group_callback' : A function to call to group #items to enable the use
* of fewer tags by aggregating files and / or using multiple @ import
* statements within a single tag .
* - '#aggregate_callback' : A function to call to aggregate the items within
* the groups arranged by the #group_callback function.
*
* @ return
* A render array that will render to a string of XHTML CSS tags .
*
* @ see drupal_get_css ()
*/
function drupal_pre_render_styles ( $elements ) {
// Group and aggregate the items.
if ( isset ( $elements [ '#group_callback' ])) {
$elements [ '#groups' ] = $elements [ '#group_callback' ]( $elements [ '#items' ]);
}
if ( isset ( $elements [ '#aggregate_callback' ])) {
$elements [ '#aggregate_callback' ]( $elements [ '#groups' ]);
}
// A dummy query-string is added to filenames, to gain control over
// browser-caching. The string changes on every update or full cache
// flush, forcing browsers to load a new copy of the files, as the
// URL changed.
2010-03-23 21:44:10 +00:00
$query_string = variable_get ( 'css_js_query_string' , '0' );
2010-02-25 20:57:39 +00:00
2010-11-22 05:22:43 +00:00
// For inline CSS to validate as XHTML, all CSS containing XHTML needs to be
// wrapped in CDATA. To make that backwards compatible with HTML 4, we need to
// comment out the CDATA-tag.
$embed_prefix = " \n <!--/*--><![CDATA[/*><!--*/ \n " ;
$embed_suffix = " \n /*]]>*/--> \n " ;
2010-02-25 20:57:39 +00:00
// Defaults for LINK and STYLE elements.
$link_element_defaults = array (
'#type' => 'html_tag' ,
2009-11-03 06:47:23 +00:00
'#tag' => 'link' ,
'#attributes' => array (
'rel' => 'stylesheet' ,
),
);
2010-02-25 20:57:39 +00:00
$style_element_defaults = array (
'#type' => 'html_tag' ,
'#tag' => 'style' ,
);
// Loop through each group.
foreach ( $elements [ '#groups' ] as $group ) {
switch ( $group [ 'type' ]) {
// For file items, there are three possibilites.
// - The group has been aggregated: in this case, output a LINK tag for
// the aggregate file.
// - The group can be aggregated but has not been (most likely because
// the site administrator disabled the site-wide setting): in this case,
// output as few STYLE tags for the group as possible, using @import
// statement for each file in the group. This enables us to stay within
// IE's limit of 31 total CSS inclusion tags.
// - The group contains items not eligible for aggregation (their
// 'preprocess' flag has been set to FALSE): in this case, output a LINK
2010-03-06 06:39:01 +00:00
// tag for each file.
2009-07-30 19:57:10 +00:00
case 'file' :
2010-02-25 20:57:39 +00:00
// The group has been aggregated into a single file: output a LINK tag
// for the aggregate file.
if ( isset ( $group [ 'data' ])) {
$element = $link_element_defaults ;
2010-05-14 03:38:49 +00:00
$element [ '#attributes' ][ 'href' ] = file_create_url ( $group [ 'data' ]);
2010-02-25 20:57:39 +00:00
$element [ '#attributes' ][ 'media' ] = $group [ 'media' ];
$element [ '#browsers' ] = $group [ 'browsers' ];
$elements [] = $element ;
}
// The group can be aggregated, but hasn't been: combine multiple items
// into as few STYLE tags as possible.
elseif ( $group [ 'preprocess' ]) {
$import = array ();
foreach ( $group [ 'items' ] as $item ) {
2010-10-03 05:11:16 +00:00
// A theme's .info file may have an entry for a file that doesn't
// exist as a way of overriding a module or base theme CSS file from
// being added to the page. Normally, file_exists() calls that need
// to run for every page request should be minimized, but this one
// is okay, because it only runs when CSS aggregation is disabled.
// On a server under heavy enough load that file_exists() calls need
// to be minimized, CSS aggregation should be enabled, in which case
// this code is not run. When aggregation is enabled,
// drupal_load_stylesheet() checks file_exists(), but only when
// building the aggregate file, which is then reused for many page
// requests.
if ( file_exists ( $item [ 'data' ])) {
// The dummy query string needs to be added to the URL to control
// browser-caching. IE7 does not support a media type on the
// @import statement, so we instead specify the media for the
// group on the STYLE tag.
$import [] = '@import url("' . check_plain ( file_create_url ( $item [ 'data' ]) . '?' . $query_string ) . '");' ;
}
2010-02-25 20:57:39 +00:00
}
// In addition to IE's limit of 31 total CSS inclusion tags, it also
// has a limit of 31 @import statements per STYLE tag.
while ( ! empty ( $import )) {
$import_batch = array_slice ( $import , 0 , 31 );
$import = array_slice ( $import , 31 );
$element = $style_element_defaults ;
$element [ '#value' ] = implode ( " \n " , $import_batch );
$element [ '#attributes' ][ 'media' ] = $group [ 'media' ];
$element [ '#browsers' ] = $group [ 'browsers' ];
$elements [] = $element ;
}
2009-03-30 05:13:45 +00:00
}
2010-02-25 20:57:39 +00:00
// The group contains items ineligible for aggregation: output a LINK
// tag for each file.
2009-07-30 19:57:10 +00:00
else {
2010-02-25 20:57:39 +00:00
foreach ( $group [ 'items' ] as $item ) {
$element = $link_element_defaults ;
2010-10-03 05:11:16 +00:00
// We do not check file_exists() here, because this code runs for
// files whose 'preprocess' is set to FALSE, and therefore, even
// when aggregation is enabled, and we want to avoid needlessly
// taxing a server that may be under heavy load. The file_exists()
// performed above for files whose 'preprocess' is TRUE is done for
// the benefit of theme .info files, but code that deals with files
// whose 'preprocess' is FALSE is responsible for ensuring the file
// exists.
2010-02-25 20:57:39 +00:00
// The dummy query string needs to be added to the URL to control
// browser-caching.
2010-03-02 08:47:41 +00:00
$query_string_separator = ( strpos ( $item [ 'data' ], '?' ) !== FALSE ) ? '&' : '?' ;
$element [ '#attributes' ][ 'href' ] = file_create_url ( $item [ 'data' ]) . $query_string_separator . $query_string ;
2010-02-25 20:57:39 +00:00
$element [ '#attributes' ][ 'media' ] = $item [ 'media' ];
$element [ '#browsers' ] = $group [ 'browsers' ];
$elements [] = $element ;
}
2006-12-10 09:54:35 +00:00
}
2009-07-30 19:57:10 +00:00
break ;
2010-02-25 20:57:39 +00:00
// For inline content, the 'data' property contains the CSS content. If
// the group's 'data' property is set, then output it in a single STYLE
// tag. Otherwise, output a separate STYLE tag for each item.
2009-07-30 19:57:10 +00:00
case 'inline' :
2010-02-25 20:57:39 +00:00
if ( isset ( $group [ 'data' ])) {
$element = $style_element_defaults ;
$element [ '#value' ] = $group [ 'data' ];
2010-11-22 05:22:43 +00:00
$element [ '#value_prefix' ] = $embed_prefix ;
$element [ '#value_suffix' ] = $embed_suffix ;
2010-02-25 20:57:39 +00:00
$element [ '#attributes' ][ 'media' ] = $group [ 'media' ];
$element [ '#browsers' ] = $group [ 'browsers' ];
$elements [] = $element ;
}
else {
foreach ( $group [ 'items' ] as $item ) {
$element = $style_element_defaults ;
$element [ '#value' ] = $item [ 'data' ];
2010-11-22 05:22:43 +00:00
$element [ '#value_prefix' ] = $embed_prefix ;
$element [ '#value_suffix' ] = $embed_suffix ;
2010-02-25 20:57:39 +00:00
$element [ '#attributes' ][ 'media' ] = $item [ 'media' ];
$element [ '#browsers' ] = $group [ 'browsers' ];
$elements [] = $element ;
}
}
2009-07-30 19:57:10 +00:00
break ;
2010-02-25 20:57:39 +00:00
// Output a LINK tag for each external item. The item's 'data' property
// contains the full URL.
2009-08-14 16:15:38 +00:00
case 'external' :
2010-02-25 20:57:39 +00:00
foreach ( $group [ 'items' ] as $item ) {
$element = $link_element_defaults ;
$element [ '#attributes' ][ 'href' ] = $item [ 'data' ];
$element [ '#attributes' ][ 'media' ] = $item [ 'media' ];
$element [ '#browsers' ] = $group [ 'browsers' ];
$elements [] = $element ;
}
2009-08-14 16:15:38 +00:00
break ;
2006-12-10 09:54:35 +00:00
}
2009-07-30 19:57:10 +00:00
}
2006-12-10 20:34:03 +00:00
2010-02-25 20:57:39 +00:00
return $elements ;
2006-12-10 09:54:35 +00:00
}
/**
2010-05-09 19:44:25 +00:00
* Aggregates and optimizes CSS files into a cache file in the files directory .
*
* The file name for the CSS cache file is generated from the hash of the
* aggregated contents of the files in $css . This forces proxies and browsers
* to download new CSS when the CSS changes .
*
* The cache file name is retrieved on a page load via a lookup variable that
* contains an associative array . The array key is the hash of the file names
* in $css while the value is the cache file name . The cache file is generated
* in two cases . First , if there is no file name value for the key , which will
* happen if a new file name has been added to $css or after the lookup
* variable is emptied to force a rebuild of the cache . Second , the cache
* file is generated if it is missing on disk . Old cache files are not deleted
* immediately when the lookup variable is emptied , but are deleted after a set
* period by drupal_delete_file_if_stale () . This ensures that files referenced
* by a cached page will still be available .
2006-12-10 09:54:35 +00:00
*
2009-07-30 19:57:10 +00:00
* @ param $css
* An array of CSS files to aggregate and compress into one file .
2010-05-09 19:44:25 +00:00
*
2006-12-10 09:54:35 +00:00
* @ return
2010-05-09 19:44:25 +00:00
* The URI of the CSS cache file , or FALSE if the file could not be saved .
2006-12-10 09:54:35 +00:00
*/
2010-05-09 19:44:25 +00:00
function drupal_build_css_cache ( $css ) {
2006-12-10 09:54:35 +00:00
$data = '' ;
2010-05-09 19:44:25 +00:00
$uri = '' ;
$map = variable_get ( 'drupal_css_cache_files' , array ());
$key = hash ( 'sha256' , serialize ( $css ));
if ( isset ( $map [ $key ])) {
$uri = $map [ $key ];
}
2006-12-10 09:54:35 +00:00
2010-05-09 19:44:25 +00:00
if ( empty ( $uri ) || ! file_exists ( $uri )) {
2006-12-10 09:54:35 +00:00
// Build aggregate CSS file.
2009-07-30 19:57:10 +00:00
foreach ( $css as $stylesheet ) {
// Only 'file' stylesheets can be aggregated.
if ( $stylesheet [ 'type' ] == 'file' ) {
$contents = drupal_load_stylesheet ( $stylesheet [ 'data' ], TRUE );
2010-11-13 14:06:43 +00:00
// Build the base URL of this CSS file: start with the full URL.
$css_base_url = file_create_url ( $stylesheet [ 'data' ]);
// Move to the parent.
$css_base_url = substr ( $css_base_url , 0 , strrpos ( $css_base_url , '/' ));
// Simplify to a relative URL if the stylesheet URL starts with the
// base URL of the website.
if ( substr ( $css_base_url , 0 , strlen ( $GLOBALS [ 'base_root' ])) == $GLOBALS [ 'base_root' ]) {
$css_base_url = substr ( $css_base_url , strlen ( $GLOBALS [ 'base_root' ]));
}
_drupal_build_css_path ( NULL , $css_base_url . '/' );
// Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
2010-08-03 02:14:50 +00:00
$data .= preg_replace_callback ( '/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i' , '_drupal_build_css_path' , $contents );
2006-12-10 09:54:35 +00:00
}
}
2006-12-10 20:34:03 +00:00
2007-01-29 20:30:53 +00:00
// Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
2006-12-10 09:54:35 +00:00
// @import rules must proceed any other style, so we move those to the top.
$regexp = '/@import[^;]+;/i' ;
preg_match_all ( $regexp , $data , $matches );
$data = preg_replace ( $regexp , '' , $data );
$data = implode ( '' , $matches [ 0 ]) . $data ;
2006-12-10 20:34:03 +00:00
2010-05-09 19:44:25 +00:00
// Prefix filename to prevent blocking by firewalls which reject files
// starting with "ad*".
$filename = 'css_' . drupal_hash_base64 ( $data ) . '.css' ;
// Create the css/ within the files folder.
$csspath = 'public://css' ;
$uri = $csspath . '/' . $filename ;
2006-12-10 09:54:35 +00:00
// Create the CSS file.
2010-03-31 15:35:29 +00:00
file_prepare_directory ( $csspath , FILE_CREATE_DIRECTORY );
2010-05-09 19:44:25 +00:00
if ( ! file_exists ( $uri ) && ! file_unmanaged_save_data ( $data , $uri , FILE_EXISTS_REPLACE )) {
2010-03-31 15:35:29 +00:00
return FALSE ;
}
2010-10-11 23:49:48 +00:00
// If CSS gzip compression is enabled, clean URLs are enabled (which means
// that rewrite rules are working) and the zlib extension is available then
// create a gzipped version of this file. This file is served conditionally
// to browsers that accept gzip using .htaccess rules.
if ( variable_get ( 'css_gzip_compression' , TRUE ) && variable_get ( 'clean_url' , 0 ) && extension_loaded ( 'zlib' )) {
if ( ! file_exists ( $uri . '.gz' ) && ! file_unmanaged_save_data ( gzencode ( $data , 9 , FORCE_GZIP ), $uri . '.gz' , FILE_EXISTS_REPLACE )) {
return FALSE ;
}
}
2010-05-09 19:44:25 +00:00
// Save the updated map.
$map [ $key ] = $uri ;
variable_set ( 'drupal_css_cache_files' , $map );
2006-12-10 09:54:35 +00:00
}
2010-03-31 15:35:29 +00:00
return $uri ;
2006-12-10 09:54:35 +00:00
}
2007-12-07 11:14:05 +00:00
/**
* Helper function for drupal_build_css_cache () .
2007-12-08 14:06:23 +00:00
*
2007-12-07 11:14:05 +00:00
* This function will prefix all paths within a CSS file .
*/
function _drupal_build_css_path ( $matches , $base = NULL ) {
2009-05-31 07:00:12 +00:00
$_base = & drupal_static ( __FUNCTION__ );
2007-12-07 11:14:05 +00:00
// Store base path for preg_replace_callback.
if ( isset ( $base )) {
$_base = $base ;
}
// Prefix with base and remove '../' segments where possible.
$path = $_base . $matches [ 1 ];
$last = '' ;
while ( $path != $last ) {
$last = $path ;
2008-11-08 20:05:00 +00:00
$path = preg_replace ( '`(^|/)(?!\.\./)([^/]+)/\.\./`' , '$1' , $path );
2007-12-07 11:14:05 +00:00
}
2008-04-14 17:48:46 +00:00
return 'url(' . $path . ')' ;
2007-12-07 11:14:05 +00:00
}
/**
* Loads the stylesheet and resolves all @ import commands .
*
* Loads a stylesheet and replaces @ import commands with the contents of the
* imported file . Use this instead of file_get_contents when processing
* stylesheets .
*
* The returned contents are compressed removing white space and comments only
* when CSS aggregation is enabled . This optimization will not apply for
* color . module enabled themes with CSS aggregation turned off .
*
* @ param $file
* Name of the stylesheet to be processed .
* @ param $optimize
* Defines if CSS contents should be compressed or not .
2010-11-13 14:06:43 +00:00
* @ param $reset_basepath
* Used internally to facilitate recursive resolution of @ import commands .
*
2007-12-07 11:14:05 +00:00
* @ return
2009-03-30 05:13:45 +00:00
* Contents of the stylesheet , including any resolved @ import commands .
2007-12-07 11:14:05 +00:00
*/
2010-11-13 14:06:43 +00:00
function drupal_load_stylesheet ( $file , $optimize = NULL , $reset_basepath = TRUE ) {
// These statics are not cache variables, so we don't use drupal_static().
static $_optimize , $basepath ;
if ( $reset_basepath ) {
$basepath = '' ;
}
// Store the value of $optimize for preg_replace_callback with nested
// @import loops.
2007-12-07 11:14:05 +00:00
if ( isset ( $optimize )) {
$_optimize = $optimize ;
}
2010-11-13 14:06:43 +00:00
// Stylesheets are relative one to each other. Start by adding a base path
// prefix provided by the parent stylesheet (if necessary).
if ( $basepath && ! file_uri_scheme ( $file )) {
$file = $basepath . '/' . $file ;
}
$basepath = dirname ( $file );
2007-12-07 11:14:05 +00:00
2010-11-13 14:06:43 +00:00
// Load the CSS stylesheet. We suppress errors because themes may specify
// stylesheets in their .info file that don't exist in the theme's path,
// but are merely there to disable certain module CSS files.
if ( $contents = @ file_get_contents ( $file )) {
// Return the processed stylesheet.
return drupal_load_stylesheet_content ( $contents , $_optimize );
2007-12-07 11:14:05 +00:00
}
2010-11-13 14:06:43 +00:00
return '' ;
2007-12-07 11:14:05 +00:00
}
2009-03-30 05:13:45 +00:00
/**
* Process the contents of a stylesheet for aggregation .
*
* @ param $contents
* The contents of the stylesheet .
* @ param $optimize
* ( optional ) Boolean whether CSS contents should be minified . Defaults to
* FALSE .
* @ return
* Contents of the stylesheet including the imported stylesheets .
*/
2009-05-24 17:39:35 +00:00
function drupal_load_stylesheet_content ( $contents , $optimize = FALSE ) {
2009-03-30 05:13:45 +00:00
// Remove multiple charset declarations for standards compliance (and fixing Safari problems).
$contents = preg_replace ( '/^@charset\s+[\'"](\S*)\b[\'"];/i' , '' , $contents );
if ( $optimize ) {
// Perform some safe CSS optimizations.
2010-06-21 01:35:25 +00:00
// Regexp to match comment blocks.
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/' ;
// Regexp to match double quoted strings.
$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"' ;
// Regexp to match single quoted strings.
$single_quot = " '[^' \\ \\ ]*(?: \\ \\ .[^' \\ \\ ]*)*' " ;
2010-10-08 15:36:12 +00:00
// Strip all comment blocks, but keep double/single quoted strings.
2010-06-21 01:35:25 +00:00
$contents = preg_replace (
2010-10-08 15:36:12 +00:00
" <( $double_quot | $single_quot )| $comment >Ss " ,
" $ 1 " ,
$contents
);
// Remove certain whitespace.
// There are different conditions for removing leading and trailing
2010-11-20 08:27:52 +00:00
// whitespace.
2010-10-08 15:36:12 +00:00
// @see http://php.net/manual/en/regexp.reference.subpatterns.php
2011-01-14 08:37:20 +00:00
$contents = preg_replace ( ' <
2010-10-08 15:36:12 +00:00
# Strip leading and trailing whitespace.
\s * ([ @ {};,]) \s *
# Strip only leading whitespace from:
# - Closing parenthesis: Retain "@media (bar) and foo".
| \s + ([ \ )])
# Strip only trailing whitespace from:
# - Opening parenthesis: Retain "@media (bar) and foo".
# - Colon: Retain :pseudo-selectors.
| ([ \ ( : ]) \s +
> xS ' ,
2011-01-14 08:37:20 +00:00
// Only one of the three capturing groups will match, so its reference
// will contain the wanted value and the references for the
// two non-matching groups will be replaced with empty strings.
'$1$2$3' ,
2010-10-08 15:36:12 +00:00
$contents
);
2010-06-21 01:35:25 +00:00
// End the file with a new line.
2010-11-17 04:15:37 +00:00
$contents = trim ( $contents );
2010-06-21 01:35:25 +00:00
$contents .= " \n " ;
2009-03-30 05:13:45 +00:00
}
2009-09-29 17:52:46 +00:00
// Replaces @import commands with the actual stylesheet content.
// This happens recursively but omits external files.
2010-08-03 02:14:50 +00:00
$contents = preg_replace_callback ( '/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/' , '_drupal_load_stylesheet' , $contents );
2009-03-30 05:13:45 +00:00
return $contents ;
}
2007-12-07 11:14:05 +00:00
/**
* Loads stylesheets recursively and returns contents with corrected paths .
2007-12-08 14:06:23 +00:00
*
2007-12-07 11:14:05 +00:00
* This function is used for recursive loading of stylesheets and
* returns the stylesheet content with all url () paths corrected .
*/
function _drupal_load_stylesheet ( $matches ) {
$filename = $matches [ 1 ];
// Load the imported stylesheet and replace @import commands in there as well.
2010-11-13 14:06:43 +00:00
$file = drupal_load_stylesheet ( $filename , NULL , FALSE );
2010-01-07 07:45:03 +00:00
// Determine the file's directory.
$directory = dirname ( $filename );
// If the file is in the current directory, make sure '.' doesn't appear in
// the url() path.
$directory = $directory == '.' ? '' : $directory . '/' ;
// Alter all internal url() paths. Leave external paths alone. We don't need
// to normalize absolute paths here (i.e. remove folder/... segments) because
// that will be done later.
2010-08-03 02:14:50 +00:00
return preg_replace ( '/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i' , 'url(\1' . $directory , $file );
2007-12-07 11:14:05 +00:00
}
2006-12-10 09:54:35 +00:00
/**
2010-05-09 19:44:25 +00:00
* Deletes old cached CSS files .
2006-12-10 09:54:35 +00:00
*/
function drupal_clear_css_cache () {
2010-05-09 19:44:25 +00:00
variable_del ( 'drupal_css_cache_files' );
file_scan_directory ( 'public://css' , '/.*/' , array ( 'callback' => 'drupal_delete_file_if_stale' ));
}
/**
* Callback to delete files modified more than a set time ago .
*/
function drupal_delete_file_if_stale ( $uri ) {
// Default stale file threshold is 30 days.
if ( REQUEST_TIME - filemtime ( $uri ) > variable_get ( 'drupal_stale_file_threshold' , 2592000 )) {
file_unmanaged_delete ( $uri );
}
2006-08-03 07:06:36 +00:00
}
2009-10-03 19:16:04 +00:00
/**
2009-10-09 08:08:56 +00:00
* Prepare a string for use as a valid CSS identifier ( element , class or ID name ) .
2009-10-03 19:16:04 +00:00
*
* http :// www . w3 . org / TR / CSS21 / syndata . html #characters shows the syntax for valid
2009-10-09 08:08:56 +00:00
* CSS identifiers ( including element names , classes , and IDs in selectors . )
2009-10-03 19:16:04 +00:00
*
* @ param $identifier
2009-10-05 01:18:26 +00:00
* The identifier to clean .
2009-10-03 19:16:04 +00:00
* @ param $filter
* An array of string replacements to use on the identifier .
* @ return
* The cleaned identifier .
*/
2009-10-17 05:50:29 +00:00
function drupal_clean_css_identifier ( $identifier , $filter = array ( ' ' => '-' , '_' => '-' , '/' => '-' , '[' => '-' , ']' => '' )) {
2009-10-03 19:16:04 +00:00
// By default, we filter using Drupal's coding standards.
$identifier = strtr ( $identifier , $filter );
2009-10-09 08:08:56 +00:00
// Valid characters in a CSS identifier are:
2009-10-03 19:16:04 +00:00
// - the hyphen (U+002D)
// - a-z (U+0030 - U+0039)
// - A-Z (U+0041 - U+005A)
// - the underscore (U+005F)
// - 0-9 (U+0061 - U+007A)
// - ISO 10646 characters U+00A1 and higher
// We strip out any character not in the above list.
$identifier = preg_replace ( '/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u' , '' , $identifier );
return $identifier ;
}
/**
2009-10-05 01:18:26 +00:00
* Prepare a string for use as a valid class name .
2009-10-03 19:16:04 +00:00
*
* Do not pass one string containing multiple classes as they will be
* incorrectly concatenated with dashes , i . e . " one two " will become " one-two " .
*
* @ param $class
* The class name to clean .
* @ return
* The cleaned class name .
*/
2009-10-05 01:18:26 +00:00
function drupal_html_class ( $class ) {
2009-10-09 08:08:56 +00:00
return drupal_clean_css_identifier ( drupal_strtolower ( $class ));
2009-10-03 19:16:04 +00:00
}
/**
* Prepare a string for use as a valid HTML ID and guarantee uniqueness .
*
2010-04-07 17:30:43 +00:00
* This function ensures that each passed HTML ID value only exists once on the
* page . By tracking the already returned ids , this function enables forms ,
* blocks , and other content to be output multiple times on the same page ,
* without breaking ( X ) HTML validation .
*
2011-02-19 00:09:11 +00:00
* For already existing IDs , a counter is appended to the ID string . Therefore ,
2010-04-07 17:30:43 +00:00
* JavaScript and CSS code should not rely on any value that was generated by
* this function and instead should rely on manually added CSS classes or
* similarly reliable constructs .
*
2011-02-19 00:09:11 +00:00
* Two consecutive hyphens separate the counter from the original ID . To manage
* uniqueness across multiple Ajax requests on the same page , Ajax requests
2010-04-07 17:30:43 +00:00
* POST an array of all IDs currently present on the page , which are used to
* prime this function ' s cache upon first invocation .
*
2011-02-19 00:09:11 +00:00
* To allow reverse - parsing of IDs submitted via Ajax , any multiple consecutive
2010-04-07 17:30:43 +00:00
* hyphens in the originally passed $id are replaced with a single hyphen .
*
2009-10-03 19:16:04 +00:00
* @ param $id
* The ID to clean .
2010-04-07 17:30:43 +00:00
*
2009-10-03 19:16:04 +00:00
* @ return
* The cleaned ID .
*/
2009-10-05 01:18:26 +00:00
function drupal_html_id ( $id ) {
2011-02-19 00:09:11 +00:00
// If this is an Ajax request, then content returned by this page request will
// be merged with content already on the base page. The HTML IDs must be
2010-04-07 17:30:43 +00:00
// unique for the fully merged content. Therefore, initialize $seen_ids to
2011-02-19 00:09:11 +00:00
// take into account IDs that are already in use on the base page.
2010-04-07 17:30:43 +00:00
$seen_ids_init = & drupal_static ( __FUNCTION__ . ':init' );
if ( ! isset ( $seen_ids_init )) {
// Ideally, Drupal would provide an API to persist state information about
// prior page requests in the database, and we'd be able to add this
// function's $seen_ids static variable to that state information in order
// to have it properly initialized for this page request. However, no such
// page state API exists, so instead, ajax.js adds all of the in-use HTML
2011-02-19 00:09:11 +00:00
// IDs to the POST data of Ajax submissions. Direct use of $_POST is
2010-04-07 17:30:43 +00:00
// normally not recommended as it could open up security risks, but because
// the raw POST data is cast to a number before being returned by this
// function, this usage is safe.
if ( empty ( $_POST [ 'ajax_html_ids' ])) {
$seen_ids_init = array ();
}
else {
// This function ensures uniqueness by appending a counter to the base id
// requested by the calling function after the first occurrence of that
// requested id. $_POST['ajax_html_ids'] contains the ids as they were
// returned by this function, potentially with the appended counter, so
// we parse that to reconstruct the $seen_ids array.
foreach ( $_POST [ 'ajax_html_ids' ] as $seen_id ) {
// We rely on '--' being used solely for separating a base id from the
// counter, which this function ensures when returning an id.
$parts = explode ( '--' , $seen_id , 2 );
if ( ! empty ( $parts [ 1 ]) && is_numeric ( $parts [ 1 ])) {
list ( $seen_id , $i ) = $parts ;
}
else {
$i = 1 ;
}
if ( ! isset ( $seen_ids_init [ $seen_id ]) || ( $i > $seen_ids_init [ $seen_id ])) {
$seen_ids_init [ $seen_id ] = $i ;
}
}
}
}
$seen_ids = & drupal_static ( __FUNCTION__ , $seen_ids_init );
2009-10-09 08:08:56 +00:00
$id = strtr ( drupal_strtolower ( $id ), array ( ' ' => '-' , '_' => '-' , '[' => '-' , ']' => '' ));
// As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
// only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
// colons (":"), and periods ("."). We strip out any character not in that
// list. Note that the CSS spec doesn't allow colons or periods in identifiers
// (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two
// characters as well.
$id = preg_replace ( '/[^A-Za-z0-9\-_]/' , '' , $id );
2009-10-03 19:16:04 +00:00
2010-04-07 17:30:43 +00:00
// Removing multiple consecutive hyphens.
$id = preg_replace ( '/\-+/' , '-' , $id );
// Ensure IDs are unique by appending a counter after the first occurrence.
// The counter needs to be appended with a delimiter that does not exist in
// the base ID. Requiring a unique delimiter helps ensure that we really do
// return unique IDs and also helps us re-create the $seen_ids array during
2011-02-19 00:09:11 +00:00
// Ajax requests.
2009-10-03 19:16:04 +00:00
if ( isset ( $seen_ids [ $id ])) {
2010-04-07 17:30:43 +00:00
$id = $id . '--' . ++ $seen_ids [ $id ];
2009-10-03 19:16:04 +00:00
}
else {
$seen_ids [ $id ] = 1 ;
}
return $id ;
}
#610234 by Gábor Hojtsy, ksenzee, cwgordon7, David_Rothstein, seutje, marcvangend, sun, JoshuaRogers, markus_petrux, Bojhan, Rob Loach, Everett Zufelt, drifter, markboulton, leisareichelt, et al: Added Overlay module to core, which shows administrative pages in a JS overlay, retaining context on the front-end site.
2009-12-02 07:28:22 +00:00
/**
* Provides a standard HTML class name that identifies a page region .
*
* It is recommended that template preprocess functions apply this class to any
* page region that is output by the theme ( Drupal core already handles this in
* the standard template preprocess implementation ) . Standardizing the class
* names in this way allows modules to implement certain features , such as
2011-02-19 00:09:11 +00:00
* drag - and - drop or dynamic Ajax loading , in a theme - independent way .
#610234 by Gábor Hojtsy, ksenzee, cwgordon7, David_Rothstein, seutje, marcvangend, sun, JoshuaRogers, markus_petrux, Bojhan, Rob Loach, Everett Zufelt, drifter, markboulton, leisareichelt, et al: Added Overlay module to core, which shows administrative pages in a JS overlay, retaining context on the front-end site.
2009-12-02 07:28:22 +00:00
*
* @ param $region
* The name of the page region ( for example , 'page_top' or 'content' ) .
*
* @ return
* An HTML class that identifies the region ( for example , 'region-page-top'
* or 'region-content' ) .
*
* @ see template_preprocess_region ()
*/
function drupal_region_class ( $region ) {
return drupal_html_class ( " region- $region " );
}
2005-05-24 06:00:22 +00:00
/**
2010-03-27 05:55:24 +00:00
* Adds a JavaScript file , setting , or inline code to the page .
2005-05-24 06:00:22 +00:00
*
2006-08-22 09:00:31 +00:00
* The behavior of this function depends on the parameters it is called with .
* Generally , it handles the addition of JavaScript to the page , either as
* reference to an existing file or as inline code . The following actions can be
* performed using this function :
2010-03-27 05:55:24 +00:00
* - Add a file ( 'file' ) : Adds a reference to a JavaScript file to the page .
* - Add inline JavaScript code ( 'inline' ) : Executes a piece of JavaScript code
* on the current page by placing the code directly in the page ( for example ,
* to tell the user that a new message arrived , by opening a pop up , alert
* box , etc . ) . This should only be used for JavaScript that cannot be executed
* from a file . When adding inline code , make sure that you are not relying on
* $ () being the jQuery function . Wrap your code in
* @ code ( function ( $ ) { ... })( jQuery ); @ endcode
* or use jQuery () instead of $ () .
* - Add external JavaScript ( 'external' ) : Allows the inclusion of external
* JavaScript files that are not hosted on the local server . Note that these
* external JavaScript references do not get aggregated when preprocessing is
* on .
* - Add settings ( 'setting' ) : Adds settings to Drupal ' s global storage of
* JavaScript settings . Per - page settings are required by some modules to
* function properly . All settings will be accessible at Drupal . settings .
2006-08-22 09:00:31 +00:00
*
2008-10-22 19:39:36 +00:00
* Examples :
* @ code
2011-10-31 04:05:57 +00:00
* drupal_add_js ( 'core/misc/collapse.js' );
* drupal_add_js ( 'core/misc/collapse.js' , 'file' );
2009-04-27 20:19:38 +00:00
* drupal_add_js ( 'jQuery(document).ready(function () { alert("Hello!"); });' , 'inline' );
* drupal_add_js ( 'jQuery(document).ready(function () { alert("Hello!"); });' ,
2008-11-10 05:23:01 +00:00
* array ( 'type' => 'inline' , 'scope' => 'footer' , 'weight' => 5 )
2008-10-22 19:39:36 +00:00
* );
2009-02-28 07:36:06 +00:00
* drupal_add_js ( 'http://example.com/example.js' , 'external' );
2010-12-10 16:44:53 +00:00
* drupal_add_js ( array ( 'myModule' => array ( 'key' => 'value' )), 'setting' );
2008-10-22 19:39:36 +00:00
* @ endcode
*
2009-05-16 13:26:31 +00:00
* Calling drupal_static_reset ( 'drupal_add_js' ) will clear all JavaScript added
* so far .
*
2010-07-30 02:47:28 +00:00
* If JavaScript aggregation is enabled , all JavaScript files added with
* $options [ 'preprocess' ] set to TRUE will be merged into one aggregate file .
* Preprocessed inline JavaScript will not be aggregated into this single file .
* Externally hosted JavaScripts are never aggregated .
*
* The reason for aggregating the files is outlined quite thoroughly here :
* http :// www . die . net / musings / page_load_time / " Load fewer external objects. Due
* to request overhead , one bigger file just loads faster than two smaller ones
* half its size . "
*
* $options [ 'preprocess' ] should be only set to TRUE when a file is required for
* all typical visitors and most pages of a site . It is critical that all
* preprocessed files are added unconditionally on every page , even if the
* files are not needed on a page . This is normally done by calling
2011-01-01 03:25:01 +00:00
* drupal_add_js () in a hook_init () implementation .
2010-07-30 02:47:28 +00:00
*
* Non - preprocessed files should only be added to the page when they are
* actually needed .
*
2006-08-22 09:00:31 +00:00
* @ param $data
2008-10-22 19:39:36 +00:00
* ( optional ) If given , the value depends on the $options parameter :
2008-11-10 05:23:01 +00:00
* - 'file' : Path to the file relative to base_path () .
2006-08-22 09:00:31 +00:00
* - 'inline' : The JavaScript code that should be placed in the given scope .
2009-08-14 16:15:38 +00:00
* - 'external' : The absolute path to an external JavaScript file that is not
2009-08-24 00:14:23 +00:00
* hosted on the local server . These files will not be aggregated if
2009-08-14 16:15:38 +00:00
* JavaScript aggregation is enabled .
2010-03-27 05:55:24 +00:00
* - 'setting' : An associative array with configuration options . The array is
2010-11-20 05:02:46 +00:00
* merged directly into Drupal . settings . All modules should wrap their
* actual configuration settings in another variable to prevent conflicts in
* the Drupal . settings namespace . Items added with a string key will replace
* existing settings with that key ; items with numeric array keys will be
* added to the existing settings array .
2008-10-22 19:39:36 +00:00
* @ param $options
2010-03-27 05:55:24 +00:00
* ( optional ) A string defining the type of JavaScript that is being added in
* the $data parameter ( 'file' / 'setting' / 'inline' / 'external' ), or an
* associative array . JavaScript settings should always pass the string
* 'setting' only . Other types can have the following elements in the array :
* - type : The type of JavaScript that is to be added to the page . Allowed
* values are 'file' , 'inline' , 'external' or 'setting' . Defaults
* to 'file' .
* - scope : The location in which you want to place the script . Possible
* values are 'header' or 'footer' . If your theme implements different
* regions , you can also use these . Defaults to 'header' .
2010-10-20 00:45:39 +00:00
* - group : A number identifying the group in which to add the JavaScript .
2010-10-05 19:59:10 +00:00
* Available constants are :
2010-03-27 05:55:24 +00:00
* - JS_LIBRARY : Any libraries , settings , or jQuery plugins .
* - JS_DEFAULT : Any module - layer JavaScript .
* - JS_THEME : Any theme - layer JavaScript .
2010-10-05 19:59:10 +00:00
* The group number serves as a weight : JavaScript within a lower weight
* group is presented on the page before JavaScript within a higher weight
* group .
2010-10-20 00:45:39 +00:00
* - every_page : For optimal front - end performance when aggregation is
2010-10-05 19:59:10 +00:00
* enabled , this should be set to TRUE if the JavaScript is present on every
* page of the website for users for whom it is present at all . This
* defaults to FALSE . It is set to TRUE for JavaScript files that are added
* via module and theme . info files . Modules that add JavaScript within
* hook_init () implementations , or from other code that ensures that the
* JavaScript is added to all website pages , should also set this flag to
* TRUE . All JavaScript files within the same group and that have the
* 'every_page' flag set to TRUE and do not have 'preprocess' set to FALSE
* are aggregated together into a single aggregate file , and that aggregate
* file can be reused across a user ' s entire site visit , leading to faster
* navigation between pages . However , JavaScript that is only needed on
* pages less frequently visited , can be added by code that only runs for
* those particular pages , and that code should not set the 'every_page'
* flag . This minimizes the size of the aggregate file that the user needs
* to download when first visiting the website . JavaScript without the
* 'every_page' flag is aggregated into a separate aggregate file . This
* other aggregate file is likely to change from page to page , and each new
* aggregate file needs to be downloaded when first encountered , so it
* should be kept relatively small by ensuring that most commonly needed
* JavaScript is added to every page .
* - weight : A number defining the order in which the JavaScript is added to
* the page relative to other JavaScript with the same 'scope' , 'group' ,
* and 'every_page' value . In some cases , the order in which the JavaScript
* is presented on the page is very important . jQuery , for example , must be
* added to the page before any jQuery code is run , so jquery . js uses the
* JS_LIBRARY group and a weight of - 20 , jquery . once . js ( a library drupal . js
* depends on ) uses the JS_LIBRARY group and a weight of - 19 , drupal . js uses
* the JS_LIBRARY group and a weight of - 1 , other libraries use the
* JS_LIBRARY group and a weight of 0 or higher , and all other scripts use
* one of the other group constants . The exact ordering of JavaScript is as
* follows :
* - First by scope , with 'header' first , 'footer' last , and any other
* scopes provided by a custom theme coming in between , as determined by
* the theme .
* - Then by group .
* - Then by the 'every_page' flag , with TRUE coming before FALSE .
* - Then by weight .
* - Then by the order in which the JavaScript was added . For example , all
* else being the same , JavaScript added by a call to drupal_add_js () that
* happened later in the page request gets added to the page after one for
* which drupal_add_js () happened earlier in the page request .
2010-03-27 05:55:24 +00:00
* - defer : If set to TRUE , the defer attribute is set on the & lt ; script & gt ;
* tag . Defaults to FALSE .
* - cache : If set to FALSE , the JavaScript file is loaded anew on every page
* call ; in other words , it is not cached . Used only when 'type' references
* a JavaScript file . Defaults to TRUE .
2010-07-30 02:47:28 +00:00
* - preprocess : If TRUE and JavaScript aggregation is enabled , the script
2010-10-05 19:59:10 +00:00
* file will be aggregated . Defaults to TRUE .
2011-11-03 11:00:04 +00:00
* - browsers : An array containing information specifying which browsers
* should load the JavaScript item . See
* drupal_pre_render_conditional_comments () for details .
2010-03-27 05:55:24 +00:00
*
2006-08-22 09:00:31 +00:00
* @ return
2010-03-27 05:55:24 +00:00
* The current array of JavaScript files , settings , and in - line code ,
* including Drupal defaults , anything previously added with calls to
* drupal_add_js (), and this function call ' s additions .
*
2008-10-22 19:39:36 +00:00
* @ see drupal_get_js ()
2005-05-24 06:00:22 +00:00
*/
2009-01-22 03:56:12 +00:00
function drupal_add_js ( $data = NULL , $options = NULL ) {
2009-05-16 13:26:31 +00:00
$javascript = & drupal_static ( __FUNCTION__ , array ());
2006-01-29 07:36:29 +00:00
2008-10-22 19:39:36 +00:00
// Construct the options, taking the defaults into consideration.
if ( isset ( $options )) {
if ( ! is_array ( $options )) {
$options = array ( 'type' => $options );
}
}
else {
$options = array ();
}
2008-11-23 16:00:08 +00:00
$options += drupal_js_defaults ( $data );
2008-10-22 19:39:36 +00:00
// Preprocess can only be set if caching is enabled.
$options [ 'preprocess' ] = $options [ 'cache' ] ? $options [ 'preprocess' ] : FALSE ;
2009-07-10 05:45:56 +00:00
// Tweak the weight so that files of the same weight are included in the
// order of the calls to drupal_add_js().
$options [ 'weight' ] += count ( $javascript ) / 1000 ;
2008-10-22 19:39:36 +00:00
if ( isset ( $data )) {
2007-12-17 23:43:43 +00:00
// Add jquery.js and drupal.js, as well as the basePath setting, the
2011-02-19 00:09:11 +00:00
// first time a JavaScript file is added.
2007-11-30 15:31:13 +00:00
if ( empty ( $javascript )) {
2011-05-14 12:33:11 +00:00
// url() generates the prefix using hook_url_outbound_alter(). Instead of
// running the hook_url_outbound_alter() again here, extract the prefix
// from url().
url ( '' , array ( 'prefix' => & $prefix ));
2008-10-22 19:39:36 +00:00
$javascript = array (
2008-11-10 05:23:01 +00:00
'settings' => array (
'data' => array (
2008-10-22 19:39:36 +00:00
array ( 'basePath' => base_path ()),
2011-05-14 12:33:11 +00:00
array ( 'pathPrefix' => empty ( $prefix ) ? '' : $prefix ),
2008-10-22 19:39:36 +00:00
),
2008-11-10 05:23:01 +00:00
'type' => 'setting' ,
'scope' => 'header' ,
2011-11-03 11:00:04 +00:00
'group' => JS_SETTING ,
2010-10-05 19:59:10 +00:00
'every_page' => TRUE ,
'weight' => 0 ,
2011-11-03 11:00:04 +00:00
'browsers' => array (),
2008-11-10 05:23:01 +00:00
),
2011-10-31 04:05:57 +00:00
'core/misc/drupal.js' => array (
'data' => 'core/misc/drupal.js' ,
2008-11-10 05:23:01 +00:00
'type' => 'file' ,
'scope' => 'header' ,
2010-10-05 19:59:10 +00:00
'group' => JS_LIBRARY ,
'every_page' => TRUE ,
'weight' => - 1 ,
'preprocess' => TRUE ,
2008-11-10 05:23:01 +00:00
'cache' => TRUE ,
'defer' => FALSE ,
2011-11-03 11:00:04 +00:00
'browsers' => array (),
2008-11-10 05:23:01 +00:00
),
2007-11-30 15:31:13 +00:00
);
2009-08-31 05:51:08 +00:00
// Register all required libraries.
2010-10-05 19:59:10 +00:00
drupal_add_library ( 'system' , 'jquery' , TRUE );
2010-11-30 17:16:37 +00:00
drupal_add_library ( 'system' , 'jquery.once' , TRUE );
2011-11-17 07:33:06 +00:00
drupal_add_library ( 'system' , 'html5shiv' , TRUE );
2007-11-30 15:31:13 +00:00
}
2007-12-08 14:06:23 +00:00
2008-11-10 05:23:01 +00:00
switch ( $options [ 'type' ]) {
2006-08-22 09:00:31 +00:00
case 'setting' :
2008-11-10 05:23:01 +00:00
// All JavaScript settings are placed in the header of the page with
// the library weight so that inline scripts appear afterwards.
$javascript [ 'settings' ][ 'data' ][] = $data ;
2006-08-22 09:00:31 +00:00
break ;
2008-11-10 05:23:01 +00:00
2006-08-22 09:00:31 +00:00
case 'inline' :
2008-11-10 05:23:01 +00:00
$javascript [] = $options ;
2006-08-22 09:00:31 +00:00
break ;
2009-02-28 07:36:06 +00:00
default : // 'file' and 'external'
// Local and external files must keep their name as the associative key
2011-02-19 00:09:11 +00:00
// so the same JavaScript file is not added twice.
2008-11-10 05:23:01 +00:00
$javascript [ $options [ 'data' ]] = $options ;
}
2007-06-08 12:51:59 +00:00
}
2008-11-10 05:23:01 +00:00
return $javascript ;
2005-05-24 06:00:22 +00:00
}
2008-11-23 16:00:08 +00:00
/**
* Constructs an array of the defaults that are used for JavaScript items .
*
* @ param $data
* ( optional ) The default data parameter for the JavaScript item array .
* @ see drupal_get_js ()
* @ see drupal_add_js ()
*/
function drupal_js_defaults ( $data = NULL ) {
return array (
'type' => 'file' ,
2010-10-05 19:59:10 +00:00
'group' => JS_DEFAULT ,
'every_page' => FALSE ,
'weight' => 0 ,
2008-11-23 16:00:08 +00:00
'scope' => 'header' ,
'cache' => TRUE ,
'defer' => FALSE ,
2010-10-05 19:59:10 +00:00
'preprocess' => TRUE ,
2010-03-09 03:39:44 +00:00
'version' => NULL ,
2008-11-23 16:00:08 +00:00
'data' => $data ,
2011-11-03 11:00:04 +00:00
'browsers' => array (),
2008-11-23 16:00:08 +00:00
);
}
- Patch #28483 by Steven: JavaScript enabled uploading.
Comment from Steven: It does this by redirecting the submission of the form to a hidden <iframe> when you click "Attach" (we cannot submit data through Ajax directly because you cannot read file contents from JS for security reasons). Once the file is submitted, the upload-section of the form is updated. Things to note:
* The feature degrades back to the current behaviour without JS.
* If there are errors with the uploaded file (disallowed type, too big, ...), they are displayed at the top of the file attachments fieldset.
* Though the hidden-iframe method sounds dirty, it's quite compact and is 100% implemented in .js files. The drupal.js api makes it a snap to use.
* I included some minor improvements to the Drupal JS API and code.
* I added an API drupal_call_js() to bridge the PHP/JS gap: it takes a function name and arguments, and outputs a <script> tag. The kicker is that it preserves the structure and type of arguments, so e.g. PHP associative arrays end up as objects in JS.
* I also included a progressbar widget that I wrote for drumm's ongoing update.php work. It includes Ajax status updating/monitoring, but it is only used as a pure throbber in this patch. But as the code was already written and is going to be used in the near future, I left that part in. It's pretty small ;). If PHP supports ad-hoc upload info in the future like Ruby on Rails, we can implement that in 5 minutes.
2005-08-31 18:37:30 +00:00
/**
2006-08-22 09:00:31 +00:00
* Returns a themed presentation of all JavaScript code for the current page .
2007-10-08 14:08:19 +00:00
*
2006-08-22 09:00:31 +00:00
* References to JavaScript files are placed in a certain order : first , all
* 'core' files , then all 'module' and finally all 'theme' JavaScript files
* are added to the page . Then , all settings are output , followed by 'inline'
2007-11-16 15:35:24 +00:00
* JavaScript code . If running update . php , all preprocessing is disabled .
- Patch #28483 by Steven: JavaScript enabled uploading.
Comment from Steven: It does this by redirecting the submission of the form to a hidden <iframe> when you click "Attach" (we cannot submit data through Ajax directly because you cannot read file contents from JS for security reasons). Once the file is submitted, the upload-section of the form is updated. Things to note:
* The feature degrades back to the current behaviour without JS.
* If there are errors with the uploaded file (disallowed type, too big, ...), they are displayed at the top of the file attachments fieldset.
* Though the hidden-iframe method sounds dirty, it's quite compact and is 100% implemented in .js files. The drupal.js api makes it a snap to use.
* I included some minor improvements to the Drupal JS API and code.
* I added an API drupal_call_js() to bridge the PHP/JS gap: it takes a function name and arguments, and outputs a <script> tag. The kicker is that it preserves the structure and type of arguments, so e.g. PHP associative arrays end up as objects in JS.
* I also included a progressbar widget that I wrote for drumm's ongoing update.php work. It includes Ajax status updating/monitoring, but it is only used as a pure throbber in this patch. But as the code was already written and is going to be used in the near future, I left that part in. It's pretty small ;). If PHP supports ad-hoc upload info in the future like Ruby on Rails, we can implement that in 5 minutes.
2005-08-31 18:37:30 +00:00
*
2008-11-23 16:00:08 +00:00
* Note that hook_js_alter ( & $javascript ) is called during this function call
* to allow alterations of the JavaScript during its presentation . Calls to
* drupal_add_js () from hook_js_alter () will not be added to the output
* presentation . The correct way to add JavaScript during hook_js_alter ()
* is to add another element to the $javascript array , deriving from
* drupal_js_defaults () . See locale_js_alter () for an example of this .
*
2008-05-14 13:15:09 +00:00
* @ param $scope
2006-08-22 09:00:31 +00:00
* ( optional ) The scope for which the JavaScript rules should be returned .
* Defaults to 'header' .
2008-05-14 13:15:09 +00:00
* @ param $javascript
2006-08-22 09:00:31 +00:00
* ( optional ) An array with all JavaScript code . Defaults to the default
* JavaScript array for the given scope .
2010-10-04 17:46:01 +00:00
* @ param $skip_alter
* ( optional ) If set to TRUE , this function skips calling drupal_alter () on
* $javascript , useful when the calling function passes a $javascript array
* that has already been altered .
2006-08-22 09:00:31 +00:00
* @ return
* All JavaScript code segments and includes for the scope as HTML tags .
2008-10-22 19:39:36 +00:00
* @ see drupal_add_js ()
2008-11-23 16:00:08 +00:00
* @ see locale_js_alter ()
* @ see drupal_js_defaults ()
- Patch #28483 by Steven: JavaScript enabled uploading.
Comment from Steven: It does this by redirecting the submission of the form to a hidden <iframe> when you click "Attach" (we cannot submit data through Ajax directly because you cannot read file contents from JS for security reasons). Once the file is submitted, the upload-section of the form is updated. Things to note:
* The feature degrades back to the current behaviour without JS.
* If there are errors with the uploaded file (disallowed type, too big, ...), they are displayed at the top of the file attachments fieldset.
* Though the hidden-iframe method sounds dirty, it's quite compact and is 100% implemented in .js files. The drupal.js api makes it a snap to use.
* I included some minor improvements to the Drupal JS API and code.
* I added an API drupal_call_js() to bridge the PHP/JS gap: it takes a function name and arguments, and outputs a <script> tag. The kicker is that it preserves the structure and type of arguments, so e.g. PHP associative arrays end up as objects in JS.
* I also included a progressbar widget that I wrote for drumm's ongoing update.php work. It includes Ajax status updating/monitoring, but it is only used as a pure throbber in this patch. But as the code was already written and is going to be used in the near future, I left that part in. It's pretty small ;). If PHP supports ad-hoc upload info in the future like Ruby on Rails, we can implement that in 5 minutes.
2005-08-31 18:37:30 +00:00
*/
2010-10-04 17:46:01 +00:00
function drupal_get_js ( $scope = 'header' , $javascript = NULL , $skip_alter = FALSE ) {
2007-06-01 09:05:45 +00:00
if ( ! isset ( $javascript )) {
2008-11-10 05:23:01 +00:00
$javascript = drupal_add_js ();
2006-08-22 09:00:31 +00:00
}
2007-11-30 15:31:13 +00:00
if ( empty ( $javascript )) {
2007-06-01 09:05:45 +00:00
return '' ;
2007-06-04 07:22:23 +00:00
}
2008-11-23 16:00:08 +00:00
// Allow modules to alter the JavaScript.
2010-10-04 17:46:01 +00:00
if ( ! $skip_alter ) {
drupal_alter ( 'js' , $javascript );
}
2008-11-23 16:00:08 +00:00
2008-11-10 05:23:01 +00:00
// Filter out elements of the given scope.
$items = array ();
2010-10-04 17:46:01 +00:00
foreach ( $javascript as $key => $item ) {
2008-11-10 05:23:01 +00:00
if ( $item [ 'scope' ] == $scope ) {
2010-10-04 17:46:01 +00:00
$items [ $key ] = $item ;
2008-11-10 05:23:01 +00:00
}
}
2011-11-03 11:00:04 +00:00
// Sort the JavaScript so that it appears in the correct order.
uasort ( $items , 'drupal_sort_css_js' );
// Provide the page with information about the individual JavaScript files
// used, information not otherwise available when aggregation is enabled.
$setting [ 'ajaxPageState' ][ 'js' ] = array_fill_keys ( array_keys ( $items ), 1 );
unset ( $setting [ 'ajaxPageState' ][ 'js' ][ 'settings' ]);
drupal_add_js ( $setting , 'setting' );
// If we're outputting the header scope, then this might be the final time
// that drupal_get_js() is running, so add the setting to this output as well
// as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
// because drupal_get_js() was intentionally passed a $javascript argument
// stripped of settings, potentially in order to override how settings get
// output, so in this case, do not add the setting to this output.
if ( $scope == 'header' && isset ( $items [ 'settings' ])) {
$items [ 'settings' ][ 'data' ][] = $setting ;
}
// Render the HTML needed to load the JavaScript.
$elements = array (
'#type' => 'scripts' ,
'#items' => $items ,
);
return drupal_render ( $elements );
}
/**
* #pre_render callback to add the elements needed for JavaScript tags to be rendered.
*
* This function evaluates the aggregation enabled / disabled condition on a group
* by group basis by testing whether an aggregate file has been made for the
* group rather than by testing the site - wide aggregation setting . This allows
* this function to work correctly even if modules have implemented custom
* logic for grouping and aggregating files .
*
* @ param $element
* A render array containing :
* - #items: The JavaScript items as returned by drupal_add_js() and
* altered by drupal_get_js () .
* - #group_callback: A function to call to group #items. Following
* this function , #aggregate_callback is called to aggregate items within
* the same group into a single file .
* - #aggregate_callback: A function to call to aggregate the items within
* the groups arranged by the #group_callback function.
*
* @ return
* A render array that will render to a string of JavaScript tags .
*
* @ see drupal_get_js ()
*/
function drupal_pre_render_scripts ( $elements ) {
// Group and aggregate the items.
if ( isset ( $elements [ '#group_callback' ])) {
$elements [ '#groups' ] = $elements [ '#group_callback' ]( $elements [ '#items' ]);
}
if ( isset ( $elements [ '#aggregate_callback' ])) {
$elements [ '#aggregate_callback' ]( $elements [ '#groups' ]);
}
2007-06-01 09:05:45 +00:00
2008-01-07 19:43:29 +00:00
// A dummy query-string is added to filenames, to gain control over
// browser-caching. The string changes on every update or full cache
// flush, forcing browsers to load a new copy of the files, as the
// URL changed. Files that should not be cached (see drupal_add_js())
2008-09-17 07:11:59 +00:00
// get REQUEST_TIME as query-string instead, to enforce reload on every
2008-01-07 19:43:29 +00:00
// page request.
2010-03-23 21:44:10 +00:00
$default_query_string = variable_get ( 'css_js_query_string' , '0' );
2008-01-07 19:43:29 +00:00
2011-02-19 00:09:11 +00:00
// For inline JavaScript to validate as XHTML, all JavaScript containing
2008-08-02 18:56:53 +00:00
// XHTML needs to be wrapped in CDATA. To make that backwards compatible
// with HTML 4, we need to comment out the CDATA-tag.
$embed_prefix = " \n <!--//--><![CDATA[//><!-- \n " ;
$embed_suffix = " \n //--><!]]> \n " ;
2008-08-21 19:36:39 +00:00
2011-02-19 00:09:11 +00:00
// Since JavaScript may look for arguments in the URL and act on them, some
2010-03-09 03:39:44 +00:00
// third-party code might require the use of a different query string.
$js_version_string = variable_get ( 'drupal_js_version_query_string' , 'v=' );
2011-11-03 11:00:04 +00:00
// Defaults for each SCRIPT element.
$element_defaults = array (
'#type' => 'html_tag' ,
2009-11-03 06:47:23 +00:00
'#tag' => 'script' ,
'#value' => '' ,
'#attributes' => array (
'type' => 'text/javascript' ,
),
);
2010-03-09 03:39:44 +00:00
2011-11-03 11:00:04 +00:00
// Loop through each group.
foreach ( $elements [ '#groups' ] as $group ) {
// If a group of files has been aggregated into a single file,
// $group['data'] contains the URI of the aggregate file. Add a single
// script element for this file.
if ( $group [ 'type' ] == 'file' && isset ( $group [ 'data' ])) {
$element = $element_defaults ;
$element [ '#attributes' ][ 'src' ] = file_create_url ( $group [ 'data' ]);
$element [ '#browsers' ] = $group [ 'browsers' ];
$elements [] = $element ;
}
// For non-file types, and non-aggregated files, add a script element per
// item.
else {
foreach ( $group [ 'items' ] as $item ) {
// Element properties that do not depend on item type.
$element = $element_defaults ;
if ( ! empty ( $item [ 'defer' ])) {
$element [ '#attributes' ][ 'defer' ] = 'defer' ;
}
$element [ '#browsers' ] = $item [ 'browsers' ];
2008-11-10 05:23:01 +00:00
2011-11-03 11:00:04 +00:00
// Element properties that depend on item type.
switch ( $item [ 'type' ]) {
case 'setting' :
$element [ '#value_prefix' ] = $embed_prefix ;
$element [ '#value' ] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode ( drupal_array_merge_deep_array ( $item [ 'data' ])) . " ); " ;
$element [ '#value_suffix' ] = $embed_suffix ;
break ;
case 'inline' :
$element [ '#value_prefix' ] = $embed_prefix ;
$element [ '#value' ] = $item [ 'data' ];
$element [ '#value_suffix' ] = $embed_suffix ;
break ;
case 'file' :
$query_string = empty ( $item [ 'version' ]) ? $default_query_string : $js_version_string . $item [ 'version' ];
$query_string_separator = ( strpos ( $item [ 'data' ], '?' ) !== FALSE ) ? '&' : '?' ;
$element [ '#attributes' ][ 'src' ] = file_create_url ( $item [ 'data' ]) . $query_string_separator . ( $item [ 'cache' ] ? $query_string : REQUEST_TIME );
break ;
case 'external' :
$element [ '#attributes' ][ 'src' ] = $item [ 'data' ];
break ;
2009-11-03 06:47:23 +00:00
}
2008-11-10 05:23:01 +00:00
2011-11-03 11:00:04 +00:00
$elements [] = $element ;
}
}
}
return $elements ;
}
/**
* Default callback to group JavaScript items .
*
* This function arranges the JavaScript items that are in the #items property
* of the scripts element into groups . When aggregation is enabled , files within
* a group are aggregated into a single file , significantly improving page
* loading performance by minimizing network traffic overhead .
*
* This function puts multiple items into the same group if they are groupable
* and if they are for the same browsers . Items of the 'file' type are groupable
* if their 'preprocess' flag is TRUE . Items of the 'inline' , 'settings' , or
* 'external' type are not groupable .
*
* This function also ensures that the process of grouping items does not change
* their relative order . This requirement may result in multiple groups for the
* same type and browsers , if needed to accommodate other items in
* between .
*
* @ param $javascript
* An array of JavaScript items , as returned by drupal_add_js (), but after
* alteration performed by drupal_get_js () .
*
* @ return
* An array of JavaScript groups . Each group contains the same keys ( e . g . ,
* 'data' , etc . ) as a JavaScript item from the $javascript parameter , with the
* value of each key applying to the group as a whole . Each group also
* contains an 'items' key , which is the subset of items from $javascript that
* are in the group .
*
* @ see drupal_pre_render_scripts ()
*/
function drupal_group_js ( $javascript ) {
$groups = array ();
// If a group can contain multiple items, we track the information that must
// be the same for each item in the group, so that when we iterate the next
// item, we can determine if it can be put into the current group, or if a
// new group needs to be made for it.
$current_group_keys = NULL ;
$index = - 1 ;
foreach ( $javascript as $item ) {
// The browsers for which the JavaScript item needs to be loaded is part of
// the information that determines when a new group is needed, but the order
// of keys in the array doesn't matter, and we don't want a new group if all
// that's different is that order.
ksort ( $item [ 'browsers' ]);
switch ( $item [ 'type' ]) {
2008-11-10 05:23:01 +00:00
case 'file' :
2011-11-03 11:00:04 +00:00
// Group file items if their 'preprocess' flag is TRUE.
// Help ensure maximum reuse of aggregate files by only grouping
// together items that share the same 'group' value and 'every_page'
// flag. See drupal_add_js() for details about that.
$group_keys = $item [ 'preprocess' ] ? array ( $item [ 'type' ], $item [ 'group' ], $item [ 'every_page' ], $item [ 'browsers' ]) : FALSE ;
2008-11-10 05:23:01 +00:00
break ;
2009-02-28 07:36:06 +00:00
case 'external' :
2011-11-03 11:00:04 +00:00
case 'setting' :
case 'inline' :
// Do not group external, settings, and inline items.
$group_keys = FALSE ;
2009-02-28 07:36:06 +00:00
break ;
2006-08-22 09:00:31 +00:00
}
2011-11-03 11:00:04 +00:00
// If the group keys don't match the most recent group we're working with,
// then a new group must be made.
if ( $group_keys !== $current_group_keys ) {
$index ++ ;
// Initialize the new group with the same properties as the first item
// being placed into it. The item's 'data' and 'weight' properties are
// unique to the item and should not be carried over to the group.
$groups [ $index ] = $item ;
unset ( $groups [ $index ][ 'data' ], $groups [ $index ][ 'weight' ]);
$groups [ $index ][ 'items' ] = array ();
$current_group_keys = $group_keys ? $group_keys : NULL ;
}
// Add the item to the current group.
$groups [ $index ][ 'items' ][] = $item ;
2006-08-22 09:00:31 +00:00
}
2007-06-04 07:22:23 +00:00
2011-11-03 11:00:04 +00:00
return $groups ;
}
/**
* Default callback to aggregate JavaScript files .
*
* Having the browser load fewer JavaScript files results in much faster page
* loads than when it loads many small files . This function aggregates files
* within the same group into a single file unless the site - wide setting to do
* so is disabled ( commonly the case during site development ) . To optimize
* download , it also compresses the aggregate files by removing comments ,
* whitespace , and other unnecessary content .
*
* @ param $js_groups
* An array of JavaScript groups as returned by drupal_group_js () . For each
* group that is aggregated , this function sets the value of the group ' s
* 'data' key to the URI of the aggregate file .
*
* @ see drupal_group_js ()
* @ see drupal_pre_render_scripts ()
*/
function drupal_aggregate_js ( & $js_groups ) {
// Only aggregate when the site is configured to do so, and not during an
// update.
if ( variable_get ( 'preprocess_js' , FALSE ) && ( ! defined ( 'MAINTENANCE_MODE' ) || MAINTENANCE_MODE != 'update' )) {
foreach ( $js_groups as $key => $group ) {
if ( $group [ 'type' ] == 'file' && $group [ 'preprocess' ]) {
$js_groups [ $key ][ 'data' ] = drupal_build_js_cache ( $group [ 'items' ]);
2010-03-31 15:35:29 +00:00
}
2010-03-09 03:39:44 +00:00
}
2007-06-01 09:05:45 +00:00
}
- Patch #28483 by Steven: JavaScript enabled uploading.
Comment from Steven: It does this by redirecting the submission of the form to a hidden <iframe> when you click "Attach" (we cannot submit data through Ajax directly because you cannot read file contents from JS for security reasons). Once the file is submitted, the upload-section of the form is updated. Things to note:
* The feature degrades back to the current behaviour without JS.
* If there are errors with the uploaded file (disallowed type, too big, ...), they are displayed at the top of the file attachments fieldset.
* Though the hidden-iframe method sounds dirty, it's quite compact and is 100% implemented in .js files. The drupal.js api makes it a snap to use.
* I included some minor improvements to the Drupal JS API and code.
* I added an API drupal_call_js() to bridge the PHP/JS gap: it takes a function name and arguments, and outputs a <script> tag. The kicker is that it preserves the structure and type of arguments, so e.g. PHP associative arrays end up as objects in JS.
* I also included a progressbar widget that I wrote for drumm's ongoing update.php work. It includes Ajax status updating/monitoring, but it is only used as a pure throbber in this patch. But as the code was already written and is going to be used in the near future, I left that part in. It's pretty small ;). If PHP supports ad-hoc upload info in the future like Ruby on Rails, we can implement that in 5 minutes.
2005-08-31 18:37:30 +00:00
}
2009-08-25 21:16:31 +00:00
/**
2010-10-28 00:08:24 +00:00
* Adds attachments to a render () structure .
2009-08-25 21:16:31 +00:00
*
2009-09-05 15:05:05 +00:00
* Libraries , JavaScript , CSS and other types of custom structures are attached
2010-10-28 00:08:24 +00:00
* to elements using the #attached property. The #attached property is an
* associative array , where the keys are the the attachment types and the values
* are the attached data . For example :
2009-09-05 15:05:05 +00:00
* @ code
* $build [ '#attached' ] = array (
* 'js' => array ( drupal_get_path ( 'module' , 'taxonomy' ) . '/taxonomy.js' ),
* 'css' => array ( drupal_get_path ( 'module' , 'taxonomy' ) . '/taxonomy.css' ),
* );
* @ endcode
*
* 'js' , 'css' , and 'library' are types that get special handling . For any
* other kind of attached data , the array key must be the full name of the
* callback function and each value an array of arguments . For example :
* @ code
2009-09-30 18:36:02 +00:00
* $build [ '#attached' ][ 'drupal_add_http_header' ] = array (
2009-09-05 15:05:05 +00:00
* array ( 'Content-Type' , 'application/rss+xml; charset=utf-8' ),
* );
* @ endcode
*
2010-11-20 09:46:22 +00:00
* External 'js' and 'css' files can also be loaded . For example :
* @ code
* $build [ '#attached' ][ 'js' ] = array (
* 'http://code.jquery.com/jquery-1.4.2.min.js' => array (
* 'type' => 'external' ,
* ),
* );
* @ endcode
*
2009-09-05 15:05:05 +00:00
* @ param $elements
* The structured array describing the data being rendered .
2010-10-05 19:59:10 +00:00
* @ param $group
* The default group of JavaScript and CSS being added . This is only applied
* to the stylesheets and JavaScript items that don ' t have an explicit group
2009-08-25 21:16:31 +00:00
* assigned to them .
* @ param $dependency_check
* When TRUE , will exit if a given library ' s dependencies are missing . When
2010-10-28 00:08:24 +00:00
* set to FALSE , will continue to add the libraries , even though one or more
2009-08-25 21:16:31 +00:00
* dependencies are missing . Defaults to FALSE .
2010-10-28 00:08:24 +00:00
* @ param $every_page
* Set to TRUE to indicate that the attachments are added to every page on the
* site . Only attachments with the every_page flag set to TRUE can participate
* in JavaScript / CSS aggregation .
2010-03-26 17:14:46 +00:00
*
2009-08-25 21:16:31 +00:00
* @ return
2010-10-28 00:08:24 +00:00
* FALSE if there were any missing library dependencies ; TRUE if all library
* dependencies were met .
2009-08-25 21:16:31 +00:00
*
2010-03-26 17:14:46 +00:00
* @ see drupal_add_library ()
* @ see drupal_add_js ()
* @ see drupal_add_css ()
* @ see drupal_render ()
2009-08-25 21:16:31 +00:00
*/
2010-10-05 19:59:10 +00:00
function drupal_process_attached ( $elements , $group = JS_DEFAULT , $dependency_check = FALSE , $every_page = NULL ) {
2009-09-05 15:05:05 +00:00
// Add defaults to the special attached structures that should be processed differently.
$elements [ '#attached' ] += array (
'library' => array (),
'js' => array (),
'css' => array (),
);
2009-08-25 21:16:31 +00:00
// Add the libraries first.
$success = TRUE ;
2009-09-05 15:05:05 +00:00
foreach ( $elements [ '#attached' ][ 'library' ] as $library ) {
2010-10-05 19:59:10 +00:00
if ( drupal_add_library ( $library [ 0 ], $library [ 1 ], $every_page ) === FALSE ) {
2009-08-25 21:16:31 +00:00
$success = FALSE ;
// Exit if the dependency is missing.
if ( $dependency_check ) {
return $success ;
}
}
}
2009-09-05 15:05:05 +00:00
unset ( $elements [ '#attached' ][ 'library' ]);
2009-08-25 21:16:31 +00:00
// Add both the JavaScript and the CSS.
2009-09-05 15:05:05 +00:00
// The parameters for drupal_add_js() and drupal_add_css() require special
// handling.
foreach ( array ( 'js' , 'css' ) as $type ) {
foreach ( $elements [ '#attached' ][ $type ] as $data => $options ) {
2009-08-25 21:16:31 +00:00
// If the value is not an array, it's a filename and passed as first
// (and only) argument.
if ( ! is_array ( $options )) {
$data = $options ;
$options = NULL ;
}
// In some cases, the first parameter ($data) is an array. Arrays can't be
// passed as keys in PHP, so we have to get $data from the value array.
if ( is_numeric ( $data )) {
$data = $options [ 'data' ];
unset ( $options [ 'data' ]);
}
2010-10-05 19:59:10 +00:00
// Apply the default group if it isn't explicitly given.
if ( ! isset ( $options [ 'group' ])) {
$options [ 'group' ] = $group ;
}
// Set the every_page flag if one was passed.
if ( isset ( $every_page )) {
$options [ 'every_page' ] = $every_page ;
2009-08-25 21:16:31 +00:00
}
call_user_func ( 'drupal_add_' . $type , $data , $options );
}
2009-09-05 15:05:05 +00:00
unset ( $elements [ '#attached' ][ $type ]);
}
// Add additional types of attachments specified in the render() structure.
2011-02-19 00:09:11 +00:00
// Libraries, JavaScript and CSS have been added already, as they require
2009-09-05 15:05:05 +00:00
// special handling.
foreach ( $elements [ '#attached' ] as $callback => $options ) {
if ( function_exists ( $callback )) {
foreach ( $elements [ '#attached' ][ $callback ] as $args ) {
call_user_func_array ( $callback , $args );
}
}
2009-08-25 21:16:31 +00:00
}
2009-09-05 15:05:05 +00:00
2009-08-25 21:16:31 +00:00
return $success ;
}
2009-10-16 19:20:34 +00:00
/**
2010-05-01 00:05:04 +00:00
* Adds JavaScript to change the state of an element based on another element .
2009-10-16 19:20:34 +00:00
*
2010-05-01 00:05:04 +00:00
* A " state " means a certain property on a DOM element , such as " visible " or
* " checked " . A state can be applied to an element , depending on the state of
* another element on the page . In general , states depend on HTML attributes and
* DOM element properties , which change due to user interaction .
*
* Since states are driven by JavaScript only , it is important to understand
* that all states are applied on presentation only , none of the states force
* any server - side logic , and that they will not be applied for site visitors
* without JavaScript support . All modules implementing states have to make
* sure that the intended logic also works without JavaScript being enabled .
*
* #states is an associative array in the form of:
* @ code
* array (
* STATE1 => CONDITIONS_ARRAY1 ,
* STATE2 => CONDITIONS_ARRAY2 ,
* ...
* )
* @ endcode
* Each key is the name of a state to apply to the element , such as 'visible' .
* Each value is a list of conditions that denote when the state should be
* applied .
*
* Multiple different states may be specified to act on complex conditions :
* @ code
* array (
* 'visible' => CONDITIONS ,
* 'checked' => OTHER_CONDITIONS ,
* )
* @ endcode
*
* Every condition is a key / value pair , whose key is a jQuery selector that
* denotes another element on the page , and whose value is an array of
* conditions , which must bet met on that element :
* @ code
* array (
* 'visible' => array (
* JQUERY_SELECTOR => REMOTE_CONDITIONS ,
* JQUERY_SELECTOR => REMOTE_CONDITIONS ,
* ...
* ),
* )
* @ endcode
* All conditions must be met for the state to be applied .
*
* Each remote condition is a key / value pair specifying conditions on the other
* element that need to be met to apply the state to the element :
* @ code
* array (
* 'visible' => array (
* ':input[name="remote_checkbox"]' => array ( 'checked' => TRUE ),
* ),
* )
* @ endcode
*
* For example , to show a textfield only when a checkbox is checked :
* @ code
* $form [ 'toggle_me' ] = array (
* '#type' => 'checkbox' ,
* '#title' => t ( 'Tick this box to type' ),
* );
* $form [ 'settings' ] = array (
* '#type' => 'textfield' ,
* '#states' => array (
* // Only show this field when the 'toggle_me' checkbox is enabled.
* 'visible' => array (
* ':input[name="toggle_me"]' => array ( 'checked' => TRUE ),
2009-10-16 19:20:34 +00:00
* ),
2010-05-01 00:05:04 +00:00
* ),
* );
* @ endcode
*
* The following states may be applied to an element :
* - enabled
* - disabled
2011-08-10 19:47:50 +00:00
* - required
* - optional
2010-05-01 00:05:04 +00:00
* - visible
* - invisible
* - checked
* - unchecked
* - expanded
* - collapsed
*
* The following states may be used in remote conditions :
2011-08-10 19:47:50 +00:00
* - empty
* - filled
2010-05-01 00:05:04 +00:00
* - checked
* - unchecked
2011-08-10 19:47:50 +00:00
* - expanded
* - collapsed
2010-05-01 00:05:04 +00:00
* - value
*
2011-08-10 19:47:50 +00:00
* The following states exist for both elements and remote conditions , but are
* not fully implemented and may not change anything on the element :
2010-05-01 00:05:04 +00:00
* - relevant
* - irrelevant
* - valid
* - invalid
* - touched
* - untouched
* - readwrite
* - readonly
*
* When referencing select lists and radio buttons in remote conditions , a
* 'value' condition must be used :
* @ code
* '#states' => array (
* // Show the settings if 'bar' has been selected for 'foo'.
* 'visible' => array (
* ':input[name="foo"]' => array ( 'value' => 'bar' ),
* ),
* ),
* @ endcode
*
* @ param $elements
* A renderable array element having a #states property as described above.
*
* @ see form_example_states_form ()
2009-10-16 19:20:34 +00:00
*/
function drupal_process_states ( & $elements ) {
2010-11-30 17:16:37 +00:00
$elements [ '#attached' ][ 'library' ][] = array ( 'system' , 'drupal.states' );
2009-11-16 05:15:21 +00:00
$elements [ '#attached' ][ 'js' ][] = array (
'type' => 'setting' ,
'data' => array ( 'states' => array ( '#' . $elements [ '#id' ] => $elements [ '#states' ])),
);
2009-10-16 19:20:34 +00:00
}
2009-07-04 18:26:42 +00:00
/**
* Adds multiple JavaScript or CSS files at the same time .
*
* A library defines a set of JavaScript and / or CSS files , optionally using
* settings , and optionally requiring another library . For example , a library
* can be a jQuery plugin , a JavaScript framework , or a CSS framework . This
* function allows modules to load a library defined / shipped by itself or a
2010-10-28 00:08:24 +00:00
* depending module , without having to add all files of the library separately .
2009-07-04 18:26:42 +00:00
* Each library is only loaded once .
*
* @ param $module
* The name of the module that registered the library .
* @ param $name
* The name of the library to add .
2010-10-28 00:08:24 +00:00
* @ param $every_page
* Set to TRUE if this library is added to every page on the site . Only items
* with the every_page flag set to TRUE can participate in aggregation .
*
2009-07-04 18:26:42 +00:00
* @ return
2010-10-28 00:08:24 +00:00
* TRUE if the library was successfully added ; FALSE if the library or one of
* its dependencies could not be added .
2009-07-04 18:26:42 +00:00
*
* @ see drupal_get_library ()
2011-09-24 19:50:19 +00:00
* @ see hook_library_info ()
* @ see hook_library_info_alter ()
2009-07-04 18:26:42 +00:00
*/
2010-10-05 19:59:10 +00:00
function drupal_add_library ( $module , $name , $every_page = NULL ) {
2009-07-04 18:26:42 +00:00
$added = & drupal_static ( __FUNCTION__ , array ());
// Only process the library if it exists and it was not added already.
2009-08-25 21:16:31 +00:00
if ( ! isset ( $added [ $module ][ $name ])) {
if ( $library = drupal_get_library ( $module , $name )) {
// Add all components within the library.
2009-09-05 15:05:05 +00:00
$elements [ '#attached' ] = array (
'library' => $library [ 'dependencies' ],
'js' => $library [ 'js' ],
'css' => $library [ 'css' ],
);
2010-10-05 19:59:10 +00:00
$added [ $module ][ $name ] = drupal_process_attached ( $elements , JS_LIBRARY , TRUE , $every_page );
2009-07-04 18:26:42 +00:00
}
2009-08-25 21:16:31 +00:00
else {
// Requested library does not exist.
$added [ $module ][ $name ] = FALSE ;
2009-07-04 18:26:42 +00:00
}
}
return $added [ $module ][ $name ];
}
/**
* Retrieves information for a JavaScript / CSS library .
*
* Library information is statically cached . Libraries are keyed by module for
* several reasons :
* - Libraries are not unique . Multiple modules might ship with the same library
* in a different version or variant . This registry cannot ( and does not
* attempt to ) prevent library conflicts .
* - Modules implementing and thereby depending on a library that is registered
* by another module can only rely on that module ' s library .
* - Two ( or more ) modules can still register the same library and use it
* without conflicts in case the libraries are loaded on certain pages only .
*
* @ param $module
* The name of a module that registered a library .
2010-12-15 04:21:39 +00:00
* @ param $name
* ( optional ) The name of a registered library to retrieve . By default , all
* libraries registered by $module are returned .
*
2009-07-04 18:26:42 +00:00
* @ return
2010-12-15 04:21:39 +00:00
* The definition of the requested library , if $name was passed and it exists ,
* or FALSE if it does not exist . If no $name was passed , an associative array
* of libraries registered by $module is returned ( which may be empty ) .
2009-07-04 18:26:42 +00:00
*
* @ see drupal_add_library ()
2011-09-24 19:50:19 +00:00
* @ see hook_library_info ()
* @ see hook_library_info_alter ()
2009-07-04 18:26:42 +00:00
*
* @ todo The purpose of drupal_get_ * () is completely different to other page
* requisite API functions ; find and use a different name .
*/
2010-12-15 04:21:39 +00:00
function drupal_get_library ( $module , $name = NULL ) {
2009-07-04 18:26:42 +00:00
$libraries = & drupal_static ( __FUNCTION__ , array ());
2010-03-09 03:39:44 +00:00
if ( ! isset ( $libraries [ $module ])) {
2009-07-04 18:26:42 +00:00
// Retrieve all libraries associated with the module.
2011-09-24 19:50:19 +00:00
$module_libraries = module_invoke ( $module , 'library_info' );
2010-03-09 03:39:44 +00:00
if ( empty ( $module_libraries )) {
$module_libraries = array ();
}
2009-07-04 18:26:42 +00:00
// Allow modules to alter the module's registered libraries.
2011-09-24 19:50:19 +00:00
drupal_alter ( 'library_info' , $module_libraries , $module );
2010-03-09 03:39:44 +00:00
foreach ( $module_libraries as $key => $data ) {
if ( is_array ( $data )) {
// Add default elements to allow for easier processing.
$module_libraries [ $key ] += array ( 'dependencies' => array (), 'js' => array (), 'css' => array ());
foreach ( $module_libraries [ $key ][ 'js' ] as $file => $options ) {
$module_libraries [ $key ][ 'js' ][ $file ][ 'version' ] = $module_libraries [ $key ][ 'version' ];
}
}
2009-07-04 18:26:42 +00:00
}
$libraries [ $module ] = $module_libraries ;
}
2010-12-15 04:21:39 +00:00
if ( isset ( $name )) {
if ( ! isset ( $libraries [ $module ][ $name ])) {
$libraries [ $module ][ $name ] = FALSE ;
}
return $libraries [ $module ][ $name ];
2009-07-04 18:26:42 +00:00
}
2010-12-15 04:21:39 +00:00
return $libraries [ $module ];
2009-07-04 18:26:42 +00:00
}
2007-11-14 09:50:00 +00:00
/**
* Assist in adding the tableDrag JavaScript behavior to a themed table .
*
* Draggable tables should be used wherever an outline or list of sortable items
* needs to be arranged by an end - user . Draggable tables are very flexible and
* can manipulate the value of form elements placed within individual columns .
*
2007-11-26 16:36:44 +00:00
* To set up a table to use drag and drop in place of weight select - lists or
2007-11-14 09:50:00 +00:00
* in place of a form that contains parent relationships , the form must be
* themed into a table . The table must have an id attribute set . If using
* theme_table (), the id may be set as such :
* @ code
2009-10-09 01:00:08 +00:00
* $output = theme ( 'table' , array ( 'header' => $header , 'rows' => $rows , 'attributes' => array ( 'id' => 'my-module-table' )));
2007-11-14 09:50:00 +00:00
* return $output ;
* @ endcode
*
* In the theme function for the form , a special class must be added to each
* form element within the same column , " grouping " them together .
*
* In a situation where a single weight column is being sorted in the table , the
* classes could be added like this ( in the theme function ) :
* @ code
2009-08-22 14:34:23 +00:00
* $form [ 'my_elements' ][ $delta ][ 'weight' ][ '#attributes' ][ 'class' ] = array ( 'my-elements-weight' );
2007-11-14 09:50:00 +00:00
* @ endcode
*
2007-11-20 20:13:04 +00:00
* Each row of the table must also have a class of " draggable " in order to enable the
* drag handles :
* @ code
* $row = array ( ... );
* $rows [] = array (
* 'data' => $row ,
2009-08-22 14:34:23 +00:00
* 'class' => array ( 'draggable' ),
2007-11-20 20:13:04 +00:00
* );
* @ endcode
2007-11-23 13:34:55 +00:00
*
2007-12-19 10:58:35 +00:00
* When tree relationships are present , the two additional classes
* 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior :
* - Rows with the 'tabledrag-leaf' class cannot have child rows .
* - Rows with the 'tabledrag-root' class cannot be nested under a parent row .
*
2007-11-14 09:50:00 +00:00
* Calling drupal_add_tabledrag () would then be written as such :
* @ code
2007-11-20 10:18:43 +00:00
* drupal_add_tabledrag ( 'my-module-table' , 'order' , 'sibling' , 'my-elements-weight' );
2007-11-14 09:50:00 +00:00
* @ endcode
*
* In a more complex case where there are several groups in one column ( such as
2009-07-20 18:51:36 +00:00
* the block regions on the admin / structure / block page ), a separate subgroup class
2007-11-14 09:50:00 +00:00
* must also be added to differentiate the groups .
* @ code
2009-08-22 14:34:23 +00:00
* $form [ 'my_elements' ][ $region ][ $delta ][ 'weight' ][ '#attributes' ][ 'class' ] = array ( 'my-elements-weight' , 'my-elements-weight-' . $region );
2007-11-14 09:50:00 +00:00
* @ endcode
*
* $group is still 'my-element-weight' , and the additional $subgroup variable
2008-04-14 17:48:46 +00:00
* will be passed in as 'my-elements-weight-' . $region . This also means that
2007-11-14 09:50:00 +00:00
* you ' ll need to call drupal_add_tabledrag () once for every region added .
*
* @ code
* foreach ( $regions as $region ) {
2008-04-14 17:48:46 +00:00
* drupal_add_tabledrag ( 'my-module-table' , 'order' , 'sibling' , 'my-elements-weight' , 'my-elements-weight-' . $region );
2007-11-14 09:50:00 +00:00
* }
* @ endcode
*
* In a situation where tree relationships are present , adding multiple
* subgroups is not necessary , because the table will contain indentations that
* provide enough information about the sibling and parent relationships .
* See theme_menu_overview_form () for an example creating a table containing
* parent relationships .
*
* Please note that this function should be called from the theme layer , such as
* in a . tpl . php file , theme_ function , or in a template_preprocess function ,
2008-12-30 16:43:20 +00:00
* not in a form declaration . Though the same JavaScript could be added to the
2007-11-14 09:50:00 +00:00
* page using drupal_add_js () directly , this function helps keep template files
* clean and readable . It also prevents tabledrag . js from being added twice
* accidentally .
*
* @ param $table_id
* String containing the target table ' s id attribute . If the table does not
* have an id , one will need to be set , such as < table id = " my-module-table " >.
* @ param $action
2007-11-20 10:18:43 +00:00
* String describing the action to be done on the form item . Either 'match'
* 'depth' , or 'order' . Match is typically used for parent relationships .
* Order is typically used to set weights on other form elements with the same
* group . Depth updates the target element with the current indentation .
2007-11-14 09:50:00 +00:00
* @ param $relationship
* String describing where the $action variable should be performed . Either
2007-11-26 19:46:52 +00:00
* 'parent' , 'sibling' , 'group' , or 'self' . Parent will only look for fields
* up the tree . Sibling will look for fields in the same group in rows above
* and below it . Self affects the dragged row itself . Group affects the
* dragged row , plus any children below it ( the entire dragged group ) .
2007-11-14 09:50:00 +00:00
* @ param $group
* A class name applied on all related form elements for this action .
* @ param $subgroup
* ( optional ) If the group has several subgroups within it , this string should
* contain the class name identifying fields in the same subgroup .
* @ param $source
* ( optional ) If the $action is 'match' , this string should contain the class
* name identifying what field will be used as the source value when matching
* the value in $subgroup .
* @ param $hidden
* ( optional ) The column containing the field elements may be entirely hidden
* from view dynamically when the JavaScript is loaded . Set to FALSE if the
* column should not be hidden .
2007-11-26 16:19:37 +00:00
* @ param $limit
* ( optional ) Limit the maximum amount of parenting in this table .
2007-11-14 09:50:00 +00:00
* @ see block - admin - display - form . tpl . php
* @ see theme_menu_overview_form ()
*/
2007-11-26 16:19:37 +00:00
function drupal_add_tabledrag ( $table_id , $action , $relationship , $group , $subgroup = NULL , $source = NULL , $hidden = TRUE , $limit = 0 ) {
2009-05-31 07:00:12 +00:00
$js_added = & drupal_static ( __FUNCTION__ , FALSE );
2007-11-14 09:50:00 +00:00
if ( ! $js_added ) {
2008-11-10 05:23:01 +00:00
// Add the table drag JavaScript to the page before the module JavaScript
// to ensure that table drag behaviors are registered before any module
// uses it.
2010-11-30 17:16:37 +00:00
drupal_add_library ( 'system' , 'jquery.cookie' );
2011-10-31 04:05:57 +00:00
drupal_add_js ( 'core/misc/tabledrag.js' , array ( 'weight' => - 1 ));
2007-11-14 09:50:00 +00:00
$js_added = TRUE ;
}
// If a subgroup or source isn't set, assume it is the same as the group.
$target = isset ( $subgroup ) ? $subgroup : $group ;
$source = isset ( $source ) ? $source : $target ;
$settings [ 'tableDrag' ][ $table_id ][ $group ][] = array (
'target' => $target ,
'source' => $source ,
'relationship' => $relationship ,
'action' => $action ,
'hidden' => $hidden ,
2007-11-26 16:19:37 +00:00
'limit' => $limit ,
2007-11-14 09:50:00 +00:00
);
drupal_add_js ( $settings , 'setting' );
}
2007-06-01 09:05:45 +00:00
/**
2010-05-09 19:44:25 +00:00
* Aggregates JavaScript files into a cache file in the files directory .
*
* The file name for the JavaScript cache file is generated from the hash of
* the aggregated contents of the files in $files . This forces proxies and
* browsers to download new JavaScript when the JavaScript changes .
*
* The cache file name is retrieved on a page load via a lookup variable that
* contains an associative array . The array key is the hash of the names in
* $files while the value is the cache file name . The cache file is generated
* in two cases . First , if there is no file name value for the key , which will
* happen if a new file name has been added to $files or after the lookup
* variable is emptied to force a rebuild of the cache . Second , the cache
* file is generated if it is missing on disk . Old cache files are not deleted
* immediately when the lookup variable is emptied , but are deleted after a set
* period by drupal_delete_file_if_stale () . This ensures that files referenced
* by a cached page will still be available .
2007-06-01 09:05:45 +00:00
*
* @ param $files
2010-05-09 19:44:25 +00:00
* An array of JavaScript files to aggregate and compress into one file .
*
2007-06-01 09:05:45 +00:00
* @ return
2010-05-09 19:44:25 +00:00
* The URI of the cache file , or FALSE if the file could not be saved .
2007-06-01 09:05:45 +00:00
*/
2010-05-09 19:44:25 +00:00
function drupal_build_js_cache ( $files ) {
2007-06-01 09:05:45 +00:00
$contents = '' ;
2010-05-09 19:44:25 +00:00
$uri = '' ;
$map = variable_get ( 'drupal_js_cache_files' , array ());
$key = hash ( 'sha256' , serialize ( $files ));
if ( isset ( $map [ $key ])) {
$uri = $map [ $key ];
}
2007-06-01 09:05:45 +00:00
2010-05-09 19:44:25 +00:00
if ( empty ( $uri ) || ! file_exists ( $uri )) {
2007-06-04 07:22:23 +00:00
// Build aggregate JS file.
2011-11-03 11:00:04 +00:00
foreach ( $files as $info ) {
2007-06-04 07:22:23 +00:00
if ( $info [ 'preprocess' ]) {
2010-05-16 19:21:45 +00:00
// Append a ';' and a newline after each JS file to prevent them from running together.
2011-11-03 11:00:04 +00:00
$contents .= file_get_contents ( $info [ 'data' ]) . " ; \n " ;
2007-06-01 09:05:45 +00:00
}
}
2010-05-09 19:44:25 +00:00
// Prefix filename to prevent blocking by firewalls which reject files
// starting with "ad*".
$filename = 'js_' . drupal_hash_base64 ( $contents ) . '.js' ;
// Create the js/ within the files folder.
$jspath = 'public://js' ;
$uri = $jspath . '/' . $filename ;
2007-06-01 09:05:45 +00:00
// Create the JS file.
2010-03-31 15:35:29 +00:00
file_prepare_directory ( $jspath , FILE_CREATE_DIRECTORY );
2010-10-11 23:49:48 +00:00
if ( ! file_exists ( $uri ) && ! file_unmanaged_save_data ( $contents , $uri , FILE_EXISTS_REPLACE )) {
2010-03-31 15:35:29 +00:00
return FALSE ;
}
2010-10-11 23:49:48 +00:00
// If JS gzip compression is enabled, clean URLs are enabled (which means
// that rewrite rules are working) and the zlib extension is available then
// create a gzipped version of this file. This file is served conditionally
// to browsers that accept gzip using .htaccess rules.
if ( variable_get ( 'js_gzip_compression' , TRUE ) && variable_get ( 'clean_url' , 0 ) && extension_loaded ( 'zlib' )) {
if ( ! file_exists ( $uri . '.gz' ) && ! file_unmanaged_save_data ( gzencode ( $contents , 9 , FORCE_GZIP ), $uri . '.gz' , FILE_EXISTS_REPLACE )) {
return FALSE ;
}
}
2010-05-09 19:44:25 +00:00
$map [ $key ] = $uri ;
variable_set ( 'drupal_js_cache_files' , $map );
2007-06-01 09:05:45 +00:00
}
2010-03-31 15:35:29 +00:00
return $uri ;
2007-06-01 09:05:45 +00:00
}
/**
2010-05-09 19:44:25 +00:00
* Deletes old cached JavaScript files and variables .
2007-06-01 09:05:45 +00:00
*/
function drupal_clear_js_cache () {
2010-05-09 19:44:25 +00:00
variable_del ( 'javascript_parsed' );
variable_del ( 'drupal_js_cache_files' );
file_scan_directory ( 'public://js' , '/.*/' , array ( 'callback' => 'drupal_delete_file_if_stale' ));
2007-06-01 09:05:45 +00:00
}
- Patch #28483 by Steven: JavaScript enabled uploading.
Comment from Steven: It does this by redirecting the submission of the form to a hidden <iframe> when you click "Attach" (we cannot submit data through Ajax directly because you cannot read file contents from JS for security reasons). Once the file is submitted, the upload-section of the form is updated. Things to note:
* The feature degrades back to the current behaviour without JS.
* If there are errors with the uploaded file (disallowed type, too big, ...), they are displayed at the top of the file attachments fieldset.
* Though the hidden-iframe method sounds dirty, it's quite compact and is 100% implemented in .js files. The drupal.js api makes it a snap to use.
* I included some minor improvements to the Drupal JS API and code.
* I added an API drupal_call_js() to bridge the PHP/JS gap: it takes a function name and arguments, and outputs a <script> tag. The kicker is that it preserves the structure and type of arguments, so e.g. PHP associative arrays end up as objects in JS.
* I also included a progressbar widget that I wrote for drumm's ongoing update.php work. It includes Ajax status updating/monitoring, but it is only used as a pure throbber in this patch. But as the code was already written and is going to be used in the near future, I left that part in. It's pretty small ;). If PHP supports ad-hoc upload info in the future like Ruby on Rails, we can implement that in 5 minutes.
2005-08-31 18:37:30 +00:00
/**
2011-02-19 00:09:11 +00:00
* Converts a PHP variable into its JavaScript equivalent .
2006-02-05 19:04:58 +00:00
*
* We use HTML - safe strings , i . e . with < , > and & escaped .
2009-11-21 00:43:42 +00:00
*
* @ see drupal_json_decode ()
2010-01-04 12:27:33 +00:00
* @ ingroup php_wrappers
- Patch #28483 by Steven: JavaScript enabled uploading.
Comment from Steven: It does this by redirecting the submission of the form to a hidden <iframe> when you click "Attach" (we cannot submit data through Ajax directly because you cannot read file contents from JS for security reasons). Once the file is submitted, the upload-section of the form is updated. Things to note:
* The feature degrades back to the current behaviour without JS.
* If there are errors with the uploaded file (disallowed type, too big, ...), they are displayed at the top of the file attachments fieldset.
* Though the hidden-iframe method sounds dirty, it's quite compact and is 100% implemented in .js files. The drupal.js api makes it a snap to use.
* I included some minor improvements to the Drupal JS API and code.
* I added an API drupal_call_js() to bridge the PHP/JS gap: it takes a function name and arguments, and outputs a <script> tag. The kicker is that it preserves the structure and type of arguments, so e.g. PHP associative arrays end up as objects in JS.
* I also included a progressbar widget that I wrote for drumm's ongoing update.php work. It includes Ajax status updating/monitoring, but it is only used as a pure throbber in this patch. But as the code was already written and is going to be used in the near future, I left that part in. It's pretty small ;). If PHP supports ad-hoc upload info in the future like Ruby on Rails, we can implement that in 5 minutes.
2005-08-31 18:37:30 +00:00
*/
2009-09-21 07:56:09 +00:00
function drupal_json_encode ( $var ) {
2009-11-21 00:43:42 +00:00
// json_encode() does not escape <, > and &, so we do it with str_replace().
2010-01-09 23:36:55 +00:00
return str_replace ( array ( '<' , '>' , '&' ), array ( '\u003c' , '\u003e' , '\u0026' ), json_encode ( $var ));
2009-11-21 00:43:42 +00:00
}
/**
* Converts an HTML - safe JSON string into its PHP equivalent .
*
* @ see drupal_json_encode ()
2010-01-04 23:08:34 +00:00
* @ ingroup php_wrappers
2009-11-21 00:43:42 +00:00
*/
function drupal_json_decode ( $var ) {
2010-01-09 23:36:55 +00:00
return json_decode ( $var , TRUE );
- Patch #28483 by Steven: JavaScript enabled uploading.
Comment from Steven: It does this by redirecting the submission of the form to a hidden <iframe> when you click "Attach" (we cannot submit data through Ajax directly because you cannot read file contents from JS for security reasons). Once the file is submitted, the upload-section of the form is updated. Things to note:
* The feature degrades back to the current behaviour without JS.
* If there are errors with the uploaded file (disallowed type, too big, ...), they are displayed at the top of the file attachments fieldset.
* Though the hidden-iframe method sounds dirty, it's quite compact and is 100% implemented in .js files. The drupal.js api makes it a snap to use.
* I included some minor improvements to the Drupal JS API and code.
* I added an API drupal_call_js() to bridge the PHP/JS gap: it takes a function name and arguments, and outputs a <script> tag. The kicker is that it preserves the structure and type of arguments, so e.g. PHP associative arrays end up as objects in JS.
* I also included a progressbar widget that I wrote for drumm's ongoing update.php work. It includes Ajax status updating/monitoring, but it is only used as a pure throbber in this patch. But as the code was already written and is going to be used in the near future, I left that part in. It's pretty small ;). If PHP supports ad-hoc upload info in the future like Ruby on Rails, we can implement that in 5 minutes.
2005-08-31 18:37:30 +00:00
}
2007-06-22 08:46:16 +00:00
/**
* Return data in JSON format .
2007-06-25 12:42:07 +00:00
*
2007-06-22 08:46:16 +00:00
* This function should be used for JavaScript callback functions returning
* data in JSON format . It sets the header for JavaScript output .
*
* @ param $var
* ( optional ) If set , the variable will be converted to JSON and output .
*/
2009-09-21 07:56:09 +00:00
function drupal_json_output ( $var = NULL ) {
2010-05-04 14:55:22 +00:00
// We are returning JSON, so tell the browser.
drupal_add_http_header ( 'Content-Type' , 'application/json' );
2007-06-22 08:46:16 +00:00
if ( isset ( $var )) {
2009-09-21 07:56:09 +00:00
echo drupal_json_encode ( $var );
2007-06-22 08:46:16 +00:00
}
}
2010-01-14 18:45:17 +00:00
/**
* Get a salt useful for hardening against SQL injection .
*
* @ return
* A salt based on information in settings . php , not in the database .
*/
function drupal_get_hash_salt () {
global $drupal_hash_salt , $databases ;
// If the $drupal_hash_salt variable is empty, a hash of the serialized
// database credentials is used as a fallback salt.
2010-05-01 08:12:23 +00:00
return empty ( $drupal_hash_salt ) ? hash ( 'sha256' , serialize ( $databases )) : $drupal_hash_salt ;
2010-01-14 18:45:17 +00:00
}
2006-10-31 08:06:18 +00:00
/**
* Ensure the private key variable used to generate tokens is set .
*
* @ return
2007-10-08 14:08:19 +00:00
* The private key .
2006-10-31 08:06:18 +00:00
*/
function drupal_get_private_key () {
if ( ! ( $key = variable_get ( 'drupal_private_key' , 0 ))) {
2010-05-01 08:12:23 +00:00
$key = drupal_hash_base64 ( drupal_random_bytes ( 55 ));
2006-10-31 08:06:18 +00:00
variable_set ( 'drupal_private_key' , $key );
}
return $key ;
}
/**
* Generate a token based on $value , the current user session and private key .
*
* @ param $value
2007-10-08 14:08:19 +00:00
* An additional value to base the token on .
2006-10-31 08:06:18 +00:00
*/
function drupal_get_token ( $value = '' ) {
2010-05-01 08:12:23 +00:00
return drupal_hmac_base64 ( $value , session_id () . drupal_get_private_key () . drupal_get_hash_salt ());
2006-10-31 08:06:18 +00:00
}
/**
* Validate a token based on $value , the current user session and private key .
*
* @ param $token
* The token to be validated .
* @ param $value
* An additional value to base the token on .
* @ param $skip_anonymous
* Set to true to skip token validation for anonymous users .
* @ return
2007-10-08 14:08:19 +00:00
* True for a valid token , false for an invalid token . When $skip_anonymous
* is true , the return value will always be true for anonymous users .
2006-10-31 08:06:18 +00:00
*/
function drupal_valid_token ( $token , $value = '' , $skip_anonymous = FALSE ) {
global $user ;
2010-01-14 18:45:17 +00:00
return (( $skip_anonymous && $user -> uid == 0 ) || ( $token == drupal_get_token ( $value )));
2006-10-31 08:06:18 +00:00
}
2005-06-22 20:19:58 +00:00
function _drupal_bootstrap_full () {
2011-02-19 13:19:26 +00:00
static $called = FALSE ;
2003-03-17 07:01:12 +00:00
2005-06-22 20:19:58 +00:00
if ( $called ) {
return ;
}
2011-02-19 13:19:26 +00:00
$called = TRUE ;
2011-10-31 04:05:57 +00:00
require_once DRUPAL_ROOT . '/' . variable_get ( 'path_inc' , 'core/includes/path.inc' );
require_once DRUPAL_ROOT . '/core/includes/theme.inc' ;
require_once DRUPAL_ROOT . '/core/includes/pager.inc' ;
require_once DRUPAL_ROOT . '/' . variable_get ( 'menu_inc' , 'core/includes/menu.inc' );
require_once DRUPAL_ROOT . '/core/includes/tablesort.inc' ;
require_once DRUPAL_ROOT . '/core/includes/file.inc' ;
require_once DRUPAL_ROOT . '/core/includes/unicode.inc' ;
require_once DRUPAL_ROOT . '/core/includes/image.inc' ;
require_once DRUPAL_ROOT . '/core/includes/form.inc' ;
require_once DRUPAL_ROOT . '/core/includes/mail.inc' ;
require_once DRUPAL_ROOT . '/core/includes/actions.inc' ;
require_once DRUPAL_ROOT . '/core/includes/ajax.inc' ;
require_once DRUPAL_ROOT . '/core/includes/token.inc' ;
require_once DRUPAL_ROOT . '/core/includes/errors.inc' ;
2008-10-15 16:05:51 +00:00
2005-07-25 20:40:35 +00:00
// Detect string handling method
2005-07-27 01:58:43 +00:00
unicode_check ();
2005-11-29 20:17:10 +00:00
// Undo magic quotes
2005-06-22 20:19:58 +00:00
fix_gpc_magic ();
2006-06-08 21:23:40 +00:00
// Load all enabled modules
module_load_all ();
2009-07-27 19:53:18 +00:00
// Make sure all stream wrappers are registered.
file_get_stream_wrappers ();
2010-06-28 19:57:34 +00:00
$test_info = & $GLOBALS [ 'drupal_test_info' ];
if ( ! empty ( $test_info [ 'in_child_site' ])) {
// Running inside the simpletest child site, log fatal errors to test
// specific file directory.
2009-08-24 00:14:23 +00:00
ini_set ( 'log_errors' , 1 );
2010-09-01 20:08:17 +00:00
ini_set ( 'error_log' , 'public://error.log' );
2009-08-24 00:14:23 +00:00
}
2010-06-28 19:57:34 +00:00
2009-10-24 05:13:44 +00:00
// Initialize $_GET['q'] prior to invoking hook_init().
drupal_path_initialize ();
2010-08-22 12:46:21 +00:00
// Let all modules take action before the menu system handles the request.
2007-11-16 15:35:24 +00:00
// We do not want this while running update.php.
2008-01-07 19:43:29 +00:00
if ( ! defined ( 'MAINTENANCE_MODE' ) || MAINTENANCE_MODE != 'update' ) {
2010-08-22 12:46:21 +00:00
// Prior to invoking hook_init(), initialize the theme (potentially a custom
// one for this page), so that:
// - Modules with hook_init() implementations that call theme() or
// theme_get_registry() don't initialize the incorrect theme.
// - The theme can have hook_*_alter() implementations affect page building
// (e.g., hook_form_alter(), hook_node_view_alter(), hook_page_alter()),
// ahead of when rendering starts.
menu_set_custom_theme ();
drupal_theme_initialize ();
2007-11-16 15:35:24 +00:00
module_invoke_all ( 'init' );
}
2003-11-18 19:44:36 +00:00
}
2006-01-23 07:54:08 +00:00
/**
* Store the current page in the cache .
*
2009-11-02 03:12:05 +00:00
* If page_compression is enabled , a gzipped version of the page is stored in
* the cache to avoid compressing the output on each request . The cache entry
* is unzipped in the relatively rare event that the page is requested by a
* client without gzip support .
*
* Page compression requires the PHP zlib extension
* ( http :// php . net / manual / en / ref . zlib . php ) .
2006-01-23 07:54:08 +00:00
*
2010-01-30 07:54:01 +00:00
* @ see drupal_page_header ()
2006-01-23 07:54:08 +00:00
*/
2009-06-02 06:58:17 +00:00
function drupal_page_set_cache () {
global $base_root ;
2006-01-23 07:54:08 +00:00
2009-06-02 06:58:17 +00:00
if ( drupal_page_is_cacheable ()) {
2009-04-22 09:45:03 +00:00
$cache = ( object ) array (
'cid' => $base_root . request_uri (),
2010-05-18 18:26:30 +00:00
'data' => array (
'path' => $_GET [ 'q' ],
'body' => ob_get_clean (),
'title' => drupal_get_title (),
'headers' => array (),
),
2009-04-22 09:45:03 +00:00
'expire' => CACHE_TEMPORARY ,
'created' => REQUEST_TIME ,
);
2009-06-02 06:58:17 +00:00
2009-04-24 08:16:56 +00:00
// Restore preferred header names based on the lower-case names returned
2009-09-30 18:36:02 +00:00
// by drupal_get_http_header().
2009-04-24 08:16:56 +00:00
$header_names = _drupal_set_preferred_header_name ();
2009-09-30 18:36:02 +00:00
foreach ( drupal_get_http_header () as $name_lower => $value ) {
2010-05-18 18:26:30 +00:00
$cache -> data [ 'headers' ][ $header_names [ $name_lower ]] = $value ;
2009-12-15 08:45:32 +00:00
if ( $name_lower == 'expires' ) {
// Use the actual timestamp from an Expires header if available.
$cache -> expire = strtotime ( $value );
}
2009-04-24 08:16:56 +00:00
}
2009-06-02 06:58:17 +00:00
2010-05-18 18:26:30 +00:00
if ( $cache -> data [ 'body' ]) {
2009-11-02 03:12:05 +00:00
if ( variable_get ( 'page_compression' , TRUE ) && extension_loaded ( 'zlib' )) {
2010-05-18 18:26:30 +00:00
$cache -> data [ 'body' ] = gzencode ( $cache -> data [ 'body' ], 9 , FORCE_GZIP );
2006-01-23 07:54:08 +00:00
}
2011-09-11 16:14:18 +00:00
cache ( 'page' ) -> set ( $cache -> cid , $cache -> data , $cache -> expire );
2006-01-23 07:54:08 +00:00
}
2009-06-02 06:58:17 +00:00
return $cache ;
2006-01-23 07:54:08 +00:00
}
2006-03-01 21:30:17 +00:00
}
2006-07-10 08:12:31 +00:00
2006-08-09 07:42:55 +00:00
/**
2009-10-13 05:37:46 +00:00
* Executes a cron run when called .
*
* Do not call this function from test , use $this -> cronRun () instead .
*
2006-08-09 07:42:55 +00:00
* @ return
2009-10-13 05:37:46 +00:00
* Returns TRUE if ran successfully
2006-08-09 07:42:55 +00:00
*/
function drupal_cron_run () {
2008-05-30 17:41:51 +00:00
// Allow execution to continue even if the request gets canceled.
@ ignore_user_abort ( TRUE );
2010-03-12 14:20:32 +00:00
// Prevent session information from being saved while cron is running.
drupal_save_session ( FALSE );
// Force the current user to anonymous to ensure consistent permissions on
// cron runs.
$original_user = $GLOBALS [ 'user' ];
$GLOBALS [ 'user' ] = drupal_anonymous_user ();
2009-08-05 15:58:35 +00:00
// Try to allocate enough time to run all the hook_cron implementations.
drupal_set_time_limit ( 240 );
2006-08-09 07:42:55 +00:00
2009-09-25 15:20:12 +00:00
$return = FALSE ;
// Grab the defined cron queues.
$queues = module_invoke_all ( 'cron_queue_info' );
drupal_alter ( 'cron_queue_info' , $queues );
2006-10-03 00:24:19 +00:00
2010-07-02 15:31:10 +00:00
// Try to acquire cron lock.
if ( ! lock_acquire ( 'cron' , 240.0 )) {
// Cron is still running normally.
2011-07-04 16:58:33 +00:00
watchdog ( 'cron' , 'Attempting to re-run cron while it is already running.' , array (), WATCHDOG_WARNING );
2006-08-09 07:42:55 +00:00
}
else {
2009-09-25 15:20:12 +00:00
// Make sure every queue exists. There is no harm in trying to recreate an
// existing queue.
foreach ( $queues as $queue_name => $info ) {
DrupalQueue :: get ( $queue_name ) -> createQueue ();
}
2011-11-01 05:04:16 +00:00
// Register shutdown callback.
2010-02-17 22:44:52 +00:00
drupal_register_shutdown_function ( 'drupal_cron_cleanup' );
2006-10-03 00:24:19 +00:00
// Iterate through the modules calling their cron handlers (if any):
2011-08-30 08:06:58 +00:00
foreach ( module_implements ( 'cron' ) as $module ) {
// Do not let an exception thrown by one module disturb another.
try {
module_invoke ( $module , 'cron' );
}
catch ( Exception $e ) {
watchdog_exception ( 'cron' , $e );
}
}
2006-08-09 07:42:55 +00:00
2011-11-01 05:04:16 +00:00
// Record cron time.
2008-09-17 07:11:59 +00:00
variable_set ( 'cron_last' , REQUEST_TIME );
2011-07-04 16:58:33 +00:00
watchdog ( 'cron' , 'Cron run completed.' , array (), WATCHDOG_NOTICE );
2006-08-09 07:42:55 +00:00
2010-07-02 15:31:10 +00:00
// Release cron lock.
lock_release ( 'cron' );
2006-08-09 07:42:55 +00:00
2006-10-03 00:24:19 +00:00
// Return TRUE so other functions can check if it did run successfully
2009-09-25 15:20:12 +00:00
$return = TRUE ;
}
2009-09-28 22:22:54 +00:00
2009-09-25 15:20:12 +00:00
foreach ( $queues as $queue_name => $info ) {
$function = $info [ 'worker callback' ];
$end = time () + ( isset ( $info [ 'time' ]) ? $info [ 'time' ] : 15 );
$queue = DrupalQueue :: get ( $queue_name );
while ( time () < $end && ( $item = $queue -> claimItem ())) {
$function ( $item -> data );
$queue -> deleteItem ( $item );
}
2006-10-03 00:24:19 +00:00
}
2010-03-12 14:20:32 +00:00
// Restore the user.
$GLOBALS [ 'user' ] = $original_user ;
drupal_save_session ( TRUE );
2009-09-25 15:20:12 +00:00
return $return ;
2006-10-03 00:24:19 +00:00
}
/**
* Shutdown function for cron cleanup .
*/
function drupal_cron_cleanup () {
// See if the semaphore is still locked.
if ( variable_get ( 'cron_semaphore' , FALSE )) {
2011-07-04 16:58:33 +00:00
watchdog ( 'cron' , 'Cron run exceeded the time limit and was aborted.' , array (), WATCHDOG_WARNING );
2006-10-03 00:24:19 +00:00
2011-11-01 05:04:16 +00:00
// Release cron semaphore.
2006-10-03 00:24:19 +00:00
variable_del ( 'cron_semaphore' );
}
2006-08-09 07:42:55 +00:00
}
2006-08-10 15:42:33 +00:00
2006-10-23 06:45:17 +00:00
/**
2010-04-30 20:17:28 +00:00
* Returns information about system object files ( modules , themes , etc . ) .
2010-05-26 19:51:01 +00:00
*
2010-04-30 20:17:28 +00:00
* This function is used to find all or some system object files ( module files ,
* theme files , etc . ) that exist on the site . It searches in several locations ,
* depending on what type of object you are looking for . For instance , if you
* are looking for modules and call :
* @ code
* drupal_system_listing ( " / \ .module $ / " , " modules " , 'name' , 0 );
* @ endcode
* this function will search the site - wide modules directory ( i . e . , / modules / ),
* your install profile ' s directory ( i . e . ,
* / profiles / your_site_profile / modules / ), the all - sites directory ( i . e . ,
* / sites / all / modules / ), and your site - specific directory ( i . e . ,
* / sites / your_site_dir / modules / ), in that order , and return information about
* all of the files ending in . module in those directories .
*
* The information is returned in an associative array , which can be keyed on
* the file name ( $key = 'filename' ), the file name without the extension ( $key
* = 'name' ), or the full file stream URI ( $key = 'uri' ) . If you use a key of
* 'filename' or 'name' , files found later in the search will take precedence
2010-10-09 08:05:15 +00:00
* over files found earlier ( unless they belong to a module or theme not
* compatible with Drupal core ); if you choose a key of 'uri' , you will get all
2010-04-30 20:17:28 +00:00
* files found .
*
* @ param string $mask
* The preg_match () regular expression for the files to find .
* @ param string $directory
2006-10-23 06:45:17 +00:00
* The subdirectory name in which the files are found . For example ,
2011-10-31 04:05:57 +00:00
* 'core/modules' will search in sub - directories of the / core / modules
2010-04-30 20:17:28 +00:00
* directory , sub - directories of / sites / all / modules / , etc .
* @ param string $key
* The key to be used for the associative array returned . Possible values are
* 'uri' , for the file 's URI; ' filename ' , for the basename of the file ; and
* 'name' for the name of the file without the extension . If you choose 'name'
* or 'filename' , only the highest - precedence file will be returned .
* @ param int $min_depth
* Minimum depth of directories to return files from , relative to each
* directory searched . For instance , a minimum depth of 2 would find modules
2011-10-31 04:05:57 +00:00
* inside / core / modules / node / tests , but not modules directly in
* / core / modules / node .
2010-04-30 20:17:28 +00:00
*
* @ return array
* An associative array of file objects , keyed on the chosen key . Each element
* in the array is an object containing file information , with properties :
* - 'uri' : Full URI of the file .
* - 'filename' : File name .
* - 'name' : Name of file without the extension .
2006-10-23 06:45:17 +00:00
*/
function drupal_system_listing ( $mask , $directory , $key = 'name' , $min_depth = 1 ) {
$config = conf_path ();
$files = array ();
2011-10-31 04:05:57 +00:00
// Search for the directory in core.
$searchdir = array ( 'core/' . $directory );
2006-10-23 06:45:17 +00:00
// The 'profiles' directory contains pristine collections of modules and
2011-10-31 04:05:57 +00:00
// themes as provided by a distribution. It is pristine in the same way that
// the 'core/modules' directory is pristine for core; users should avoid
// any modification by using the sites/all or sites/<domain> directories.
$profile = drupal_get_profile ();
2006-10-23 06:45:17 +00:00
if ( file_exists ( " profiles/ $profile / $directory " )) {
$searchdir [] = " profiles/ $profile / $directory " ;
}
2011-11-01 05:04:16 +00:00
// Always search sites/all/* as well as the global directories.
2009-06-03 02:50:21 +00:00
$searchdir [] = 'sites/all/' . $directory ;
2006-10-23 06:45:17 +00:00
if ( file_exists ( " $config / $directory " )) {
$searchdir [] = " $config / $directory " ;
}
2011-11-01 05:04:16 +00:00
// Get current list of items.
2009-08-04 04:02:26 +00:00
if ( ! function_exists ( 'file_scan_directory' )) {
2011-10-31 04:05:57 +00:00
require_once DRUPAL_ROOT . '/core/includes/file.inc' ;
2009-08-04 04:02:26 +00:00
}
2006-10-23 06:45:17 +00:00
foreach ( $searchdir as $dir ) {
2010-10-09 08:05:15 +00:00
$files_to_add = file_scan_directory ( $dir , $mask , array ( 'key' => $key , 'min_depth' => $min_depth ));
// Duplicate files found in later search directories take precedence over
// earlier ones, so we want them to overwrite keys in our resulting
// $files array.
// The exception to this is if the later file is from a module or theme not
// compatible with Drupal core. This may occur during upgrades of Drupal
// core when new modules exist in core while older contrib modules with the
// same name exist in a directory such as sites/all/modules/.
2010-12-15 23:50:56 +00:00
foreach ( array_intersect_key ( $files_to_add , $files ) as $file_key => $file ) {
2010-10-09 08:05:15 +00:00
// If it has no info file, then we just behave liberally and accept the
// new resource on the list for merging.
if ( file_exists ( $info_file = dirname ( $file -> uri ) . '/' . $file -> name . '.info' )) {
// Get the .info file for the module or theme this file belongs to.
$info = drupal_parse_info_file ( $info_file );
// If the module or theme is incompatible with Drupal core, remove it
// from the array for the current search directory, so it is not
// overwritten when merged with the $files array.
if ( isset ( $info [ 'core' ]) && $info [ 'core' ] != DRUPAL_CORE_COMPATIBILITY ) {
2010-12-15 23:50:56 +00:00
unset ( $files_to_add [ $file_key ]);
2010-10-09 08:05:15 +00:00
}
}
}
$files = array_merge ( $files , $files_to_add );
2006-10-23 06:45:17 +00:00
}
return $files ;
}
2009-01-27 00:22:27 +00:00
/**
2009-05-21 21:12:25 +00:00
* Set the main page content value for later use .
2009-01-27 00:22:27 +00:00
*
2009-05-21 21:12:25 +00:00
* Given the nature of the Drupal page handling , this will be called once with
* a string or array . We store that and return it later as the block is being
* displayed .
2009-01-27 00:22:27 +00:00
*
* @ param $content
* A string or renderable array representing the body of the page .
* @ return
2010-02-17 02:52:15 +00:00
* If called without $content , a renderable array representing the body of
2010-02-15 22:08:49 +00:00
* the page .
2009-01-27 00:22:27 +00:00
*/
2009-05-21 21:12:25 +00:00
function drupal_set_page_content ( $content = NULL ) {
$content_block = & drupal_static ( __FUNCTION__ , NULL );
2009-10-18 05:28:43 +00:00
$main_content_display = & drupal_static ( 'system_main_content_added' , FALSE );
2009-05-21 21:12:25 +00:00
if ( ! empty ( $content )) {
$content_block = ( is_array ( $content ) ? $content : array ( 'main' => array ( '#markup' => $content )));
}
else {
2009-10-18 05:28:43 +00:00
// Indicate that the main content has been requested. We assume that
// the module requesting the content will be adding it to the page.
// A module can indicate that it does not handle the content by setting
// the static variable back to FALSE after calling this function.
$main_content_display = TRUE ;
2009-05-21 21:12:25 +00:00
return $content_block ;
}
2009-01-27 00:22:27 +00:00
}
2010-02-25 20:57:39 +00:00
/**
* #pre_render callback to render #browsers into #prefix and #suffix.
*
* @ param $elements
* A render array with a '#browsers' property . The '#browsers' property can
* contain any or all of the following keys :
* - 'IE' : If FALSE , the element is not rendered by Internet Explorer . If
* TRUE , the element is rendered by Internet Explorer . Can also be a string
* containing an expression for Internet Explorer to evaluate as part of a
* conditional comment . For example , this can be set to 'lt IE 7' for the
* element to be rendered in Internet Explorer 6 , but not in Internet
2010-03-06 06:39:01 +00:00
* Explorer 7 or higher . Defaults to TRUE .
2010-02-25 20:57:39 +00:00
* - '!IE' : If FALSE , the element is not rendered by browsers other than
* Internet Explorer . If TRUE , the element is rendered by those browsers .
* Defaults to TRUE .
* Examples :
* - To render an element in all browsers , '#browsers' can be left out or set
* to array ( 'IE' => TRUE , '!IE' => TRUE ) .
* - To render an element in Internet Explorer only , '#browsers' can be set
* to array ( '!IE' => FALSE ) .
* - To render an element in Internet Explorer 6 only , '#browsers' can be set
* to array ( 'IE' => 'lt IE 7' , '!IE' => FALSE ) .
* - To render an element in Internet Explorer 8 and higher and in all other
* browsers , '#browsers' can be set to array ( 'IE' => 'gte IE 8' ) .
*
* @ return
2011-04-12 20:54:16 +00:00
* The passed - in element with markup for conditional comments potentially
2010-02-25 20:57:39 +00:00
* added to '#prefix' and '#suffix' .
*/
function drupal_pre_render_conditional_comments ( $elements ) {
$browsers = isset ( $elements [ '#browsers' ]) ? $elements [ '#browsers' ] : array ();
$browsers += array (
'IE' => TRUE ,
'!IE' => TRUE ,
);
// If rendering in all browsers, no need for conditional comments.
if ( $browsers [ 'IE' ] === TRUE && $browsers [ '!IE' ]) {
return $elements ;
}
// Determine the conditional comment expression for Internet Explorer to
// evaluate.
if ( $browsers [ 'IE' ] === TRUE ) {
$expression = 'IE' ;
}
elseif ( $browsers [ 'IE' ] === FALSE ) {
$expression = '!IE' ;
}
else {
$expression = $browsers [ 'IE' ];
}
// Wrap the element's potentially existing #prefix and #suffix properties with
// conditional comment markup. The conditional comment expression is evaluated
// by Internet Explorer only. To control the rendering by other browsers,
// either the "downlevel-hidden" or "downlevel-revealed" technique must be
// used. See http://en.wikipedia.org/wiki/Conditional_comment for details.
$elements += array (
'#prefix' => '' ,
'#suffix' => '' ,
);
if ( ! $browsers [ '!IE' ]) {
// "downlevel-hidden".
$elements [ '#prefix' ] = " \n <!--[if $expression ]> \n " . $elements [ '#prefix' ];
$elements [ '#suffix' ] .= " <![endif]--> \n " ;
}
else {
// "downlevel-revealed".
$elements [ '#prefix' ] = " \n <!--[if $expression ]><!--> \n " . $elements [ '#prefix' ];
$elements [ '#suffix' ] .= " <!--<![endif]--> \n " ;
}
return $elements ;
}
2009-11-03 05:27:18 +00:00
/**
* #pre_render callback to render a link into #markup.
*
* Doing so during pre_render gives modules a chance to alter the link parts .
*
* @ param $elements
* A structured array whose keys form the arguments to l () :
* - #title: The link text to pass as argument to l().
* - #href: The URL path component to pass as argument to l().
* - #options: (optional) An array of options to pass to l().
*
* @ return
2011-04-12 20:54:16 +00:00
* The passed - in elements containing a rendered link in '#markup' .
2009-11-03 05:27:18 +00:00
*/
2010-10-21 19:31:39 +00:00
function drupal_pre_render_link ( $element ) {
// By default, link options to pass to l() are normally set in #options.
$element += array ( '#options' => array ());
// However, within the scope of renderable elements, #attributes is a valid
// way to specify attributes, too. Take them into account, but do not override
// attributes from #options.
if ( isset ( $element [ '#attributes' ])) {
$element [ '#options' ] += array ( 'attributes' => array ());
$element [ '#options' ][ 'attributes' ] += $element [ '#attributes' ];
}
// This #pre_render callback can be invoked from inside or outside of a Form
// API context, and depending on that, a HTML ID may be already set in
// different locations. #options should have precedence over Form API's #id.
// #attributes have been taken over into #options above already.
if ( isset ( $element [ '#options' ][ 'attributes' ][ 'id' ])) {
$element [ '#id' ] = $element [ '#options' ][ 'attributes' ][ 'id' ];
}
elseif ( isset ( $element [ '#id' ])) {
$element [ '#options' ][ 'attributes' ][ 'id' ] = $element [ '#id' ];
}
// Conditionally invoke ajax_pre_render_element(), if #ajax is set.
if ( isset ( $element [ '#ajax' ]) && ! isset ( $element [ '#ajax_processed' ])) {
// If no HTML ID was found above, automatically create one.
if ( ! isset ( $element [ '#id' ])) {
$element [ '#id' ] = $element [ '#options' ][ 'attributes' ][ 'id' ] = drupal_html_id ( 'ajax-link' );
}
2011-02-19 00:09:11 +00:00
// If #ajax['path] was not specified, use the href as Ajax request URL.
2010-10-21 19:31:39 +00:00
if ( ! isset ( $element [ '#ajax' ][ 'path' ])) {
$element [ '#ajax' ][ 'path' ] = $element [ '#href' ];
$element [ '#ajax' ][ 'options' ] = $element [ '#options' ];
}
$element = ajax_pre_render_element ( $element );
}
$element [ '#markup' ] = l ( $element [ '#title' ], $element [ '#href' ], $element [ '#options' ]);
return $element ;
2009-11-03 05:27:18 +00:00
}
2010-11-14 21:04:45 +00:00
/**
* #pre_render callback that collects child links into a single array.
*
* This function can be added as a pre_render callback for a renderable array ,
* usually one which will be themed by theme_links () . It iterates through all
* unrendered children of the element , collects any #links properties it finds,
* merges them into the parent element ' s #links array, and prevents those
* children from being rendered separately .
*
* The purpose of this is to allow links to be logically grouped into related
* categories , so that each child group can be rendered as its own list of
* links if drupal_render () is called on it , but calling drupal_render () on the
* parent element will still produce a single list containing all the remaining
* links , regardless of what group they were in .
*
* A typical example comes from node links , which are stored in a renderable
* array similar to this :
* @ code
* $node -> content [ 'links' ] = array (
* '#theme' => 'links__node' ,
* '#pre_render' = array ( 'drupal_pre_render_links' ),
* 'comment' => array (
* '#theme' => 'links__node__comment' ,
* '#links' => array (
* // An array of links associated with node comments, suitable for
* // passing in to theme_links().
* ),
* ),
* 'statistics' => array (
* '#theme' => 'links__node__statistics' ,
* '#links' => array (
* // An array of links associated with node statistics, suitable for
* // passing in to theme_links().
* ),
* ),
* 'translation' => array (
* '#theme' => 'links__node__translation' ,
* '#links' => array (
* // An array of links associated with node translation, suitable for
* // passing in to theme_links().
* ),
* ),
* );
* @ endcode
*
* In this example , the links are grouped by functionality , which can be
* helpful to themers who want to display certain kinds of links independently .
* For example , adding this code to node . tpl . php will result in the comment
* links being rendered as a single list :
* @ code
* print render ( $content [ 'links' ][ 'comment' ]);
* @ endcode
*
* ( where $node -> content has been transformed into $content before handing
* control to the node . tpl . php template ) .
*
* The pre_render function defined here allows the above flexibility , but also
* allows the following code to be used to render all remaining links into a
* single list , regardless of their group :
* @ code
* print render ( $content [ 'links' ]);
* @ endcode
*
* In the above example , this will result in the statistics and translation
* links being rendered together in a single list ( but not the comment links ,
* which were rendered previously on their own ) .
*
* Because of the way this function works , the individual properties of each
* group ( for example , a group - specific #theme property such as
* 'links__node__comment' in the example above , or any other property such as
* #attributes or #pre_render that is attached to it) are only used when that
* group is rendered on its own . When the group is rendered together with other
* children , these child - specific properties are ignored , and only the overall
* properties of the parent are used .
*/
function drupal_pre_render_links ( $element ) {
$element += array ( '#links' => array ());
foreach ( element_children ( $element ) as $key ) {
$child = & $element [ $key ];
// If the child has links which have not been printed yet and the user has
// access to it, merge its links in to the parent.
if ( isset ( $child [ '#links' ]) && empty ( $child [ '#printed' ]) && ( ! isset ( $child [ '#access' ]) || $child [ '#access' ])) {
$element [ '#links' ] += $child [ '#links' ];
// Mark the child as having been printed already (so that its links
// cannot be mistakenly rendered twice).
$child [ '#printed' ] = TRUE ;
}
}
return $element ;
}
2009-11-03 05:27:18 +00:00
/**
* #pre_render callback to append contents in #markup to #children.
*
* This needs to be a #pre_render callback, because eventually assigned
* #theme_wrappers will expect the element's rendered content in #children.
* Note that if also a #theme is defined for the element, then the result of
* the theme callback will override #children.
*
* @ see drupal_render ()
*
* @ param $elements
* A structured array using the #markup key.
*
* @ return
2011-04-12 20:54:16 +00:00
* The passed - in elements , but #markup appended to #children.
2009-11-03 05:27:18 +00:00
*/
function drupal_pre_render_markup ( $elements ) {
$elements [ '#children' ] = $elements [ '#markup' ];
return $elements ;
}
2009-01-27 00:22:27 +00:00
/**
* Renders the page , including all theming .
*
* @ param $page
* A string or array representing the content of a page . The array consists of
* the following keys :
* - #type: Value is always 'page'. This pushes the theming through page.tpl.php (required).
* - #show_messages: Suppress drupal_get_message() items. Used by Batch API (optional).
*
* @ see hook_page_alter ()
2010-03-26 17:14:46 +00:00
* @ see element_info ()
2009-01-27 00:22:27 +00:00
*/
function drupal_render_page ( $page ) {
2009-10-18 05:28:43 +00:00
$main_content_display = & drupal_static ( 'system_main_content_added' , FALSE );
2009-05-21 21:12:25 +00:00
// Allow menu callbacks to return strings or arbitrary arrays to render.
// If the array returned is not of #type page directly, we need to fill
// in the page with defaults.
if ( is_string ( $page ) || ( is_array ( $page ) && ( ! isset ( $page [ '#type' ]) || ( $page [ '#type' ] != 'page' )))) {
drupal_set_page_content ( $page );
$page = element_info ( 'page' );
2009-01-27 00:22:27 +00:00
}
2009-08-31 16:46:32 +00:00
// Modules can add elements to $page as needed in hook_page_build().
foreach ( module_implements ( 'page_build' ) as $module ) {
$function = $module . '_page_build' ;
$function ( $page );
}
2009-01-27 00:22:27 +00:00
// Modules alter the $page as needed. Blocks are populated into regions like
2009-08-11 12:20:26 +00:00
// 'sidebar_first', 'footer', etc.
2009-01-27 00:22:27 +00:00
drupal_alter ( 'page' , $page );
2009-10-18 05:28:43 +00:00
// If no module has taken care of the main content, add it to the page now.
// This allows the site to still be usable even if no modules that
// control page regions (for example, the Block module) are enabled.
if ( ! $main_content_display ) {
$page [ 'content' ][ 'system_main' ] = drupal_set_page_content ();
}
2009-01-27 00:22:27 +00:00
return drupal_render ( $page );
}
2006-08-10 15:42:33 +00:00
/**
2007-10-08 14:08:19 +00:00
* Renders HTML given a structured array tree .
*
* Recursively iterates over each of the array elements , generating HTML code .
2009-02-03 18:55:32 +00:00
*
2011-10-09 14:49:55 +00:00
* Renderable arrays have two kinds of key / value pairs : properties and
* children . Properties have keys starting with '#' and their values influence
* how the array will be rendered . Children are all elements whose keys do not
* start with a '#' . Their values should be renderable arrays themselves ,
* which will be rendered during the rendering of the parent array . The markup
* provided by the children is typically inserted into the markup generated by
* the parent array .
*
* HTML generation for a renderable array , and the treatment of any children ,
* is controlled by two properties containing theme functions , #theme and
* #theme_wrappers.
2009-02-03 18:55:32 +00:00
*
2009-05-08 12:23:32 +00:00
* #theme is the theme function called first. If it is set and the element has
2011-10-09 14:49:55 +00:00
* any children , it is the responsibility of the theme function to render
* these children . For elements that are not allowed to have any children ,
* e . g . buttons or textfields , the theme function can be used to render the
* element itself . If #theme is not present and the element has children, they
* are rendered and concatenated into a string by drupal_render_children () .
2009-05-08 12:23:32 +00:00
*
2009-08-04 06:38:57 +00:00
* The #theme_wrappers property contains an array of theme functions which will
* be called , in order , after #theme has run. These can be used to add further
* markup around the rendered children ; e . g . , fieldsets add the required markup
* for a fieldset around their rendered child elements . All wrapper theme
* functions have to include the element ' s #children property in their output,
* as it contains the output of the previous theme functions and the rendered
2009-05-08 12:23:32 +00:00
* children .
2009-02-03 18:55:32 +00:00
*
2009-08-04 06:38:57 +00:00
* For example , for the form element type , by default only the #theme_wrappers
2009-05-08 12:23:32 +00:00
* property is set , which adds the form markup around the rendered child
* elements of the form . This allows you to set the #theme property on a
* specific form to a custom theme function , giving you complete control over
* the placement of the form ' s children while not at all having to deal with
* the form markup itself .
2009-02-03 18:55:32 +00:00
*
2009-08-31 17:06:10 +00:00
* drupal_render () can optionally cache the rendered output of elements to
* improve performance . To use drupal_render () caching , set the element ' s #cache
* property to an associative array with one or several of the following keys :
* - 'keys' : An array of one or more keys that identify the element . If 'keys'
2010-03-26 17:14:46 +00:00
* is set , the cache ID is created automatically from these keys . See
* drupal_render_cid_create () .
2009-08-31 17:06:10 +00:00
* - 'granularity' ( optional ) : Define the cache granularity using binary
* combinations of the cache granularity constants , e . g . DRUPAL_CACHE_PER_USER
2010-01-25 10:38:35 +00:00
* to cache for each user separately or
* DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for each
2009-08-31 17:06:10 +00:00
* page and role . If not specified the element is cached globally for each
* theme and language .
* - 'cid' : Specify the cache ID directly . Either 'keys' or 'cid' is required .
* If 'cid' is set , 'keys' and 'granularity' are ignored . Use only if you
* have special requirements .
* - 'expire' : Set to one of the cache lifetime constants .
* - 'bin' : Specify a cache bin to cache the element in . Defaults to 'cache' .
*
2009-08-04 06:38:57 +00:00
* This function is usually called from within another function , like
2009-05-08 12:23:32 +00:00
* drupal_get_form () or a theme function . Elements are sorted internally
* using uasort () . Since this is expensive , when passing already sorted
* elements to drupal_render (), for example from a database query , set
2009-01-23 14:23:27 +00:00
* $elements [ '#sorted' ] = TRUE to avoid sorting them a second time .
2010-04-28 20:25:22 +00:00
*
2010-04-22 08:18:56 +00:00
* drupal_render () flags each element with a '#printed' status to indicate that
* the element has been rendered , which allows individual elements of a given
* array to be rendered independently and prevents them from being rendered
* more than once on subsequent calls to drupal_render () ( e . g . , as part of a
* larger array ) . If the same array or array element is passed more than once
* to drupal_render (), it simply returns a NULL value .
2006-08-10 15:42:33 +00:00
*
* @ param $elements
* The structured array describing the data to be rendered .
* @ return
* The rendered HTML .
*/
function drupal_render ( & $elements ) {
2009-01-23 14:23:27 +00:00
// Early-return nothing if user does not have access.
2009-11-16 05:11:01 +00:00
if ( empty ( $elements ) || ( isset ( $elements [ '#access' ]) && ! $elements [ '#access' ])) {
2009-02-03 18:55:32 +00:00
return ;
}
// Do not print elements twice.
2009-12-14 13:32:53 +00:00
if ( ! empty ( $elements [ '#printed' ])) {
2009-02-03 18:55:32 +00:00
return ;
2006-08-10 15:42:33 +00:00
}
2006-08-22 11:13:04 +00:00
2009-08-31 17:06:10 +00:00
// Try to fetch the element's markup from cache and return.
2009-09-11 15:12:29 +00:00
if ( isset ( $elements [ '#cache' ]) && $cached_output = drupal_render_cache_get ( $elements )) {
2009-08-31 17:06:10 +00:00
return $cached_output ;
}
2009-11-16 05:11:01 +00:00
2011-04-10 21:38:47 +00:00
// If #markup is set, ensure #type is set. This allows to specify just #markup
// on an element without setting #type.
if ( isset ( $elements [ '#markup' ]) && ! isset ( $elements [ '#type' ])) {
2009-11-03 05:27:18 +00:00
$elements [ '#type' ] = 'markup' ;
}
2009-08-31 17:06:10 +00:00
2009-02-03 18:55:32 +00:00
// If the default values for this element have not been loaded yet, populate
2007-06-28 07:48:41 +00:00
// them.
2009-02-03 18:55:32 +00:00
if ( isset ( $elements [ '#type' ]) && empty ( $elements [ '#defaults_loaded' ])) {
$elements += element_info ( $elements [ '#type' ]);
}
2009-03-17 22:35:07 +00:00
2007-06-28 07:48:41 +00:00
// Make any final changes to the element before it is rendered. This means
2007-07-02 14:41:37 +00:00
// that the $element or the children can be altered or corrected before the
2007-06-28 07:48:41 +00:00
// element is rendered into the final text.
if ( isset ( $elements [ '#pre_render' ])) {
foreach ( $elements [ '#pre_render' ] as $function ) {
2009-08-24 00:14:23 +00:00
if ( function_exists ( $function )) {
2007-06-28 07:48:41 +00:00
$elements = $function ( $elements );
}
}
}
2009-12-14 13:32:53 +00:00
// Allow #pre_render to abort rendering.
if ( ! empty ( $elements [ '#printed' ])) {
return ;
}
2010-01-03 06:54:41 +00:00
// Get the children of the element, sorted by weight.
$children = element_children ( $elements , TRUE );
2009-11-03 05:27:18 +00:00
// Initialize this element's #children, unless a #pre_render callback already
// preset #children.
if ( ! isset ( $elements [ '#children' ])) {
$elements [ '#children' ] = '' ;
}
2009-02-03 18:55:32 +00:00
// Call the element's #theme function if it is set. Then any children of the
// element have to be rendered there.
if ( isset ( $elements [ '#theme' ])) {
$elements [ '#children' ] = theme ( $elements [ '#theme' ], $elements );
2006-08-10 15:42:33 +00:00
}
2009-11-07 14:02:31 +00:00
// If #theme was not set and the element has children, render them now.
// This is the same process as drupal_render_children() but is inlined
// for speed.
2009-02-03 18:55:32 +00:00
if ( $elements [ '#children' ] == '' ) {
2010-01-03 06:54:41 +00:00
foreach ( $children as $key ) {
2009-11-07 14:02:31 +00:00
$elements [ '#children' ] .= drupal_render ( $elements [ $key ]);
}
2006-08-10 15:42:33 +00:00
}
2009-08-04 06:38:57 +00:00
// Let the theme functions in #theme_wrappers add markup around the rendered
2009-02-03 18:55:32 +00:00
// children.
2009-08-04 06:38:57 +00:00
if ( isset ( $elements [ '#theme_wrappers' ])) {
foreach ( $elements [ '#theme_wrappers' ] as $theme_wrapper ) {
$elements [ '#children' ] = theme ( $theme_wrapper , $elements );
}
2006-08-10 15:42:33 +00:00
}
2009-02-03 18:55:32 +00:00
// Filter the outputted content and make any last changes before the
// content is sent to the browser. The changes are made on $content
// which allows the output'ed text to be filtered.
if ( isset ( $elements [ '#post_render' ])) {
foreach ( $elements [ '#post_render' ] as $function ) {
2009-08-24 00:14:23 +00:00
if ( function_exists ( $function )) {
2009-02-03 18:55:32 +00:00
$elements [ '#children' ] = $function ( $elements [ '#children' ], $elements );
2007-06-28 07:48:41 +00:00
}
}
2006-08-10 15:42:33 +00:00
}
2009-07-13 21:09:54 +00:00
2009-10-16 19:20:34 +00:00
// Add any JavaScript state information associated with the element.
2009-11-16 05:15:21 +00:00
if ( ! empty ( $elements [ '#states' ])) {
drupal_process_states ( $elements );
}
2009-10-16 19:20:34 +00:00
2009-09-05 15:05:05 +00:00
// Add additional libraries, CSS, JavaScript an other custom
// attached data associated with this element.
2009-11-16 05:15:21 +00:00
if ( ! empty ( $elements [ '#attached' ])) {
drupal_process_attached ( $elements );
}
2009-02-03 18:55:32 +00:00
$prefix = isset ( $elements [ '#prefix' ]) ? $elements [ '#prefix' ] : '' ;
$suffix = isset ( $elements [ '#suffix' ]) ? $elements [ '#suffix' ] : '' ;
2009-12-11 16:56:10 +00:00
$output = $prefix . $elements [ '#children' ] . $suffix ;
2009-02-03 18:55:32 +00:00
2009-08-31 17:06:10 +00:00
// Cache the processed element if #cache is set.
if ( isset ( $elements [ '#cache' ])) {
2009-12-11 16:56:10 +00:00
drupal_render_cache_set ( $output , $elements );
2009-08-31 17:06:10 +00:00
}
2009-02-03 18:55:32 +00:00
$elements [ '#printed' ] = TRUE ;
2009-12-11 16:56:10 +00:00
return $output ;
2009-02-03 18:55:32 +00:00
}
/**
* Render children of an element and concatenate them .
*
* This renders all children of an element using drupal_render () and then
* joins them together into a single string .
*
* @ param $element
* The structured array whose children shall be rendered .
* @ param $children_keys
* If the keys of the element ' s children are already known , they can be passed
* in to save another run of element_children () .
*/
2009-02-05 01:21:16 +00:00
function drupal_render_children ( & $element , $children_keys = NULL ) {
2009-02-03 18:55:32 +00:00
if ( $children_keys === NULL ) {
$children_keys = element_children ( $element );
}
$output = '' ;
foreach ( $children_keys as $key ) {
2009-11-16 05:11:01 +00:00
if ( ! empty ( $element [ $key ])) {
$output .= drupal_render ( $element [ $key ]);
}
2009-02-03 18:55:32 +00:00
}
return $output ;
2006-08-10 15:42:33 +00:00
}
2009-06-18 21:19:02 +00:00
/**
2011-01-12 23:15:26 +00:00
* Render an element .
2009-06-18 21:19:02 +00:00
*
2009-07-13 21:09:54 +00:00
* This function renders an element using drupal_render () . The top level
2011-01-12 23:15:26 +00:00
* element is shown with show () before rendering , so it will always be rendered
* even if hide () had been previously used on it .
2009-06-18 21:19:02 +00:00
*
2011-01-12 23:15:26 +00:00
* @ param $element
* The element to be rendered .
*
* @ return
* The rendered element .
2009-06-18 21:19:02 +00:00
*
* @ see drupal_render ()
* @ see show ()
* @ see hide ()
*/
function render ( & $element ) {
if ( is_array ( $element )) {
show ( $element );
2009-07-13 21:09:54 +00:00
return drupal_render ( $element );
2009-06-18 21:19:02 +00:00
}
else {
2009-07-13 21:09:54 +00:00
// Safe-guard for inappropriate use of render() on flat variables: return
// the variable as-is.
return $element ;
2009-06-18 21:19:02 +00:00
}
}
/**
* Hide an element from later rendering .
*
2011-01-12 23:15:26 +00:00
* The first time render () or drupal_render () is called on an element tree ,
* as each element in the tree is rendered , it is marked with a #printed flag
* and the rendered children of the element are cached . Subsequent calls to
* render () or drupal_render () will not traverse the child tree of this element
* again : they will just use the cached children . So if you want to hide an
* element , be sure to call hide () on the element before its parent tree is
* rendered for the first time , as it will have no effect on subsequent
* renderings of the parent tree .
*
* @ param $element
* The element to be hidden .
*
* @ return
* The element .
*
2009-06-18 21:19:02 +00:00
* @ see render ()
* @ see show ()
*/
function hide ( & $element ) {
$element [ '#printed' ] = TRUE ;
return $element ;
}
/**
2011-01-12 23:15:26 +00:00
* Show a hidden element for later rendering .
*
* You can also use render ( $element ), which shows the element while rendering
* it .
*
* The first time render () or drupal_render () is called on an element tree ,
* as each element in the tree is rendered , it is marked with a #printed flag
* and the rendered children of the element are cached . Subsequent calls to
* render () or drupal_render () will not traverse the child tree of this element
* again : they will just use the cached children . So if you want to show an
* element , be sure to call show () on the element before its parent tree is
* rendered for the first time , as it will have no effect on subsequent
* renderings of the parent tree .
2009-06-18 21:19:02 +00:00
*
2011-01-12 23:15:26 +00:00
* @ param $element
* The element to be shown .
*
* @ return
* The element .
2009-06-18 21:19:02 +00:00
*
* @ see render ()
* @ see hide ()
*/
function show ( & $element ) {
$element [ '#printed' ] = FALSE ;
return $element ;
}
2009-08-31 17:06:10 +00:00
/**
* Get the rendered output of a renderable element from cache .
*
* @ see drupal_render ()
* @ see drupal_render_cache_set ()
2009-09-28 22:22:54 +00:00
*
2009-08-31 17:06:10 +00:00
* @ param $elements
* A renderable array .
* @ return
* A markup string containing the rendered content of the element , or FALSE
* if no cached copy of the element is available .
*/
function drupal_render_cache_get ( $elements ) {
if ( ! in_array ( $_SERVER [ 'REQUEST_METHOD' ], array ( 'GET' , 'HEAD' )) || ! $cid = drupal_render_cid_create ( $elements )) {
return FALSE ;
}
$bin = isset ( $elements [ '#cache' ][ 'bin' ]) ? $elements [ '#cache' ][ 'bin' ] : 'cache' ;
2011-09-11 16:14:18 +00:00
if ( ! empty ( $cid ) && $cache = cache ( $bin ) -> get ( $cid )) {
2009-09-05 15:05:05 +00:00
// Add additional libraries, JavaScript, CSS and other data attached
// to this element.
2009-11-16 20:31:27 +00:00
if ( isset ( $cache -> data [ '#attached' ])) {
drupal_process_attached ( $cache -> data );
}
2009-08-31 17:06:10 +00:00
// Return the rendered output.
2010-02-09 12:29:39 +00:00
return $cache -> data [ '#markup' ];
2009-08-31 17:06:10 +00:00
}
return FALSE ;
}
/**
* Cache the rendered output of a renderable element .
*
* This is called by drupal_render () if the #cache property is set on an element.
*
* @ see drupal_render ()
* @ see drupal_render_cache_get ()
*
* @ param $markup
* The rendered output string of $elements .
* @ param $elements
* A renderable array .
*/
2009-12-11 16:56:10 +00:00
function drupal_render_cache_set ( & $markup , $elements ) {
2009-09-28 22:22:54 +00:00
// Create the cache ID for the element.
2009-08-31 17:06:10 +00:00
if ( ! in_array ( $_SERVER [ 'REQUEST_METHOD' ], array ( 'GET' , 'HEAD' )) || ! $cid = drupal_render_cid_create ( $elements )) {
return FALSE ;
}
2009-12-11 16:56:10 +00:00
// Cache implementations are allowed to modify the markup, to support
// replacing markup with edge-side include commands. The supporting cache
// backend will store the markup in some other key (like
// $data['#real-value']) and return an include command instead. When the
// ESI command is executed by the content accelerator, the real value can
// be retrieved and used.
$data [ '#markup' ] = & $markup ;
2009-09-05 15:05:05 +00:00
// Persist attached data associated with this element.
2011-02-04 21:36:31 +00:00
$attached = drupal_render_collect_attached ( $elements , TRUE );
if ( $attached ) {
$data [ '#attached' ] = $attached ;
2009-11-16 05:11:01 +00:00
}
2009-08-31 17:06:10 +00:00
$bin = isset ( $elements [ '#cache' ][ 'bin' ]) ? $elements [ '#cache' ][ 'bin' ] : 'cache' ;
$expire = isset ( $elements [ '#cache' ][ 'expire' ]) ? $elements [ '#cache' ][ 'expire' ] : CACHE_PERMANENT ;
2011-09-11 16:14:18 +00:00
cache ( $bin ) -> set ( $cid , $data , $expire );
2009-08-31 17:06:10 +00:00
}
2011-02-04 21:36:31 +00:00
/**
* Collect #attached for an element and all child elements into a single array.
*
2011-02-19 00:09:11 +00:00
* When caching elements , it is necessary to collect all libraries , JavaScript
2011-02-04 21:36:31 +00:00
* and CSS into a single array , from both the element itself and all child
* elements . This allows drupal_render () to add these back to the page when the
* element is returned from cache .
*
* @ param $elements
* The element to collect #attached from.
* @ param $return
* Whether to return the attached elements and reset the internal static .
*
* @ return
* The #attached array for this element and its descendants.
*/
function drupal_render_collect_attached ( $elements , $return = FALSE ) {
$attached = & drupal_static ( __FUNCTION__ , array ());
// Collect all #attached for this element.
if ( isset ( $elements [ '#attached' ])) {
foreach ( $elements [ '#attached' ] as $key => $value ) {
if ( ! isset ( $attached [ $key ])) {
$attached [ $key ] = array ();
}
$attached [ $key ] = array_merge ( $attached [ $key ], $value );
}
}
if ( $children = element_children ( $elements )) {
foreach ( $children as $child ) {
drupal_render_collect_attached ( $elements [ $child ]);
}
}
// If this was the first call to the function, return all attached elements
// and reset the static cache.
if ( $return ) {
$return = $attached ;
$attached = array ();
return $return ;
}
}
2010-01-14 06:20:15 +00:00
/**
* Prepare an element for caching based on a query . This smart caching strategy
* saves Drupal from querying and rendering to HTML when the underlying query is
* unchanged .
*
* Expensive queries should use the query builder to create the query and then
* call this function . Executing the query and formatting results should happen
* in a #pre_render callback.
*
* @ param $query
* A select query object as returned by db_select () .
* @ param $function
* The name of the function doing this caching . A _pre_render suffix will be
* added to this string and is also part of the cache key in
* drupal_render_cache_set () and drupal_render_cache_get () .
* @ param $expire
* The cache expire time , passed eventually to cache_set () .
* @ param $granularity
* One or more granularity constants passed to drupal_render_cid_parts () .
*
* @ return
* A renderable array with the following keys and values :
2011-04-12 20:54:16 +00:00
* - #query: The passed-in $query.
2010-01-14 06:20:15 +00:00
* - #pre_render: $function with a _pre_render suffix.
* - #cache: An associative array prepared for drupal_render_cache_set().
*/
function drupal_render_cache_by_query ( $query , $function , $expire = CACHE_TEMPORARY , $granularity = NULL ) {
$cache_keys = array_merge ( array ( $function ), drupal_render_cid_parts ( $granularity ));
$query -> preExecute ();
2010-05-01 08:12:23 +00:00
$cache_keys [] = hash ( 'sha256' , serialize ( array (( string ) $query , $query -> getArguments ())));
2010-01-14 06:20:15 +00:00
return array (
'#query' => $query ,
'#pre_render' => array ( $function . '_pre_render' ),
'#cache' => array (
'keys' => $cache_keys ,
'expire' => $expire ,
),
);
}
2009-08-31 17:06:10 +00:00
/**
* Helper function for building cache ids .
*
* @ param $granularity
* One or more cache granularity constants , e . g . DRUPAL_CACHE_PER_USER to cache
2010-01-25 10:38:35 +00:00
* for each user separately or DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to
* cache separately for each page and role .
2009-08-31 17:06:10 +00:00
*
* @ return
* An array of cache ID parts , always containing the active theme . If the
* locale module is enabled it also contains the active language . If
* $granularity was passed in , more parts are added .
*/
function drupal_render_cid_parts ( $granularity = NULL ) {
global $theme , $base_root , $user ;
$cid_parts [] = $theme ;
2010-07-16 02:37:06 +00:00
// If Locale is enabled but we have only one language we do not need it as cid
// part.
if ( drupal_multilingual ()) {
foreach ( language_types_configurable () as $language_type ) {
$cid_parts [] = $GLOBALS [ $language_type ] -> language ;
}
2009-08-31 17:06:10 +00:00
}
if ( ! empty ( $granularity )) {
// 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
// resource drag for sites with many users, so when a module is being
// equivocal, we favor the less expensive 'PER_ROLE' pattern.
if ( $granularity & DRUPAL_CACHE_PER_ROLE ) {
$cid_parts [] = 'r.' . implode ( ',' , array_keys ( $user -> roles ));
}
elseif ( $granularity & DRUPAL_CACHE_PER_USER ) {
$cid_parts [] = " u. $user->uid " ;
}
if ( $granularity & DRUPAL_CACHE_PER_PAGE ) {
$cid_parts [] = $base_root . request_uri ();
}
}
return $cid_parts ;
}
/**
* Create the cache ID for a renderable element .
*
* This creates the cache ID string , either by returning the #cache['cid']
* property if present or by building the cache ID out of the #cache['keys']
* and , optionally , the #cache['granularity'] properties.
*
* @ param $elements
* A renderable array .
*
* @ return
* The cache ID string , or FALSE if the element may not be cached .
*/
function drupal_render_cid_create ( $elements ) {
if ( isset ( $elements [ '#cache' ][ 'cid' ])) {
return $elements [ '#cache' ][ 'cid' ];
}
elseif ( isset ( $elements [ '#cache' ][ 'keys' ])) {
$granularity = isset ( $elements [ '#cache' ][ 'granularity' ]) ? $elements [ '#cache' ][ 'granularity' ] : NULL ;
// Merge in additional cache ID parts based provided by drupal_render_cid_parts().
$cid_parts = array_merge ( $elements [ '#cache' ][ 'keys' ], drupal_render_cid_parts ( $granularity ));
return implode ( ':' , $cid_parts );
}
return FALSE ;
}
2006-08-10 15:42:33 +00:00
/**
2007-10-25 10:30:40 +00:00
* Function used by uasort to sort structured arrays by weight .
2006-08-10 15:42:33 +00:00
*/
2007-10-25 10:30:40 +00:00
function element_sort ( $a , $b ) {
2006-08-10 15:42:33 +00:00
$a_weight = ( is_array ( $a ) && isset ( $a [ '#weight' ])) ? $a [ '#weight' ] : 0 ;
$b_weight = ( is_array ( $b ) && isset ( $b [ '#weight' ])) ? $b [ '#weight' ] : 0 ;
if ( $a_weight == $b_weight ) {
return 0 ;
}
return ( $a_weight < $b_weight ) ? - 1 : 1 ;
}
2010-09-05 15:38:16 +00:00
/**
* Array sorting callback ; sorts elements by title .
*/
function element_sort_by_title ( $a , $b ) {
$a_title = ( is_array ( $a ) && isset ( $a [ '#title' ])) ? $a [ '#title' ] : '' ;
$b_title = ( is_array ( $b ) && isset ( $b [ '#title' ])) ? $b [ '#title' ] : '' ;
return strnatcasecmp ( $a_title , $b_title );
}
2009-02-03 18:55:32 +00:00
/**
* Retrieve the default properties for the defined element type .
2010-11-12 02:59:30 +00:00
*
* @ param $type
* An element type as defined by hook_element_info () .
2009-02-03 18:55:32 +00:00
*/
2009-05-31 07:00:12 +00:00
function element_info ( $type ) {
2010-04-17 12:35:35 +00:00
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast ;
if ( ! isset ( $drupal_static_fast )) {
$drupal_static_fast [ 'cache' ] = & drupal_static ( __FUNCTION__ );
}
$cache = & $drupal_static_fast [ 'cache' ];
2009-02-03 18:55:32 +00:00
2009-05-31 07:00:12 +00:00
if ( ! isset ( $cache )) {
2009-09-10 06:31:39 +00:00
$cache = module_invoke_all ( 'element_info' );
foreach ( $cache as $element_type => $info ) {
$cache [ $element_type ][ '#type' ] = $element_type ;
2009-02-03 18:55:32 +00:00
}
2009-07-23 21:20:16 +00:00
// Allow modules to alter the element type defaults.
drupal_alter ( 'element_info' , $cache );
2009-02-03 18:55:32 +00:00
}
2009-09-10 06:31:39 +00:00
return isset ( $cache [ $type ]) ? $cache [ $type ] : array ();
2009-02-03 18:55:32 +00:00
}
2010-11-12 02:59:30 +00:00
/**
* Retrieve a single property for the defined element type .
*
* @ param $type
* An element type as defined by hook_element_info () .
* @ param $property_name
* The property within the element type that should be returned .
* @ param $default
* ( Optional ) The value to return if the element type does not specify a
* value for the property . Defaults to NULL .
*/
function element_info_property ( $type , $property_name , $default = NULL ) {
return (( $info = element_info ( $type )) && array_key_exists ( $property_name , $info )) ? $info [ $property_name ] : $default ;
}
2008-11-10 05:23:01 +00:00
/**
* Function used by uasort to sort structured arrays by weight , without the property weight prefix .
*/
function drupal_sort_weight ( $a , $b ) {
$a_weight = ( is_array ( $a ) && isset ( $a [ 'weight' ])) ? $a [ 'weight' ] : 0 ;
$b_weight = ( is_array ( $b ) && isset ( $b [ 'weight' ])) ? $b [ 'weight' ] : 0 ;
if ( $a_weight == $b_weight ) {
return 0 ;
}
return ( $a_weight < $b_weight ) ? - 1 : 1 ;
}
2010-11-07 18:33:53 +00:00
/**
* Array sorting callback ; sorts elements by 'title' key .
*/
function drupal_sort_title ( $a , $b ) {
if ( ! isset ( $b [ 'title' ])) {
return - 1 ;
}
if ( ! isset ( $a [ 'title' ])) {
return 1 ;
}
return strcasecmp ( $a [ 'title' ], $b [ 'title' ]);
}
2006-08-10 15:42:33 +00:00
/**
* Check if the key is a property .
*/
function element_property ( $key ) {
return $key [ 0 ] == '#' ;
}
/**
* Get properties of a structured array element . Properties begin with '#' .
*/
function element_properties ( $element ) {
return array_filter ( array_keys (( array ) $element ), 'element_property' );
}
/**
* Check if the key is a child .
*/
function element_child ( $key ) {
2007-01-31 15:49:26 +00:00
return ! isset ( $key [ 0 ]) || $key [ 0 ] != '#' ;
2006-08-10 15:42:33 +00:00
}
/**
2011-10-16 08:50:06 +00:00
* Identifies the children of an element array , optionally sorted by weight .
*
* The children of a element array are those key / value pairs whose key does
* not start with a '#' . See drupal_render () for details .
2009-02-09 03:29:54 +00:00
*
* @ param $elements
2011-10-16 08:50:06 +00:00
* The element array whose children are to be identified .
2009-02-09 03:29:54 +00:00
* @ param $sort
* Boolean to indicate whether the children should be sorted by weight .
* @ return
* The array keys of the element ' s children .
*/
function element_children ( & $elements , $sort = FALSE ) {
// Do not attempt to sort elements which have already been sorted.
$sort = isset ( $elements [ '#sorted' ]) ? ! $elements [ '#sorted' ] : $sort ;
// Filter out properties from the element, leaving only children.
$children = array ();
$sortable = FALSE ;
foreach ( $elements as $key => $value ) {
2010-09-13 06:41:23 +00:00
if ( $key === '' || $key [ 0 ] !== '#' ) {
2011-09-28 14:21:28 +00:00
if ( is_array ( $value )) {
$children [ $key ] = $value ;
if ( isset ( $value [ '#weight' ])) {
$sortable = TRUE ;
}
}
// Only trigger an error if the value is not null.
// @see http://drupal.org/node/1283892
elseif ( isset ( $value )) {
trigger_error ( t ( '"@key" is an invalid render array key' , array ( '@key' => $key )), E_USER_ERROR );
2009-02-09 03:29:54 +00:00
}
2009-02-03 18:55:32 +00:00
}
}
2009-02-13 04:43:00 +00:00
// Sort the children if necessary.
2009-02-09 03:29:54 +00:00
if ( $sort && $sortable ) {
uasort ( $children , 'element_sort' );
2009-02-13 04:43:00 +00:00
// Put the sorted children back into $elements in the correct order, to
// preserve sorting if the same element is passed through
// element_children() twice.
foreach ( $children as $key => $child ) {
unset ( $elements [ $key ]);
$elements [ $key ] = $child ;
}
$elements [ '#sorted' ] = TRUE ;
2009-02-09 03:29:54 +00:00
}
2009-02-13 04:43:00 +00:00
2009-02-09 03:29:54 +00:00
return array_keys ( $children );
2006-08-22 11:13:04 +00:00
}
2007-04-06 13:27:23 +00:00
2010-01-10 22:45:58 +00:00
/**
2011-09-07 10:38:50 +00:00
* Returns the visible children of an element .
2010-01-10 22:45:58 +00:00
*
* @ param $elements
* The parent element .
* @ return
* The array keys of the element ' s visible children .
*/
function element_get_visible_children ( array $elements ) {
$visible_children = array ();
foreach ( element_children ( $elements ) as $key ) {
$child = $elements [ $key ];
// Skip un-accessible children.
if ( isset ( $child [ '#access' ]) && ! $child [ '#access' ]) {
continue ;
}
// Skip value and hidden elements, since they are not rendered.
if ( isset ( $child [ '#type' ]) && in_array ( $child [ '#type' ], array ( 'value' , 'hidden' ))) {
continue ;
}
$visible_children [ $key ] = $child ;
}
return array_keys ( $visible_children );
}
2010-09-16 20:14:49 +00:00
/**
* Sets HTML attributes based on element properties .
*
* @ param $element
* The renderable element to process .
* @ param $map
* An associative array whose keys are element property names and whose values
* are the HTML attribute names to set for corresponding the property ; e . g . ,
* array ( '#propertyname' => 'attributename' ) . If both names are identical
* except for the leading '#' , then an attribute name value is sufficient and
* no property name needs to be specified .
*/
function element_set_attributes ( array & $element , array $map ) {
foreach ( $map as $property => $attribute ) {
// If the key is numeric, the attribute name needs to be taken over.
if ( is_int ( $property )) {
$property = '#' . $attribute ;
}
2010-09-24 21:36:22 +00:00
// Do not overwrite already existing attributes.
if ( isset ( $element [ $property ]) && ! isset ( $element [ '#attributes' ][ $attribute ])) {
2010-09-16 20:14:49 +00:00
$element [ '#attributes' ][ $attribute ] = $element [ $property ];
}
}
}
2010-08-27 11:54:32 +00:00
/**
* Sets a value in a nested array with variable depth .
*
* This helper function should be used when the depth of the array element you
* are changing may vary ( that is , the number of parent keys is variable ) . It
* is primarily used for form structures and renderable arrays .
*
* Example :
* @ code
* // Assume you have a 'signature' element somewhere in a form. It might be:
* $form [ 'signature_settings' ][ 'signature' ] = array (
* '#type' => 'text_format' ,
* '#title' => t ( 'Signature' ),
* );
* // Or, it might be further nested:
* $form [ 'signature_settings' ][ 'user' ][ 'signature' ] = array (
* '#type' => 'text_format' ,
* '#title' => t ( 'Signature' ),
* );
* @ endcode
*
* To deal with the situation , the code needs to figure out the route to the
* element , given an array of parents that is either
* @ code array ( 'signature_settings' , 'signature' ) @ endcode in the first case or
* @ code array ( 'signature_settings' , 'user' , 'signature' ) @ endcode in the second
* case .
*
* Without this helper function the only way to set the signature element in one
* line would be using eval (), which should be avoided :
* @ code
* // Do not do this! Avoid eval().
* eval ( '$form[\'' . implode ( " '][' " , $parents ) . '\'] = $element;' );
* @ endcode
*
* Instead , use this helper function :
* @ code
* drupal_array_set_nested_value ( $form , $parents , $element );
* @ endcode
*
* However if the number of array parent keys is static , the value should always
* be set directly rather than calling this function . For instance , for the
* first example we could just do :
* @ code
* $form [ 'signature_settings' ][ 'signature' ] = $element ;
* @ endcode
*
* @ param $array
* A reference to the array to modify .
* @ param $parents
* An array of parent keys , starting with the outermost key .
* @ param $value
* The value to set .
2010-11-13 14:04:08 +00:00
* @ param $force
* ( Optional ) If TRUE , the value is forced into the structure even if it
* requires the deletion of an already existing non - array parent value . If
* FALSE , PHP throws an error if trying to add into a value that is not an
* array . Defaults to FALSE .
2010-08-27 11:54:32 +00:00
*
* @ see drupal_array_get_nested_value ()
*/
2010-11-13 14:04:08 +00:00
function drupal_array_set_nested_value ( array & $array , array $parents , $value , $force = FALSE ) {
2010-08-27 11:54:32 +00:00
$ref = & $array ;
foreach ( $parents as $parent ) {
2010-11-13 14:04:08 +00:00
// PHP auto-creates container arrays and NULL entries without error if $ref
// is NULL, but throws an error if $ref is set, but not an array.
if ( $force && isset ( $ref ) && ! is_array ( $ref )) {
$ref = array ();
}
2010-08-27 11:54:32 +00:00
$ref = & $ref [ $parent ];
}
$ref = $value ;
}
/**
* Retrieves a value from a nested array with variable depth .
*
2010-09-10 07:58:40 +00:00
* This helper function should be used when the depth of the array element being
* retrieved may vary ( that is , the number of parent keys is variable ) . It is
2010-08-27 11:54:32 +00:00
* primarily used for form structures and renderable arrays .
*
* Without this helper function the only way to get a nested array value with
* variable depth in one line would be using eval (), which should be avoided :
* @ code
* // Do not do this! Avoid eval().
* // May also throw a PHP notice, if the variable array keys do not exist.
* eval ( '$value = $array[\'' . implode ( " '][' " , $parents ) . " ']; " );
* @ endcode
*
* Instead , use this helper function :
* @ code
2010-09-10 07:58:40 +00:00
* $value = drupal_array_get_nested_value ( $form , $parents );
* @ endcode
*
* The return value will be NULL , regardless of whether the actual value is NULL
* or whether the requested key does not exist . If it is required to know
* whether the nested array key actually exists , pass a third argument that is
* altered by reference :
* @ code
* $key_exists = NULL ;
2010-09-13 06:44:58 +00:00
* $value = drupal_array_get_nested_value ( $form , $parents , $key_exists );
2010-09-10 07:58:40 +00:00
* if ( $key_exists ) {
2010-08-27 11:54:32 +00:00
* // ... do something with $value ...
* }
* @ endcode
*
* However if the number of array parent keys is static , the value should always
2010-09-10 07:58:40 +00:00
* be retrieved directly rather than calling this function . For instance :
2010-08-27 11:54:32 +00:00
* @ code
* $value = $form [ 'signature_settings' ][ 'signature' ];
* @ endcode
*
* @ param $array
* The array from which to get the value .
* @ param $parents
* An array of parent keys of the value , starting with the outermost key .
2011-05-08 19:50:38 +00:00
* @ param $key_exists
2010-09-10 07:58:40 +00:00
* ( optional ) If given , an already defined variable that is altered by
* reference .
2010-08-27 11:54:32 +00:00
*
* @ return
2010-09-10 07:58:40 +00:00
* The requested nested value . Possibly NULL if the value is NULL or not all
* nested parent keys exist . $key_exists is altered by reference and is a
* Boolean that indicates whether all nested parent keys exist ( TRUE ) or not
* ( FALSE ) . This allows to distinguish between the two possibilities when NULL
* is returned .
2010-08-27 11:54:32 +00:00
*
* @ see drupal_array_set_nested_value ()
*/
2011-11-03 07:32:28 +00:00
function & drupal_array_get_nested_value ( array & $array , array $parents , & $key_exists = NULL ) {
2010-09-10 07:58:40 +00:00
$ref = & $array ;
2010-08-27 11:54:32 +00:00
foreach ( $parents as $parent ) {
2010-11-13 14:04:08 +00:00
if ( is_array ( $ref ) && array_key_exists ( $parent , $ref )) {
2010-09-10 07:58:40 +00:00
$ref = & $ref [ $parent ];
2010-08-27 11:54:32 +00:00
}
else {
2010-09-10 07:58:40 +00:00
$key_exists = FALSE ;
2011-11-03 07:32:28 +00:00
$null = NULL ;
return $null ;
2010-08-27 11:54:32 +00:00
}
}
2010-09-10 07:58:40 +00:00
$key_exists = TRUE ;
return $ref ;
2010-08-27 11:54:32 +00:00
}
/**
2010-09-10 07:58:40 +00:00
* Determines whether a nested array with variable depth contains all of the requested keys .
2010-08-27 11:54:32 +00:00
*
* This helper function should be used when the depth of the array element to be
* checked may vary ( that is , the number of parent keys is variable ) . See
2010-09-10 07:58:40 +00:00
* drupal_array_set_nested_value () for details . It is primarily used for form
* structures and renderable arrays .
*
* If it is required to also get the value of the checked nested key , use
* drupal_array_get_nested_value () instead .
*
* If the number of array parent keys is static , this helper function is
* unnecessary and the following code can be used instead :
* @ code
* $value_exists = isset ( $form [ 'signature_settings' ][ 'signature' ]);
* $key_exists = array_key_exists ( 'signature' , $form [ 'signature_settings' ]);
* @ endcode
2010-08-27 11:54:32 +00:00
*
* @ param $array
* The array with the value to check for .
* @ param $parents
* An array of parent keys of the value , starting with the outermost key .
*
* @ return
* TRUE if all the parent keys exist , FALSE otherwise .
*
* @ see drupal_array_get_nested_value ()
*/
2010-09-10 07:58:40 +00:00
function drupal_array_nested_key_exists ( array $array , array $parents ) {
// Although this function is similar to PHP's array_key_exists(), its
// arguments should be consistent with drupal_array_get_nested_value().
$key_exists = NULL ;
drupal_array_get_nested_value ( $array , $parents , $key_exists );
return $key_exists ;
2010-08-27 11:54:32 +00:00
}
2007-04-06 13:27:23 +00:00
/**
* Provide theme registration for themes across . inc files .
*/
2007-11-30 12:19:10 +00:00
function drupal_common_theme () {
2007-04-06 13:27:23 +00:00
return array (
2011-11-01 05:04:16 +00:00
// From theme.inc.
2009-09-15 17:10:39 +00:00
'html' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'page' ,
2009-09-15 17:10:39 +00:00
'template' => 'html' ,
),
2007-04-06 13:27:23 +00:00
'page' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'page' ,
2007-08-26 07:46:11 +00:00
'template' => 'page' ,
2007-04-06 13:27:23 +00:00
),
2009-10-05 02:43:01 +00:00
'region' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'elements' ,
2009-10-05 02:43:01 +00:00
'template' => 'region' ,
),
2007-04-06 13:27:23 +00:00
'status_messages' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'display' => NULL ),
2007-04-06 13:27:23 +00:00
),
2009-11-08 12:43:41 +00:00
'link' => array (
'variables' => array ( 'text' => NULL , 'path' => NULL , 'options' => array ()),
),
2007-04-06 13:27:23 +00:00
'links' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'links' => NULL , 'attributes' => array ( 'class' => array ( 'links' )), 'heading' => array ()),
2007-04-06 13:27:23 +00:00
),
'image' => array (
2010-04-30 20:07:03 +00:00
// HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft
// allows the alt attribute to be omitted in some cases. Therefore,
// default the alt attribute to an empty string, but allow code calling
// theme('image') to pass explicit NULL for it to be omitted. Usually,
// neither omission nor an empty string satisfies accessibility
// requirements, so it is strongly encouraged for code calling
// theme('image') to pass a meaningful value for the alt variable.
// - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
// - http://www.w3.org/TR/xhtml1/dtds.html
// - http://dev.w3.org/html5/spec/Overview.html#alt
// The title attribute is optional in all cases, so it is omitted by
// default.
2011-11-25 03:09:40 +00:00
'variables' => array ( 'uri' => NULL , 'width' => NULL , 'height' => NULL , 'alt' => '' , 'title' => NULL , 'attributes' => array ()),
2007-04-06 13:27:23 +00:00
),
'breadcrumb' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'breadcrumb' => NULL ),
2007-04-06 13:27:23 +00:00
),
'help' => array (
2009-10-23 22:24:19 +00:00
'variables' => array (),
2007-04-06 13:27:23 +00:00
),
'table' => array (
2009-12-02 14:56:32 +00:00
'variables' => array ( 'header' => NULL , 'rows' => NULL , 'attributes' => array (), 'caption' => NULL , 'colgroups' => array (), 'sticky' => TRUE , 'empty' => '' ),
2007-04-06 13:27:23 +00:00
),
'tablesort_indicator' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'style' => NULL ),
2007-04-06 13:27:23 +00:00
),
'mark' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'type' => MARK_NEW ),
2007-04-06 13:27:23 +00:00
),
'item_list' => array (
2011-09-28 03:00:18 +00:00
'variables' => array ( 'items' => array (), 'title' => '' , 'type' => 'ul' , 'attributes' => array ()),
2007-04-06 13:27:23 +00:00
),
'more_help_link' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'url' => NULL ),
2007-04-06 13:27:23 +00:00
),
'feed_icon' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'url' => NULL , 'title' => NULL ),
2007-04-06 13:27:23 +00:00
),
2007-10-22 09:36:05 +00:00
'more_link' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'url' => NULL , 'title' => NULL )
2007-10-22 09:36:05 +00:00
),
2007-04-06 13:27:23 +00:00
'progress_bar' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'percent' => NULL , 'message' => NULL ),
2007-04-06 13:27:23 +00:00
),
2007-11-20 10:18:43 +00:00
'indentation' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'size' => 1 ),
2007-11-20 10:18:43 +00:00
),
2009-11-03 06:47:23 +00:00
'html_tag' => array (
'render element' => 'element' ,
),
2011-11-01 05:04:16 +00:00
// From theme.maintenance.inc.
2009-10-15 21:19:31 +00:00
'maintenance_page' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'content' => NULL , 'show_messages' => TRUE ),
2009-10-15 21:19:31 +00:00
'template' => 'maintenance-page' ,
),
'update_page' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'content' => NULL , 'show_messages' => TRUE ),
2009-10-15 21:19:31 +00:00
),
'install_page' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'content' => NULL ),
2009-10-15 21:19:31 +00:00
),
'task_list' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'items' => NULL , 'active' => NULL ),
2009-10-15 21:19:31 +00:00
),
'authorize_message' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'message' => NULL , 'success' => TRUE ),
2009-10-15 21:19:31 +00:00
),
'authorize_report' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'messages' => array ()),
2009-10-15 21:19:31 +00:00
),
2011-11-01 05:04:16 +00:00
// From pager.inc.
2007-04-06 13:27:23 +00:00
'pager' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'tags' => array (), 'element' => 0 , 'parameters' => array (), 'quantity' => 9 ),
2007-04-06 13:27:23 +00:00
),
'pager_first' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'text' => NULL , 'element' => 0 , 'parameters' => array ()),
2007-04-06 13:27:23 +00:00
),
'pager_previous' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'text' => NULL , 'element' => 0 , 'interval' => 1 , 'parameters' => array ()),
2007-04-06 13:27:23 +00:00
),
'pager_next' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'text' => NULL , 'element' => 0 , 'interval' => 1 , 'parameters' => array ()),
2007-04-06 13:27:23 +00:00
),
'pager_last' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'text' => NULL , 'element' => 0 , 'parameters' => array ()),
2007-04-06 13:27:23 +00:00
),
'pager_link' => array (
2009-10-23 22:24:19 +00:00
'variables' => array ( 'text' => NULL , 'page_new' => NULL , 'element' => NULL , 'parameters' => array (), 'attributes' => array ()),
2007-04-06 13:27:23 +00:00
),
2011-11-01 05:04:16 +00:00
// From menu.inc.
2009-09-18 10:54:20 +00:00
'menu_link' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'menu_tree' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'tree' ,
2007-04-06 13:27:23 +00:00
),
'menu_local_task' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
2009-08-22 19:58:28 +00:00
'menu_local_action' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2009-08-22 19:58:28 +00:00
),
2007-04-06 13:27:23 +00:00
'menu_local_tasks' => array (
2010-11-20 04:03:51 +00:00
'variables' => array ( 'primary' => array (), 'secondary' => array ()),
2007-04-06 13:27:23 +00:00
),
2011-11-01 05:04:16 +00:00
// From form.inc.
2007-04-06 13:27:23 +00:00
'select' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'fieldset' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'radio' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'radios' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'date' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
2010-09-17 14:53:22 +00:00
'exposed_filters' => array (
'render element' => 'form' ,
),
2007-04-06 13:27:23 +00:00
'checkbox' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'checkboxes' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'button' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
2007-07-29 17:28:23 +00:00
'image_button' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-07-29 17:28:23 +00:00
),
2007-04-06 13:27:23 +00:00
'hidden' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'textfield' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'form' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'textarea' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'password' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
'file' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
2009-01-28 07:43:26 +00:00
'tableselect' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2009-01-28 07:43:26 +00:00
),
2007-04-06 13:27:23 +00:00
'form_element' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2007-04-06 13:27:23 +00:00
),
2009-11-02 03:00:28 +00:00
'form_required_marker' => array (
2010-10-28 02:00:27 +00:00
'render element' => 'element' ,
2009-11-02 03:00:28 +00:00
),
2009-12-02 15:09:16 +00:00
'form_element_label' => array (
'render element' => 'element' ,
),
2009-04-11 22:19:46 +00:00
'vertical_tabs' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2009-10-16 19:20:34 +00:00
),
'container' => array (
2009-10-23 22:24:19 +00:00
'render element' => 'element' ,
2009-04-11 22:19:46 +00:00
),
2007-04-06 13:27:23 +00:00
);
}
2007-04-17 07:19:39 +00:00
2007-05-25 12:46:46 +00:00
/**
* @ ingroup schemaapi
* @ {
*/
/**
2009-12-03 02:20:28 +00:00
* Creates all tables in a module ' s hook_schema () implementation .
2007-05-25 12:46:46 +00:00
*
* Note : This function does not pass the module ' s schema through
2007-10-08 14:08:19 +00:00
* hook_schema_alter () . The module ' s tables will be created exactly as the
* module defines them .
2007-05-25 12:46:46 +00:00
*
* @ param $module
2007-10-08 14:08:19 +00:00
* The module for which the tables will be created .
2007-05-25 12:46:46 +00:00
*/
function drupal_install_schema ( $module ) {
$schema = drupal_get_schema_unprocessed ( $module );
2010-09-25 02:00:06 +00:00
_drupal_schema_initialize ( $schema , $module , FALSE );
2007-05-25 12:46:46 +00:00
2007-06-26 20:24:19 +00:00
foreach ( $schema as $name => $table ) {
2009-09-29 15:13:57 +00:00
db_create_table ( $name , $table );
2007-05-25 12:46:46 +00:00
}
}
/**
* Remove all tables that a module defines in its hook_schema () .
*
* Note : This function does not pass the module ' s schema through
2007-10-08 14:08:19 +00:00
* hook_schema_alter () . The module ' s tables will be created exactly as the
* module defines them .
2007-05-25 12:46:46 +00:00
*
* @ param $module
2007-10-08 14:08:19 +00:00
* The module for which the tables will be removed .
2007-11-16 09:27:22 +00:00
* @ return
* An array of arrays with the following key / value pairs :
2008-07-02 19:36:52 +00:00
* - success : a boolean indicating whether the query succeeded .
* - query : the SQL query ( s ) executed , passed through check_plain () .
2007-05-25 12:46:46 +00:00
*/
function drupal_uninstall_schema ( $module ) {
$schema = drupal_get_schema_unprocessed ( $module );
2010-09-25 02:00:06 +00:00
_drupal_schema_initialize ( $schema , $module , FALSE );
2007-05-25 12:46:46 +00:00
foreach ( $schema as $table ) {
2008-08-21 19:36:39 +00:00
if ( db_table_exists ( $table [ 'name' ])) {
2009-09-29 15:13:57 +00:00
db_drop_table ( $table [ 'name' ]);
2008-08-21 19:36:39 +00:00
}
2007-05-25 12:46:46 +00:00
}
}
/**
* Returns the unprocessed and unaltered version of a module ' s schema .
*
* Use this function only if you explicitly need the original
* specification of a schema , as it was defined in a module ' s
* hook_schema () . No additional default values will be set ,
* hook_schema_alter () is not invoked and these unprocessed
* definitions won ' t be cached .
*
* This function can be used to retrieve a schema specification in
* hook_schema (), so it allows you to derive your tables from existing
* specifications .
*
* It is also used by drupal_install_schema () and
* drupal_uninstall_schema () to ensure that a module ' s tables are
* created exactly as specified without any changes introduced by a
* module that implements hook_schema_alter () .
*
* @ param $module
* The module to which the table belongs .
* @ param $table
* The name of the table . If not given , the module ' s complete schema
* is returned .
*/
function drupal_get_schema_unprocessed ( $module , $table = NULL ) {
2007-10-05 14:43:26 +00:00
// Load the .install file to get hook_schema.
2008-05-06 12:18:54 +00:00
module_load_install ( $module );
2007-05-25 12:46:46 +00:00
$schema = module_invoke ( $module , 'schema' );
2010-09-24 02:48:35 +00:00
if ( isset ( $table ) && isset ( $schema [ $table ])) {
2007-05-25 12:46:46 +00:00
return $schema [ $table ];
}
2010-07-24 17:28:27 +00:00
elseif ( ! empty ( $schema )) {
2007-05-25 12:46:46 +00:00
return $schema ;
}
2009-09-10 06:38:20 +00:00
return array ();
2007-05-25 12:46:46 +00:00
}
/**
2007-10-08 14:08:19 +00:00
* Fill in required default values for table definitions returned by hook_schema () .
2007-05-25 12:46:46 +00:00
*
* @ param $schema
* The schema definition array as it was returned by the module ' s
* hook_schema () .
2010-09-25 02:00:06 +00:00
* @ param $module
* The module for which hook_schema () was invoked .
* @ param $remove_descriptions
* ( optional ) Whether to additionally remove 'description' keys of all tables
* and fields to improve performance of serialize () and unserialize () .
* Defaults to TRUE .
2007-05-25 12:46:46 +00:00
*/
2010-09-25 02:00:06 +00:00
function _drupal_schema_initialize ( & $schema , $module , $remove_descriptions = TRUE ) {
2007-05-25 12:46:46 +00:00
// Set the name and module key for all tables.
2010-09-25 02:00:06 +00:00
foreach ( $schema as $name => & $table ) {
2007-05-25 12:46:46 +00:00
if ( empty ( $table [ 'module' ])) {
2010-09-25 02:00:06 +00:00
$table [ 'module' ] = $module ;
2007-05-25 12:46:46 +00:00
}
if ( ! isset ( $table [ 'name' ])) {
2010-09-25 02:00:06 +00:00
$table [ 'name' ] = $name ;
}
if ( $remove_descriptions ) {
unset ( $table [ 'description' ]);
foreach ( $table [ 'fields' ] as & $field ) {
unset ( $field [ 'description' ]);
}
2007-05-25 12:46:46 +00:00
}
}
}
2007-10-02 16:15:56 +00:00
/**
* Retrieve a list of fields from a table schema . The list is suitable for use in a SQL query .
*
* @ param $table
* The name of the table from which to retrieve fields .
* @ param
* An optional prefix to to all fields .
*
* @ return An array of fields .
**/
function drupal_schema_fields_sql ( $table , $prefix = NULL ) {
$schema = drupal_get_schema ( $table );
$fields = array_keys ( $schema [ 'fields' ]);
if ( $prefix ) {
$columns = array ();
foreach ( $fields as $field ) {
$columns [] = " $prefix . $field " ;
}
return $columns ;
}
else {
return $fields ;
}
}
/**
2011-02-19 01:01:41 +00:00
* Saves ( inserts or updates ) a record to the database based upon the schema .
2007-10-02 16:15:56 +00:00
*
* @ param $table
2010-03-03 19:55:46 +00:00
* The name of the table ; this must be defined by a hook_schema ()
* implementation .
2010-08-05 08:36:08 +00:00
* @ param $record
2010-03-03 19:55:46 +00:00
* An object or array representing the record to write , passed in by
2011-02-19 01:01:41 +00:00
* reference . If inserting a new record , values not provided in $record will
* be populated in $record and in the database with the default values from
* the schema , as well as a single serial ( auto - increment ) field ( if present ) .
* If updating an existing record , only provided values are updated in the
* database , and $record is not modified .
2008-10-31 02:23:24 +00:00
* @ param $primary_keys
2011-02-19 01:01:41 +00:00
* To indicate that this is a new record to be inserted , omit this argument .
* If this is an update , this argument specifies the primary keys ' field
* names . If there is only 1 field in the key , you may pass in a string ; if
* there are multiple fields in the key , pass in an array .
2010-03-03 19:55:46 +00:00
*
2008-01-07 19:28:06 +00:00
* @ return
2011-02-19 01:01:41 +00:00
* If the record insert or update failed , returns FALSE . If it succeeded ,
* returns SAVED_NEW or SAVED_UPDATED , depending on the operation performed .
2007-10-02 16:15:56 +00:00
*/
2010-08-05 08:36:08 +00:00
function drupal_write_record ( $table , & $record , $primary_keys = array ()) {
2008-10-31 02:23:24 +00:00
// Standardize $primary_keys to an array.
if ( is_string ( $primary_keys )) {
$primary_keys = array ( $primary_keys );
2007-10-02 16:15:56 +00:00
}
2008-05-15 21:15:10 +00:00
$schema = drupal_get_schema ( $table );
if ( empty ( $schema )) {
return FALSE ;
}
2010-08-05 08:36:08 +00:00
$object = ( object ) $record ;
2008-10-31 02:23:24 +00:00
$fields = array ();
2007-10-02 16:15:56 +00:00
2010-08-05 08:36:08 +00:00
// Go through the schema to determine fields to write.
2007-10-02 16:15:56 +00:00
foreach ( $schema [ 'fields' ] as $field => $info ) {
2010-01-08 06:07:03 +00:00
if ( $info [ 'type' ] == 'serial' ) {
// Skip serial types if we are updating.
if ( ! empty ( $primary_keys )) {
continue ;
}
// Track serial field so we can helpfully populate them after the query.
// NOTE: Each table should come with one serial field only.
$serial = $field ;
2007-10-02 16:15:56 +00:00
}
2010-08-09 00:13:06 +00:00
// Skip field if it is in $primary_keys as it is unnecessary to update a
// field to the value it is already set to.
if ( in_array ( $field , $primary_keys )) {
continue ;
}
2010-01-08 06:07:03 +00:00
if ( ! property_exists ( $object , $field )) {
2010-09-24 00:37:45 +00:00
// Skip fields that are not provided, default values are already known
2010-08-05 08:36:08 +00:00
// by the database.
continue ;
2007-10-02 16:15:56 +00:00
}
2010-01-08 06:07:03 +00:00
// Build array of fields to update or insert.
if ( empty ( $info [ 'serialize' ])) {
$fields [ $field ] = $object -> $field ;
}
else {
2010-08-05 08:36:08 +00:00
$fields [ $field ] = serialize ( $object -> $field );
2007-10-02 16:15:56 +00:00
}
2010-01-08 06:07:03 +00:00
// Type cast to proper datatype, except when the value is NULL and the
// column allows this.
//
// MySQL PDO silently casts e.g. FALSE and '' to 0 when inserting the value
// into an integer column, but PostgreSQL PDO does not. Also type cast NULL
// when the column does not allow this.
2010-09-24 02:48:35 +00:00
if ( isset ( $object -> $field ) || ! empty ( $info [ 'not null' ])) {
2010-01-08 06:07:03 +00:00
if ( $info [ 'type' ] == 'int' || $info [ 'type' ] == 'serial' ) {
$fields [ $field ] = ( int ) $fields [ $field ];
2007-10-02 16:15:56 +00:00
}
2010-01-08 06:07:03 +00:00
elseif ( $info [ 'type' ] == 'float' ) {
$fields [ $field ] = ( float ) $fields [ $field ];
2007-10-02 16:15:56 +00:00
}
2008-02-18 16:53:37 +00:00
else {
2010-01-08 06:07:03 +00:00
$fields [ $field ] = ( string ) $fields [ $field ];
2008-02-18 16:53:37 +00:00
}
}
}
if ( empty ( $fields )) {
return ;
2007-10-02 16:15:56 +00:00
}
// Build the SQL.
2008-10-31 02:23:24 +00:00
if ( empty ( $primary_keys )) {
2010-03-03 19:55:46 +00:00
// We are doing an insert.
2009-05-20 05:44:03 +00:00
$options = array ( 'return' => Database :: RETURN_INSERT_ID );
if ( isset ( $serial ) && isset ( $fields [ $serial ])) {
2009-05-24 17:39:35 +00:00
// If the serial column has been explicitly set with an ID, then we don't
2009-05-20 05:44:03 +00:00
// require the database to return the last insert id.
if ( $fields [ $serial ]) {
$options [ 'return' ] = Database :: RETURN_AFFECTED ;
}
// If a serial column does exist with no value (i.e. 0) then remove it as
// the database will insert the correct value for us.
else {
unset ( $fields [ $serial ]);
}
}
$query = db_insert ( $table , $options ) -> fields ( $fields );
2007-10-02 16:15:56 +00:00
$return = SAVED_NEW ;
}
else {
2008-10-31 02:23:24 +00:00
$query = db_update ( $table ) -> fields ( $fields );
2009-04-26 15:50:34 +00:00
foreach ( $primary_keys as $key ) {
2008-10-31 02:23:24 +00:00
$query -> condition ( $key , $object -> $key );
2007-10-02 16:15:56 +00:00
}
$return = SAVED_UPDATED ;
}
2008-01-07 19:28:06 +00:00
// Execute the SQL.
2010-03-03 19:55:46 +00:00
if ( $query_return = $query -> execute ()) {
2008-10-31 02:23:24 +00:00
if ( isset ( $serial )) {
2009-05-20 05:44:03 +00:00
// If the database was not told to return the last insert id, it will be
// because we already know it.
if ( isset ( $options ) && $options [ 'return' ] != Database :: RETURN_INSERT_ID ) {
$object -> $serial = $fields [ $serial ];
}
else {
2010-03-03 19:55:46 +00:00
$object -> $serial = $query_return ;
2009-05-20 05:44:03 +00:00
}
2008-01-07 19:28:06 +00:00
}
2008-05-15 21:15:10 +00:00
}
2009-02-13 02:25:59 +00:00
// If we have a single-field primary key but got no insert ID, the
2009-12-06 17:01:52 +00:00
// query failed. Note that we explicitly check for FALSE, because
// a valid update query which doesn't change any values will return
// zero (0) affected rows.
2010-03-03 19:55:46 +00:00
elseif ( $query_return === FALSE && count ( $primary_keys ) == 1 ) {
2008-05-15 21:15:10 +00:00
$return = FALSE ;
}
2008-01-07 19:28:06 +00:00
2010-08-05 08:36:08 +00:00
// If we are inserting, populate empty fields with default values.
if ( empty ( $primary_keys )) {
foreach ( $schema [ 'fields' ] as $field => $info ) {
if ( isset ( $info [ 'default' ]) && ! property_exists ( $object , $field )) {
$object -> $field = $info [ 'default' ];
}
}
}
// If we began with an array, convert back.
if ( is_array ( $record )) {
$record = ( array ) $object ;
2007-10-02 16:15:56 +00:00
}
2008-05-15 21:15:10 +00:00
return $return ;
2007-10-02 16:15:56 +00:00
}
2007-05-25 12:46:46 +00:00
/**
* @ } End of " ingroup schemaapi " .
*/
2007-04-17 07:19:39 +00:00
/**
2011-02-19 00:33:05 +00:00
* Parses Drupal module and theme . info files .
2007-04-17 07:19:39 +00:00
*
2009-10-02 00:50:45 +00:00
* Info files are NOT for placing arbitrary theme and module - specific settings .
* Use variable_get () and variable_set () for that .
2007-04-17 07:19:39 +00:00
*
2009-10-02 00:50:45 +00:00
* Information stored in a module . info file :
* - name : The real name of the module for display purposes .
* - description : A brief description of the module .
* - dependencies : An array of shortnames of other modules this module requires .
* - package : The name of the package of modules this module belongs to .
*
2011-02-19 00:33:05 +00:00
* See forum . info for an example of a module . info file .
2009-10-02 00:50:45 +00:00
*
* Information stored in a theme . info file :
2011-02-09 02:23:52 +00:00
* - name : The real name of the theme for display purposes .
* - description : Brief description .
2009-10-02 00:50:45 +00:00
* - screenshot : Path to screenshot relative to the theme ' s . info file .
2010-11-07 00:27:20 +00:00
* - engine : Theme engine ; typically phptemplate .
2011-02-09 02:23:52 +00:00
* - base : Name of a base theme , if applicable ; e . g . , base = zen .
* - regions : Listed regions ; e . g . , region [ left ] = Left sidebar .
* - features : Features available ; e . g . , features [] = logo .
* - stylesheets : Theme stylesheets ; e . g . , stylesheets [ all ][] = my - style . css .
* - scripts : Theme scripts ; e . g . , scripts [] = my - script . js .
2009-10-02 00:50:45 +00:00
*
2011-02-19 00:33:05 +00:00
* See bartik . info for an example of a theme . info file .
2009-10-02 00:50:45 +00:00
*
* @ param $filename
* The file we are parsing . Accepts file with relative or absolute path .
2011-02-19 00:33:05 +00:00
*
2009-10-02 00:50:45 +00:00
* @ return
* The info array .
*
* @ see drupal_parse_info_format ()
*/
function drupal_parse_info_file ( $filename ) {
2011-04-10 19:38:45 +00:00
$info = & drupal_static ( __FUNCTION__ , array ());
2009-10-02 00:50:45 +00:00
2011-04-10 19:38:45 +00:00
if ( ! isset ( $info [ $filename ])) {
if ( ! file_exists ( $filename )) {
$info [ $filename ] = array ();
}
else {
$data = file_get_contents ( $filename );
$info [ $filename ] = drupal_parse_info_format ( $data );
}
}
return $info [ $filename ];
2009-10-02 00:50:45 +00:00
}
/**
* Parse data in Drupal ' s . info format .
*
* Data should be in an . ini - like format to specify values . White - space
* generally doesn ' t matter , except inside values :
* @ code
2007-04-18 02:49:33 +00:00
* key = value
* key = " value "
* key = 'value'
* key = " multi-line
* value "
* key = ' multi - line
* value '
* key
* =
* 'value'
2009-10-02 00:50:45 +00:00
* @ endcode
2007-04-17 07:19:39 +00:00
*
2009-10-02 00:50:45 +00:00
* Arrays are created using a HTTP GET alike syntax :
* @ code
2007-04-18 02:49:33 +00:00
* key [] = " numeric array "
* key [ index ] = " associative array "
* key [ index ][] = " nested numeric array "
* key [ index ][ index ] = " nested associative array "
2009-10-02 00:50:45 +00:00
* @ endcode
2007-04-17 07:19:39 +00:00
*
2009-10-02 00:50:45 +00:00
* PHP constants are substituted in , but only when used as the entire value .
2007-04-17 07:19:39 +00:00
* Comments should start with a semi - colon at the beginning of a line .
*
2009-10-02 00:50:45 +00:00
* @ param $data
* A string to parse .
2007-04-17 07:19:39 +00:00
* @ return
* The info array .
2009-10-02 00:50:45 +00:00
*
* @ see drupal_parse_info_file ()
2007-04-17 07:19:39 +00:00
*/
2009-10-02 00:50:45 +00:00
function drupal_parse_info_format ( $data ) {
2007-04-17 07:19:39 +00:00
$info = array ();
2009-11-11 00:48:56 +00:00
$constants = get_defined_constants ();
2007-04-17 07:19:39 +00:00
if ( preg_match_all ( '
@^ \s * # Start at the beginning of a line, ignoring leading whitespace
(( ? :
[ ^= ; \ [ \ ]] | # Key names cannot contain equal signs, semi-colons or square brackets,
\ [[ ^ \ [ \ ]] * \ ] # unless they are balanced and not nested
) + ? )
\s *= \s * # Key/value pairs are separated by equal signs (ignoring white-space)
( ? :
( " (?:[^ " ] | ( ? <= \\\\ ) " )* " ) | # Double-quoted string, which may contain slash-escaped quotes/slashes
( \ ' ( ? : [ ^ \ ' ] | ( ? <= \\\\ ) \ ' ) * \ ' ) | # Single-quoted string, which may contain slash-escaped quotes/slashes
([ ^ \r\n ] * ? ) # Non-quoted string
) \s * $ # Stop at the next end of a line, ignoring trailing whitespace
@ msx ' , $data , $matches , PREG_SET_ORDER )) {
foreach ( $matches as $match ) {
2011-11-01 05:04:16 +00:00
// Fetch the key and value string.
2007-04-17 07:19:39 +00:00
$i = 0 ;
foreach ( array ( 'key' , 'value1' , 'value2' , 'value3' ) as $var ) {
$$var = isset ( $match [ ++ $i ]) ? $match [ $i ] : '' ;
}
$value = stripslashes ( substr ( $value1 , 1 , - 1 )) . stripslashes ( substr ( $value2 , 1 , - 1 )) . $value3 ;
2011-11-01 05:04:16 +00:00
// Parse array syntax.
2007-04-17 07:19:39 +00:00
$keys = preg_split ( '/\]?\[/' , rtrim ( $key , ']' ));
$last = array_pop ( $keys );
$parent = & $info ;
2011-11-01 05:04:16 +00:00
// Create nested arrays.
2007-04-17 07:19:39 +00:00
foreach ( $keys as $key ) {
if ( $key == '' ) {
$key = count ( $parent );
}
if ( ! isset ( $parent [ $key ]) || ! is_array ( $parent [ $key ])) {
$parent [ $key ] = array ();
}
$parent = & $parent [ $key ];
}
2009-11-11 00:48:56 +00:00
// Handle PHP constants.
if ( isset ( $constants [ $value ])) {
2010-01-03 11:04:58 +00:00
$value = $constants [ $value ];
2007-04-17 07:19:39 +00:00
}
2011-11-01 05:04:16 +00:00
// Insert actual value.
2007-04-17 07:19:39 +00:00
if ( $last == '' ) {
$last = count ( $parent );
}
$parent [ $last ] = $value ;
}
}
return $info ;
}
2007-04-30 11:12:35 +00:00
/**
2008-05-26 17:12:55 +00:00
* Severity levels , as defined in RFC 3164 : http :// www . ietf . org / rfc / rfc3164 . txt .
*
2007-04-30 11:12:35 +00:00
* @ return
* Array of the possible severity levels for log messages .
*
2008-05-26 17:12:55 +00:00
* @ see watchdog ()
2011-07-04 16:58:33 +00:00
* @ ingroup logging_severity_levels
2007-04-30 11:12:35 +00:00
*/
function watchdog_severity_levels () {
return array (
2011-07-04 16:58:33 +00:00
WATCHDOG_EMERGENCY => t ( 'emergency' ),
WATCHDOG_ALERT => t ( 'alert' ),
WATCHDOG_CRITICAL => t ( 'critical' ),
WATCHDOG_ERROR => t ( 'error' ),
WATCHDOG_WARNING => t ( 'warning' ),
WATCHDOG_NOTICE => t ( 'notice' ),
WATCHDOG_INFO => t ( 'info' ),
WATCHDOG_DEBUG => t ( 'debug' ),
2007-04-30 11:12:35 +00:00
);
}
2007-06-29 18:06:51 +00:00
/**
* Explode a string of given tags into an array .
2010-06-27 16:49:58 +00:00
*
* @ see drupal_implode_tags ()
2007-06-29 18:06:51 +00:00
*/
function drupal_explode_tags ( $tags ) {
// This regexp allows the following types of user input:
// this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x' ;
preg_match_all ( $regexp , $tags , $matches );
$typed_tags = array_unique ( $matches [ 1 ]);
$tags = array ();
foreach ( $typed_tags as $tag ) {
// If a user has escaped a term (to demonstrate that it is a group,
// or includes a comma or quote character), we remove the escape
// formatting so to save the term into the database as the user intends.
$tag = trim ( str_replace ( '""' , '"' , preg_replace ( '/^"(.*)"$/' , '\1' , $tag )));
if ( $tag != " " ) {
$tags [] = $tag ;
}
}
return $tags ;
}
/**
* Implode an array of tags into a string .
2010-06-27 16:49:58 +00:00
*
* @ see drupal_explode_tags ()
2007-06-29 18:06:51 +00:00
*/
function drupal_implode_tags ( $tags ) {
$encoded_tags = array ();
foreach ( $tags as $tag ) {
// Commas and quotes in tag names are special cases, so encode them.
if ( strpos ( $tag , ',' ) !== FALSE || strpos ( $tag , '"' ) !== FALSE ) {
2008-04-14 17:48:46 +00:00
$tag = '"' . str_replace ( '"' , '""' , $tag ) . '"' ;
2007-06-29 18:06:51 +00:00
}
$encoded_tags [] = $tag ;
}
return implode ( ', ' , $encoded_tags );
2007-07-01 17:41:16 +00:00
}
2007-10-08 14:08:19 +00:00
2007-11-26 16:25:14 +00:00
/**
* Flush all cached data on the site .
*
* Empties cache tables , rebuilds the menu cache and theme registries , and
2008-07-02 19:36:52 +00:00
* invokes a hook so that other modules ' cache data can be cleared as well .
2007-11-26 16:25:14 +00:00
*/
function drupal_flush_all_caches () {
2008-01-07 19:43:29 +00:00
// Change query-strings on css/js files to enforce reload for all users.
_drupal_flush_css_js ();
2008-08-02 19:01:02 +00:00
registry_rebuild ();
2007-11-26 16:25:14 +00:00
drupal_clear_css_cache ();
drupal_clear_js_cache ();
2010-04-29 05:33:43 +00:00
// Rebuild the theme data. Note that the module data is rebuilt above, as
// part of registry_rebuild().
2010-03-31 19:10:39 +00:00
system_rebuild_theme_data ();
2008-08-02 19:01:02 +00:00
drupal_theme_rebuild ();
2010-04-29 05:33:43 +00:00
2007-11-26 16:25:14 +00:00
node_types_rebuild ();
2010-10-07 00:28:20 +00:00
// node_menu() defines menu items based on node types so it needs to come
// after node types are rebuilt.
menu_rebuild ();
2010-04-29 05:33:43 +00:00
2010-10-01 18:37:23 +00:00
// Synchronize to catch any actions that were added or removed.
actions_synchronize ();
2007-11-26 16:25:14 +00:00
// Don't clear cache_form - in-progress form submissions may break.
// Ordered so clearing the page cache will always be the last action.
2011-09-12 00:26:41 +00:00
$core = array ( 'cache' , 'path' , 'filter' , 'bootstrap' , 'page' );
$cache_bins = array_merge ( module_invoke_all ( 'flush_caches' ), $core );
foreach ( $cache_bins as $bin ) {
cache ( $bin ) -> flush ();
2007-11-26 16:25:14 +00:00
}
2010-07-29 02:00:27 +00:00
// Rebuild the bootstrap module list. We do this here so that developers
// can get new hook_boot() implementations registered without having to
// write a hook_update_N() function.
_system_update_bootstrap_status ();
2007-11-26 16:25:14 +00:00
}
2008-01-07 19:43:29 +00:00
/**
* Helper function to change query - strings on css / js files .
*
2010-03-23 21:44:10 +00:00
* Changes the character added to all css / js files as dummy query - string , so
* that all browsers are forced to reload fresh files .
2008-01-07 19:43:29 +00:00
*/
function _drupal_flush_css_js () {
2010-03-23 21:44:10 +00:00
// The timestamp is converted to base 36 in order to make it more compact.
variable_set ( 'css_js_query_string' , base_convert ( REQUEST_TIME , 10 , 36 ));
2008-01-07 19:43:29 +00:00
}
2009-08-13 03:03:04 +00:00
2009-08-15 06:20:20 +00:00
/**
* Debug function used for outputting debug information .
*
* The debug information is passed on to trigger_error () after being converted
* to a string using _drupal_debug_message () .
*
* @ param $data
* Data to be output .
* @ param $label
* Label to prefix the data .
* @ param $print_r
* Flag to switch between print_r () and var_export () for data conversion to
* string . Set $print_r to TRUE when dealing with a recursive data structure
* as var_export () will generate an error .
*/
function debug ( $data , $label = NULL , $print_r = FALSE ) {
// Print $data contents to string.
2010-10-16 00:00:17 +00:00
$string = check_plain ( $print_r ? print_r ( $data , TRUE ) : var_export ( $data , TRUE ));
// Display values with pre-formatting to increase readability.
$string = '<pre>' . $string . '</pre>' ;
2009-08-15 06:20:20 +00:00
trigger_error ( trim ( $label ? " $label : $string " : $string ));
}
2009-08-13 03:03:04 +00:00
/**
* Parse a dependency for comparison by drupal_check_incompatibility () .
*
* @ param $dependency
2011-03-21 00:28:12 +00:00
* A dependency string , for example 'foo (>=8.x-4.5-beta5, 3.x)' .
2009-08-13 03:03:04 +00:00
* @ return
* An associative array with three keys :
* - 'name' includes the name of the thing to depend on ( e . g . 'foo' ) .
* - 'original_version' contains the original version string ( which can be
* used in the UI for reporting incompatibilities ) .
* - 'versions' is a list of associative arrays , each containing the keys
* 'op' and 'version' . 'op' can be one of : '=' , '==' , '!=' , '<>' , '<' ,
* '<=' , '>' , or '>=' . 'version' is one piece like '4.5-beta3' .
* Callers should pass this structure to drupal_check_incompatibility () .
*
* @ see drupal_check_incompatibility ()
*/
function drupal_parse_dependency ( $dependency ) {
// We use named subpatterns and support every op that version_compare
// supports. Also, op is optional and defaults to equals.
$p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?' ;
2011-03-21 00:28:12 +00:00
// Core version is always optional: 8.x-2.x and 2.x is treated the same.
2009-08-13 03:03:04 +00:00
$p_core = '(?:' . preg_quote ( DRUPAL_CORE_COMPATIBILITY ) . '-)?' ;
$p_major = '(?P<major>\d+)' ;
// By setting the minor version to x, branches can be matched.
$p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)' ;
$value = array ();
$parts = explode ( '(' , $dependency , 2 );
$value [ 'name' ] = trim ( $parts [ 0 ]);
if ( isset ( $parts [ 1 ])) {
$value [ 'original_version' ] = ' (' . $parts [ 1 ];
foreach ( explode ( ',' , $parts [ 1 ]) as $version ) {
if ( preg_match ( " /^ \ s* $p_op\s * $p_core $p_major\ . $p_minor / " , $version , $matches )) {
$op = ! empty ( $matches [ 'operation' ]) ? $matches [ 'operation' ] : '=' ;
if ( $matches [ 'minor' ] == 'x' ) {
// Drupal considers "2.x" to mean any version that begins with
// "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
// on the other hand, treats "x" as a string; so to
// version_compare(), "2.x" is considered less than 2.0. This
// means that >=2.x and <2.x are handled by version_compare()
// as we need, but > and <= are not.
if ( $op == '>' || $op == '<=' ) {
$matches [ 'major' ] ++ ;
}
// Equivalence can be checked by adding two restrictions.
if ( $op == '=' || $op == '==' ) {
$value [ 'versions' ][] = array ( 'op' => '<' , 'version' => ( $matches [ 'major' ] + 1 ) . '.x' );
$op = '>=' ;
}
}
$value [ 'versions' ][] = array ( 'op' => $op , 'version' => $matches [ 'major' ] . '.' . $matches [ 'minor' ]);
}
}
}
return $value ;
}
/**
* Check whether a version is compatible with a given dependency .
*
* @ param $v
* The parsed dependency structure from drupal_parse_dependency () .
* @ param $current_version
* The version to check against ( like 4.2 ) .
* @ return
* NULL if compatible , otherwise the original dependency version string that
2010-01-25 10:38:35 +00:00
* caused the incompatibility .
2009-08-13 03:03:04 +00:00
*
* @ see drupal_parse_dependency ()
*/
function drupal_check_incompatibility ( $v , $current_version ) {
if ( ! empty ( $v [ 'versions' ])) {
foreach ( $v [ 'versions' ] as $required_version ) {
if (( isset ( $required_version [ 'op' ]) && ! version_compare ( $current_version , $required_version [ 'version' ], $required_version [ 'op' ]))) {
return $v [ 'original_version' ];
}
}
}
}
2009-08-25 21:53:48 +00:00
2009-08-24 00:14:23 +00:00
/**
* Performs one or more XML - RPC request ( s ) .
*
2010-08-14 03:15:01 +00:00
* Usage example :
* @ code
* $result = xmlrpc ( 'http://example.com/xmlrpc.php' , array (
* 'service.methodName' => array ( $parameter , $second , $third ),
* ));
* @ endcode
*
2009-08-24 00:14:23 +00:00
* @ param $url
* An absolute URL of the XML - RPC endpoint .
2010-08-14 03:15:01 +00:00
* @ param $args
* An associative array whose keys are the methods to call and whose values
* are the arguments to pass to the respective method . If multiple methods
* are specified , a system . multicall is performed .
* @ param $options
* ( optional ) An array of options to pass along to drupal_http_request () .
*
2009-08-24 00:14:23 +00:00
* @ return
* For one request :
* Either the return value of the method on success , or FALSE .
* If FALSE is returned , see xmlrpc_errno () and xmlrpc_error_msg () .
* For multiple requests :
* An array of results . Each result will either be the result
* returned by the method called , or an xmlrpc_error object if the call
* failed . See xmlrpc_error () .
*/
2010-08-14 03:15:01 +00:00
function xmlrpc ( $url , $args , $options = array ()) {
2011-10-31 04:05:57 +00:00
require_once DRUPAL_ROOT . '/core/includes/xmlrpc.inc' ;
2010-08-14 03:15:01 +00:00
return _xmlrpc ( $url , $args , $options );
2009-08-24 00:14:23 +00:00
}
2009-10-15 17:55:55 +00:00
/**
2010-02-10 20:22:57 +00:00
* Retrieves a list of all available archivers .
*
* @ see hook_archiver_info ()
* @ see hook_archiver_info_alter ()
2009-10-15 17:55:55 +00:00
*/
function archiver_get_info () {
$archiver_info = & drupal_static ( __FUNCTION__ , array ());
if ( empty ( $archiver_info )) {
2011-09-11 16:14:18 +00:00
$cache = cache () -> get ( 'archiver_info' );
2009-10-15 17:55:55 +00:00
if ( $cache === FALSE ) {
// Rebuild the cache and save it.
$archiver_info = module_invoke_all ( 'archiver_info' );
drupal_alter ( 'archiver_info' , $archiver_info );
uasort ( $archiver_info , 'drupal_sort_weight' );
2011-09-11 16:14:18 +00:00
cache () -> set ( 'archiver_info' , $archiver_info );
2009-10-15 17:55:55 +00:00
}
else {
$archiver_info = $cache -> data ;
}
}
return $archiver_info ;
}
2010-07-29 02:27:43 +00:00
/**
* Returns a string of supported archive extensions .
*
* @ return
* A space - separated string of extensions suitable for use by the file
* validation system .
*/
function archiver_get_extensions () {
$valid_extensions = array ();
foreach ( archiver_get_info () as $archive ) {
foreach ( $archive [ 'extensions' ] as $extension ) {
foreach ( explode ( '.' , $extension ) as $part ) {
if ( ! in_array ( $part , $valid_extensions )) {
$valid_extensions [] = $part ;
}
}
}
}
return implode ( ' ' , $valid_extensions );
}
2009-10-15 17:55:55 +00:00
/**
* Create the appropriate archiver for the specified file .
*
* @ param $file
* The full path of the archive file . Note that stream wrapper
2009-10-23 00:55:59 +00:00
* paths are supported , but not remote ones .
2009-10-15 17:55:55 +00:00
* @ return
* A newly created instance of the archiver class appropriate
* for the specified file , already bound to that file .
2009-10-23 00:55:59 +00:00
* If no appropriate archiver class was found , will return FALSE .
2009-10-15 17:55:55 +00:00
*/
function archiver_get_archiver ( $file ) {
2009-10-23 00:55:59 +00:00
// Archivers can only work on local paths
$filepath = drupal_realpath ( $file );
if ( ! is_file ( $filepath )) {
throw new Exception ( t ( 'Archivers can only operate on local files: %file not supported' , array ( '%file' => $file )));
}
2009-10-15 17:55:55 +00:00
$archiver_info = archiver_get_info ();
foreach ( $archiver_info as $implementation ) {
2009-10-16 13:18:32 +00:00
foreach ( $implementation [ 'extensions' ] as $extension ) {
// Because extensions may be multi-part, such as .tar.gz,
// we cannot use simpler approaches like substr() or pathinfo().
// This method isn't quite as clean but gets the job done.
// Also note that the file may not yet exist, so we cannot rely
// on fileinfo() or other disk-level utilities.
2009-10-23 00:55:59 +00:00
if ( strrpos ( $filepath , '.' . $extension ) === strlen ( $filepath ) - strlen ( '.' . $extension )) {
return new $implementation [ 'class' ]( $filepath );
2009-10-16 13:18:32 +00:00
}
}
2009-10-15 17:55:55 +00:00
}
}
2009-10-15 21:19:31 +00:00
/**
* Drupal Updater registry .
*
* An Updater is a class that knows how to update various parts of the Drupal
* file system , for example to update modules that have newer releases , or to
* install a new theme .
*
* @ return
* Returns the Drupal Updater class registry .
*
* @ see hook_updater_info ()
* @ see hook_updater_info_alter ()
*/
function drupal_get_updaters () {
$updaters = & drupal_static ( __FUNCTION__ );
if ( ! isset ( $updaters )) {
$updaters = module_invoke_all ( 'updater_info' );
drupal_alter ( 'updater_info' , $updaters );
uasort ( $updaters , 'drupal_sort_weight' );
}
return $updaters ;
}
2010-12-01 00:23:36 +00:00
/**
* Drupal FileTransfer registry .
*
* @ return
* Returns the Drupal FileTransfer class registry .
*
* @ see FileTransfer
* @ see hook_filetransfer_info ()
* @ see hook_filetransfer_info_alter ()
*/
function drupal_get_filetransfer_info () {
$info = & drupal_static ( __FUNCTION__ );
if ( ! isset ( $info )) {
// Since we have to manually set the 'file path' default for each
// module separately, we can't use module_invoke_all().
$info = array ();
foreach ( module_implements ( 'filetransfer_info' ) as $module ) {
$function = $module . '_filetransfer_info' ;
if ( function_exists ( $function )) {
$result = $function ();
if ( isset ( $result ) && is_array ( $result )) {
foreach ( $result as & $values ) {
if ( empty ( $values [ 'file path' ])) {
$values [ 'file path' ] = drupal_get_path ( 'module' , $module );
}
}
$info = array_merge_recursive ( $info , $result );
}
}
}
drupal_alter ( 'filetransfer_info' , $info );
uasort ( $info , 'drupal_sort_weight' );
}
return $info ;
}