Skip navigation
Help

common.inc

  1. drupal
    1. 6 drupal/includes/common.inc
    2. 7 drupal/includes/common.inc

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.

Functions & methods

NameDescription
archiver_get_archiverCreate the appropriate archiver for the specified file.
archiver_get_extensionsReturns a string of supported archive extensions.
archiver_get_infoRetrieves a list of all available archivers.
base_pathReturn the base URL path (i.e., directory) of the Drupal installation.
check_urlStrips dangerous protocols (e.g. 'javascript:') from a URI and encodes it for output to an HTML attribute value.
date_iso8601Returns an ISO8601 formatted date based on the given date.
debugDebug function used for outputting debug information.
drupal_access_deniedDeliver a "access denied" error to the browser.
drupal_add_cssAdds a cascading stylesheet to the stylesheet queue.
drupal_add_feedAdd a feed URL for the current page.
drupal_add_html_headAdd output to the head tag of the HTML page.
drupal_add_html_head_linkAdd a LINK tag with a distinct 'rel' attribute to the page's HEAD.
drupal_add_jsAdds a JavaScript file, setting, or inline code to the page.
drupal_add_libraryAdds multiple JavaScript or CSS files at the same time.
drupal_add_region_contentAdd content to a specified region.
drupal_add_tabledragAssist in adding the tableDrag JavaScript behavior to a themed table.
drupal_aggregate_cssDefault callback to aggregate CSS files and inline content.
drupal_array_get_nested_valueRetrieves a value from a nested array with variable depth.
drupal_array_nested_key_existsDetermines whether a nested array with variable depth contains all of the requested keys.
drupal_array_set_nested_valueSets a value in a nested array with variable depth.
drupal_attributesConverts an associative array to an attribute string for use in XML/HTML tags.
drupal_build_css_cacheAggregates and optimizes CSS files into a cache file in the files directory.
drupal_build_js_cacheAggregates JavaScript files into a cache file in the files directory.
drupal_check_incompatibilityCheck whether a version is compatible with a given dependency.
drupal_clean_css_identifierPrepare a string for use as a valid CSS identifier (element, class or ID name).
drupal_clear_css_cacheDeletes old cached CSS files.
drupal_clear_js_cacheDeletes old cached JavaScript files and variables.
drupal_common_themeProvide theme registration for themes across .inc files.
drupal_cron_cleanupShutdown function for cron cleanup.
drupal_cron_runExecutes a cron run when called.
drupal_delete_file_if_staleCallback to delete files modified more than a set time ago.
drupal_deliver_html_pagePackage and send the result of a page callback to the browser as HTML.
drupal_deliver_pageDelivers a page callback result to the browser in the appropriate format.
drupal_encode_pathEncodes a Drupal path for use in a URL.
drupal_exitPerform end-of-request tasks.
drupal_explode_tagsExplode a string of given tags into an array.
drupal_flush_all_cachesFlush all cached data on the site.
drupal_get_breadcrumbGet the breadcrumb trail for the current page.
drupal_get_cssReturns a themed representation of all stylesheets that should be attached to the page.
drupal_get_destinationPrepare a 'destination' URL query parameter for use in combination with drupal_goto().
drupal_get_feedsGet the feed URLs for the current page.
drupal_get_filetransfer_infoDrupal FileTransfer registry.
drupal_get_hash_saltGet a salt useful for hardening against SQL injection.
drupal_get_html_headRetrieve output to be displayed in the HEAD tag of the HTML page.
drupal_get_jsReturns a themed presentation of all JavaScript code for the current page.
drupal_get_libraryRetrieves information for a JavaScript/CSS library.
drupal_get_pathReturns the path to a system item (module, theme, etc.).
drupal_get_private_keyEnsure the private key variable used to generate tokens is set.
drupal_get_profileGet the name of the currently active install profile.
drupal_get_query_arraySplit an URL-encoded query string into an array.
drupal_get_query_parametersProcess a URL query parameter array to remove unwanted elements.
drupal_get_rdf_namespacesReturns a string containing RDF namespace declarations for use in XML and XHTML output.
drupal_get_region_contentGet assigned content for a given region.
drupal_get_schema_unprocessedReturns the unprocessed and unaltered version of a module's schema.
drupal_get_tokenGenerate a token based on $value, the current user session and private key.
drupal_get_updatersDrupal Updater registry.
drupal_gotoSend the user to a different Drupal page.
drupal_group_cssDefault callback to group CSS items.
drupal_html_classPrepare a string for use as a valid class name.
drupal_html_idPrepare a string for use as a valid HTML ID and guarantee uniqueness.
drupal_http_build_queryParse an array into a valid, rawurlencoded query string.
drupal_http_header_attributesFormat an attribute string for a HTTP header.
drupal_http_requestPerform an HTTP request.
drupal_implode_tagsImplode an array of tags into a string.
drupal_install_schemaCreates all tables in a module's hook_schema() implementation.
drupal_json_decodeConverts an HTML-safe JSON string into its PHP equivalent.
drupal_json_encodeConverts a PHP variable into its JavaScript equivalent.
drupal_json_outputReturn data in JSON format.
drupal_js_defaultsConstructs an array of the defaults that are used for JavaScript items.
drupal_load_stylesheetLoads the stylesheet and resolves all @import commands.
drupal_load_stylesheet_contentProcess the contents of a stylesheet for aggregation.
drupal_map_assocForm an associative array from a linear array.
drupal_not_foundDeliver a "page not found" error to the browser.
drupal_page_footerPerform end-of-request tasks.
drupal_page_set_cacheStore the current page in the cache.
drupal_parse_dependencyParse a dependency for comparison by drupal_check_incompatibility().
drupal_parse_info_fileParses Drupal module and theme .info files.
drupal_parse_info_formatParse data in Drupal's .info format.
drupal_parse_urlWrapper around parse_url() to parse a system URL string into an associative array, suitable for url().
drupal_pre_render_conditional_comments#pre_render callback to render #browsers into #prefix and #suffix.
drupal_pre_render_link#pre_render callback to render a link into #markup.
drupal_pre_render_links#pre_render callback that collects child links into a single array.
drupal_pre_render_markup#pre_render callback to append contents in #markup to #children.
drupal_pre_render_styles#pre_render callback to add the elements needed for CSS tags to be rendered.
drupal_process_attachedAdds attachments to a render() structure.
drupal_process_statesAdds JavaScript to change the state of an element based on another element.
drupal_region_classProvides a standard HTML class name that identifies a page region.
drupal_renderRenders HTML given a structured array tree.
drupal_render_cache_by_queryPrepare 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.
drupal_render_cache_getGet the rendered output of a renderable element from cache.
drupal_render_cache_setCache the rendered output of a renderable element.
drupal_render_childrenRender children of an element and concatenate them.
drupal_render_cid_createCreate the cache ID for a renderable element.
drupal_render_cid_partsHelper function for building cache ids.
drupal_render_collect_attachedCollect #attached for an element and all child elements into a single array.
drupal_render_pageRenders the page, including all theming.
drupal_schema_fields_sqlRetrieve a list of fields from a table schema. The list is suitable for use in a SQL query.
drupal_set_breadcrumbSet the breadcrumb trail for the current page.
drupal_set_page_contentSet the main page content value for later use.
drupal_set_time_limitAttempts to set the PHP maximum execution time.
drupal_site_offlineDeliver a "site is under maintenance" message to the browser.
drupal_sort_css_jsFunction used by uasort to sort the array structures returned by drupal_add_css() and drupal_add_js().
drupal_sort_titleArray sorting callback; sorts elements by 'title' key.
drupal_sort_weightFunction used by uasort to sort structured arrays by weight, without the property weight prefix.
drupal_strip_dangerous_protocolsStrips dangerous protocols (e.g. 'javascript:') from a URI.
drupal_system_listingReturns information about system object files (modules, themes, etc.).
drupal_uninstall_schemaRemove all tables that a module defines in its hook_schema().
drupal_valid_tokenValidate a token based on $value, the current user session and private key.
drupal_write_recordSaves (inserts or updates) a record to the database based upon the schema.
element_childCheck if the key is a child.
element_childrenReturn the children of an element, optionally sorted by weight.
element_get_visible_childrenReturn the visibile children of an element.
element_infoRetrieve the default properties for the defined element type.
element_info_propertyRetrieve a single property for the defined element type.
element_propertiesGet properties of a structured array element. Properties begin with '#'.
element_propertyCheck if the key is a property.
element_set_attributesSets HTML attributes based on element properties.
element_sortFunction used by uasort to sort structured arrays by weight.
element_sort_by_titleArray sorting callback; sorts elements by title.
entity_create_stub_entityHelper function to assemble an object structure with initial ids.
entity_extract_idsHelper function to extract id, vid, and bundle name from an entity.
entity_form_field_validateHelper function for attaching field API validation to entity forms.
entity_form_submit_build_entityHelper function for copying submitted values to entity properties for simple entity forms.
entity_get_controllerGet the entity controller class for an entity type.
entity_get_infoGet the entity info array of an entity type.
entity_info_cache_clearResets the cached information about entity types.
entity_labelReturns the label of an entity.
entity_loadLoad entities from the database.
entity_load_unchangedLoads the unchanged, i.e. not modified, entity from the database.
entity_prepare_viewInvoke hook_entity_prepare_view().
entity_uriReturns the uri elements of an entity.
filter_xssFilters an HTML string to prevent cross-site-scripting (XSS) vulnerabilities.
filter_xss_adminVery permissive XSS/HTML filter for admin-only use.
filter_xss_bad_protocolProcesses an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:).
fix_gpc_magicFix double-escaping problems caused by "magic quotes" in some PHP installations.
flood_clear_eventMake the flood control mechanism forget about an event for the current visitor.
flood_is_allowedChecks whether user is allowed to proceed with the specified event.
flood_register_eventRegister an event for the current visitor to the flood control mechanism.
format_dateFormats a date, using a date type or a custom date format string.
format_intervalFormat a time interval with the requested granularity.
format_pluralFormat a string containing a count of items.
format_rss_channelFormats an RSS channel.
format_rss_itemFormat a single RSS item.
format_sizeGenerate a string representation for the given byte count.
format_usernameFormat a username.
format_xml_elementsFormat XML elements.
hideHide an element from later rendering.
lFormats an internal or external URL link as an HTML anchor tag.
parse_sizeParse a given byte count.
renderRender an element.
showShow a hidden element for later rendering.
urlGenerates an internal or external URL.
url_is_externalReturn TRUE if a path is external to Drupal (e.g. http://example.com).
valid_email_addressVerify the syntax of the given e-mail address.
valid_urlVerify the syntax of the given URL.
watchdog_severity_levelsSeverity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
xmlrpcPerforms one or more XML-RPC request(s).
_drupal_bootstrap_full
_drupal_build_css_pathHelper function for drupal_build_css_cache().
_drupal_default_html_headReturns elements that are always displayed in the HEAD tag of the HTML page.
_drupal_flush_css_jsHelper function to change query-strings on css/js files.
_drupal_load_stylesheetLoads stylesheets recursively and returns contents with corrected paths.
_drupal_schema_initializeFill in required default values for table definitions returned by hook_schema().
_filter_xss_attributesProcesses a string of HTML attributes.
_filter_xss_splitProcesses an HTML tag.
_fix_gpc_magic
_fix_gpc_magic_filesHelper function to strip slashes from $_FILES skipping over the tmp_name keys since PHP generates single backslashes for file paths on Windows systems.
_format_date_callbackCallback function for preg_replace_callback().

Constants

NameDescription
CSS_DEFAULTThe default group for module CSS files added to the page.
CSS_SYSTEMThe default group for system CSS files added to the page.
CSS_THEMEThe default group for theme CSS files added to the page.
DRUPAL_CACHE_CUSTOMThe 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…
DRUPAL_CACHE_GLOBALThe block or element is the same for every user on every page where it is visible.
DRUPAL_CACHE_PER_PAGEThe block or element can change depending on the page being viewed.
DRUPAL_CACHE_PER_ROLEThe 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.
DRUPAL_CACHE_PER_USERThe 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.
DRUPAL_NO_CACHEThe block should not get cached. This setting should be used:
HTTP_REQUEST_TIMEOUTError code indicating that the request made by drupal_http_request() exceeded the specified timeout.
JS_DEFAULTThe default group for module JavaScript code added to the page.
JS_LIBRARYThe default group for JavaScript libraries, settings or jQuery plugins added to the page.
JS_THEMEThe default group for theme JavaScript code added to the page.
SAVED_DELETEDReturn status for saving which deleted an existing item.
SAVED_NEWReturn status for saving which involved creating a new item.
SAVED_UPDATEDReturn status for saving which involved an update to an existing item.

File

drupal/includes/common.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * Common functions that many Drupal modules will need to reference.
  5. *
  6. * The functions that are critical and need to be available even when serving
  7. * a cached page are instead located in bootstrap.inc.
  8. */
  9. /**
  10. * @defgroup php_wrappers PHP wrapper functions
  11. * @{
  12. * Functions that are wrappers or custom implementations of PHP functions.
  13. *
  14. * Certain PHP functions should not be used in Drupal. Instead, Drupal's
  15. * replacement functions should be used.
  16. *
  17. * For example, for improved or more secure UTF8-handling, or RFC-compliant
  18. * handling of URLs in Drupal.
  19. *
  20. * For ease of use and memorizing, all these wrapper functions use the same name
  21. * as the original PHP function, but prefixed with "drupal_". Beware, however,
  22. * that not all wrapper functions support the same arguments as the original
  23. * functions.
  24. *
  25. * You should always use these wrapper functions in your code.
  26. *
  27. * Wrong:
  28. * @code
  29. * $my_substring = substr($original_string, 0, 5);
  30. * @endcode
  31. *
  32. * Correct:
  33. * @code
  34. * $my_substring = drupal_substr($original_string, 0, 5);
  35. * @endcode
  36. *
  37. * @}
  38. */
  39. /**
  40. * Return status for saving which involved creating a new item.
  41. */
  42. define('SAVED_NEW', 1);
  43. /**
  44. * Return status for saving which involved an update to an existing item.
  45. */
  46. define('SAVED_UPDATED', 2);
  47. /**
  48. * Return status for saving which deleted an existing item.
  49. */
  50. define('SAVED_DELETED', 3);
  51. /**
  52. * The default group for system CSS files added to the page.
  53. */
  54. define('CSS_SYSTEM', -100);
  55. /**
  56. * The default group for module CSS files added to the page.
  57. */
  58. define('CSS_DEFAULT', 0);
  59. /**
  60. * The default group for theme CSS files added to the page.
  61. */
  62. define('CSS_THEME', 100);
  63. /**
  64. * The default group for JavaScript libraries, settings or jQuery plugins added
  65. * to the page.
  66. */
  67. define('JS_LIBRARY', -100);
  68. /**
  69. * The default group for module JavaScript code added to the page.
  70. */
  71. define('JS_DEFAULT', 0);
  72. /**
  73. * The default group for theme JavaScript code added to the page.
  74. */
  75. define('JS_THEME', 100);
  76. /**
  77. * Error code indicating that the request made by drupal_http_request() exceeded
  78. * the specified timeout.
  79. */
  80. define('HTTP_REQUEST_TIMEOUT', -1);
  81. /**
  82. * Constants defining cache granularity for blocks and renderable arrays.
  83. *
  84. * Modules specify the caching patterns for their blocks using binary
  85. * combinations of these constants in their hook_block_info():
  86. * $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE;
  87. * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is
  88. * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
  89. * implement
  90. *
  91. * The block cache is cleared in cache_clear_all(), and uses the same clearing
  92. * policy than page cache (node, comment, user, taxonomy added or updated...).
  93. * Blocks requiring more fine-grained clearing might consider disabling the
  94. * built-in block cache (DRUPAL_NO_CACHE) and roll their own.
  95. *
  96. * Note that user 1 is excluded from block caching.
  97. */
  98. /**
  99. * The block should not get cached. This setting should be used:
  100. * - for simple blocks (notably those that do not perform any db query),
  101. * where querying the db cache would be more expensive than directly generating
  102. * the content.
  103. * - for blocks that change too frequently.
  104. */
  105. define('DRUPAL_NO_CACHE', -1);
  106. /**
  107. * The block is handling its own caching in its hook_block_view(). From the
  108. * perspective of the block cache system, this is equivalent to DRUPAL_NO_CACHE.
  109. * Useful when time based expiration is needed or a site uses a node access
  110. * which invalidates standard block cache.
  111. */
  112. define('DRUPAL_CACHE_CUSTOM', -2);
  113. /**
  114. * The block or element can change depending on the roles the user viewing the
  115. * page belongs to. This is the default setting for blocks, used when the block
  116. * does not specify anything.
  117. */
  118. define('DRUPAL_CACHE_PER_ROLE', 0x0001);
  119. /**
  120. * The block or element can change depending on the user viewing the page.
  121. * This setting can be resource-consuming for sites with large number of users,
  122. * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
  123. */
  124. define('DRUPAL_CACHE_PER_USER', 0x0002);
  125. /**
  126. * The block or element can change depending on the page being viewed.
  127. */
  128. define('DRUPAL_CACHE_PER_PAGE', 0x0004);
  129. /**
  130. * The block or element is the same for every user on every page where it is visible.
  131. */
  132. define('DRUPAL_CACHE_GLOBAL', 0x0008);
  133. /**
  134. * Add content to a specified region.
  135. *
  136. * @param $region
  137. * Page region the content is added to.
  138. * @param $data
  139. * Content to be added.
  140. */
  141. function drupal_add_region_content($region = NULL, $data = NULL) {
  142. static $content = array();
  143. if (isset($region) && isset($data)) {
  144. $content[$region][] = $data;
  145. }
  146. return $content;
  147. }
  148. /**
  149. * Get assigned content for a given region.
  150. *
  151. * @param $region
  152. * A specified region to fetch content for. If NULL, all regions will be
  153. * returned.
  154. * @param $delimiter
  155. * Content to be inserted between imploded array elements.
  156. */
  157. function drupal_get_region_content($region = NULL, $delimiter = ' ') {
  158. $content = drupal_add_region_content();
  159. if (isset($region)) {
  160. if (isset($content[$region]) && is_array($content[$region])) {
  161. return implode($delimiter, $content[$region]);
  162. }
  163. }
  164. else {
  165. foreach (array_keys($content) as $region) {
  166. if (is_array($content[$region])) {
  167. $content[$region] = implode($delimiter, $content[$region]);
  168. }
  169. }
  170. return $content;
  171. }
  172. }
  173. /**
  174. * Get the name of the currently active install profile.
  175. *
  176. * When this function is called during Drupal's initial installation process,
  177. * the name of the profile that's about to be installed is stored in the global
  178. * installation state. At all other times, the standard Drupal systems variable
  179. * table contains the name of the current profile, and we can call variable_get()
  180. * to determine what one is active.
  181. *
  182. * @return $profile
  183. * The name of the install profile.
  184. */
  185. function drupal_get_profile() {
  186. global $install_state;
  187. if (isset($install_state['parameters']['profile'])) {
  188. $profile = $install_state['parameters']['profile'];
  189. }
  190. else {
  191. $profile = variable_get('install_profile', 'standard');
  192. }
  193. return $profile;
  194. }
  195. /**
  196. * Set the breadcrumb trail for the current page.
  197. *
  198. * @param $breadcrumb
  199. * Array of links, starting with "home" and proceeding up to but not including
  200. * the current page.
  201. */
  202. function drupal_set_breadcrumb($breadcrumb = NULL) {
  203. $stored_breadcrumb = &drupal_static(__FUNCTION__);
  204. if (isset($breadcrumb)) {
  205. $stored_breadcrumb = $breadcrumb;
  206. }
  207. return $stored_breadcrumb;
  208. }
  209. /**
  210. * Get the breadcrumb trail for the current page.
  211. */
  212. function drupal_get_breadcrumb() {
  213. $breadcrumb = drupal_set_breadcrumb();
  214. if (!isset($breadcrumb)) {
  215. $breadcrumb = menu_get_active_breadcrumb();
  216. }
  217. return $breadcrumb;
  218. }
  219. /**
  220. * Returns a string containing RDF namespace declarations for use in XML and
  221. * XHTML output.
  222. */
  223. function drupal_get_rdf_namespaces() {
  224. $xml_rdf_namespaces = array();
  225. // Serializes the RDF namespaces in XML namespace syntax.
  226. if (function_exists('rdf_get_namespaces')) {
  227. foreach (rdf_get_namespaces() as $prefix => $uri) {
  228. $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
  229. }
  230. }
  231. return count($xml_rdf_namespaces) ? "\n " . implode("\n ", $xml_rdf_namespaces) : '';
  232. }
  233. /**
  234. * Add output to the head tag of the HTML page.
  235. *
  236. * This function can be called as long the headers aren't sent. Pass no
  237. * arguments (or NULL for both) to retrieve the currently stored elements.
  238. *
  239. * @param $data
  240. * A renderable array. If the '#type' key is not set then 'html_tag' will be
  241. * added as the default '#type'.
  242. * @param $key
  243. * A unique string key to allow implementations of hook_html_head_alter() to
  244. * identify the element in $data. Required if $data is not NULL.
  245. *
  246. * @return
  247. * An array of all stored HEAD elements.
  248. *
  249. * @see theme_html_tag()
  250. */
  251. function drupal_add_html_head($data = NULL, $key = NULL) {
  252. $stored_head = &drupal_static(__FUNCTION__);
  253. if (!isset($stored_head)) {
  254. // Make sure the defaults, including Content-Type, come first.
  255. $stored_head = _drupal_default_html_head();
  256. }
  257. if (isset($data) && isset($key)) {
  258. if (!isset($data['#type'])) {
  259. $data['#type'] = 'html_tag';
  260. }
  261. $stored_head[$key] = $data;
  262. }
  263. return $stored_head;
  264. }
  265. /**
  266. * Returns elements that are always displayed in the HEAD tag of the HTML page.
  267. */
  268. function _drupal_default_html_head() {
  269. // Add default elements. Make sure the Content-Type comes first because the
  270. // IE browser may be vulnerable to XSS via encoding attacks from any content
  271. // that comes before this META tag, such as a TITLE tag.
  272. $elements['system_meta_content_type'] = array(
  273. '#type' => 'html_tag',
  274. '#tag' => 'meta',
  275. '#attributes' => array(
  276. 'http-equiv' => 'Content-Type',
  277. 'content' => 'text/html; charset=utf-8',
  278. ),
  279. // Security: This always has to be output first.
  280. '#weight' => -1000,
  281. );
  282. // Show Drupal and the major version number in the META GENERATOR tag.
  283. // Get the major version.
  284. list($version, ) = explode('.', VERSION);
  285. $elements['system_meta_generator'] = array(
  286. '#type' => 'html_tag',
  287. '#tag' => 'meta',
  288. '#attributes' => array(
  289. 'name' => 'Generator',
  290. 'content' => 'Drupal ' . $version . ' (http://drupal.org)',
  291. ),
  292. );
  293. // Also send the generator in the HTTP header.
  294. $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
  295. return $elements;
  296. }
  297. /**
  298. * Retrieve output to be displayed in the HEAD tag of the HTML page.
  299. */
  300. function drupal_get_html_head() {
  301. $elements = drupal_add_html_head();
  302. drupal_alter('html_head', $elements);
  303. return drupal_render($elements);
  304. }
  305. /**
  306. * Add a feed URL for the current page.
  307. *
  308. * This function can be called as long the HTML header hasn't been sent.
  309. *
  310. * @param $url
  311. * An internal system path or a fully qualified external URL of the feed.
  312. * @param $title
  313. * The title of the feed.
  314. */
  315. function drupal_add_feed($url = NULL, $title = '') {
  316. $stored_feed_links = &drupal_static(__FUNCTION__, array());
  317. if (isset($url)) {
  318. $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
  319. drupal_add_html_head_link(array(
  320. 'rel' => 'alternate',
  321. 'type' => 'application/rss+xml',
  322. 'title' => $title,
  323. // Force the URL to be absolute, for consistency with other <link> tags
  324. // output by Drupal.
  325. 'href' => url($url, array('absolute' => TRUE)),
  326. ));
  327. }
  328. return $stored_feed_links;
  329. }
  330. /**
  331. * Get the feed URLs for the current page.
  332. *
  333. * @param $delimiter
  334. * A delimiter to split feeds by.
  335. */
  336. function drupal_get_feeds($delimiter = "\n") {
  337. $feeds = drupal_add_feed();
  338. return implode($feeds, $delimiter);
  339. }
  340. /**
  341. * @defgroup http_handling HTTP handling
  342. * @{
  343. * Functions to properly handle HTTP responses.
  344. */
  345. /**
  346. * Process a URL query parameter array to remove unwanted elements.
  347. *
  348. * @param $query
  349. * (optional) An array to be processed. Defaults to $_GET.
  350. * @param $exclude
  351. * (optional) A list of $query array keys to remove. Use "parent[child]" to
  352. * exclude nested items. Defaults to array('q').
  353. * @param $parent
  354. * Internal use only. Used to build the $query array key for nested items.
  355. *
  356. * @return
  357. * An array containing query parameters, which can be used for url().
  358. */
  359. function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') {
  360. // Set defaults, if none given.
  361. if (!isset($query)) {
  362. $query = $_GET;
  363. }
  364. // If $exclude is empty, there is nothing to filter.
  365. if (empty($exclude)) {
  366. return $query;
  367. }
  368. elseif (!$parent) {
  369. $exclude = array_flip($exclude);
  370. }
  371. $params = array();
  372. foreach ($query as $key => $value) {
  373. $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
  374. if (isset($exclude[$string_key])) {
  375. continue;
  376. }
  377. if (is_array($value)) {
  378. $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
  379. }
  380. else {
  381. $params[$key] = $value;
  382. }
  383. }
  384. return $params;
  385. }
  386. /**
  387. * Split an URL-encoded query string into an array.
  388. *
  389. * @param $query
  390. * The query string to split.
  391. *
  392. * @return
  393. * An array of url decoded couples $param_name => $value.
  394. */
  395. function drupal_get_query_array($query) {
  396. $result = array();
  397. if (!empty($query)) {
  398. foreach (explode('&', $query) as $param) {
  399. $param = explode('=', $param);
  400. $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
  401. }
  402. }
  403. return $result;
  404. }
  405. /**
  406. * Parse an array into a valid, rawurlencoded query string.
  407. *
  408. * This differs from http_build_query() as we need to rawurlencode() (instead of
  409. * urlencode()) all query parameters.
  410. *
  411. * @param $query
  412. * The query parameter array to be processed, e.g. $_GET.
  413. * @param $parent
  414. * Internal use only. Used to build the $query array key for nested items.
  415. *
  416. * @return
  417. * A rawurlencoded string which can be used as or appended to the URL query
  418. * string.
  419. *
  420. * @see drupal_get_query_parameters()
  421. * @ingroup php_wrappers
  422. */
  423. function drupal_http_build_query(array $query, $parent = '') {
  424. $params = array();
  425. foreach ($query as $key => $value) {
  426. $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key));
  427. // Recurse into children.
  428. if (is_array($value)) {
  429. $params[] = drupal_http_build_query($value, $key);
  430. }
  431. // If a query parameter value is NULL, only append its key.
  432. elseif (!isset($value)) {
  433. $params[] = $key;
  434. }
  435. else {
  436. // For better readability of paths in query strings, we decode slashes.
  437. $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
  438. }
  439. }
  440. return implode('&', $params);
  441. }
  442. /**
  443. * Prepare a 'destination' URL query parameter for use in combination with drupal_goto().
  444. *
  445. * Used to direct the user back to the referring page after completing a form.
  446. * By default the current URL is returned. If a destination exists in the
  447. * previous request, that destination is returned. As such, a destination can
  448. * persist across multiple pages.
  449. *
  450. * @see drupal_goto()
  451. */
  452. function drupal_get_destination() {
  453. $destination = &drupal_static(__FUNCTION__);
  454. if (isset($destination)) {
  455. return $destination;
  456. }
  457. if (isset($_GET['destination'])) {
  458. $destination = array('destination' => $_GET['destination']);
  459. }
  460. else {
  461. $path = $_GET['q'];
  462. $query = drupal_http_build_query(drupal_get_query_parameters());
  463. if ($query != '') {
  464. $path .= '?' . $query;
  465. }
  466. $destination = array('destination' => $path);
  467. }
  468. return $destination;
  469. }
  470. /**
  471. * Wrapper around parse_url() to parse a system URL string into an associative array, suitable for url().
  472. *
  473. * This function should only be used for URLs that have been generated by the
  474. * system, resp. url(). It should not be used for URLs that come from external
  475. * sources, or URLs that link to external resources.
  476. *
  477. * The returned array contains a 'path' that may be passed separately to url().
  478. * For example:
  479. * @code
  480. * $options = drupal_parse_url($_GET['destination']);
  481. * $my_url = url($options['path'], $options);
  482. * $my_link = l('Example link', $options['path'], $options);
  483. * @endcode
  484. *
  485. * This is required, because url() does not support relative URLs containing a
  486. * query string or fragment in its $path argument. Instead, any query string
  487. * needs to be parsed into an associative query parameter array in
  488. * $options['query'] and the fragment into $options['fragment'].
  489. *
  490. * @param $url
  491. * The URL string to parse, f.e. $_GET['destination'].
  492. *
  493. * @return
  494. * An associative array containing the keys:
  495. * - 'path': The path of the URL. If the given $url is external, this includes
  496. * the scheme and host.
  497. * - 'query': An array of query parameters of $url, if existent.
  498. * - 'fragment': The fragment of $url, if existent.
  499. *
  500. * @see url()
  501. * @see drupal_goto()
  502. * @ingroup php_wrappers
  503. */
  504. function drupal_parse_url($url) {
  505. $options = array(
  506. 'path' => NULL,
  507. 'query' => array(),
  508. 'fragment' => '',
  509. );
  510. // External URLs: not using parse_url() here, so we do not have to rebuild
  511. // the scheme, host, and path without having any use for it.
  512. if (strpos($url, '://') !== FALSE) {
  513. // Split off everything before the query string into 'path'.
  514. $parts = explode('?', $url);
  515. $options['path'] = $parts[0];
  516. // If there is a query string, transform it into keyed query parameters.
  517. if (isset($parts[1])) {
  518. $query_parts = explode('#', $parts[1]);
  519. parse_str($query_parts[0], $options['query']);
  520. // Take over the fragment, if there is any.
  521. if (isset($query_parts[1])) {
  522. $options['fragment'] = $query_parts[1];
  523. }
  524. }
  525. }
  526. // Internal URLs.
  527. else {
  528. // parse_url() does not support relative URLs, so make it absolute. E.g. the
  529. // relative URL "foo/bar:1" isn't properly parsed.
  530. $parts = parse_url('http://example.com/' . $url);
  531. // Strip the leading slash that was just added.
  532. $options['path'] = substr($parts['path'], 1);
  533. if (isset($parts['query'])) {
  534. parse_str($parts['query'], $options['query']);
  535. }
  536. if (isset($parts['fragment'])) {
  537. $options['fragment'] = $parts['fragment'];
  538. }
  539. }
  540. // The 'q' parameter contains the path of the current page if clean URLs are
  541. // disabled. It overrides the 'path' of the URL when present, even if clean
  542. // URLs are enabled, due to how Apache rewriting rules work.
  543. if (isset($options['query']['q'])) {
  544. $options['path'] = $options['query']['q'];
  545. unset($options['query']['q']);
  546. }
  547. return $options;
  548. }
  549. /**
  550. * Encodes a Drupal path for use in a URL.
  551. *
  552. * For aesthetic reasons slashes are not escaped.
  553. *
  554. * Note that url() takes care of calling this function, so a path passed to that
  555. * function should not be encoded in advance.
  556. *
  557. * @param $path
  558. * The Drupal path to encode.
  559. */
  560. function drupal_encode_path($path) {
  561. return str_replace('%2F', '/', rawurlencode($path));
  562. }
  563. /**
  564. * Send the user to a different Drupal page.
  565. *
  566. * This issues an on-site HTTP redirect. The function makes sure the redirected
  567. * URL is formatted correctly.
  568. *
  569. * Usually the redirected URL is constructed from this function's input
  570. * parameters. However you may override that behavior by setting a
  571. * destination in either the $_REQUEST-array (i.e. by using
  572. * the query string of an URI) This is used to direct the user back to
  573. * the proper page after completing a form. For example, after editing
  574. * a post on the 'admin/content'-page or after having logged on using the
  575. * 'user login'-block in a sidebar. The function drupal_get_destination()
  576. * can be used to help set the destination URL.
  577. *
  578. * Drupal will ensure that messages set by drupal_set_message() and other
  579. * session data are written to the database before the user is redirected.
  580. *
  581. * This function ends the request; use it instead of a return in your menu
  582. * callback.
  583. *
  584. * @param $path
  585. * A Drupal path or a full URL.
  586. * @param $options
  587. * An associative array of additional URL options to pass to url().
  588. * @param $http_response_code
  589. * Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
  590. * - 301 Moved Permanently (the recommended value for most redirects)
  591. * - 302 Found (default in Drupal and PHP, sometimes used for spamming search
  592. * engines)
  593. * - 303 See Other
  594. * - 304 Not Modified
  595. * - 305 Use Proxy
  596. * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
  597. * Note: Other values are defined by RFC 2616, but are rarely used and poorly
  598. * supported.
  599. *
  600. * @see drupal_get_destination()
  601. * @see url()
  602. */
  603. function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
  604. // A destination in $_GET always overrides the function arguments.
  605. // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
  606. if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
  607. $destination = drupal_parse_url($_GET['destination']);
  608. $path = $destination['path'];
  609. $options['query'] = $destination['query'];
  610. $options['fragment'] = $destination['fragment'];
  611. }
  612. drupal_alter('drupal_goto', $path, $options, $http_response_code);
  613. // The 'Location' HTTP header must be absolute.
  614. $options['absolute'] = TRUE;
  615. $url = url($path, $options);
  616. header('Location: ' . $url, TRUE, $http_response_code);
  617. // The "Location" header sends a redirect status code to the HTTP daemon. In
  618. // some cases this can be wrong, so we make sure none of the code below the
  619. // drupal_goto() call gets executed upon redirection.
  620. drupal_exit($url);
  621. }
  622. /**
  623. * Deliver a "site is under maintenance" message to the browser.
  624. *
  625. * Page callback functions wanting to report a "site offline" message should
  626. * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
  627. * functions that are invoked in contexts where that return value might not
  628. * bubble up to menu_execute_active_handler() should call drupal_site_offline().
  629. */
  630. function drupal_site_offline() {
  631. drupal_deliver_page(MENU_SITE_OFFLINE);
  632. }
  633. /**
  634. * Deliver a "page not found" error to the browser.
  635. *
  636. * Page callback functions wanting to report a "page not found" message should
  637. * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
  638. * functions that are invoked in contexts where that return value might not
  639. * bubble up to menu_execute_active_handler() should call drupal_not_found().
  640. */
  641. function drupal_not_found() {
  642. drupal_deliver_page(MENU_NOT_FOUND);
  643. }
  644. /**
  645. * Deliver a "access denied" error to the browser.
  646. *
  647. * Page callback functions wanting to report an "access denied" message should
  648. * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
  649. * functions that are invoked in contexts where that return value might not
  650. * bubble up to menu_execute_active_handler() should call drupal_access_denied().
  651. */
  652. function drupal_access_denied() {
  653. drupal_deliver_page(MENU_ACCESS_DENIED);
  654. }
  655. /**
  656. * Perform an HTTP request.
  657. *
  658. * This is a flexible and powerful HTTP client implementation. Correctly
  659. * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
  660. *
  661. * @param $url
  662. * A string containing a fully qualified URI.
  663. * @param array $options
  664. * (optional) An array that can have one or more of the following elements:
  665. * - headers: An array containing request headers to send as name/value pairs.
  666. * - method: A string containing the request method. Defaults to 'GET'.
  667. * - data: A string containing the request body, formatted as
  668. * 'param=value&param=value&...'. Defaults to NULL.
  669. * - max_redirects: An integer representing how many times a redirect
  670. * may be followed. Defaults to 3.
  671. * - timeout: A float representing the maximum number of seconds the function
  672. * call may take. The default is 30 seconds. If a timeout occurs, the error
  673. * code is set to the HTTP_REQUEST_TIMEOUT constant.
  674. * - context: A context resource created with stream_context_create().
  675. *
  676. * @return object
  677. * An object that can have one or more of the following components:
  678. * - request: A string containing the request body that was sent.
  679. * - code: An integer containing the response status code, or the error code
  680. * if an error occurred.
  681. * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
  682. * - status_message: The status message from the response, if a response was
  683. * received.
  684. * - redirect_code: If redirected, an integer containing the initial response
  685. * status code.
  686. * - redirect_url: If redirected, a string containing the URL of the redirect
  687. * target.
  688. * - error: If an error occurred, the error message. Otherwise not set.
  689. * - headers: An array containing the response headers as name/value pairs.
  690. * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
  691. * easy access the array keys are returned in lower case.
  692. * - data: A string containing the response body that was received.
  693. */
  694. function drupal_http_request($url, array $options = array()) {
  695. $result = new stdClass();
  696. // Parse the URL and make sure we can handle the schema.
  697. $uri = @parse_url($url);
  698. if ($uri == FALSE) {
  699. $result->error = 'unable to parse URL';
  700. $result->code = -1001;
  701. return $result;
  702. }
  703. if (!isset($uri['scheme'])) {
  704. $result->error = 'missing schema';
  705. $result->code = -1002;
  706. return $result;
  707. }
  708. timer_start(__FUNCTION__);
  709. // Merge the default options.
  710. $options += array(
  711. 'headers' => array(),
  712. 'method' => 'GET',
  713. 'data' => NULL,
  714. 'max_redirects' => 3,
  715. 'timeout' => 30.0,
  716. 'context' => NULL,
  717. );
  718. // stream_socket_client() requires timeout to be a float.
  719. $options['timeout'] = (float) $options['timeout'];
  720. switch ($uri['scheme']) {
  721. case 'http':
  722. case 'feed':
  723. $port = isset($uri['port']) ? $uri['port'] : 80;
  724. $socket = 'tcp://' . $uri['host'] . ':' . $port;
  725. // RFC 2616: "non-standard ports MUST, default ports MAY be included".
  726. // We don't add the standard port to prevent from breaking rewrite rules
  727. // checking the host that do not take into account the port number.
  728. $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
  729. break;
  730. case 'https':
  731. // Note: Only works when PHP is compiled with OpenSSL support.
  732. $port = isset($uri['port']) ? $uri['port'] : 443;
  733. $socket = 'ssl://' . $uri['host'] . ':' . $port;
  734. $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
  735. break;
  736. default:
  737. $result->error = 'invalid schema ' . $uri['scheme'];
  738. $result->code = -1003;
  739. return $result;
  740. }
  741. if (empty($options['context'])) {
  742. $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
  743. }
  744. else {
  745. // Create a stream with context. Allows verification of a SSL certificate.
  746. $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);
  747. }
  748. // Make sure the socket opened properly.
  749. if (!$fp) {
  750. // When a network error occurs, we use a negative number so it does not
  751. // clash with the HTTP status codes.
  752. $result->code = -$errno;
  753. $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
  754. // Mark that this request failed. This will trigger a check of the web
  755. // server's ability to make outgoing HTTP requests the next time that
  756. // requirements checking is performed.
  757. // See system_requirements()
  758. variable_set('drupal_http_request_fails', TRUE);
  759. return $result;
  760. }
  761. // Construct the path to act on.
  762. $path = isset($uri['path']) ? $uri['path'] : '/';
  763. if (isset($uri['query'])) {
  764. $path .= '?' . $uri['query'];
  765. }
  766. // Merge the default headers.
  767. $options['headers'] += array(
  768. 'User-Agent' => 'Drupal (+http://drupal.org/)',
  769. );
  770. // Only add Content-Length if we actually have any content or if it is a POST
  771. // or PUT request. Some non-standard servers get confused by Content-Length in
  772. // at least HEAD/GET requests, and Squid always requires Content-Length in
  773. // POST/PUT requests.
  774. $content_length = strlen($options['data']);
  775. if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
  776. $options['headers']['Content-Length'] = $content_length;
  777. }
  778. // If the server URL has a user then attempt to use basic authentication.
  779. if (isset($uri['user'])) {
  780. $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
  781. }
  782. // If the database prefix is being used by SimpleTest to run the tests in a copied
  783. // database then set the user-agent header to the database prefix so that any
  784. // calls to other Drupal pages will run the SimpleTest prefixed database. The
  785. // user-agent is used to ensure that multiple testing sessions running at the
  786. // same time won't interfere with each other as they would if the database
  787. // prefix were stored statically in a file or database variable.
  788. $test_info = &$GLOBALS['drupal_test_info'];
  789. if (!empty($test_info['test_run_id'])) {
  790. $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
  791. }
  792. $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
  793. foreach ($options['headers'] as $name => $value) {
  794. $request .= $name . ': ' . trim($value) . "\r\n";
  795. }
  796. $request .= "\r\n" . $options['data'];
  797. $result->request = $request;
  798. // Calculate how much time is left of the original timeout value.
  799. $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
  800. if ($timeout > 0) {
  801. stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
  802. fwrite($fp, $request);
  803. }
  804. // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
  805. // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
  806. // instead must invoke stream_get_meta_data() each iteration.
  807. $info = stream_get_meta_data($fp);
  808. $alive = !$info['eof'] && !$info['timed_out'];
  809. $response = '';
  810. while ($alive) {
  811. // Calculate how much time is left of the original timeout value.
  812. $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
  813. if ($timeout <= 0) {
  814. $info['timed_out'] = TRUE;
  815. break;
  816. }
  817. stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
  818. $chunk = fread($fp, 1024);
  819. $response .= $chunk;
  820. $info = stream_get_meta_data($fp);
  821. $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
  822. }
  823. fclose($fp);
  824. if ($info['timed_out']) {
  825. $result->code = HTTP_REQUEST_TIMEOUT;
  826. $result->error = 'request timed out';
  827. return $result;
  828. }
  829. // Parse response headers from the response body.
  830. // Be tolerant of malformed HTTP responses that separate header and body with
  831. // \n\n or \r\r instead of \r\n\r\n.
  832. list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
  833. $response = preg_split("/\r\n|\n|\r/", $response);
  834. // Parse the response status line.
  835. list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
  836. $result->protocol = $protocol;
  837. $result->status_message = $status_message;
  838. $result->headers = array();
  839. // Parse the response headers.
  840. while ($line = trim(array_shift($response))) {
  841. list($name, $value) = explode(':', $line, 2);
  842. $name = strtolower($name);
  843. if (isset($result->headers[$name]) && $name == 'set-cookie') {
  844. // RFC 2109: the Set-Cookie response header comprises the token Set-
  845. // Cookie:, followed by a comma-separated list of one or more cookies.
  846. $result->headers[$name] .= ',' . trim($value);
  847. }
  848. else {
  849. $result->headers[$name] = trim($value);
  850. }
  851. }
  852. $responses = array(
  853. 100 => 'Continue',
  854. 101 => 'Switching Protocols',
  855. 200 => 'OK',
  856. 201 => 'Created',
  857. 202 => 'Accepted',
  858. 203 => 'Non-Authoritative Information',
  859. 204 => 'No Content',
  860. 205 => 'Reset Content',
  861. 206 => 'Partial Content',
  862. 300 => 'Multiple Choices',
  863. 301 => 'Moved Permanently',
  864. 302 => 'Found',
  865. 303 => 'See Other',
  866. 304 => 'Not Modified',
  867. 305 => 'Use Proxy',
  868. 307 => 'Temporary Redirect',
  869. 400 => 'Bad Request',
  870. 401 => 'Unauthorized',
  871. 402 => 'Payment Required',
  872. 403 => 'Forbidden',
  873. 404 => 'Not Found',
  874. 405 => 'Method Not Allowed',
  875. 406 => 'Not Acceptable',
  876. 407 => 'Proxy Authentication Required',
  877. 408 => 'Request Time-out',
  878. 409 => 'Conflict',
  879. 410 => 'Gone',
  880. 411 => 'Length Required',
  881. 412 => 'Precondition Failed',
  882. 413 => 'Request Entity Too Large',
  883. 414 => 'Request-URI Too Large',
  884. 415 => 'Unsupported Media Type',
  885. 416 => 'Requested range not satisfiable',
  886. 417 => 'Expectation Failed',
  887. 500 => 'Internal Server Error',
  888. 501 => 'Not Implemented',
  889. 502 => 'Bad Gateway',
  890. 503 => 'Service Unavailable',
  891. 504 => 'Gateway Time-out',
  892. 505 => 'HTTP Version not supported',
  893. );
  894. // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  895. // base code in their class.
  896. if (!isset($responses[$code])) {
  897. $code = floor($code / 100) * 100;
  898. }
  899. $result->code = $code;
  900. switch ($code) {
  901. case 200: // OK
  902. case 304: // Not modified
  903. break;
  904. case 301: // Moved permanently
  905. case 302: // Moved temporarily
  906. case 307: // Moved temporarily
  907. $location = $result->headers['location'];
  908. $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
  909. if ($options['timeout'] <= 0) {
  910. $result->code = HTTP_REQUEST_TIMEOUT;
  911. $result->error = 'request timed out';
  912. }
  913. elseif ($options['max_redirects']) {
  914. // Redirect to the new location.
  915. $options['max_redirects']--;
  916. $result = drupal_http_request($location, $options);
  917. $result->redirect_code = $code;
  918. }
  919. if (!isset($result->redirect_url)) {
  920. $result->redirect_url = $location;
  921. }
  922. break;
  923. default:
  924. $result->error = $status_message;
  925. }
  926. return $result;
  927. }
  928. /**
  929. * @} End of "HTTP handling".
  930. */
  931. function _fix_gpc_magic(&$item) {
  932. if (is_array($item)) {
  933. array_walk($item, '_fix_gpc_magic');
  934. }
  935. else {
  936. $item = stripslashes($item);
  937. }
  938. }
  939. /**
  940. * Helper function to strip slashes from $_FILES skipping over the tmp_name keys
  941. * since PHP generates single backslashes for file paths on Windows systems.
  942. *
  943. * tmp_name does not have backslashes added see
  944. * http://php.net/manual/en/features.file-upload.php#42280
  945. */
  946. function _fix_gpc_magic_files(&$item, $key) {
  947. if ($key != 'tmp_name') {
  948. if (is_array($item)) {
  949. array_walk($item, '_fix_gpc_magic_files');
  950. }
  951. else {
  952. $item = stripslashes($item);
  953. }
  954. }
  955. }
  956. /**
  957. * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
  958. */
  959. function fix_gpc_magic() {
  960. static $fixed = FALSE;
  961. if (!$fixed && ini_get('magic_quotes_gpc')) {
  962. array_walk($_GET, '_fix_gpc_magic');
  963. array_walk($_POST, '_fix_gpc_magic');
  964. array_walk($_COOKIE, '_fix_gpc_magic');
  965. array_walk($_REQUEST, '_fix_gpc_magic');
  966. array_walk($_FILES, '_fix_gpc_magic_files');
  967. }
  968. $fixed = TRUE;
  969. }
  970. /**
  971. * @defgroup validation Input validation
  972. * @{
  973. * Functions to validate user input.
  974. */
  975. /**
  976. * Verify the syntax of the given e-mail address.
  977. *
  978. * Empty e-mail addresses are allowed. See RFC 2822 for details.
  979. *
  980. * @param $mail
  981. * A string containing an e-mail address.
  982. * @return
  983. * TRUE if the address is in a valid format.
  984. */
  985. function valid_email_address($mail) {
  986. return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
  987. }
  988. /**
  989. * Verify the syntax of the given URL.
  990. *
  991. * This function should only be used on actual URLs. It should not be used for
  992. * Drupal menu paths, which can contain arbitrary characters.
  993. * Valid values per RFC 3986.
  994. * @param $url
  995. * The URL to verify.
  996. * @param $absolute
  997. * Whether the URL is absolute (beginning with a scheme such as "http:").
  998. * @return
  999. * TRUE if the URL is in a valid format.
  1000. */
  1001. function valid_url($url, $absolute = FALSE) {
  1002. if ($absolute) {
  1003. return (bool)preg_match("
  1004. /^ # Start at the beginning of the text
  1005. (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes
  1006. (?: # Userinfo (optional) which is typically
  1007. (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
  1008. (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
  1009. )?
  1010. (?:
  1011. (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
  1012. |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
  1013. )
  1014. (?::[0-9]+)? # Server port number (optional)
  1015. (?:[\/|\?]
  1016. (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
  1017. *)?
  1018. $/xi", $url);
  1019. }
  1020. else {
  1021. return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
  1022. }
  1023. }
  1024. /**
  1025. * @} End of "defgroup validation".
  1026. */
  1027. /**
  1028. * Register an event for the current visitor to the flood control mechanism.
  1029. *
  1030. * @param $name
  1031. * The name of an event.
  1032. * @param $window
  1033. * Optional number of seconds before this event expires. Defaults to 3600 (1
  1034. * hour). Typically uses the same value as the flood_is_allowed() $window
  1035. * parameter. Expired events are purged on cron run to prevent the flood table
  1036. * from growing indefinitely.
  1037. * @param $identifier
  1038. * Optional identifier (defaults to the current user's IP address).
  1039. */
  1040. function flood_register_event($name, $window = 3600, $identifier = NULL) {
  1041. if (!isset($identifier)) {
  1042. $identifier = ip_address();
  1043. }
  1044. db_insert('flood')
  1045. ->fields(array(
  1046. 'event' => $name,
  1047. 'identifier' => $identifier,
  1048. 'timestamp' => REQUEST_TIME,
  1049. 'expiration' => REQUEST_TIME + $window,
  1050. ))
  1051. ->execute();
  1052. }
  1053. /**
  1054. * Make the flood control mechanism forget about an event for the current visitor.
  1055. *
  1056. * @param $name
  1057. * The name of an event.
  1058. * @param $identifier
  1059. * Optional identifier (defaults to the current user's IP address).
  1060. */
  1061. function flood_clear_event($name, $identifier = NULL) {
  1062. if (!isset($identifier)) {
  1063. $identifier = ip_address();
  1064. }
  1065. db_delete('flood')
  1066. ->condition('event', $name)
  1067. ->condition('identifier', $identifier)
  1068. ->execute();
  1069. }
  1070. /**
  1071. * Checks whether user is allowed to proceed with the specified event.
  1072. *
  1073. * Events can have thresholds saying that each user can only do that event
  1074. * a certain number of times in a time window. This function verifies that the
  1075. * current user has not exceeded this threshold.
  1076. *
  1077. * @param $name
  1078. * The unique name of the event.
  1079. * @param $threshold
  1080. * The maximum number of times each user can do this event per time window.
  1081. * @param $window
  1082. * Number of seconds in the time window for this event (default is 3600
  1083. * seconds, or 1 hour).
  1084. * @param $identifier
  1085. * Unique identifier of the current user. Defaults to their IP address.
  1086. *
  1087. * @return
  1088. * TRUE if the user is allowed to proceed. FALSE if they have exceeded the
  1089. * threshold and should not be allowed to proceed.
  1090. */
  1091. function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) {
  1092. if (!isset($identifier)) {
  1093. $identifier = ip_address();
  1094. }
  1095. $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array(
  1096. ':event' => $name,
  1097. ':identifier' => $identifier,
  1098. ':timestamp' => REQUEST_TIME - $window))
  1099. ->fetchField();
  1100. return ($number < $threshold);
  1101. }
  1102. /**
  1103. * @defgroup sanitization Sanitization functions
  1104. * @{
  1105. * Functions to sanitize values.
  1106. *
  1107. * See http://drupal.org/writing-secure-code for information
  1108. * on writing secure code.
  1109. */
  1110. /**
  1111. * Strips dangerous protocols (e.g. 'javascript:') from a URI.
  1112. *
  1113. * This function must be called for all URIs within user-entered input prior
  1114. * to being output to an HTML attribute value. It is often called as part of
  1115. * check_url() or filter_xss(), but those functions return an HTML-encoded
  1116. * string, so this function can be called independently when the output needs to
  1117. * be a plain-text string for passing to t(), l(), drupal_attributes(), or
  1118. * another function that will call check_plain() separately.
  1119. *
  1120. * @param $uri
  1121. * A plain-text URI that might contain dangerous protocols.
  1122. *
  1123. * @return
  1124. * A plain-text URI stripped of dangerous protocols. As with all plain-text
  1125. * strings, this return value must not be output to an HTML page without
  1126. * check_plain() being called on it. However, it can be passed to functions
  1127. * expecting plain-text strings.
  1128. *
  1129. * @see check_url()
  1130. */
  1131. function drupal_strip_dangerous_protocols($uri) {
  1132. static $allowed_protocols;
  1133. if (!isset($allowed_protocols)) {
  1134. $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal')));
  1135. }
  1136. // Iteratively remove any invalid protocol found.
  1137. do {
  1138. $before = $uri;
  1139. $colonpos = strpos($uri, ':');
  1140. if ($colonpos > 0) {
  1141. // We found a colon, possibly a protocol. Verify.
  1142. $protocol = substr($uri, 0, $colonpos);
  1143. // If a colon is preceded by a slash, question mark or hash, it cannot
  1144. // possibly be part of the URL scheme. This must be a relative URL, which
  1145. // inherits the (safe) protocol of the base document.
  1146. if (preg_match('![/?#]!', $protocol)) {
  1147. break;
  1148. }
  1149. // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
  1150. // (URI Comparison) scheme comparison must be case-insensitive.
  1151. if (!isset($allowed_protocols[strtolower($protocol)])) {
  1152. $uri = substr($uri, $colonpos + 1);
  1153. }
  1154. }
  1155. } while ($before != $uri);
  1156. return $uri;
  1157. }
  1158. /**
  1159. * Strips dangerous protocols (e.g. 'javascript:') from a URI and encodes it for output to an HTML attribute value.
  1160. *
  1161. * @param $uri
  1162. * A plain-text URI that might contain dangerous protocols.
  1163. *
  1164. * @return
  1165. * A URI stripped of dangerous protocols and encoded for output to an HTML
  1166. * attribute value. Because it is already encoded, it should not be set as a
  1167. * value within a $attributes array passed to drupal_attributes(), because
  1168. * drupal_attributes() expects those values to be plain-text strings. To pass
  1169. * a filtered URI to drupal_attributes(), call
  1170. * drupal_strip_dangerous_protocols() instead.
  1171. *
  1172. * @see drupal_strip_dangerous_protocols()
  1173. */
  1174. function check_url($uri) {
  1175. return check_plain(drupal_strip_dangerous_protocols($uri));
  1176. }
  1177. /**
  1178. * Very permissive XSS/HTML filter for admin-only use.
  1179. *
  1180. * Use only for fields where it is impractical to use the
  1181. * whole filter system, but where some (mainly inline) mark-up
  1182. * is desired (so check_plain() is not acceptable).
  1183. *
  1184. * Allows all tags that can be used inside an HTML body, save
  1185. * for scripts and styles.
  1186. */
  1187. function filter_xss_admin($string) {
  1188. 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'));
  1189. }
  1190. /**
  1191. * Filters an HTML string to prevent cross-site-scripting (XSS) vulnerabilities.
  1192. *
  1193. * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
  1194. * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
  1195. *
  1196. * This code does four things:
  1197. * - Removes characters and constructs that can trick browsers.
  1198. * - Makes sure all HTML entities are well-formed.
  1199. * - Makes sure all HTML tags and attributes are well-formed.
  1200. * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
  1201. * javascript:).
  1202. *
  1203. * @param $string
  1204. * The string with raw HTML in it. It will be stripped of everything that can
  1205. * cause an XSS attack.
  1206. * @param $allowed_tags
  1207. * An array of allowed tags.
  1208. *
  1209. * @return
  1210. * An XSS safe version of $string, or an empty string if $string is not
  1211. * valid UTF-8.
  1212. *
  1213. * @see drupal_validate_utf8()
  1214. * @ingroup sanitization
  1215. */
  1216. function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
  1217. // Only operate on valid UTF-8 strings. This is necessary to prevent cross
  1218. // site scripting issues on Internet Explorer 6.
  1219. if (!drupal_validate_utf8($string)) {
  1220. return '';
  1221. }
  1222. // Store the text format
  1223. _filter_xss_split($allowed_tags, TRUE);
  1224. // Remove NULL characters (ignored by some browsers)
  1225. $string = str_replace(chr(0), '', $string);
  1226. // Remove Netscape 4 JS entities
  1227. $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
  1228. // Defuse all HTML entities
  1229. $string = str_replace('&', '&amp;', $string);
  1230. // Change back only well-formed entities in our whitelist
  1231. // Decimal numeric entities
  1232. $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
  1233. // Hexadecimal numeric entities
  1234. $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
  1235. // Named entities
  1236. $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
  1237. return preg_replace_callback('%
  1238. (
  1239. <(?=[^a-zA-Z!/]) # a lone <
  1240. | # or
  1241. <!--.*?--> # a comment
  1242. | # or
  1243. <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
  1244. | # or
  1245. > # just a >
  1246. )%x', '_filter_xss_split', $string);
  1247. }
  1248. /**
  1249. * Processes an HTML tag.
  1250. *
  1251. * @param $m
  1252. * An array with various meaning depending on the value of $store.
  1253. * If $store is TRUE then the array contains the allowed tags.
  1254. * If $store is FALSE then the array has one element, the HTML tag to process.
  1255. * @param $store
  1256. * Whether to store $m.
  1257. * @return
  1258. * If the element isn't allowed, an empty string. Otherwise, the cleaned up
  1259. * version of the HTML element.
  1260. */
  1261. function _filter_xss_split($m, $store = FALSE) {
  1262. static $allowed_html;
  1263. if ($store) {
  1264. $allowed_html = array_flip($m);
  1265. return;
  1266. }
  1267. $string = $m[1];
  1268. if (substr($string, 0, 1) != '<') {
  1269. // We matched a lone ">" character
  1270. return '&gt;';
  1271. }
  1272. elseif (strlen($string) == 1) {
  1273. // We matched a lone "<" character
  1274. return '&lt;';
  1275. }
  1276. if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
  1277. // Seriously malformed
  1278. return '';
  1279. }
  1280. $slash = trim($matches[1]);
  1281. $elem = &$matches[2];
  1282. $attrlist = &$matches[3];
  1283. $comment = &$matches[4];
  1284. if ($comment) {
  1285. $elem = '!--';
  1286. }
  1287. if (!isset($allowed_html[strtolower($elem)])) {
  1288. // Disallowed HTML element
  1289. return '';
  1290. }
  1291. if ($comment) {
  1292. return $comment;
  1293. }
  1294. if ($slash != '') {
  1295. return "</$elem>";
  1296. }
  1297. // Is there a closing XHTML slash at the end of the attributes?
  1298. $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
  1299. $xhtml_slash = $count ? ' /' : '';
  1300. // Clean up attributes
  1301. $attr2 = implode(' ', _filter_xss_attributes($attrlist));
  1302. $attr2 = preg_replace('/[<>]/', '', $attr2);
  1303. $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
  1304. return "<$elem$attr2$xhtml_slash>";
  1305. }
  1306. /**
  1307. * Processes a string of HTML attributes.
  1308. *
  1309. * @return
  1310. * Cleaned up version of the HTML attributes.
  1311. */
  1312. function _filter_xss_attributes($attr) {
  1313. $attrarr = array();
  1314. $mode = 0;
  1315. $attrname = '';
  1316. while (strlen($attr) != 0) {
  1317. // Was the last operation successful?
  1318. $working = 0;
  1319. switch ($mode) {
  1320. case 0:
  1321. // Attribute name, href for instance
  1322. if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
  1323. $attrname = strtolower($match[1]);
  1324. $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
  1325. $working = $mode = 1;
  1326. $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
  1327. }
  1328. break;
  1329. case 1:
  1330. // Equals sign or valueless ("selected")
  1331. if (preg_match('/^\s*=\s*/', $attr)) {
  1332. $working = 1; $mode = 2;
  1333. $attr = preg_replace('/^\s*=\s*/', '', $attr);
  1334. break;
  1335. }
  1336. if (preg_match('/^\s+/', $attr)) {
  1337. $working = 1; $mode = 0;
  1338. if (!$skip) {
  1339. $attrarr[] = $attrname;
  1340. }
  1341. $attr = preg_replace('/^\s+/', '', $attr);
  1342. }
  1343. break;
  1344. case 2:
  1345. // Attribute value, a URL after href= for instance
  1346. if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
  1347. $thisval = filter_xss_bad_protocol($match[1]);
  1348. if (!$skip) {
  1349. $attrarr[] = "$attrname=\"$thisval\"";
  1350. }
  1351. $working = 1;
  1352. $mode = 0;
  1353. $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
  1354. break;
  1355. }
  1356. if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
  1357. $thisval = filter_xss_bad_protocol($match[1]);
  1358. if (!$skip) {
  1359. $attrarr[] = "$attrname='$thisval'";
  1360. }
  1361. $working = 1; $mode = 0;
  1362. $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
  1363. break;
  1364. }
  1365. if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
  1366. $thisval = filter_xss_bad_protocol($match[1]);
  1367. if (!$skip) {
  1368. $attrarr[] = "$attrname=\"$thisval\"";
  1369. }
  1370. $working = 1; $mode = 0;
  1371. $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
  1372. }
  1373. break;
  1374. }
  1375. if ($working == 0) {
  1376. // not well formed, remove and try again
  1377. $attr = preg_replace('/
  1378. ^
  1379. (
  1380. "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
  1381. | # or
  1382. \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
  1383. | # or
  1384. \S # - a non-whitespace character
  1385. )* # any number of the above three
  1386. \s* # any number of whitespaces
  1387. /x', '', $attr);
  1388. $mode = 0;
  1389. }
  1390. }
  1391. // The attribute list ends with a valueless attribute like "selected".
  1392. if ($mode == 1 && !$skip) {
  1393. $attrarr[] = $attrname;
  1394. }
  1395. return $attrarr;
  1396. }
  1397. /**
  1398. * Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:).
  1399. *
  1400. * @param $string
  1401. * The string with the attribute value.
  1402. * @param $decode
  1403. * (Deprecated) Whether to decode entities in the $string. Set to FALSE if the
  1404. * $string is in plain text, TRUE otherwise. Defaults to TRUE. This parameter
  1405. * is deprecated and will be removed in Drupal 8. To process a plain-text URI,
  1406. * call drupal_strip_dangerous_protocols() or check_url() instead.
  1407. * @return
  1408. * Cleaned up and HTML-escaped version of $string.
  1409. */
  1410. function filter_xss_bad_protocol($string, $decode = TRUE) {
  1411. // Get the plain text representation of the attribute value (i.e. its meaning).
  1412. // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML
  1413. // string that needs decoding.
  1414. if ($decode) {
  1415. if (!function_exists('decode_entities')) {
  1416. require_once DRUPAL_ROOT . '/includes/unicode.inc';
  1417. }
  1418. $string = decode_entities($string);
  1419. }
  1420. return check_plain(drupal_strip_dangerous_protocols($string));
  1421. }
  1422. /**
  1423. * @} End of "defgroup sanitization".
  1424. */
  1425. /**
  1426. * @defgroup format Formatting
  1427. * @{
  1428. * Functions to format numbers, strings, dates, etc.
  1429. */
  1430. /**
  1431. * Formats an RSS channel.
  1432. *
  1433. * Arbitrary elements may be added using the $args associative array.
  1434. */
  1435. function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) {
  1436. global $language_content;
  1437. $langcode = $langcode ? $langcode : $language_content->language;
  1438. $output = "<channel>\n";
  1439. $output .= ' <title>' . check_plain($title) . "</title>\n";
  1440. $output .= ' <link>' . check_url($link) . "</link>\n";
  1441. // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description.
  1442. // We strip all HTML tags, but need to prevent double encoding from properly
  1443. // escaped source data (such as &amp becoming &amp;amp;).
  1444. $output .= ' <description>' . check_plain(decode_entities(strip_tags($description))) . "</description>\n";
  1445. $output .= ' <language>' . check_plain($langcode) . "</language>\n";
  1446. $output .= format_xml_elements($args);
  1447. $output .= $items;
  1448. $output .= "</channel>\n";
  1449. return $output;
  1450. }
  1451. /**
  1452. * Format a single RSS item.
  1453. *
  1454. * Arbitrary elements may be added using the $args associative array.
  1455. */
  1456. function format_rss_item($title, $link, $description, $args = array()) {
  1457. $output = "<item>\n";
  1458. $output .= ' <title>' . check_plain($title) . "</title>\n";
  1459. $output .= ' <link>' . check_url($link) . "</link>\n";
  1460. $output .= ' <description>' . check_plain($description) . "</description>\n";
  1461. $output .= format_xml_elements($args);
  1462. $output .= "</item>\n";
  1463. return $output;
  1464. }
  1465. /**
  1466. * Format XML elements.
  1467. *
  1468. * @param $array
  1469. * An array where each item represents an element and is either a:
  1470. * - (key => value) pair (<key>value</key>)
  1471. * - Associative array with fields:
  1472. * - 'key': element name
  1473. * - 'value': element contents
  1474. * - 'attributes': associative array of element attributes
  1475. *
  1476. * In both cases, 'value' can be a simple string, or it can be another array
  1477. * with the same format as $array itself for nesting.
  1478. */
  1479. function format_xml_elements($array) {
  1480. $output = '';
  1481. foreach ($array as $key => $value) {
  1482. if (is_numeric($key)) {
  1483. if ($value['key']) {
  1484. $output .= ' <' . $value['key'];
  1485. if (isset($value['attributes']) && is_array($value['attributes'])) {
  1486. $output .= drupal_attributes($value['attributes']);
  1487. }
  1488. if (isset($value['value']) && $value['value'] != '') {
  1489. $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '</' . $value['key'] . ">\n";
  1490. }
  1491. else {
  1492. $output .= " />\n";
  1493. }
  1494. }
  1495. }
  1496. else {
  1497. $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : check_plain($value)) . "</$key>\n";
  1498. }
  1499. }
  1500. return $output;
  1501. }
  1502. /**
  1503. * Format a string containing a count of items.
  1504. *
  1505. * This function ensures that the string is pluralized correctly. Since t() is
  1506. * called by this function, make sure not to pass already-localized strings to
  1507. * it.
  1508. *
  1509. * For example:
  1510. * @code
  1511. * $output = format_plural($node->comment_count, '1 comment', '@count comments');
  1512. * @endcode
  1513. *
  1514. * Example with additional replacements:
  1515. * @code
  1516. * $output = format_plural($update_count,
  1517. * 'Changed the content type of 1 post from %old-type to %new-type.',
  1518. * 'Changed the content type of @count posts from %old-type to %new-type.',
  1519. * array('%old-type' => $info->old_type, '%new-type' => $info->new_type)));
  1520. * @endcode
  1521. *
  1522. * @param $count
  1523. * The item count to display.
  1524. * @param $singular
  1525. * The string for the singular case. Please make sure it is clear this is
  1526. * singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
  1527. * Do not use @count in the singular string.
  1528. * @param $plural
  1529. * The string for the plural case. Please make sure it is clear this is plural,
  1530. * to ease translation. Use @count in place of the item count, as in "@count
  1531. * new comments".
  1532. * @param $args
  1533. * An associative array of replacements to make after translation. Incidences
  1534. * of any key in this array are replaced with the corresponding value.
  1535. * Based on the first character of the key, the value is escaped and/or themed:
  1536. * - !variable: inserted as is
  1537. * - @variable: escape plain text to HTML (check_plain)
  1538. * - %variable: escape text and theme as a placeholder for user-submitted
  1539. * content (check_plain + drupal_placeholder)
  1540. * Note that you do not need to include @count in this array.
  1541. * This replacement is done automatically for the plural case.
  1542. * @param $options
  1543. * An associative array of additional options, with the following keys:
  1544. * - 'langcode' (default to the current language) The language code to
  1545. * translate to a language other than what is used to display the page.
  1546. * - 'context' (default to the empty context) The context the source string
  1547. * belongs to.
  1548. * @return
  1549. * A translated string.
  1550. */
  1551. function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
  1552. $args['@count'] = $count;
  1553. if ($count == 1) {
  1554. return t($singular, $args, $options);
  1555. }
  1556. // Get the plural index through the gettext formula.
  1557. $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1;
  1558. // Backwards compatibility.
  1559. if ($index < 0) {
  1560. return t($plural, $args, $options);
  1561. }
  1562. else {
  1563. switch ($index) {
  1564. case "0":
  1565. return t($singular, $args, $options);
  1566. case "1":
  1567. return t($plural, $args, $options);
  1568. default:
  1569. unset($args['@count']);
  1570. $args['@count[' . $index . ']'] = $count;
  1571. return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options);
  1572. }
  1573. }
  1574. }
  1575. /**
  1576. * Parse a given byte count.
  1577. *
  1578. * @param $size
  1579. * A size expressed as a number of bytes with optional SI or IEC binary unit
  1580. * prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
  1581. * @return
  1582. * An integer representation of the size in bytes.
  1583. */
  1584. function parse_size($size) {
  1585. $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size.
  1586. $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size.
  1587. if ($unit) {
  1588. // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by.
  1589. return round($size * pow(DRUPAL_KILOBYTE, stripos('bkmgtpezy', $unit[0])));
  1590. }
  1591. else {
  1592. return round($size);
  1593. }
  1594. }
  1595. /**
  1596. * Generate a string representation for the given byte count.
  1597. *
  1598. * @param $size
  1599. * A size in bytes.
  1600. * @param $langcode
  1601. * Optional language code to translate to a language other than what is used
  1602. * to display the page.
  1603. * @return
  1604. * A translated string representation of the size.
  1605. */
  1606. function format_size($size, $langcode = NULL) {
  1607. if ($size < DRUPAL_KILOBYTE) {
  1608. return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode));
  1609. }
  1610. else {
  1611. $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes.
  1612. $units = array(
  1613. t('@size KB', array(), array('langcode' => $langcode)),
  1614. t('@size MB', array(), array('langcode' => $langcode)),
  1615. t('@size GB', array(), array('langcode' => $langcode)),
  1616. t('@size TB', array(), array('langcode' => $langcode)),
  1617. t('@size PB', array(), array('langcode' => $langcode)),
  1618. t('@size EB', array(), array('langcode' => $langcode)),
  1619. t('@size ZB', array(), array('langcode' => $langcode)),
  1620. t('@size YB', array(), array('langcode' => $langcode)),
  1621. );
  1622. foreach ($units as $unit) {
  1623. if (round($size, 2) >= DRUPAL_KILOBYTE) {
  1624. $size = $size / DRUPAL_KILOBYTE;
  1625. }
  1626. else {
  1627. break;
  1628. }
  1629. }
  1630. return str_replace('@size', round($size, 2), $unit);
  1631. }
  1632. }
  1633. /**
  1634. * Format a time interval with the requested granularity.
  1635. *
  1636. * @param $timestamp
  1637. * The length of the interval in seconds.
  1638. * @param $granularity
  1639. * How many different units to display in the string.
  1640. * @param $langcode
  1641. * Optional language code to translate to a language other than
  1642. * what is used to display the page.
  1643. * @return
  1644. * A translated string representation of the interval.
  1645. */
  1646. function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
  1647. $units = array(
  1648. '1 year|@count years' => 31536000,
  1649. '1 month|@count months' => 2592000,
  1650. '1 week|@count weeks' => 604800,
  1651. '1 day|@count days' => 86400,
  1652. '1 hour|@count hours' => 3600,
  1653. '1 min|@count min' => 60,
  1654. '1 sec|@count sec' => 1
  1655. );
  1656. $output = '';
  1657. foreach ($units as $key => $value) {
  1658. $key = explode('|', $key);
  1659. if ($timestamp >= $value) {
  1660. $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
  1661. $timestamp %= $value;
  1662. $granularity--;
  1663. }
  1664. if ($granularity == 0) {
  1665. break;
  1666. }
  1667. }
  1668. return $output ? $output : t('0 sec', array(), array('langcode' => $langcode));
  1669. }
  1670. /**
  1671. * Formats a date, using a date type or a custom date format string.
  1672. *
  1673. * @param $timestamp
  1674. * A UNIX timestamp to format.
  1675. * @param $type
  1676. * (optional) The format to use, one of:
  1677. * - 'short', 'medium', or 'long' (the corresponding built-in date formats).
  1678. * - The name of a date type defined by a module in hook_date_format_types(),
  1679. * if it's been assigned a format.
  1680. * - The machine name of an administrator-defined date format.
  1681. * - 'custom', to use $format.
  1682. * Defaults to 'medium'.
  1683. * @param $format
  1684. * (optional) If $type is 'custom', a PHP date format string suitable for
  1685. * input to date(). Use a backslash to escape ordinary text, so it does not
  1686. * get interpreted as date format characters.
  1687. * @param $timezone
  1688. * (optional) Time zone identifier, as described at
  1689. * http://php.net/manual/en/timezones.php Defaults to the time zone used to
  1690. * display the page.
  1691. * @param $langcode
  1692. * (optional) Language code to translate to. Defaults to the language used to
  1693. * display the page.
  1694. *
  1695. * @return
  1696. * A translated date string in the requested format.
  1697. */
  1698. function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) {
  1699. // Use the advanced drupal_static() pattern, since this is called very often.
  1700. static $drupal_static_fast;
  1701. if (!isset($drupal_static_fast)) {
  1702. $drupal_static_fast['timezones'] = &drupal_static(__FUNCTION__);
  1703. }
  1704. $timezones = &$drupal_static_fast['timezones'];
  1705. if (!isset($timezone)) {
  1706. $timezone = date_default_timezone_get();
  1707. }
  1708. // Store DateTimeZone objects in an array rather than repeatedly
  1709. // constructing identical objects over the life of a request.
  1710. if (!isset($timezones[$timezone])) {
  1711. $timezones[$timezone] = timezone_open($timezone);
  1712. }
  1713. // Use the default langcode if none is set.
  1714. global $language;
  1715. if (empty($langcode)) {
  1716. $langcode = isset($language->language) ? $language->language : 'en';
  1717. }
  1718. switch ($type) {
  1719. case 'short':
  1720. $format = variable_get('date_format_short', 'm/d/Y - H:i');
  1721. break;
  1722. case 'long':
  1723. $format = variable_get('date_format_long', 'l, F j, Y - H:i');
  1724. break;
  1725. case 'custom':
  1726. // No change to format.
  1727. break;
  1728. case 'medium':
  1729. default:
  1730. // Retrieve the format of the custom $type passed.
  1731. if ($type != 'medium') {
  1732. $format = variable_get('date_format_' . $type, '');
  1733. }
  1734. // Fall back to 'medium'.
  1735. if ($format === '') {
  1736. $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
  1737. }
  1738. break;
  1739. }
  1740. // Create a DateTime object from the timestamp.
  1741. $date_time = date_create('@' . $timestamp);
  1742. // Set the time zone for the DateTime object.
  1743. date_timezone_set($date_time, $timezones[$timezone]);
  1744. // Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'.
  1745. // xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the
  1746. // input string.
  1747. // Paired backslashes are isolated to prevent errors in read-ahead evaluation.
  1748. // The read-ahead expression ensures that A matches, but not \A.
  1749. $format = preg_replace(array('/\\\\\\\\/', '/(?<!\\\\)([AaeDlMTF])/'), array("\xEF\\\\\\\\\xFF", "\xEF\\\\\$1\$1\xFF"), $format);
  1750. // Call date_format().
  1751. $format = date_format($date_time, $format);
  1752. // Pass the langcode to _format_date_callback().
  1753. _format_date_callback(NULL, $langcode);
  1754. // Translate the marked sequences.
  1755. return preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', '_format_date_callback', $format);
  1756. }
  1757. /**
  1758. * Returns an ISO8601 formatted date based on the given date.
  1759. *
  1760. * Can be used as a callback for RDF mappings.
  1761. *
  1762. * @param $date
  1763. * A UNIX timestamp.
  1764. * @return string
  1765. * An ISO8601 formatted date.
  1766. */
  1767. function date_iso8601($date) {
  1768. // The DATE_ISO8601 constant cannot be used here because it does not match
  1769. // date('c') and produces invalid RDF markup.
  1770. return date('c', $date);
  1771. }
  1772. /**
  1773. * Callback function for preg_replace_callback().
  1774. */
  1775. function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
  1776. // We cache translations to avoid redundant and rather costly calls to t().
  1777. static $cache, $langcode;
  1778. if (!isset($matches)) {
  1779. $langcode = $new_langcode;
  1780. return;
  1781. }
  1782. $code = $matches[1];
  1783. $string = $matches[2];
  1784. if (!isset($cache[$langcode][$code][$string])) {
  1785. $options = array(
  1786. 'langcode' => $langcode,
  1787. );
  1788. if ($code == 'F') {
  1789. $options['context'] = 'Long month name';
  1790. }
  1791. if ($code == '') {
  1792. $cache[$langcode][$code][$string] = $string;
  1793. }
  1794. else {
  1795. $cache[$langcode][$code][$string] = t($string, array(), $options);
  1796. }
  1797. }
  1798. return $cache[$langcode][$code][$string];
  1799. }
  1800. /**
  1801. * Format a username.
  1802. *
  1803. * By default, the passed-in object's 'name' property is used if it exists, or
  1804. * else, the site-defined value for the 'anonymous' variable. However, a module
  1805. * may override this by implementing hook_username_alter(&$name, $account).
  1806. *
  1807. * @see hook_username_alter()
  1808. *
  1809. * @param $account
  1810. * The account object for the user whose name is to be formatted.
  1811. *
  1812. * @return
  1813. * An unsanitized string with the username to display. The code receiving
  1814. * this result must ensure that check_plain() is called on it before it is
  1815. * printed to the page.
  1816. */
  1817. function format_username($account) {
  1818. $name = !empty($account->name) ? $account->name : variable_get('anonymous', t('Anonymous'));
  1819. drupal_alter('username', $name, $account);
  1820. return $name;
  1821. }
  1822. /**
  1823. * @} End of "defgroup format".
  1824. */
  1825. /**
  1826. * Generates an internal or external URL.
  1827. *
  1828. * When creating links in modules, consider whether l() could be a better
  1829. * alternative than url().
  1830. *
  1831. * @param $path
  1832. * The internal path or external URL being linked to, such as "node/34" or
  1833. * "http://example.com/foo". A few notes:
  1834. * - If you provide a full URL, it will be considered an external URL.
  1835. * - If you provide only the path (e.g. "node/34"), it will be
  1836. * considered an internal link. In this case, it should be a system URL,
  1837. * and it will be replaced with the alias, if one exists. Additional query
  1838. * arguments for internal paths must be supplied in $options['query'], not
  1839. * included in $path.
  1840. * - If you provide an internal path and $options['alias'] is set to TRUE, the
  1841. * path is assumed already to be the correct path alias, and the alias is
  1842. * not looked up.
  1843. * - The special string '<front>' generates a link to the site's base URL.
  1844. * - If your external URL contains a query (e.g. http://example.com/foo?a=b),
  1845. * then you can either URL encode the query keys and values yourself and
  1846. * include them in $path, or use $options['query'] to let this function
  1847. * URL encode them.
  1848. * @param $options
  1849. * An associative array of additional options, with the following elements:
  1850. * - 'query': An array of query key/value-pairs (without any URL-encoding) to
  1851. * append to the URL.
  1852. * - 'fragment': A fragment identifier (named anchor) to append to the URL.
  1853. * Do not include the leading '#' character.
  1854. * - 'absolute': Defaults to FALSE. Whether to force the output to be an
  1855. * absolute link (beginning with http:). Useful for links that will be
  1856. * displayed outside the site, such as in an RSS feed.
  1857. * - 'alias': Defaults to FALSE. Whether the given path is a URL alias
  1858. * already.
  1859. * - 'external': Whether the given path is an external URL.
  1860. * - 'language': An optional language object. If the path being linked to is
  1861. * internal to the site, $options['language'] is used to look up the alias
  1862. * for the URL. If $options['language'] is omitted, the global $language_url
  1863. * will be used.
  1864. * - 'https': Whether this URL should point to a secure location. If not
  1865. * defined, the current scheme is used, so the user stays on http or https
  1866. * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can
  1867. * only be enforced when the variable 'https' is set to TRUE.
  1868. * - 'base_url': Only used internally, to modify the base URL when a language
  1869. * dependent URL requires so.
  1870. * - 'prefix': Only used internally, to modify the path when a language
  1871. * dependent URL requires so.
  1872. * - 'script': The script filename in Drupal's root directory to use when
  1873. * clean URLs are disabled, such as 'index.php'. Defaults to an empty
  1874. * string, as most modern web servers automatically find 'index.php'. If
  1875. * clean URLs are disabled, the value of $path is appended as query
  1876. * parameter 'q' to $options['script'] in the returned URL. When deploying
  1877. * Drupal on a web server that cannot be configured to automatically find
  1878. * index.php, then hook_url_outbound_alter() can be implemented to force
  1879. * this value to 'index.php'.
  1880. * - 'entity_type': The entity type of the object that called url(). Only set if
  1881. * url() is invoked by entity_uri().
  1882. * - 'entity': The entity object (such as a node) for which the URL is being
  1883. * generated. Only set if url() is invoked by entity_uri().
  1884. *
  1885. * @return
  1886. * A string containing a URL to the given path.
  1887. */
  1888. function url($path = NULL, array $options = array()) {
  1889. // Merge in defaults.
  1890. $options += array(
  1891. 'fragment' => '',
  1892. 'query' => array(),
  1893. 'absolute' => FALSE,
  1894. 'alias' => FALSE,
  1895. 'prefix' => ''
  1896. );
  1897. if (!isset($options['external'])) {
  1898. // Return an external link if $path contains an allowed absolute URL. Only
  1899. // call the slow drupal_strip_dangerous_protocols() if $path contains a ':'
  1900. // before any / ? or #. Note: we could use url_is_external($path) here, but
  1901. // that would require another function call, and performance inside url() is
  1902. // critical.
  1903. $colonpos = strpos($path, ':');
  1904. $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path);
  1905. }
  1906. // Preserve the original path before altering or aliasing.
  1907. $original_path = $path;
  1908. // Allow other modules to alter the outbound URL and options.
  1909. drupal_alter('url_outbound', $path, $options, $original_path);
  1910. if (isset($options['fragment']) && $options['fragment'] !== '') {
  1911. $options['fragment'] = '#' . $options['fragment'];
  1912. }
  1913. if ($options['external']) {
  1914. // Split off the fragment.
  1915. if (strpos($path, '#') !== FALSE) {
  1916. list($path, $old_fragment) = explode('#', $path, 2);
  1917. // If $options contains no fragment, take it over from the path.
  1918. if (isset($old_fragment) && !$options['fragment']) {
  1919. $options['fragment'] = '#' . $old_fragment;
  1920. }
  1921. }
  1922. // Append the query.
  1923. if ($options['query']) {
  1924. $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($options['query']);
  1925. }
  1926. if (isset($options['https']) && variable_get('https', FALSE)) {
  1927. if ($options['https'] === TRUE) {
  1928. $path = str_replace('http://', 'https://', $path);
  1929. }
  1930. elseif ($options['https'] === FALSE) {
  1931. $path = str_replace('https://', 'http://', $path);
  1932. }
  1933. }
  1934. // Reassemble.
  1935. return $path . $options['fragment'];
  1936. }
  1937. global $base_url, $base_secure_url, $base_insecure_url;
  1938. // The base_url might be rewritten from the language rewrite in domain mode.
  1939. if (!isset($options['base_url'])) {
  1940. if (isset($options['https']) && variable_get('https', FALSE)) {
  1941. if ($options['https'] === TRUE) {
  1942. $options['base_url'] = $base_secure_url;
  1943. $options['absolute'] = TRUE;
  1944. }
  1945. elseif ($options['https'] === FALSE) {
  1946. $options['base_url'] = $base_insecure_url;
  1947. $options['absolute'] = TRUE;
  1948. }
  1949. }
  1950. else {
  1951. $options['base_url'] = $base_url;
  1952. }
  1953. }
  1954. // The special path '<front>' links to the default front page.
  1955. if ($path == '<front>') {
  1956. $path = '';
  1957. }
  1958. elseif (!empty($path) && !$options['alias']) {
  1959. $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
  1960. $alias = drupal_get_path_alias($original_path, $language);
  1961. if ($alias != $original_path) {
  1962. $path = $alias;
  1963. }
  1964. }
  1965. $base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
  1966. $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix'];
  1967. // With Clean URLs.
  1968. if (!empty($GLOBALS['conf']['clean_url'])) {
  1969. $path = drupal_encode_path($prefix . $path);
  1970. if ($options['query']) {
  1971. return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment'];
  1972. }
  1973. else {
  1974. return $base . $path . $options['fragment'];
  1975. }
  1976. }
  1977. // Without Clean URLs.
  1978. else {
  1979. $path = $prefix . $path;
  1980. $query = array();
  1981. if (!empty($path)) {
  1982. $query['q'] = $path;
  1983. }
  1984. if ($options['query']) {
  1985. // We do not use array_merge() here to prevent overriding $path via query
  1986. // parameters.
  1987. $query += $options['query'];
  1988. }
  1989. $query = $query ? ('?' . drupal_http_build_query($query)) : '';
  1990. $script = isset($options['script']) ? $options['script'] : '';
  1991. return $base . $script . $query . $options['fragment'];
  1992. }
  1993. }
  1994. /**
  1995. * Return TRUE if a path is external to Drupal (e.g. http://example.com).
  1996. *
  1997. * If a path cannot be assessed by Drupal's menu handler, then we must
  1998. * treat it as potentially insecure.
  1999. *
  2000. * @param $path
  2001. * The internal path or external URL being linked to, such as "node/34" or
  2002. * "http://example.com/foo".
  2003. * @return
  2004. * Boolean TRUE or FALSE, where TRUE indicates an external path.
  2005. */
  2006. function url_is_external($path) {
  2007. $colonpos = strpos($path, ':');
  2008. // Only call the slow drupal_strip_dangerous_protocols() if $path contains a
  2009. // ':' before any / ? or #.
  2010. return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path;
  2011. }
  2012. /**
  2013. * Format an attribute string for a HTTP header.
  2014. *
  2015. * @param $attributes
  2016. * An associative array of attributes such as 'rel'.
  2017. *
  2018. * @return
  2019. * A ; separated string ready for insertion in a HTTP header. No escaping is
  2020. * performed for HTML entities, so this string is not safe to be printed.
  2021. *
  2022. * @see drupal_add_http_header()
  2023. */
  2024. function drupal_http_header_attributes(array $attributes = array()) {
  2025. foreach ($attributes as $attribute => &$data) {
  2026. if (is_array($data)) {
  2027. $data = implode(' ', $data);
  2028. }
  2029. $data = $attribute . '="' . $data . '"';
  2030. }
  2031. return $attributes ? ' ' . implode('; ', $attributes) : '';
  2032. }
  2033. /**
  2034. * Converts an associative array to an attribute string for use in XML/HTML tags.
  2035. *
  2036. * Each array key and its value will be formatted into an attribute string.
  2037. * If a value is itself an array, then its elements are concatenated to a single
  2038. * space-delimited string (for example, a class attribute with multiple values).
  2039. *
  2040. * Attribute values are sanitized by running them through check_plain().
  2041. * Attribute names are not automatically sanitized. When using user-supplied
  2042. * attribute names, it is strongly recommended to allow only white-listed names,
  2043. * since certain attributes carry security risks and can be abused.
  2044. *
  2045. * Examples of security aspects when using drupal_attributes:
  2046. * @code
  2047. * // By running the value in the following statement through check_plain,
  2048. * // the malicious script is neutralized.
  2049. * drupal_attributes(array('title' => t('<script>steal_cookie();</script>')));
  2050. *
  2051. * // The statement below demonstrates dangerous use of drupal_attributes, and
  2052. * // will return an onmouseout attribute with JavaScript code that, when used
  2053. * // as attribute in a tag, will cause users to be redirected to another site.
  2054. * //
  2055. * // In this case, the 'onmouseout' attribute should not be whitelisted --
  2056. * // you don't want users to have the ability to add this attribute or others
  2057. * // that take JavaScript commands.
  2058. * drupal_attributes(array('onmouseout' => 'window.location="http://malicious.com/";')));
  2059. * @endcode
  2060. *
  2061. * @param $attributes
  2062. * An associative array of key-value pairs to be converted to attributes.
  2063. *
  2064. * @return
  2065. * A string ready for insertion in a tag (starts with a space).
  2066. *
  2067. * @ingroup sanitization
  2068. */
  2069. function drupal_attributes(array $attributes = array()) {
  2070. foreach ($attributes as $attribute => &$data) {
  2071. $data = implode(' ', (array) $data);
  2072. $data = $attribute . '="' . check_plain($data) . '"';
  2073. }
  2074. return $attributes ? ' ' . implode(' ', $attributes) : '';
  2075. }
  2076. /**
  2077. * Formats an internal or external URL link as an HTML anchor tag.
  2078. *
  2079. * This function correctly handles aliased paths, and adds an 'active' class
  2080. * attribute to links that point to the current page (for theming), so all
  2081. * internal links output by modules should be generated by this function if
  2082. * possible.
  2083. *
  2084. * @param $text
  2085. * The link text for the anchor tag.
  2086. * @param $path
  2087. * The internal path or external URL being linked to, such as "node/34" or
  2088. * "http://example.com/foo". After the url() function is called to construct
  2089. * the URL from $path and $options, the resulting URL is passed through
  2090. * check_plain() before it is inserted into the HTML anchor tag, to ensure
  2091. * well-formed HTML. See url() for more information and notes.
  2092. * @param array $options
  2093. * An associative array of additional options, with the following elements:
  2094. * - 'attributes': An associative array of HTML attributes to apply to the
  2095. * anchor tag. If element 'class' is included, it must be an array; 'title'
  2096. * must be a string; other elements are more flexible, as they just need
  2097. * to work in a call to drupal_attributes($options['attributes']).
  2098. * - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
  2099. * example, to make an image tag into a link, this must be set to TRUE, or
  2100. * you will see the escaped HTML image tag. $text is not sanitized if
  2101. * 'html' is TRUE. The calling function must ensure that $text is already
  2102. * safe.
  2103. * - 'language': An optional language object. If the path being linked to is
  2104. * internal to the site, $options['language'] is used to determine whether
  2105. * the link is "active", or pointing to the current page (the language as
  2106. * well as the path must match). This element is also used by url().
  2107. * - Additional $options elements used by the url() function.
  2108. *
  2109. * @return
  2110. * An HTML string containing a link to the given path.
  2111. */
  2112. function l($text, $path, array $options = array()) {
  2113. global $language_url;
  2114. static $use_theme = NULL;
  2115. // Merge in defaults.
  2116. $options += array(
  2117. 'attributes' => array(),
  2118. 'html' => FALSE,
  2119. );
  2120. // Append active class.
  2121. if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
  2122. (empty($options['language']) || $options['language']->language == $language_url->language)) {
  2123. $options['attributes']['class'][] = 'active';
  2124. }
  2125. // Remove all HTML and PHP tags from a tooltip. For best performance, we act only
  2126. // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive).
  2127. if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) {
  2128. $options['attributes']['title'] = strip_tags($options['attributes']['title']);
  2129. }
  2130. // Determine if rendering of the link is to be done with a theme function
  2131. // or the inline default. Inline is faster, but if the theme system has been
  2132. // loaded and a module or theme implements a preprocess or process function
  2133. // or overrides the theme_link() function, then invoke theme(). Preliminary
  2134. // benchmarks indicate that invoking theme() can slow down the l() function
  2135. // by 20% or more, and that some of the link-heavy Drupal pages spend more
  2136. // than 10% of the total page request time in the l() function.
  2137. if (!isset($use_theme) && function_exists('theme')) {
  2138. // Allow edge cases to prevent theme initialization and force inline link
  2139. // rendering.
  2140. if (variable_get('theme_link', TRUE)) {
  2141. drupal_theme_initialize();
  2142. $registry = theme_get_registry();
  2143. // We don't want to duplicate functionality that's in theme(), so any
  2144. // hint of a module or theme doing anything at all special with the 'link'
  2145. // theme hook should simply result in theme() being called. This includes
  2146. // the overriding of theme_link() with an alternate function or template,
  2147. // the presence of preprocess or process functions, or the presence of
  2148. // include files.
  2149. $use_theme = !isset($registry['link']['function']) || ($registry['link']['function'] != 'theme_link');
  2150. $use_theme = $use_theme || !empty($registry['link']['preprocess functions']) || !empty($registry['link']['process functions']) || !empty($registry['link']['includes']);
  2151. }
  2152. else {
  2153. $use_theme = FALSE;
  2154. }
  2155. }
  2156. if ($use_theme) {
  2157. return theme('link', array('text' => $text, 'path' => $path, 'options' => $options));
  2158. }
  2159. // The result of url() is a plain-text URL. Because we are using it here
  2160. // in an HTML argument context, we need to encode it properly.
  2161. return '<a href="' . check_plain(url($path, $options)) . '"' . drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text : check_plain($text)) . '</a>';
  2162. }
  2163. /**
  2164. * Delivers a page callback result to the browser in the appropriate format.
  2165. *
  2166. * This function is most commonly called by menu_execute_active_handler(), but
  2167. * can also be called by error conditions such as drupal_not_found(),
  2168. * drupal_access_denied(), and drupal_site_offline().
  2169. *
  2170. * When a user requests a page, index.php calls menu_execute_active_handler(),
  2171. * which calls the 'page callback' function registered in hook_menu(). The page
  2172. * callback function can return one of:
  2173. * - NULL: to indicate no content.
  2174. * - An integer menu status constant: to indicate an error condition.
  2175. * - A string of HTML content.
  2176. * - A renderable array of content.
  2177. * Returning a renderable array rather than a string of HTML is preferred,
  2178. * because that provides modules with more flexibility in customizing the final
  2179. * result.
  2180. *
  2181. * When the page callback returns its constructed content to
  2182. * menu_execute_active_handler(), this function gets called. The purpose of
  2183. * this function is to determine the most appropriate 'delivery callback'
  2184. * function to route the content to. The delivery callback function then
  2185. * sends the content to the browser in the needed format. The default delivery
  2186. * callback is drupal_deliver_html_page(), which delivers the content as an HTML
  2187. * page, complete with blocks in addition to the content. This default can be
  2188. * overridden on a per menu router item basis by setting 'delivery callback' in
  2189. * hook_menu() or hook_menu_alter(), and can also be overridden on a per request
  2190. * basis in hook_page_delivery_callback_alter().
  2191. *
  2192. * For example, the same page callback function can be used for an HTML
  2193. * version of the page and an Ajax version of the page. The page callback
  2194. * function just needs to decide what content is to be returned and the
  2195. * delivery callback function will send it as an HTML page or an Ajax
  2196. * response, as appropriate.
  2197. *
  2198. * In order for page callbacks to be reusable in different delivery formats,
  2199. * they should not issue any "print" or "echo" statements, but instead just
  2200. * return content.
  2201. *
  2202. * Also note that this function does not perform access checks. The delivery
  2203. * callback function specified in hook_menu(), hook_menu_alter(), or
  2204. * hook_page_delivery_callback_alter() will be called even if the router item
  2205. * access checks fail. This is intentional (it is needed for JSON and other
  2206. * purposes), but it has security implications. Do not call this function
  2207. * directly unless you understand the security implications, and be careful in
  2208. * writing delivery callbacks, so that they do not violate security. See
  2209. * drupal_deliver_html_page() for an example of a delivery callback that
  2210. * respects security.
  2211. *
  2212. * @param $page_callback_result
  2213. * The result of a page callback. Can be one of:
  2214. * - NULL: to indicate no content.
  2215. * - An integer menu status constant: to indicate an error condition.
  2216. * - A string of HTML content.
  2217. * - A renderable array of content.
  2218. * @param $default_delivery_callback
  2219. * (Optional) If given, it is the name of a delivery function most likely
  2220. * to be appropriate for the page request as determined by the calling
  2221. * function (e.g., menu_execute_active_handler()). If not given, it is
  2222. * determined from the menu router information of the current page.
  2223. *
  2224. * @see menu_execute_active_handler()
  2225. * @see hook_menu()
  2226. * @see hook_menu_alter()
  2227. * @see hook_page_delivery_callback_alter()
  2228. */
  2229. function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) {
  2230. if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) {
  2231. $default_delivery_callback = $router_item['delivery_callback'];
  2232. }
  2233. $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page';
  2234. // Give modules a chance to alter the delivery callback used, based on
  2235. // request-time context (e.g., HTTP request headers).
  2236. drupal_alter('page_delivery_callback', $delivery_callback);
  2237. if (function_exists($delivery_callback)) {
  2238. $delivery_callback($page_callback_result);
  2239. }
  2240. else {
  2241. // If a delivery callback is specified, but doesn't exist as a function,
  2242. // something is wrong, but don't print anything, since it's not known
  2243. // what format the response needs to be in.
  2244. watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR);
  2245. }
  2246. }
  2247. /**
  2248. * Package and send the result of a page callback to the browser as HTML.
  2249. *
  2250. * @param $page_callback_result
  2251. * The result of a page callback. Can be one of:
  2252. * - NULL: to indicate no content.
  2253. * - An integer menu status constant: to indicate an error condition.
  2254. * - A string of HTML content.
  2255. * - A renderable array of content.
  2256. *
  2257. * @see drupal_deliver_page()
  2258. */
  2259. function drupal_deliver_html_page($page_callback_result) {
  2260. // Emit the correct charset HTTP header, but not if the page callback
  2261. // result is NULL, since that likely indicates that it printed something
  2262. // in which case, no further headers may be sent, and not if code running
  2263. // for this page request has already set the content type header.
  2264. if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) {
  2265. drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
  2266. }
  2267. // Menu status constants are integers; page content is a string or array.
  2268. if (is_int($page_callback_result)) {
  2269. // @todo: Break these up into separate functions?
  2270. switch ($page_callback_result) {
  2271. case MENU_NOT_FOUND:
  2272. // Print a 404 page.
  2273. drupal_add_http_header('Status', '404 Not Found');
  2274. watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
  2275. // Keep old path for reference, and to allow forms to redirect to it.
  2276. if (!isset($_GET['destination'])) {
  2277. $_GET['destination'] = $_GET['q'];
  2278. }
  2279. $path = drupal_get_normal_path(variable_get('site_404', ''));
  2280. if ($path && $path != $_GET['q']) {
  2281. // Custom 404 handler. Set the active item in case there are tabs to
  2282. // display, or other dependencies on the path.
  2283. menu_set_active_item($path);
  2284. $return = menu_execute_active_handler($path, FALSE);
  2285. }
  2286. if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
  2287. // Standard 404 handler.
  2288. drupal_set_title(t('Page not found'));
  2289. $return = t('The requested page could not be found.');
  2290. }
  2291. drupal_set_page_content($return);
  2292. $page = element_info('page');
  2293. print drupal_render_page($page);
  2294. break;
  2295. case MENU_ACCESS_DENIED:
  2296. // Print a 403 page.
  2297. drupal_add_http_header('Status', '403 Forbidden');
  2298. watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
  2299. // Keep old path for reference, and to allow forms to redirect to it.
  2300. if (!isset($_GET['destination'])) {
  2301. $_GET['destination'] = $_GET['q'];
  2302. }
  2303. $path = drupal_get_normal_path(variable_get('site_403', ''));
  2304. if ($path && $path != $_GET['q']) {
  2305. // Custom 403 handler. Set the active item in case there are tabs to
  2306. // display or other dependencies on the path.
  2307. menu_set_active_item($path);
  2308. $return = menu_execute_active_handler($path, FALSE);
  2309. }
  2310. if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
  2311. // Standard 403 handler.
  2312. drupal_set_title(t('Access denied'));
  2313. $return = t('You are not authorized to access this page.');
  2314. }
  2315. print drupal_render_page($return);
  2316. break;
  2317. case MENU_SITE_OFFLINE:
  2318. // Print a 503 page.
  2319. drupal_maintenance_theme();
  2320. drupal_add_http_header('Status', '503 Service unavailable');
  2321. drupal_set_title(t('Site under maintenance'));
  2322. print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message',
  2323. t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))));
  2324. break;
  2325. }
  2326. }
  2327. elseif (isset($page_callback_result)) {
  2328. // Print anything besides a menu constant, assuming it's not NULL or
  2329. // undefined.
  2330. print drupal_render_page($page_callback_result);
  2331. }
  2332. // Perform end-of-request tasks.
  2333. drupal_page_footer();
  2334. }
  2335. /**
  2336. * Perform end-of-request tasks.
  2337. *
  2338. * This function sets the page cache if appropriate, and allows modules to
  2339. * react to the closing of the page by calling hook_exit().
  2340. */
  2341. function drupal_page_footer() {
  2342. global $user;
  2343. module_invoke_all('exit');
  2344. // Commit the user session, if needed.
  2345. drupal_session_commit();
  2346. if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) {
  2347. drupal_serve_page_from_cache($cache);
  2348. }
  2349. else {
  2350. ob_flush();
  2351. }
  2352. _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
  2353. drupal_cache_system_paths();
  2354. module_implements_write_cache();
  2355. system_run_automated_cron();
  2356. }
  2357. /**
  2358. * Perform end-of-request tasks.
  2359. *
  2360. * In some cases page requests need to end without calling drupal_page_footer().
  2361. * In these cases, call drupal_exit() instead. There should rarely be a reason
  2362. * to call exit instead of drupal_exit();
  2363. *
  2364. * @param $destination
  2365. * If this function is called from drupal_goto(), then this argument
  2366. * will be a fully-qualified URL that is the destination of the redirect.
  2367. * This should be passed along to hook_exit() implementations.
  2368. */
  2369. function drupal_exit($destination = NULL) {
  2370. if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) {
  2371. if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
  2372. module_invoke_all('exit', $destination);
  2373. }
  2374. drupal_session_commit();
  2375. }
  2376. exit;
  2377. }
  2378. /**
  2379. * Form an associative array from a linear array.
  2380. *
  2381. * This function walks through the provided array and constructs an associative
  2382. * array out of it. The keys of the resulting array will be the values of the
  2383. * input array. The values will be the same as the keys unless a function is
  2384. * specified, in which case the output of the function is used for the values
  2385. * instead.
  2386. *
  2387. * @param $array
  2388. * A linear array.
  2389. * @param $function
  2390. * A name of a function to apply to all values before output.
  2391. *
  2392. * @return
  2393. * An associative array.
  2394. */
  2395. function drupal_map_assoc($array, $function = NULL) {
  2396. // array_combine() fails with empty arrays:
  2397. // http://bugs.php.net/bug.php?id=34857.
  2398. $array = !empty($array) ? array_combine($array, $array) : array();
  2399. if (is_callable($function)) {
  2400. $array = array_map($function, $array);
  2401. }
  2402. return $array;
  2403. }
  2404. /**
  2405. * Attempts to set the PHP maximum execution time.
  2406. *
  2407. * This function is a wrapper around the PHP function set_time_limit().
  2408. * When called, set_time_limit() restarts the timeout counter from zero.
  2409. * In other words, if the timeout is the default 30 seconds, and 25 seconds
  2410. * into script execution a call such as set_time_limit(20) is made, the
  2411. * script will run for a total of 45 seconds before timing out.
  2412. *
  2413. * It also means that it is possible to decrease the total time limit if
  2414. * the sum of the new time limit and the current time spent running the
  2415. * script is inferior to the original time limit. It is inherent to the way
  2416. * set_time_limit() works, it should rather be called with an appropriate
  2417. * value every time you need to allocate a certain amount of time
  2418. * to execute a task than only once at the beginning of the script.
  2419. *
  2420. * Before calling set_time_limit(), we check if this function is available
  2421. * because it could be disabled by the server administrator. We also hide all
  2422. * the errors that could occur when calling set_time_limit(), because it is
  2423. * not possible to reliably ensure that PHP or a security extension will
  2424. * not issue a warning/error if they prevent the use of this function.
  2425. *
  2426. * @param $time_limit
  2427. * An integer specifying the new time limit, in seconds. A value of 0
  2428. * indicates unlimited execution time.
  2429. *
  2430. * @ingroup php_wrappers
  2431. */
  2432. function drupal_set_time_limit($time_limit) {
  2433. if (function_exists('set_time_limit')) {
  2434. @set_time_limit($time_limit);
  2435. }
  2436. }
  2437. /**
  2438. * Returns the path to a system item (module, theme, etc.).
  2439. *
  2440. * @param $type
  2441. * The type of the item (i.e. theme, theme_engine, module, profile).
  2442. * @param $name
  2443. * The name of the item for which the path is requested.
  2444. *
  2445. * @return
  2446. * The path to the requested item.
  2447. */
  2448. function drupal_get_path($type, $name) {
  2449. return dirname(drupal_get_filename($type, $name));
  2450. }
  2451. /**
  2452. * Return the base URL path (i.e., directory) of the Drupal installation.
  2453. *
  2454. * base_path() prefixes and suffixes a "/" onto the returned path if the path is
  2455. * not empty. At the very least, this will return "/".
  2456. *
  2457. * Examples:
  2458. * - http://example.com returns "/" because the path is empty.
  2459. * - http://example.com/drupal/folder returns "/drupal/folder/".
  2460. */
  2461. function base_path() {
  2462. return $GLOBALS['base_path'];
  2463. }
  2464. /**
  2465. * Add a LINK tag with a distinct 'rel' attribute to the page's HEAD.
  2466. *
  2467. * This function can be called as long the HTML header hasn't been sent,
  2468. * which on normal pages is up through the preprocess step of theme('html').
  2469. * Adding a link will overwrite a prior link with the exact same 'rel' and
  2470. * 'href' attributes.
  2471. *
  2472. * @param $attributes
  2473. * Associative array of element attributes including 'href' and 'rel'.
  2474. * @param $header
  2475. * Optional flag to determine if a HTTP 'Link:' header should be sent.
  2476. */
  2477. function drupal_add_html_head_link($attributes, $header = FALSE) {
  2478. $element = array(
  2479. '#tag' => 'link',
  2480. '#attributes' => $attributes,
  2481. );
  2482. $href = $attributes['href'];
  2483. if ($header) {
  2484. // Also add a HTTP header "Link:".
  2485. $href = '<' . check_plain($attributes['href']) . '>;';
  2486. unset($attributes['href']);
  2487. $element['#attached']['drupal_add_http_header'][] = array('Link', $href . drupal_http_header_attributes($attributes), TRUE);
  2488. }
  2489. drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . $href);
  2490. }
  2491. /**
  2492. * Adds a cascading stylesheet to the stylesheet queue.
  2493. *
  2494. * Calling drupal_static_reset('drupal_add_css') will clear all cascading
  2495. * stylesheets added so far.
  2496. *
  2497. * If CSS aggregation/compression is enabled, all cascading style sheets added
  2498. * with $options['preprocess'] set to TRUE will be merged into one aggregate
  2499. * file and compressed by removing all extraneous white space.
  2500. * Preprocessed inline stylesheets will not be aggregated into this single file;
  2501. * instead, they are just compressed upon output on the page. Externally hosted
  2502. * stylesheets are never aggregated or compressed.
  2503. *
  2504. * The reason for aggregating the files is outlined quite thoroughly here:
  2505. * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
  2506. * to request overhead, one bigger file just loads faster than two smaller ones
  2507. * half its size."
  2508. *
  2509. * $options['preprocess'] should be only set to TRUE when a file is required for
  2510. * all typical visitors and most pages of a site. It is critical that all
  2511. * preprocessed files are added unconditionally on every page, even if the
  2512. * files do not happen to be needed on a page. This is normally done by calling
  2513. * drupal_add_css() in a hook_init() implementation.
  2514. *
  2515. * Non-preprocessed files should only be added to the page when they are
  2516. * actually needed.
  2517. *
  2518. * @param $data
  2519. * (optional) The stylesheet data to be added, depending on what is passed
  2520. * through to the $options['type'] parameter:
  2521. * - 'file': The path to the CSS file relative to the base_path(), or a
  2522. * stream wrapper URI. For example: "modules/devel/devel.css" or
  2523. * "public://generated_css/stylesheet_1.css". Note that Modules should
  2524. * always prefix the names of their CSS files with the module name; for
  2525. * example, system-menus.css rather than simply menus.css. Themes can
  2526. * override module-supplied CSS files based on their filenames, and this
  2527. * prefixing helps prevent confusing name collisions for theme developers.
  2528. * See drupal_get_css() where the overrides are performed. Also, if the
  2529. * direction of the current language is right-to-left (Hebrew, Arabic,
  2530. * etc.), the function will also look for an RTL CSS file and append it to
  2531. * the list. The name of this file should have an '-rtl.css' suffix. For
  2532. * example a CSS file called 'mymodule-name.css' will have a
  2533. * 'mymodule-name-rtl.css' file added to the list, if exists in the same
  2534. * directory. This CSS file should contain overrides for properties which
  2535. * should be reversed or otherwise different in a right-to-left display.
  2536. * - 'inline': A string of CSS that should be placed in the given scope. Note
  2537. * that it is better practice to use 'file' stylesheets, rather than
  2538. * 'inline', as the CSS would then be aggregated and cached.
  2539. * - 'external': The absolute path to an external CSS file that is not hosted
  2540. * on the local server. These files will not be aggregated if CSS
  2541. * aggregation is enabled.
  2542. * @param $options
  2543. * (optional) A string defining the 'type' of CSS that is being added in the
  2544. * $data parameter ('file', 'inline', or 'external'), or an array which can
  2545. * have any or all of the following keys:
  2546. * - 'type': The type of stylesheet being added. Available options are 'file',
  2547. * 'inline' or 'external'. Defaults to 'file'.
  2548. * - 'basename': Force a basename for the file being added. Modules are
  2549. * expected to use stylesheets with unique filenames, but integration of
  2550. * external libraries may make this impossible. The basename of
  2551. * 'modules/node/node.css' is 'node.css'. If the external library "node.js"
  2552. * ships with a 'node.css', then a different, unique basename would be
  2553. * 'node.js.css'.
  2554. * - 'group': A number identifying the group in which to add the stylesheet.
  2555. * Available constants are:
  2556. * - CSS_SYSTEM: Any system-layer CSS.
  2557. * - CSS_DEFAULT: Any module-layer CSS.
  2558. * - CSS_THEME: Any theme-layer CSS.
  2559. * The group number serves as a weight: the markup for loading a stylesheet
  2560. * within a lower weight group is output to the page before the markup for
  2561. * loading a stylesheet within a higher weight group, so CSS within higher
  2562. * weight groups take precendence over CSS within lower weight groups.
  2563. * - 'every_page': For optimal front-end performance when aggregation is
  2564. * enabled, this should be set to TRUE if the stylesheet is present on every
  2565. * page of the website for users for whom it is present at all. This
  2566. * defaults to FALSE. It is set to TRUE for stylesheets added via module and
  2567. * theme .info files. Modules that add stylesheets within hook_init()
  2568. * implementations, or from other code that ensures that the stylesheet is
  2569. * added to all website pages, should also set this flag to TRUE. All
  2570. * stylesheets within the same group that have the 'every_page' flag set to
  2571. * TRUE and do not have 'preprocess' set to FALSE are aggregated together
  2572. * into a single aggregate file, and that aggregate file can be reused
  2573. * across a user's entire site visit, leading to faster navigation between
  2574. * pages. However, stylesheets that are only needed on pages less frequently
  2575. * visited, can be added by code that only runs for those particular pages,
  2576. * and that code should not set the 'every_page' flag. This minimizes the
  2577. * size of the aggregate file that the user needs to download when first
  2578. * visiting the website. Stylesheets without the 'every_page' flag are
  2579. * aggregated into a separate aggregate file. This other aggregate file is
  2580. * likely to change from page to page, and each new aggregate file needs to
  2581. * be downloaded when first encountered, so it should be kept relatively
  2582. * small by ensuring that most commonly needed stylesheets are added to
  2583. * every page.
  2584. * - 'weight': The weight of the stylesheet specifies the order in which the
  2585. * CSS will appear relative to other stylesheets with the same group and
  2586. * 'every_page' flag. The exact ordering of stylesheets is as follows:
  2587. * - First by group.
  2588. * - Then by the 'every_page' flag, with TRUE coming before FALSE.
  2589. * - Then by weight.
  2590. * - Then by the order in which the CSS was added. For example, all else
  2591. * being the same, a stylesheet added by a call to drupal_add_css() that
  2592. * happened later in the page request gets added to the page after one for
  2593. * which drupal_add_css() happened earlier in the page request.
  2594. * - 'media': The media type for the stylesheet, e.g., all, print, screen.
  2595. * Defaults to 'all'.
  2596. * - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the
  2597. * styles will be aggregated and compressed. Defaults to TRUE.
  2598. * - 'browsers': An array containing information specifying which browsers
  2599. * should load the CSS item. See drupal_pre_render_conditional_comments()
  2600. * for details.
  2601. *
  2602. * @return
  2603. * An array of queued cascading stylesheets.
  2604. *
  2605. * @see drupal_get_css()
  2606. */
  2607. function drupal_add_css($data = NULL, $options = NULL) {
  2608. $css = &drupal_static(__FUNCTION__, array());
  2609. // Construct the options, taking the defaults into consideration.
  2610. if (isset($options)) {
  2611. if (!is_array($options)) {
  2612. $options = array('type' => $options);
  2613. }
  2614. }
  2615. else {
  2616. $options = array();
  2617. }
  2618. // Create an array of CSS files for each media type first, since each type needs to be served
  2619. // to the browser differently.
  2620. if (isset($data)) {
  2621. $options += array(
  2622. 'type' => 'file',
  2623. 'group' => CSS_DEFAULT,
  2624. 'weight' => 0,
  2625. 'every_page' => FALSE,
  2626. 'media' => 'all',
  2627. 'preprocess' => TRUE,
  2628. 'data' => $data,
  2629. 'browsers' => array(),
  2630. );
  2631. $options['browsers'] += array(
  2632. 'IE' => TRUE,
  2633. '!IE' => TRUE,
  2634. );
  2635. // Files with a query string cannot be preprocessed.
  2636. if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) {
  2637. $options['preprocess'] = FALSE;
  2638. }
  2639. // Always add a tiny value to the weight, to conserve the insertion order.
  2640. $options['weight'] += count($css) / 1000;
  2641. // Add the data to the CSS array depending on the type.
  2642. switch ($options['type']) {
  2643. case 'inline':
  2644. // For inline stylesheets, we don't want to use the $data as the array
  2645. // key as $data could be a very long string of CSS.
  2646. $css[] = $options;
  2647. break;
  2648. default:
  2649. // Local and external files must keep their name as the associative key
  2650. // so the same CSS file is not be added twice.
  2651. $css[$data] = $options;
  2652. }
  2653. }
  2654. return $css;
  2655. }
  2656. /**
  2657. * Returns a themed representation of all stylesheets that should be attached to the page.
  2658. *
  2659. * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
  2660. * This ensures proper cascading of styles so themes can easily override
  2661. * module styles through CSS selectors.
  2662. *
  2663. * Themes may replace module-defined CSS files by adding a stylesheet with the
  2664. * same filename. For example, themes/bartik/system-menus.css would replace
  2665. * modules/system/system-menus.css. This allows themes to override complete
  2666. * CSS files, rather than specific selectors, when necessary.
  2667. *
  2668. * If the original CSS file is being overridden by a theme, the theme is
  2669. * responsible for supplying an accompanying RTL CSS file to replace the
  2670. * module's.
  2671. *
  2672. * @param $css
  2673. * (optional) An array of CSS files. If no array is provided, the default
  2674. * stylesheets array is used instead.
  2675. * @param $skip_alter
  2676. * (optional) If set to TRUE, this function skips calling drupal_alter() on
  2677. * $css, useful when the calling function passes a $css array that has already
  2678. * been altered.
  2679. *
  2680. * @return
  2681. * A string of XHTML CSS tags.
  2682. *
  2683. * @see drupal_add_css()
  2684. */
  2685. function drupal_get_css($css = NULL, $skip_alter = FALSE) {
  2686. if (!isset($css)) {
  2687. $css = drupal_add_css();
  2688. }
  2689. // Allow modules and themes to alter the CSS items.
  2690. if (!$skip_alter) {
  2691. drupal_alter('css', $css);
  2692. }
  2693. // Sort CSS items, so that they appear in the correct order.
  2694. uasort($css, 'drupal_sort_css_js');
  2695. // Remove the overridden CSS files. Later CSS files override former ones.
  2696. $previous_item = array();
  2697. foreach ($css as $key => $item) {
  2698. if ($item['type'] == 'file') {
  2699. // If defined, force a unique basename for this file.
  2700. $basename = isset($item['basename']) ? $item['basename'] : basename($item['data']);
  2701. if (isset($previous_item[$basename])) {
  2702. // Remove the previous item that shared the same base name.
  2703. unset($css[$previous_item[$basename]]);
  2704. }
  2705. $previous_item[$basename] = $key;
  2706. }
  2707. }
  2708. // Render the HTML needed to load the CSS.
  2709. $styles = array(
  2710. '#type' => 'styles',
  2711. '#items' => $css,
  2712. );
  2713. // Provide the page with information about the individual CSS files used,
  2714. // information not otherwise available when CSS aggregation is enabled.
  2715. $setting['ajaxPageState']['css'] = array_fill_keys(array_keys($css), 1);
  2716. $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting);
  2717. return drupal_render($styles);
  2718. }
  2719. /**
  2720. * Function used by uasort to sort the array structures returned by drupal_add_css() and drupal_add_js().
  2721. *
  2722. * This sort order helps optimize front-end performance while providing modules
  2723. * and themes with the necessary control for ordering the CSS and JavaScript
  2724. * appearing on a page.
  2725. */
  2726. function drupal_sort_css_js($a, $b) {
  2727. // First order by group, so that, for example, all items in the CSS_SYSTEM
  2728. // group appear before items in the CSS_DEFAULT group, which appear before
  2729. // all items in the CSS_THEME group. Modules may create additional groups by
  2730. // defining their own constants.
  2731. if ($a['group'] < $b['group']) {
  2732. return -1;
  2733. }
  2734. elseif ($a['group'] > $b['group']) {
  2735. return 1;
  2736. }
  2737. // Within a group, order all infrequently needed, page-specific files after
  2738. // common files needed throughout the website. Separating this way allows for
  2739. // the aggregate file generated for all of the common files to be reused
  2740. // across a site visit without being cut by a page using a less common file.
  2741. elseif ($a['every_page'] && !$b['every_page']) {
  2742. return -1;
  2743. }
  2744. elseif (!$a['every_page'] && $b['every_page']) {
  2745. return 1;
  2746. }
  2747. // Finally, order by weight.
  2748. elseif ($a['weight'] < $b['weight']) {
  2749. return -1;
  2750. }
  2751. elseif ($a['weight'] > $b['weight']) {
  2752. return 1;
  2753. }
  2754. else {
  2755. return 0;
  2756. }
  2757. }
  2758. /**
  2759. * Default callback to group CSS items.
  2760. *
  2761. * This function arranges the CSS items that are in the #items property of the
  2762. * styles element into groups. Arranging the CSS items into groups serves two
  2763. * purposes. When aggregation is enabled, files within a group are aggregated
  2764. * into a single file, significantly improving page loading performance by
  2765. * minimizing network traffic overhead. When aggregation is disabled, grouping
  2766. * allows multiple files to be loaded from a single STYLE tag, enabling sites
  2767. * with many modules enabled or a complex theme being used to stay within IE's
  2768. * 31 CSS inclusion tag limit: http://drupal.org/node/228818.
  2769. *
  2770. * This function puts multiple items into the same group if they are groupable
  2771. * and if they are for the same 'media' and 'browsers'. Items of the 'file' type
  2772. * are groupable if their 'preprocess' flag is TRUE, items of the 'inline' type
  2773. * are always groupable, and items of the 'external' type are never groupable.
  2774. * This function also ensures that the process of grouping items does not change
  2775. * their relative order. This requirement may result in multiple groups for the
  2776. * same type, media, and browsers, if needed to accomodate other items in
  2777. * between.
  2778. *
  2779. * @param $css
  2780. * An array of CSS items, as returned by drupal_add_css(), but after
  2781. * alteration performed by drupal_get_css().
  2782. *
  2783. * @return
  2784. * An array of CSS groups. Each group contains the same keys (e.g., 'media',
  2785. * 'data', etc.) as a CSS item from the $css parameter, with the value of
  2786. * each key applying to the group as a whole. Each group also contains an
  2787. * 'items' key, which is the subset of items from $css that are in the group.
  2788. *
  2789. * @see drupal_pre_render_styles()
  2790. */
  2791. function drupal_group_css($css) {
  2792. $groups = array();
  2793. // If a group can contain multiple items, we track the information that must
  2794. // be the same for each item in the group, so that when we iterate the next
  2795. // item, we can determine if it can be put into the current group, or if a
  2796. // new group needs to be made for it.
  2797. $current_group_keys = NULL;
  2798. // When creating a new group, we pre-increment $i, so by initializing it to
  2799. // -1, the first group will have index 0.
  2800. $i = -1;
  2801. foreach ($css as $item) {
  2802. // The browsers for which the CSS item needs to be loaded is part of the
  2803. // information that determines when a new group is needed, but the order of
  2804. // keys in the array doesn't matter, and we don't want a new group if all
  2805. // that's different is that order.
  2806. ksort($item['browsers']);
  2807. // If the item can be grouped with other items, set $group_keys to an array
  2808. // of information that must be the same for all items in its group. If the
  2809. // item can't be grouped with other items, set $group_keys to FALSE. We
  2810. // put items into a group that can be aggregated together: whether they will
  2811. // be aggregated is up to the _drupal_css_aggregate() function or an
  2812. // override of that function specified in hook_css_alter(), but regardless
  2813. // of the details of that function, a group represents items that can be
  2814. // aggregated. Since a group may be rendered with a single HTML tag, all
  2815. // items in the group must share the same information that would need to be
  2816. // part of that HTML tag.
  2817. switch ($item['type']) {
  2818. case 'file':
  2819. // Group file items if their 'preprocess' flag is TRUE.
  2820. // Help ensure maximum reuse of aggregate files by only grouping
  2821. // together items that share the same 'group' value and 'every_page'
  2822. // flag. See drupal_add_css() for details about that.
  2823. $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE;
  2824. break;
  2825. case 'inline':
  2826. // Always group inline items.
  2827. $group_keys = array($item['type'], $item['media'], $item['browsers']);
  2828. break;
  2829. case 'external':
  2830. // Do not group external items.
  2831. $group_keys = FALSE;
  2832. break;
  2833. }
  2834. // If the group keys don't match the most recent group we're working with,
  2835. // then a new group must be made.
  2836. if ($group_keys !== $current_group_keys) {
  2837. $i++;
  2838. // Initialize the new group with the same properties as the first item
  2839. // being placed into it. The item's 'data' and 'weight' properties are
  2840. // unique to the item and should not be carried over to the group.
  2841. $groups[$i] = $item;
  2842. unset($groups[$i]['data'], $groups[$i]['weight']);
  2843. $groups[$i]['items'] = array();
  2844. $current_group_keys = $group_keys ? $group_keys : NULL;
  2845. }
  2846. // Add the item to the current group.
  2847. $groups[$i]['items'][] = $item;
  2848. }
  2849. return $groups;
  2850. }
  2851. /**
  2852. * Default callback to aggregate CSS files and inline content.
  2853. *
  2854. * Having the browser load fewer CSS files results in much faster page loads
  2855. * than when it loads many small files. This function aggregates files within
  2856. * the same group into a single file unless the site-wide setting to do so is
  2857. * disabled (commonly the case during site development). To optimize download,
  2858. * it also compresses the aggregate files by removing comments, whitespace, and
  2859. * other unnecessary content. Additionally, this functions aggregates inline
  2860. * content together, regardless of the site-wide aggregation setting.
  2861. *
  2862. * @param $css_groups
  2863. * An array of CSS groups as returned by drupal_group_css(). This function
  2864. * modifies the group's 'data' property for each group that is aggregated.
  2865. *
  2866. * @see drupal_group_css()
  2867. * @see drupal_pre_render_styles()
  2868. */
  2869. function drupal_aggregate_css(&$css_groups) {
  2870. $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
  2871. // For each group that needs aggregation, aggregate its items.
  2872. foreach ($css_groups as $key => $group) {
  2873. switch ($group['type']) {
  2874. // If a file group can be aggregated into a single file, do so, and set
  2875. // the group's data property to the file path of the aggregate file.
  2876. case 'file':
  2877. if ($group['preprocess'] && $preprocess_css) {
  2878. $css_groups[$key]['data'] = drupal_build_css_cache($group['items']);
  2879. }
  2880. break;
  2881. // Aggregate all inline CSS content into the group's data property.
  2882. case 'inline':
  2883. $css_groups[$key]['data'] = '';
  2884. foreach ($group['items'] as $item) {
  2885. $css_groups[$key]['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']);
  2886. }
  2887. break;
  2888. }
  2889. }
  2890. }
  2891. /**
  2892. * #pre_render callback to add the elements needed for CSS tags to be rendered.
  2893. *
  2894. * For production websites, LINK tags are preferable to STYLE tags with @import
  2895. * statements, because:
  2896. * - They are the standard tag intended for linking to a resource.
  2897. * - On Firefox 2 and perhaps other browsers, CSS files included with @import
  2898. * statements don't get saved when saving the complete web page for offline
  2899. * use: http://drupal.org/node/145218.
  2900. * - On IE, if only LINK tags and no @import statements are used, all the CSS
  2901. * files are downloaded in parallel, resulting in faster page load, but if
  2902. * @import statements are used and span across multiple STYLE tags, all the
  2903. * ones from one STYLE tag must be downloaded before downloading begins for
  2904. * the next STYLE tag. Furthermore, IE7 does not support media declaration on
  2905. * the @import statement, so multiple STYLE tags must be used when different
  2906. * files are for different media types. Non-IE browsers always download in
  2907. * parallel, so this is an IE-specific performance quirk:
  2908. * http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.
  2909. *
  2910. * However, IE has an annoying limit of 31 total CSS inclusion tags
  2911. * (http://drupal.org/node/228818) and LINK tags are limited to one file per
  2912. * tag, whereas STYLE tags can contain multiple @import statements allowing
  2913. * multiple files to be loaded per tag. When CSS aggregation is disabled, a
  2914. * Drupal site can easily have more than 31 CSS files that need to be loaded, so
  2915. * using LINK tags exclusively would result in a site that would display
  2916. * incorrectly in IE. Depending on different needs, different strategies can be
  2917. * employed to decide when to use LINK tags and when to use STYLE tags.
  2918. *
  2919. * The strategy employed by this function is to use LINK tags for all aggregate
  2920. * files and for all files that cannot be aggregated (e.g., if 'preprocess' is
  2921. * set to FALSE or the type is 'external'), and to use STYLE tags for groups
  2922. * of files that could be aggregated together but aren't (e.g., if the site-wide
  2923. * aggregation setting is disabled). This results in all LINK tags when
  2924. * aggregation is enabled, a guarantee that as many or only slightly more tags
  2925. * are used with aggregation disabled than enabled (so that if the limit were to
  2926. * be crossed with aggregation enabled, the site developer would also notice the
  2927. * problem while aggregation is disabled), and an easy way for a developer to
  2928. * view HTML source while aggregation is disabled and know what files will be
  2929. * aggregated together when aggregation becomes enabled.
  2930. *
  2931. * This function evaluates the aggregation enabled/disabled condition on a group
  2932. * by group basis by testing whether an aggregate file has been made for the
  2933. * group rather than by testing the site-wide aggregation setting. This allows
  2934. * this function to work correctly even if modules have implemented custom
  2935. * logic for grouping and aggregating files.
  2936. *
  2937. * @param $element
  2938. * A render array containing:
  2939. * - '#items': The CSS items as returned by drupal_add_css() and altered by
  2940. * drupal_get_css().
  2941. * - '#group_callback': A function to call to group #items to enable the use
  2942. * of fewer tags by aggregating files and/or using multiple @import
  2943. * statements within a single tag.
  2944. * - '#aggregate_callback': A function to call to aggregate the items within
  2945. * the groups arranged by the #group_callback function.
  2946. *
  2947. * @return
  2948. * A render array that will render to a string of XHTML CSS tags.
  2949. *
  2950. * @see drupal_get_css()
  2951. */
  2952. function drupal_pre_render_styles($elements) {
  2953. // Group and aggregate the items.
  2954. if (isset($elements['#group_callback'])) {
  2955. $elements['#groups'] = $elements['#group_callback']($elements['#items']);
  2956. }
  2957. if (isset($elements['#aggregate_callback'])) {
  2958. $elements['#aggregate_callback']($elements['#groups']);
  2959. }
  2960. // A dummy query-string is added to filenames, to gain control over
  2961. // browser-caching. The string changes on every update or full cache
  2962. // flush, forcing browsers to load a new copy of the files, as the
  2963. // URL changed.
  2964. $query_string = variable_get('css_js_query_string', '0');
  2965. // For inline CSS to validate as XHTML, all CSS containing XHTML needs to be
  2966. // wrapped in CDATA. To make that backwards compatible with HTML 4, we need to
  2967. // comment out the CDATA-tag.
  2968. $embed_prefix = "\n<!--/*--><![CDATA[/*><!--*/\n";
  2969. $embed_suffix = "\n/*]]>*/-->\n";
  2970. // Defaults for LINK and STYLE elements.
  2971. $link_element_defaults = array(
  2972. '#type' => 'html_tag',
  2973. '#tag' => 'link',
  2974. '#attributes' => array(
  2975. 'type' => 'text/css',
  2976. 'rel' => 'stylesheet',
  2977. ),
  2978. );
  2979. $style_element_defaults = array(
  2980. '#type' => 'html_tag',
  2981. '#tag' => 'style',
  2982. '#attributes' => array(
  2983. 'type' => 'text/css',
  2984. ),
  2985. );
  2986. // Loop through each group.
  2987. foreach ($elements['#groups'] as $group) {
  2988. switch ($group['type']) {
  2989. // For file items, there are three possibilites.
  2990. // - The group has been aggregated: in this case, output a LINK tag for
  2991. // the aggregate file.
  2992. // - The group can be aggregated but has not been (most likely because
  2993. // the site administrator disabled the site-wide setting): in this case,
  2994. // output as few STYLE tags for the group as possible, using @import
  2995. // statement for each file in the group. This enables us to stay within
  2996. // IE's limit of 31 total CSS inclusion tags.
  2997. // - The group contains items not eligible for aggregation (their
  2998. // 'preprocess' flag has been set to FALSE): in this case, output a LINK
  2999. // tag for each file.
  3000. case 'file':
  3001. // The group has been aggregated into a single file: output a LINK tag
  3002. // for the aggregate file.
  3003. if (isset($group['data'])) {
  3004. $element = $link_element_defaults;
  3005. $element['#attributes']['href'] = file_create_url($group['data']);
  3006. $element['#attributes']['media'] = $group['media'];
  3007. $element['#browsers'] = $group['browsers'];
  3008. $elements[] = $element;
  3009. }
  3010. // The group can be aggregated, but hasn't been: combine multiple items
  3011. // into as few STYLE tags as possible.
  3012. elseif ($group['preprocess']) {
  3013. $import = array();
  3014. foreach ($group['items'] as $item) {
  3015. // A theme's .info file may have an entry for a file that doesn't
  3016. // exist as a way of overriding a module or base theme CSS file from
  3017. // being added to the page. Normally, file_exists() calls that need
  3018. // to run for every page request should be minimized, but this one
  3019. // is okay, because it only runs when CSS aggregation is disabled.
  3020. // On a server under heavy enough load that file_exists() calls need
  3021. // to be minimized, CSS aggregation should be enabled, in which case
  3022. // this code is not run. When aggregation is enabled,
  3023. // drupal_load_stylesheet() checks file_exists(), but only when
  3024. // building the aggregate file, which is then reused for many page
  3025. // requests.
  3026. if (file_exists($item['data'])) {
  3027. // The dummy query string needs to be added to the URL to control
  3028. // browser-caching. IE7 does not support a media type on the
  3029. // @import statement, so we instead specify the media for the
  3030. // group on the STYLE tag.
  3031. $import[] = '@import url("' . check_plain(file_create_url($item['data']) . '?' . $query_string) . '");';
  3032. }
  3033. }
  3034. // In addition to IE's limit of 31 total CSS inclusion tags, it also
  3035. // has a limit of 31 @import statements per STYLE tag.
  3036. while (!empty($import)) {
  3037. $import_batch = array_slice($import, 0, 31);
  3038. $import = array_slice($import, 31);
  3039. $element = $style_element_defaults;
  3040. $element['#value'] = implode("\n", $import_batch);
  3041. $element['#attributes']['media'] = $group['media'];
  3042. $element['#browsers'] = $group['browsers'];
  3043. $elements[] = $element;
  3044. }
  3045. }
  3046. // The group contains items ineligible for aggregation: output a LINK
  3047. // tag for each file.
  3048. else {
  3049. foreach ($group['items'] as $item) {
  3050. $element = $link_element_defaults;
  3051. // We do not check file_exists() here, because this code runs for
  3052. // files whose 'preprocess' is set to FALSE, and therefore, even
  3053. // when aggregation is enabled, and we want to avoid needlessly
  3054. // taxing a server that may be under heavy load. The file_exists()
  3055. // performed above for files whose 'preprocess' is TRUE is done for
  3056. // the benefit of theme .info files, but code that deals with files
  3057. // whose 'preprocess' is FALSE is responsible for ensuring the file
  3058. // exists.
  3059. // The dummy query string needs to be added to the URL to control
  3060. // browser-caching.
  3061. $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
  3062. $element['#attributes']['href'] = file_create_url($item['data']) . $query_string_separator . $query_string;
  3063. $element['#attributes']['media'] = $item['media'];
  3064. $element['#browsers'] = $group['browsers'];
  3065. $elements[] = $element;
  3066. }
  3067. }
  3068. break;
  3069. // For inline content, the 'data' property contains the CSS content. If
  3070. // the group's 'data' property is set, then output it in a single STYLE
  3071. // tag. Otherwise, output a separate STYLE tag for each item.
  3072. case 'inline':
  3073. if (isset($group['data'])) {
  3074. $element = $style_element_defaults;
  3075. $element['#value'] = $group['data'];
  3076. $element['#value_prefix'] = $embed_prefix;
  3077. $element['#value_suffix'] = $embed_suffix;
  3078. $element['#attributes']['media'] = $group['media'];
  3079. $element['#browsers'] = $group['browsers'];
  3080. $elements[] = $element;
  3081. }
  3082. else {
  3083. foreach ($group['items'] as $item) {
  3084. $element = $style_element_defaults;
  3085. $element['#value'] = $item['data'];
  3086. $element['#value_prefix'] = $embed_prefix;
  3087. $element['#value_suffix'] = $embed_suffix;
  3088. $element['#attributes']['media'] = $item['media'];
  3089. $element['#browsers'] = $group['browsers'];
  3090. $elements[] = $element;
  3091. }
  3092. }
  3093. break;
  3094. // Output a LINK tag for each external item. The item's 'data' property
  3095. // contains the full URL.
  3096. case 'external':
  3097. foreach ($group['items'] as $item) {
  3098. $element = $link_element_defaults;
  3099. $element['#attributes']['href'] = $item['data'];
  3100. $element['#attributes']['media'] = $item['media'];
  3101. $element['#browsers'] = $group['browsers'];
  3102. $elements[] = $element;
  3103. }
  3104. break;
  3105. }
  3106. }
  3107. return $elements;
  3108. }
  3109. /**
  3110. * Aggregates and optimizes CSS files into a cache file in the files directory.
  3111. *
  3112. * The file name for the CSS cache file is generated from the hash of the
  3113. * aggregated contents of the files in $css. This forces proxies and browsers
  3114. * to download new CSS when the CSS changes.
  3115. *
  3116. * The cache file name is retrieved on a page load via a lookup variable that
  3117. * contains an associative array. The array key is the hash of the file names
  3118. * in $css while the value is the cache file name. The cache file is generated
  3119. * in two cases. First, if there is no file name value for the key, which will
  3120. * happen if a new file name has been added to $css or after the lookup
  3121. * variable is emptied to force a rebuild of the cache. Second, the cache
  3122. * file is generated if it is missing on disk. Old cache files are not deleted
  3123. * immediately when the lookup variable is emptied, but are deleted after a set
  3124. * period by drupal_delete_file_if_stale(). This ensures that files referenced
  3125. * by a cached page will still be available.
  3126. *
  3127. * @param $css
  3128. * An array of CSS files to aggregate and compress into one file.
  3129. *
  3130. * @return
  3131. * The URI of the CSS cache file, or FALSE if the file could not be saved.
  3132. */
  3133. function drupal_build_css_cache($css) {
  3134. $data = '';
  3135. $uri = '';
  3136. $map = variable_get('drupal_css_cache_files', array());
  3137. $key = hash('sha256', serialize($css));
  3138. if (isset($map[$key])) {
  3139. $uri = $map[$key];
  3140. }
  3141. if (empty($uri) || !file_exists($uri)) {
  3142. // Build aggregate CSS file.
  3143. foreach ($css as $stylesheet) {
  3144. // Only 'file' stylesheets can be aggregated.
  3145. if ($stylesheet['type'] == 'file') {
  3146. $contents = drupal_load_stylesheet($stylesheet['data'], TRUE);
  3147. // Build the base URL of this CSS file: start with the full URL.
  3148. $css_base_url = file_create_url($stylesheet['data']);
  3149. // Move to the parent.
  3150. $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
  3151. // Simplify to a relative URL if the stylesheet URL starts with the
  3152. // base URL of the website.
  3153. if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
  3154. $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
  3155. }
  3156. _drupal_build_css_path(NULL, $css_base_url . '/');
  3157. // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
  3158. $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
  3159. }
  3160. }
  3161. // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
  3162. // @import rules must proceed any other style, so we move those to the top.
  3163. $regexp = '/@import[^;]+;/i';
  3164. preg_match_all($regexp, $data, $matches);
  3165. $data = preg_replace($regexp, '', $data);
  3166. $data = implode('', $matches[0]) . $data;
  3167. // Prefix filename to prevent blocking by firewalls which reject files
  3168. // starting with "ad*".
  3169. $filename = 'css_' . drupal_hash_base64($data) . '.css';
  3170. // Create the css/ within the files folder.
  3171. $csspath = 'public://css';
  3172. $uri = $csspath . '/' . $filename;
  3173. // Create the CSS file.
  3174. file_prepare_directory($csspath, FILE_CREATE_DIRECTORY);
  3175. if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) {
  3176. return FALSE;
  3177. }
  3178. // If CSS gzip compression is enabled, clean URLs are enabled (which means
  3179. // that rewrite rules are working) and the zlib extension is available then
  3180. // create a gzipped version of this file. This file is served conditionally
  3181. // to browsers that accept gzip using .htaccess rules.
  3182. if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
  3183. if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) {
  3184. return FALSE;
  3185. }
  3186. }
  3187. // Save the updated map.
  3188. $map[$key] = $uri;
  3189. variable_set('drupal_css_cache_files', $map);
  3190. }
  3191. return $uri;
  3192. }
  3193. /**
  3194. * Helper function for drupal_build_css_cache().
  3195. *
  3196. * This function will prefix all paths within a CSS file.
  3197. */
  3198. function _drupal_build_css_path($matches, $base = NULL) {
  3199. $_base = &drupal_static(__FUNCTION__);
  3200. // Store base path for preg_replace_callback.
  3201. if (isset($base)) {
  3202. $_base = $base;
  3203. }
  3204. // Prefix with base and remove '../' segments where possible.
  3205. $path = $_base . $matches[1];
  3206. $last = '';
  3207. while ($path != $last) {
  3208. $last = $path;
  3209. $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path);
  3210. }
  3211. return 'url(' . $path . ')';
  3212. }
  3213. /**
  3214. * Loads the stylesheet and resolves all @import commands.
  3215. *
  3216. * Loads a stylesheet and replaces @import commands with the contents of the
  3217. * imported file. Use this instead of file_get_contents when processing
  3218. * stylesheets.
  3219. *
  3220. * The returned contents are compressed removing white space and comments only
  3221. * when CSS aggregation is enabled. This optimization will not apply for
  3222. * color.module enabled themes with CSS aggregation turned off.
  3223. *
  3224. * @param $file
  3225. * Name of the stylesheet to be processed.
  3226. * @param $optimize
  3227. * Defines if CSS contents should be compressed or not.
  3228. * @param $reset_basepath
  3229. * Used internally to facilitate recursive resolution of @import commands.
  3230. *
  3231. * @return
  3232. * Contents of the stylesheet, including any resolved @import commands.
  3233. */
  3234. function drupal_load_stylesheet($file, $optimize = NULL, $reset_basepath = TRUE) {
  3235. // These statics are not cache variables, so we don't use drupal_static().
  3236. static $_optimize, $basepath;
  3237. if ($reset_basepath) {
  3238. $basepath = '';
  3239. }
  3240. // Store the value of $optimize for preg_replace_callback with nested
  3241. // @import loops.
  3242. if (isset($optimize)) {
  3243. $_optimize = $optimize;
  3244. }
  3245. // Stylesheets are relative one to each other. Start by adding a base path
  3246. // prefix provided by the parent stylesheet (if necessary).
  3247. if ($basepath && !file_uri_scheme($file)) {
  3248. $file = $basepath . '/' . $file;
  3249. }
  3250. $basepath = dirname($file);
  3251. // Load the CSS stylesheet. We suppress errors because themes may specify
  3252. // stylesheets in their .info file that don't exist in the theme's path,
  3253. // but are merely there to disable certain module CSS files.
  3254. if ($contents = @file_get_contents($file)) {
  3255. // Return the processed stylesheet.
  3256. return drupal_load_stylesheet_content($contents, $_optimize);
  3257. }
  3258. return '';
  3259. }
  3260. /**
  3261. * Process the contents of a stylesheet for aggregation.
  3262. *
  3263. * @param $contents
  3264. * The contents of the stylesheet.
  3265. * @param $optimize
  3266. * (optional) Boolean whether CSS contents should be minified. Defaults to
  3267. * FALSE.
  3268. * @return
  3269. * Contents of the stylesheet including the imported stylesheets.
  3270. */
  3271. function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
  3272. // Remove multiple charset declarations for standards compliance (and fixing Safari problems).
  3273. $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents);
  3274. if ($optimize) {
  3275. // Perform some safe CSS optimizations.
  3276. // Regexp to match comment blocks.
  3277. $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
  3278. // Regexp to match double quoted strings.
  3279. $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
  3280. // Regexp to match single quoted strings.
  3281. $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
  3282. // Strip all comment blocks, but keep double/single quoted strings.
  3283. $contents = preg_replace(
  3284. "<($double_quot|$single_quot)|$comment>Ss",
  3285. "$1",
  3286. $contents
  3287. );
  3288. // Remove certain whitespace.
  3289. // There are different conditions for removing leading and trailing
  3290. // whitespace.
  3291. // @see http://php.net/manual/en/regexp.reference.subpatterns.php
  3292. $contents = preg_replace('<
  3293. # Strip leading and trailing whitespace.
  3294. \s*([@{};,])\s*
  3295. # Strip only leading whitespace from:
  3296. # - Closing parenthesis: Retain "@media (bar) and foo".
  3297. | \s+([\)])
  3298. # Strip only trailing whitespace from:
  3299. # - Opening parenthesis: Retain "@media (bar) and foo".
  3300. # - Colon: Retain :pseudo-selectors.
  3301. | ([\(:])\s+
  3302. >xS',
  3303. // Only one of the three capturing groups will match, so its reference
  3304. // will contain the wanted value and the references for the
  3305. // two non-matching groups will be replaced with empty strings.
  3306. '$1$2$3',
  3307. $contents
  3308. );
  3309. // End the file with a new line.
  3310. $contents = trim($contents);
  3311. $contents .= "\n";
  3312. }
  3313. // Replaces @import commands with the actual stylesheet content.
  3314. // This happens recursively but omits external files.
  3315. $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents);
  3316. return $contents;
  3317. }
  3318. /**
  3319. * Loads stylesheets recursively and returns contents with corrected paths.
  3320. *
  3321. * This function is used for recursive loading of stylesheets and
  3322. * returns the stylesheet content with all url() paths corrected.
  3323. */
  3324. function _drupal_load_stylesheet($matches) {
  3325. $filename = $matches[1];
  3326. // Load the imported stylesheet and replace @import commands in there as well.
  3327. $file = drupal_load_stylesheet($filename, NULL, FALSE);
  3328. // Determine the file's directory.
  3329. $directory = dirname($filename);
  3330. // If the file is in the current directory, make sure '.' doesn't appear in
  3331. // the url() path.
  3332. $directory = $directory == '.' ? '' : $directory .'/';
  3333. // Alter all internal url() paths. Leave external paths alone. We don't need
  3334. // to normalize absolute paths here (i.e. remove folder/... segments) because
  3335. // that will be done later.
  3336. return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file);
  3337. }
  3338. /**
  3339. * Deletes old cached CSS files.
  3340. */
  3341. function drupal_clear_css_cache() {
  3342. variable_del('drupal_css_cache_files');
  3343. file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
  3344. }
  3345. /**
  3346. * Callback to delete files modified more than a set time ago.
  3347. */
  3348. function drupal_delete_file_if_stale($uri) {
  3349. // Default stale file threshold is 30 days.
  3350. if (REQUEST_TIME - filemtime($uri) > variable_get('drupal_stale_file_threshold', 2592000)) {
  3351. file_unmanaged_delete($uri);
  3352. }
  3353. }
  3354. /**
  3355. * Prepare a string for use as a valid CSS identifier (element, class or ID name).
  3356. *
  3357. * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
  3358. * CSS identifiers (including element names, classes, and IDs in selectors.)
  3359. *
  3360. * @param $identifier
  3361. * The identifier to clean.
  3362. * @param $filter
  3363. * An array of string replacements to use on the identifier.
  3364. * @return
  3365. * The cleaned identifier.
  3366. */
  3367. function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) {
  3368. // By default, we filter using Drupal's coding standards.
  3369. $identifier = strtr($identifier, $filter);
  3370. // Valid characters in a CSS identifier are:
  3371. // - the hyphen (U+002D)
  3372. // - a-z (U+0030 - U+0039)
  3373. // - A-Z (U+0041 - U+005A)
  3374. // - the underscore (U+005F)
  3375. // - 0-9 (U+0061 - U+007A)
  3376. // - ISO 10646 characters U+00A1 and higher
  3377. // We strip out any character not in the above list.
  3378. $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);
  3379. return $identifier;
  3380. }
  3381. /**
  3382. * Prepare a string for use as a valid class name.
  3383. *
  3384. * Do not pass one string containing multiple classes as they will be
  3385. * incorrectly concatenated with dashes, i.e. "one two" will become "one-two".
  3386. *
  3387. * @param $class
  3388. * The class name to clean.
  3389. * @return
  3390. * The cleaned class name.
  3391. */
  3392. function drupal_html_class($class) {
  3393. return drupal_clean_css_identifier(drupal_strtolower($class));
  3394. }
  3395. /**
  3396. * Prepare a string for use as a valid HTML ID and guarantee uniqueness.
  3397. *
  3398. * This function ensures that each passed HTML ID value only exists once on the
  3399. * page. By tracking the already returned ids, this function enables forms,
  3400. * blocks, and other content to be output multiple times on the same page,
  3401. * without breaking (X)HTML validation.
  3402. *
  3403. * For already existing IDs, a counter is appended to the ID string. Therefore,
  3404. * JavaScript and CSS code should not rely on any value that was generated by
  3405. * this function and instead should rely on manually added CSS classes or
  3406. * similarly reliable constructs.
  3407. *
  3408. * Two consecutive hyphens separate the counter from the original ID. To manage
  3409. * uniqueness across multiple Ajax requests on the same page, Ajax requests
  3410. * POST an array of all IDs currently present on the page, which are used to
  3411. * prime this function's cache upon first invocation.
  3412. *
  3413. * To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive
  3414. * hyphens in the originally passed $id are replaced with a single hyphen.
  3415. *
  3416. * @param $id
  3417. * The ID to clean.
  3418. *
  3419. * @return
  3420. * The cleaned ID.
  3421. */
  3422. function drupal_html_id($id) {
  3423. // If this is an Ajax request, then content returned by this page request will
  3424. // be merged with content already on the base page. The HTML IDs must be
  3425. // unique for the fully merged content. Therefore, initialize $seen_ids to
  3426. // take into account IDs that are already in use on the base page.
  3427. $seen_ids_init = &drupal_static(__FUNCTION__ . ':init');
  3428. if (!isset($seen_ids_init)) {
  3429. // Ideally, Drupal would provide an API to persist state information about
  3430. // prior page requests in the database, and we'd be able to add this
  3431. // function's $seen_ids static variable to that state information in order
  3432. // to have it properly initialized for this page request. However, no such
  3433. // page state API exists, so instead, ajax.js adds all of the in-use HTML
  3434. // IDs to the POST data of Ajax submissions. Direct use of $_POST is
  3435. // normally not recommended as it could open up security risks, but because
  3436. // the raw POST data is cast to a number before being returned by this
  3437. // function, this usage is safe.
  3438. if (empty($_POST['ajax_html_ids'])) {
  3439. $seen_ids_init = array();
  3440. }
  3441. else {
  3442. // This function ensures uniqueness by appending a counter to the base id
  3443. // requested by the calling function after the first occurrence of that
  3444. // requested id. $_POST['ajax_html_ids'] contains the ids as they were
  3445. // returned by this function, potentially with the appended counter, so
  3446. // we parse that to reconstruct the $seen_ids array.
  3447. foreach ($_POST['ajax_html_ids'] as $seen_id) {
  3448. // We rely on '--' being used solely for separating a base id from the
  3449. // counter, which this function ensures when returning an id.
  3450. $parts = explode('--', $seen_id, 2);
  3451. if (!empty($parts[1]) && is_numeric($parts[1])) {
  3452. list($seen_id, $i) = $parts;
  3453. }
  3454. else {
  3455. $i = 1;
  3456. }
  3457. if (!isset($seen_ids_init[$seen_id]) || ($i > $seen_ids_init[$seen_id])) {
  3458. $seen_ids_init[$seen_id] = $i;
  3459. }
  3460. }
  3461. }
  3462. }
  3463. $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init);
  3464. $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
  3465. // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
  3466. // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
  3467. // colons (":"), and periods ("."). We strip out any character not in that
  3468. // list. Note that the CSS spec doesn't allow colons or periods in identifiers
  3469. // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two
  3470. // characters as well.
  3471. $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id);
  3472. // Removing multiple consecutive hyphens.
  3473. $id = preg_replace('/\-+/', '-', $id);
  3474. // Ensure IDs are unique by appending a counter after the first occurrence.
  3475. // The counter needs to be appended with a delimiter that does not exist in
  3476. // the base ID. Requiring a unique delimiter helps ensure that we really do
  3477. // return unique IDs and also helps us re-create the $seen_ids array during
  3478. // Ajax requests.
  3479. if (isset($seen_ids[$id])) {
  3480. $id = $id . '--' . ++$seen_ids[$id];
  3481. }
  3482. else {
  3483. $seen_ids[$id] = 1;
  3484. }
  3485. return $id;
  3486. }
  3487. /**
  3488. * Provides a standard HTML class name that identifies a page region.
  3489. *
  3490. * It is recommended that template preprocess functions apply this class to any
  3491. * page region that is output by the theme (Drupal core already handles this in
  3492. * the standard template preprocess implementation). Standardizing the class
  3493. * names in this way allows modules to implement certain features, such as
  3494. * drag-and-drop or dynamic Ajax loading, in a theme-independent way.
  3495. *
  3496. * @param $region
  3497. * The name of the page region (for example, 'page_top' or 'content').
  3498. *
  3499. * @return
  3500. * An HTML class that identifies the region (for example, 'region-page-top'
  3501. * or 'region-content').
  3502. *
  3503. * @see template_preprocess_region()
  3504. */
  3505. function drupal_region_class($region) {
  3506. return drupal_html_class("region-$region");
  3507. }
  3508. /**
  3509. * Adds a JavaScript file, setting, or inline code to the page.
  3510. *
  3511. * The behavior of this function depends on the parameters it is called with.
  3512. * Generally, it handles the addition of JavaScript to the page, either as
  3513. * reference to an existing file or as inline code. The following actions can be
  3514. * performed using this function:
  3515. * - Add a file ('file'): Adds a reference to a JavaScript file to the page.
  3516. * - Add inline JavaScript code ('inline'): Executes a piece of JavaScript code
  3517. * on the current page by placing the code directly in the page (for example,
  3518. * to tell the user that a new message arrived, by opening a pop up, alert
  3519. * box, etc.). This should only be used for JavaScript that cannot be executed
  3520. * from a file. When adding inline code, make sure that you are not relying on
  3521. * $() being the jQuery function. Wrap your code in
  3522. * @code (function ($) {... })(jQuery); @endcode
  3523. * or use jQuery() instead of $().
  3524. * - Add external JavaScript ('external'): Allows the inclusion of external
  3525. * JavaScript files that are not hosted on the local server. Note that these
  3526. * external JavaScript references do not get aggregated when preprocessing is
  3527. * on.
  3528. * - Add settings ('setting'): Adds settings to Drupal's global storage of
  3529. * JavaScript settings. Per-page settings are required by some modules to
  3530. * function properly. All settings will be accessible at Drupal.settings.
  3531. *
  3532. * Examples:
  3533. * @code
  3534. * drupal_add_js('misc/collapse.js');
  3535. * drupal_add_js('misc/collapse.js', 'file');
  3536. * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline');
  3537. * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });',
  3538. * array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)
  3539. * );
  3540. * drupal_add_js('http://example.com/example.js', 'external');
  3541. * drupal_add_js(array('myModule' => array('key' => 'value')), 'setting');
  3542. * @endcode
  3543. *
  3544. * Calling drupal_static_reset('drupal_add_js') will clear all JavaScript added
  3545. * so far.
  3546. *
  3547. * If JavaScript aggregation is enabled, all JavaScript files added with
  3548. * $options['preprocess'] set to TRUE will be merged into one aggregate file.
  3549. * Preprocessed inline JavaScript will not be aggregated into this single file.
  3550. * Externally hosted JavaScripts are never aggregated.
  3551. *
  3552. * The reason for aggregating the files is outlined quite thoroughly here:
  3553. * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due
  3554. * to request overhead, one bigger file just loads faster than two smaller ones
  3555. * half its size."
  3556. *
  3557. * $options['preprocess'] should be only set to TRUE when a file is required for
  3558. * all typical visitors and most pages of a site. It is critical that all
  3559. * preprocessed files are added unconditionally on every page, even if the
  3560. * files are not needed on a page. This is normally done by calling
  3561. * drupal_add_js() in a hook_init() implementation.
  3562. *
  3563. * Non-preprocessed files should only be added to the page when they are
  3564. * actually needed.
  3565. *
  3566. * @param $data
  3567. * (optional) If given, the value depends on the $options parameter:
  3568. * - 'file': Path to the file relative to base_path().
  3569. * - 'inline': The JavaScript code that should be placed in the given scope.
  3570. * - 'external': The absolute path to an external JavaScript file that is not
  3571. * hosted on the local server. These files will not be aggregated if
  3572. * JavaScript aggregation is enabled.
  3573. * - 'setting': An associative array with configuration options. The array is
  3574. * merged directly into Drupal.settings. All modules should wrap their
  3575. * actual configuration settings in another variable to prevent conflicts in
  3576. * the Drupal.settings namespace. Items added with a string key will replace
  3577. * existing settings with that key; items with numeric array keys will be
  3578. * added to the existing settings array.
  3579. * @param $options
  3580. * (optional) A string defining the type of JavaScript that is being added in
  3581. * the $data parameter ('file'/'setting'/'inline'/'external'), or an
  3582. * associative array. JavaScript settings should always pass the string
  3583. * 'setting' only. Other types can have the following elements in the array:
  3584. * - type: The type of JavaScript that is to be added to the page. Allowed
  3585. * values are 'file', 'inline', 'external' or 'setting'. Defaults
  3586. * to 'file'.
  3587. * - scope: The location in which you want to place the script. Possible
  3588. * values are 'header' or 'footer'. If your theme implements different
  3589. * regions, you can also use these. Defaults to 'header'.
  3590. * - group: A number identifying the group in which to add the JavaScript.
  3591. * Available constants are:
  3592. * - JS_LIBRARY: Any libraries, settings, or jQuery plugins.
  3593. * - JS_DEFAULT: Any module-layer JavaScript.
  3594. * - JS_THEME: Any theme-layer JavaScript.
  3595. * The group number serves as a weight: JavaScript within a lower weight
  3596. * group is presented on the page before JavaScript within a higher weight
  3597. * group.
  3598. * - every_page: For optimal front-end performance when aggregation is
  3599. * enabled, this should be set to TRUE if the JavaScript is present on every
  3600. * page of the website for users for whom it is present at all. This
  3601. * defaults to FALSE. It is set to TRUE for JavaScript files that are added
  3602. * via module and theme .info files. Modules that add JavaScript within
  3603. * hook_init() implementations, or from other code that ensures that the
  3604. * JavaScript is added to all website pages, should also set this flag to
  3605. * TRUE. All JavaScript files within the same group and that have the
  3606. * 'every_page' flag set to TRUE and do not have 'preprocess' set to FALSE
  3607. * are aggregated together into a single aggregate file, and that aggregate
  3608. * file can be reused across a user's entire site visit, leading to faster
  3609. * navigation between pages. However, JavaScript that is only needed on
  3610. * pages less frequently visited, can be added by code that only runs for
  3611. * those particular pages, and that code should not set the 'every_page'
  3612. * flag. This minimizes the size of the aggregate file that the user needs
  3613. * to download when first visiting the website. JavaScript without the
  3614. * 'every_page' flag is aggregated into a separate aggregate file. This
  3615. * other aggregate file is likely to change from page to page, and each new
  3616. * aggregate file needs to be downloaded when first encountered, so it
  3617. * should be kept relatively small by ensuring that most commonly needed
  3618. * JavaScript is added to every page.
  3619. * - weight: A number defining the order in which the JavaScript is added to
  3620. * the page relative to other JavaScript with the same 'scope', 'group',
  3621. * and 'every_page' value. In some cases, the order in which the JavaScript
  3622. * is presented on the page is very important. jQuery, for example, must be
  3623. * added to the page before any jQuery code is run, so jquery.js uses the
  3624. * JS_LIBRARY group and a weight of -20, jquery.once.js (a library drupal.js
  3625. * depends on) uses the JS_LIBRARY group and a weight of -19, drupal.js uses
  3626. * the JS_LIBRARY group and a weight of -1, other libraries use the
  3627. * JS_LIBRARY group and a weight of 0 or higher, and all other scripts use
  3628. * one of the other group constants. The exact ordering of JavaScript is as
  3629. * follows:
  3630. * - First by scope, with 'header' first, 'footer' last, and any other
  3631. * scopes provided by a custom theme coming in between, as determined by
  3632. * the theme.
  3633. * - Then by group.
  3634. * - Then by the 'every_page' flag, with TRUE coming before FALSE.
  3635. * - Then by weight.
  3636. * - Then by the order in which the JavaScript was added. For example, all
  3637. * else being the same, JavaScript added by a call to drupal_add_js() that
  3638. * happened later in the page request gets added to the page after one for
  3639. * which drupal_add_js() happened earlier in the page request.
  3640. * - defer: If set to TRUE, the defer attribute is set on the &lt;script&gt;
  3641. * tag. Defaults to FALSE.
  3642. * - cache: If set to FALSE, the JavaScript file is loaded anew on every page
  3643. * call; in other words, it is not cached. Used only when 'type' references
  3644. * a JavaScript file. Defaults to TRUE.
  3645. * - preprocess: If TRUE and JavaScript aggregation is enabled, the script
  3646. * file will be aggregated. Defaults to TRUE.
  3647. *
  3648. * @return
  3649. * The current array of JavaScript files, settings, and in-line code,
  3650. * including Drupal defaults, anything previously added with calls to
  3651. * drupal_add_js(), and this function call's additions.
  3652. *
  3653. * @see drupal_get_js()
  3654. */
  3655. function drupal_add_js($data = NULL, $options = NULL) {
  3656. $javascript = &drupal_static(__FUNCTION__, array());
  3657. // Construct the options, taking the defaults into consideration.
  3658. if (isset($options)) {
  3659. if (!is_array($options)) {
  3660. $options = array('type' => $options);
  3661. }
  3662. }
  3663. else {
  3664. $options = array();
  3665. }
  3666. $options += drupal_js_defaults($data);
  3667. // Preprocess can only be set if caching is enabled.
  3668. $options['preprocess'] = $options['cache'] ? $options['preprocess'] : FALSE;
  3669. // Tweak the weight so that files of the same weight are included in the
  3670. // order of the calls to drupal_add_js().
  3671. $options['weight'] += count($javascript) / 1000;
  3672. if (isset($data)) {
  3673. // Add jquery.js and drupal.js, as well as the basePath setting, the
  3674. // first time a JavaScript file is added.
  3675. if (empty($javascript)) {
  3676. // url() generates the prefix using hook_url_outbound_alter(). Instead of
  3677. // running the hook_url_outbound_alter() again here, extract the prefix
  3678. // from url().
  3679. url('', array('prefix' => &$prefix));
  3680. $javascript = array(
  3681. 'settings' => array(
  3682. 'data' => array(
  3683. array('basePath' => base_path()),
  3684. array('pathPrefix' => empty($prefix) ? '' : $prefix),
  3685. ),
  3686. 'type' => 'setting',
  3687. 'scope' => 'header',
  3688. 'group' => JS_LIBRARY,
  3689. 'every_page' => TRUE,
  3690. 'weight' => 0,
  3691. ),
  3692. 'misc/drupal.js' => array(
  3693. 'data' => 'misc/drupal.js',
  3694. 'type' => 'file',
  3695. 'scope' => 'header',
  3696. 'group' => JS_LIBRARY,
  3697. 'every_page' => TRUE,
  3698. 'weight' => -1,
  3699. 'preprocess' => TRUE,
  3700. 'cache' => TRUE,
  3701. 'defer' => FALSE,
  3702. ),
  3703. );
  3704. // Register all required libraries.
  3705. drupal_add_library('system', 'jquery', TRUE);
  3706. drupal_add_library('system', 'jquery.once', TRUE);
  3707. }
  3708. switch ($options['type']) {
  3709. case 'setting':
  3710. // All JavaScript settings are placed in the header of the page with
  3711. // the library weight so that inline scripts appear afterwards.
  3712. $javascript['settings']['data'][] = $data;
  3713. break;
  3714. case 'inline':
  3715. $javascript[] = $options;
  3716. break;
  3717. default: // 'file' and 'external'
  3718. // Local and external files must keep their name as the associative key
  3719. // so the same JavaScript file is not added twice.
  3720. $javascript[$options['data']] = $options;
  3721. }
  3722. }
  3723. return $javascript;
  3724. }
  3725. /**
  3726. * Constructs an array of the defaults that are used for JavaScript items.
  3727. *
  3728. * @param $data
  3729. * (optional) The default data parameter for the JavaScript item array.
  3730. * @see drupal_get_js()
  3731. * @see drupal_add_js()
  3732. */
  3733. function drupal_js_defaults($data = NULL) {
  3734. return array(
  3735. 'type' => 'file',
  3736. 'group' => JS_DEFAULT,
  3737. 'every_page' => FALSE,
  3738. 'weight' => 0,
  3739. 'scope' => 'header',
  3740. 'cache' => TRUE,
  3741. 'defer' => FALSE,
  3742. 'preprocess' => TRUE,
  3743. 'version' => NULL,
  3744. 'data' => $data,
  3745. );
  3746. }
  3747. /**
  3748. * Returns a themed presentation of all JavaScript code for the current page.
  3749. *
  3750. * References to JavaScript files are placed in a certain order: first, all
  3751. * 'core' files, then all 'module' and finally all 'theme' JavaScript files
  3752. * are added to the page. Then, all settings are output, followed by 'inline'
  3753. * JavaScript code. If running update.php, all preprocessing is disabled.
  3754. *
  3755. * Note that hook_js_alter(&$javascript) is called during this function call
  3756. * to allow alterations of the JavaScript during its presentation. Calls to
  3757. * drupal_add_js() from hook_js_alter() will not be added to the output
  3758. * presentation. The correct way to add JavaScript during hook_js_alter()
  3759. * is to add another element to the $javascript array, deriving from
  3760. * drupal_js_defaults(). See locale_js_alter() for an example of this.
  3761. *
  3762. * @param $scope
  3763. * (optional) The scope for which the JavaScript rules should be returned.
  3764. * Defaults to 'header'.
  3765. * @param $javascript
  3766. * (optional) An array with all JavaScript code. Defaults to the default
  3767. * JavaScript array for the given scope.
  3768. * @param $skip_alter
  3769. * (optional) If set to TRUE, this function skips calling drupal_alter() on
  3770. * $javascript, useful when the calling function passes a $javascript array
  3771. * that has already been altered.
  3772. * @return
  3773. * All JavaScript code segments and includes for the scope as HTML tags.
  3774. * @see drupal_add_js()
  3775. * @see locale_js_alter()
  3776. * @see drupal_js_defaults()
  3777. */
  3778. function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE) {
  3779. if (!isset($javascript)) {
  3780. $javascript = drupal_add_js();
  3781. }
  3782. if (empty($javascript)) {
  3783. return '';
  3784. }
  3785. // Allow modules to alter the JavaScript.
  3786. if (!$skip_alter) {
  3787. drupal_alter('js', $javascript);
  3788. }
  3789. // Filter out elements of the given scope.
  3790. $items = array();
  3791. foreach ($javascript as $key => $item) {
  3792. if ($item['scope'] == $scope) {
  3793. $items[$key] = $item;
  3794. }
  3795. }
  3796. $output = '';
  3797. // The index counter is used to keep aggregated and non-aggregated files in
  3798. // order by weight.
  3799. $index = 1;
  3800. $processed = array();
  3801. $files = array();
  3802. $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
  3803. // A dummy query-string is added to filenames, to gain control over
  3804. // browser-caching. The string changes on every update or full cache
  3805. // flush, forcing browsers to load a new copy of the files, as the
  3806. // URL changed. Files that should not be cached (see drupal_add_js())
  3807. // get REQUEST_TIME as query-string instead, to enforce reload on every
  3808. // page request.
  3809. $default_query_string = variable_get('css_js_query_string', '0');
  3810. // For inline JavaScript to validate as XHTML, all JavaScript containing
  3811. // XHTML needs to be wrapped in CDATA. To make that backwards compatible
  3812. // with HTML 4, we need to comment out the CDATA-tag.
  3813. $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
  3814. $embed_suffix = "\n//--><!]]>\n";
  3815. // Since JavaScript may look for arguments in the URL and act on them, some
  3816. // third-party code might require the use of a different query string.
  3817. $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
  3818. // Sort the JavaScript so that it appears in the correct order.
  3819. uasort($items, 'drupal_sort_css_js');
  3820. // Provide the page with information about the individual JavaScript files
  3821. // used, information not otherwise available when aggregation is enabled.
  3822. $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1);
  3823. unset($setting['ajaxPageState']['js']['settings']);
  3824. drupal_add_js($setting, 'setting');
  3825. // If we're outputting the header scope, then this might be the final time
  3826. // that drupal_get_js() is running, so add the setting to this output as well
  3827. // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's
  3828. // because drupal_get_js() was intentionally passed a $javascript argument
  3829. // stripped off settings, potentially in order to override how settings get
  3830. // output, so in this case, do not add the setting to this output.
  3831. if ($scope == 'header' && isset($items['settings'])) {
  3832. $items['settings']['data'][] = $setting;
  3833. }
  3834. // Loop through the JavaScript to construct the rendered output.
  3835. $element = array(
  3836. '#tag' => 'script',
  3837. '#value' => '',
  3838. '#attributes' => array(
  3839. 'type' => 'text/javascript',
  3840. ),
  3841. );
  3842. foreach ($items as $item) {
  3843. $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version'];
  3844. switch ($item['type']) {
  3845. case 'setting':
  3846. $js_element = $element;
  3847. $js_element['#value_prefix'] = $embed_prefix;
  3848. $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
  3849. $js_element['#value_suffix'] = $embed_suffix;
  3850. $output .= theme('html_tag', array('element' => $js_element));
  3851. break;
  3852. case 'inline':
  3853. $js_element = $element;
  3854. if ($item['defer']) {
  3855. $js_element['#attributes']['defer'] = 'defer';
  3856. }
  3857. $js_element['#value_prefix'] = $embed_prefix;
  3858. $js_element['#value'] = $item['data'];
  3859. $js_element['#value_suffix'] = $embed_suffix;
  3860. $processed[$index++] = theme('html_tag', array('element' => $js_element));
  3861. break;
  3862. case 'file':
  3863. $js_element = $element;
  3864. if (!$item['preprocess'] || !$preprocess_js) {
  3865. if ($item['defer']) {
  3866. $js_element['#attributes']['defer'] = 'defer';
  3867. }
  3868. $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
  3869. $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
  3870. $processed[$index++] = theme('html_tag', array('element' => $js_element));
  3871. }
  3872. else {
  3873. // By increasing the index for each aggregated file, we maintain
  3874. // the relative ordering of JS by weight. We also set the key such
  3875. // that groups are split by items sharing the same 'group' value and
  3876. // 'every_page' flag. While this potentially results in more aggregate
  3877. // files, it helps make each one more reusable across a site visit,
  3878. // leading to better front-end performance of a website as a whole.
  3879. // See drupal_add_js() for details.
  3880. $key = 'aggregate_' . $item['group'] . '_' . $item['every_page'] . '_' . $index;
  3881. $processed[$key] = '';
  3882. $files[$key][$item['data']] = $item;
  3883. }
  3884. break;
  3885. case 'external':
  3886. $js_element = $element;
  3887. // Preprocessing for external JavaScript files is ignored.
  3888. if ($item['defer']) {
  3889. $js_element['#attributes']['defer'] = 'defer';
  3890. }
  3891. $js_element['#attributes']['src'] = $item['data'];
  3892. $processed[$index++] = theme('html_tag', array('element' => $js_element));
  3893. break;
  3894. }
  3895. }
  3896. // Aggregate any remaining JS files that haven't already been output.
  3897. if ($preprocess_js && count($files) > 0) {
  3898. foreach ($files as $key => $file_set) {
  3899. $uri = drupal_build_js_cache($file_set);
  3900. // Only include the file if was written successfully. Errors are logged
  3901. // using watchdog.
  3902. if ($uri) {
  3903. $preprocess_file = file_create_url($uri);
  3904. $js_element = $element;
  3905. $js_element['#attributes']['src'] = $preprocess_file;
  3906. $processed[$key] = theme('html_tag', array('element' => $js_element));
  3907. }
  3908. }
  3909. }
  3910. // Keep the order of JS files consistent as some are preprocessed and others are not.
  3911. // Make sure any inline or JS setting variables appear last after libraries have loaded.
  3912. return implode('', $processed) . $output;
  3913. }
  3914. /**
  3915. * Adds attachments to a render() structure.
  3916. *
  3917. * Libraries, JavaScript, CSS and other types of custom structures are attached
  3918. * to elements using the #attached property. The #attached property is an
  3919. * associative array, where the keys are the the attachment types and the values
  3920. * are the attached data. For example:
  3921. * @code
  3922. * $build['#attached'] = array(
  3923. * 'js' => array(drupal_get_path('module', 'taxonomy') . '/taxonomy.js'),
  3924. * 'css' => array(drupal_get_path('module', 'taxonomy') . '/taxonomy.css'),
  3925. * );
  3926. * @endcode
  3927. *
  3928. * 'js', 'css', and 'library' are types that get special handling. For any
  3929. * other kind of attached data, the array key must be the full name of the
  3930. * callback function and each value an array of arguments. For example:
  3931. * @code
  3932. * $build['#attached']['drupal_add_http_header'] = array(
  3933. * array('Content-Type', 'application/rss+xml; charset=utf-8'),
  3934. * );
  3935. * @endcode
  3936. *
  3937. * External 'js' and 'css' files can also be loaded. For example:
  3938. * @code
  3939. * $build['#attached']['js'] = array(
  3940. * 'http://code.jquery.com/jquery-1.4.2.min.js' => array(
  3941. * 'type' => 'external',
  3942. * ),
  3943. * );
  3944. * @endcode
  3945. *
  3946. * @param $elements
  3947. * The structured array describing the data being rendered.
  3948. * @param $group
  3949. * The default group of JavaScript and CSS being added. This is only applied
  3950. * to the stylesheets and JavaScript items that don't have an explicit group
  3951. * assigned to them.
  3952. * @param $dependency_check
  3953. * When TRUE, will exit if a given library's dependencies are missing. When
  3954. * set to FALSE, will continue to add the libraries, even though one or more
  3955. * dependencies are missing. Defaults to FALSE.
  3956. * @param $every_page
  3957. * Set to TRUE to indicate that the attachments are added to every page on the
  3958. * site. Only attachments with the every_page flag set to TRUE can participate
  3959. * in JavaScript/CSS aggregation.
  3960. *
  3961. * @return
  3962. * FALSE if there were any missing library dependencies; TRUE if all library
  3963. * dependencies were met.
  3964. *
  3965. * @see drupal_add_library()
  3966. * @see drupal_add_js()
  3967. * @see drupal_add_css()
  3968. * @see drupal_render()
  3969. */
  3970. function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_check = FALSE, $every_page = NULL) {
  3971. // Add defaults to the special attached structures that should be processed differently.
  3972. $elements['#attached'] += array(
  3973. 'library' => array(),
  3974. 'js' => array(),
  3975. 'css' => array(),
  3976. );
  3977. // Add the libraries first.
  3978. $success = TRUE;
  3979. foreach ($elements['#attached']['library'] as $library) {
  3980. if (drupal_add_library($library[0], $library[1], $every_page) === FALSE) {
  3981. $success = FALSE;
  3982. // Exit if the dependency is missing.
  3983. if ($dependency_check) {
  3984. return $success;
  3985. }
  3986. }
  3987. }
  3988. unset($elements['#attached']['library']);
  3989. // Add both the JavaScript and the CSS.
  3990. // The parameters for drupal_add_js() and drupal_add_css() require special
  3991. // handling.
  3992. foreach (array('js', 'css') as $type) {
  3993. foreach ($elements['#attached'][$type] as $data => $options) {
  3994. // If the value is not an array, it's a filename and passed as first
  3995. // (and only) argument.
  3996. if (!is_array($options)) {
  3997. $data = $options;
  3998. $options = NULL;
  3999. }
  4000. // In some cases, the first parameter ($data) is an array. Arrays can't be
  4001. // passed as keys in PHP, so we have to get $data from the value array.
  4002. if (is_numeric($data)) {
  4003. $data = $options['data'];
  4004. unset($options['data']);
  4005. }
  4006. // Apply the default group if it isn't explicitly given.
  4007. if (!isset($options['group'])) {
  4008. $options['group'] = $group;
  4009. }
  4010. // Set the every_page flag if one was passed.
  4011. if (isset($every_page)) {
  4012. $options['every_page'] = $every_page;
  4013. }
  4014. call_user_func('drupal_add_' . $type, $data, $options);
  4015. }
  4016. unset($elements['#attached'][$type]);
  4017. }
  4018. // Add additional types of attachments specified in the render() structure.
  4019. // Libraries, JavaScript and CSS have been added already, as they require
  4020. // special handling.
  4021. foreach ($elements['#attached'] as $callback => $options) {
  4022. if (function_exists($callback)) {
  4023. foreach ($elements['#attached'][$callback] as $args) {
  4024. call_user_func_array($callback, $args);
  4025. }
  4026. }
  4027. }
  4028. return $success;
  4029. }
  4030. /**
  4031. * Adds JavaScript to change the state of an element based on another element.
  4032. *
  4033. * A "state" means a certain property on a DOM element, such as "visible" or
  4034. * "checked". A state can be applied to an element, depending on the state of
  4035. * another element on the page. In general, states depend on HTML attributes and
  4036. * DOM element properties, which change due to user interaction.
  4037. *
  4038. * Since states are driven by JavaScript only, it is important to understand
  4039. * that all states are applied on presentation only, none of the states force
  4040. * any server-side logic, and that they will not be applied for site visitors
  4041. * without JavaScript support. All modules implementing states have to make
  4042. * sure that the intended logic also works without JavaScript being enabled.
  4043. *
  4044. * #states is an associative array in the form of:
  4045. * @code
  4046. * array(
  4047. * STATE1 => CONDITIONS_ARRAY1,
  4048. * STATE2 => CONDITIONS_ARRAY2,
  4049. * ...
  4050. * )
  4051. * @endcode
  4052. * Each key is the name of a state to apply to the element, such as 'visible'.
  4053. * Each value is a list of conditions that denote when the state should be
  4054. * applied.
  4055. *
  4056. * Multiple different states may be specified to act on complex conditions:
  4057. * @code
  4058. * array(
  4059. * 'visible' => CONDITIONS,
  4060. * 'checked' => OTHER_CONDITIONS,
  4061. * )
  4062. * @endcode
  4063. *
  4064. * Every condition is a key/value pair, whose key is a jQuery selector that
  4065. * denotes another element on the page, and whose value is an array of
  4066. * conditions, which must bet met on that element:
  4067. * @code
  4068. * array(
  4069. * 'visible' => array(
  4070. * JQUERY_SELECTOR => REMOTE_CONDITIONS,
  4071. * JQUERY_SELECTOR => REMOTE_CONDITIONS,
  4072. * ...
  4073. * ),
  4074. * )
  4075. * @endcode
  4076. * All conditions must be met for the state to be applied.
  4077. *
  4078. * Each remote condition is a key/value pair specifying conditions on the other
  4079. * element that need to be met to apply the state to the element:
  4080. * @code
  4081. * array(
  4082. * 'visible' => array(
  4083. * ':input[name="remote_checkbox"]' => array('checked' => TRUE),
  4084. * ),
  4085. * )
  4086. * @endcode
  4087. *
  4088. * For example, to show a textfield only when a checkbox is checked:
  4089. * @code
  4090. * $form['toggle_me'] = array(
  4091. * '#type' => 'checkbox',
  4092. * '#title' => t('Tick this box to type'),
  4093. * );
  4094. * $form['settings'] = array(
  4095. * '#type' => 'textfield',
  4096. * '#states' => array(
  4097. * // Only show this field when the 'toggle_me' checkbox is enabled.
  4098. * 'visible' => array(
  4099. * ':input[name="toggle_me"]' => array('checked' => TRUE),
  4100. * ),
  4101. * ),
  4102. * );
  4103. * @endcode
  4104. *
  4105. * The following states may be applied to an element:
  4106. * - enabled
  4107. * - disabled
  4108. * - required
  4109. * - optional
  4110. * - visible
  4111. * - invisible
  4112. * - checked
  4113. * - unchecked
  4114. * - expanded
  4115. * - collapsed
  4116. *
  4117. * The following states may be used in remote conditions:
  4118. * - empty
  4119. * - filled
  4120. * - checked
  4121. * - unchecked
  4122. * - expanded
  4123. * - collapsed
  4124. * - value
  4125. *
  4126. * The following states exist for both elements and remote conditions, but are
  4127. * not fully implemented and may not change anything on the element:
  4128. * - relevant
  4129. * - irrelevant
  4130. * - valid
  4131. * - invalid
  4132. * - touched
  4133. * - untouched
  4134. * - readwrite
  4135. * - readonly
  4136. *
  4137. * When referencing select lists and radio buttons in remote conditions, a
  4138. * 'value' condition must be used:
  4139. * @code
  4140. * '#states' => array(
  4141. * // Show the settings if 'bar' has been selected for 'foo'.
  4142. * 'visible' => array(
  4143. * ':input[name="foo"]' => array('value' => 'bar'),
  4144. * ),
  4145. * ),
  4146. * @endcode
  4147. *
  4148. * @param $elements
  4149. * A renderable array element having a #states property as described above.
  4150. *
  4151. * @see form_example_states_form()
  4152. */
  4153. function drupal_process_states(&$elements) {
  4154. $elements['#attached']['library'][] = array('system', 'drupal.states');
  4155. $elements['#attached']['js'][] = array(
  4156. 'type' => 'setting',
  4157. 'data' => array('states' => array('#' . $elements['#id'] => $elements['#states'])),
  4158. );
  4159. }
  4160. /**
  4161. * Adds multiple JavaScript or CSS files at the same time.
  4162. *
  4163. * A library defines a set of JavaScript and/or CSS files, optionally using
  4164. * settings, and optionally requiring another library. For example, a library
  4165. * can be a jQuery plugin, a JavaScript framework, or a CSS framework. This
  4166. * function allows modules to load a library defined/shipped by itself or a
  4167. * depending module, without having to add all files of the library separately.
  4168. * Each library is only loaded once.
  4169. *
  4170. * @param $module
  4171. * The name of the module that registered the library.
  4172. * @param $name
  4173. * The name of the library to add.
  4174. * @param $every_page
  4175. * Set to TRUE if this library is added to every page on the site. Only items
  4176. * with the every_page flag set to TRUE can participate in aggregation.
  4177. *
  4178. * @return
  4179. * TRUE if the library was successfully added; FALSE if the library or one of
  4180. * its dependencies could not be added.
  4181. *
  4182. * @see drupal_get_library()
  4183. * @see hook_library()
  4184. * @see hook_library_alter()
  4185. */
  4186. function drupal_add_library($module, $name, $every_page = NULL) {
  4187. $added = &drupal_static(__FUNCTION__, array());
  4188. // Only process the library if it exists and it was not added already.
  4189. if (!isset($added[$module][$name])) {
  4190. if ($library = drupal_get_library($module, $name)) {
  4191. // Add all components within the library.
  4192. $elements['#attached'] = array(
  4193. 'library' => $library['dependencies'],
  4194. 'js' => $library['js'],
  4195. 'css' => $library['css'],
  4196. );
  4197. $added[$module][$name] = drupal_process_attached($elements, JS_LIBRARY, TRUE, $every_page);
  4198. }
  4199. else {
  4200. // Requested library does not exist.
  4201. $added[$module][$name] = FALSE;
  4202. }
  4203. }
  4204. return $added[$module][$name];
  4205. }
  4206. /**
  4207. * Retrieves information for a JavaScript/CSS library.
  4208. *
  4209. * Library information is statically cached. Libraries are keyed by module for
  4210. * several reasons:
  4211. * - Libraries are not unique. Multiple modules might ship with the same library
  4212. * in a different version or variant. This registry cannot (and does not
  4213. * attempt to) prevent library conflicts.
  4214. * - Modules implementing and thereby depending on a library that is registered
  4215. * by another module can only rely on that module's library.
  4216. * - Two (or more) modules can still register the same library and use it
  4217. * without conflicts in case the libraries are loaded on certain pages only.
  4218. *
  4219. * @param $module
  4220. * The name of a module that registered a library.
  4221. * @param $name
  4222. * (optional) The name of a registered library to retrieve. By default, all
  4223. * libraries registered by $module are returned.
  4224. *
  4225. * @return
  4226. * The definition of the requested library, if $name was passed and it exists,
  4227. * or FALSE if it does not exist. If no $name was passed, an associative array
  4228. * of libraries registered by $module is returned (which may be empty).
  4229. *
  4230. * @see drupal_add_library()
  4231. * @see hook_library()
  4232. * @see hook_library_alter()
  4233. *
  4234. * @todo The purpose of drupal_get_*() is completely different to other page
  4235. * requisite API functions; find and use a different name.
  4236. */
  4237. function drupal_get_library($module, $name = NULL) {
  4238. $libraries = &drupal_static(__FUNCTION__, array());
  4239. if (!isset($libraries[$module])) {
  4240. // Retrieve all libraries associated with the module.
  4241. $module_libraries = module_invoke($module, 'library');
  4242. if (empty($module_libraries)) {
  4243. $module_libraries = array();
  4244. }
  4245. // Allow modules to alter the module's registered libraries.
  4246. drupal_alter('library', $module_libraries, $module);
  4247. foreach ($module_libraries as $key => $data) {
  4248. if (is_array($data)) {
  4249. // Add default elements to allow for easier processing.
  4250. $module_libraries[$key] += array('dependencies' => array(), 'js' => array(), 'css' => array());
  4251. foreach ($module_libraries[$key]['js'] as $file => $options) {
  4252. $module_libraries[$key]['js'][$file]['version'] = $module_libraries[$key]['version'];
  4253. }
  4254. }
  4255. }
  4256. $libraries[$module] = $module_libraries;
  4257. }
  4258. if (isset($name)) {
  4259. if (!isset($libraries[$module][$name])) {
  4260. $libraries[$module][$name] = FALSE;
  4261. }
  4262. return $libraries[$module][$name];
  4263. }
  4264. return $libraries[$module];
  4265. }
  4266. /**
  4267. * Assist in adding the tableDrag JavaScript behavior to a themed table.
  4268. *
  4269. * Draggable tables should be used wherever an outline or list of sortable items
  4270. * needs to be arranged by an end-user. Draggable tables are very flexible and
  4271. * can manipulate the value of form elements placed within individual columns.
  4272. *
  4273. * To set up a table to use drag and drop in place of weight select-lists or
  4274. * in place of a form that contains parent relationships, the form must be
  4275. * themed into a table. The table must have an id attribute set. If using
  4276. * theme_table(), the id may be set as such:
  4277. * @code
  4278. * $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'my-module-table')));
  4279. * return $output;
  4280. * @endcode
  4281. *
  4282. * In the theme function for the form, a special class must be added to each
  4283. * form element within the same column, "grouping" them together.
  4284. *
  4285. * In a situation where a single weight column is being sorted in the table, the
  4286. * classes could be added like this (in the theme function):
  4287. * @code
  4288. * $form['my_elements'][$delta]['weight']['#attributes']['class'] = array('my-elements-weight');
  4289. * @endcode
  4290. *
  4291. * Each row of the table must also have a class of "draggable" in order to enable the
  4292. * drag handles:
  4293. * @code
  4294. * $row = array(...);
  4295. * $rows[] = array(
  4296. * 'data' => $row,
  4297. * 'class' => array('draggable'),
  4298. * );
  4299. * @endcode
  4300. *
  4301. * When tree relationships are present, the two additional classes
  4302. * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior:
  4303. * - Rows with the 'tabledrag-leaf' class cannot have child rows.
  4304. * - Rows with the 'tabledrag-root' class cannot be nested under a parent row.
  4305. *
  4306. * Calling drupal_add_tabledrag() would then be written as such:
  4307. * @code
  4308. * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight');
  4309. * @endcode
  4310. *
  4311. * In a more complex case where there are several groups in one column (such as
  4312. * the block regions on the admin/structure/block page), a separate subgroup class
  4313. * must also be added to differentiate the groups.
  4314. * @code
  4315. * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = array('my-elements-weight', 'my-elements-weight-' . $region);
  4316. * @endcode
  4317. *
  4318. * $group is still 'my-element-weight', and the additional $subgroup variable
  4319. * will be passed in as 'my-elements-weight-' . $region. This also means that
  4320. * you'll need to call drupal_add_tabledrag() once for every region added.
  4321. *
  4322. * @code
  4323. * foreach ($regions as $region) {
  4324. * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight', 'my-elements-weight-' . $region);
  4325. * }
  4326. * @endcode
  4327. *
  4328. * In a situation where tree relationships are present, adding multiple
  4329. * subgroups is not necessary, because the table will contain indentations that
  4330. * provide enough information about the sibling and parent relationships.
  4331. * See theme_menu_overview_form() for an example creating a table containing
  4332. * parent relationships.
  4333. *
  4334. * Please note that this function should be called from the theme layer, such as
  4335. * in a .tpl.php file, theme_ function, or in a template_preprocess function,
  4336. * not in a form declaration. Though the same JavaScript could be added to the
  4337. * page using drupal_add_js() directly, this function helps keep template files
  4338. * clean and readable. It also prevents tabledrag.js from being added twice
  4339. * accidentally.
  4340. *
  4341. * @param $table_id
  4342. * String containing the target table's id attribute. If the table does not
  4343. * have an id, one will need to be set, such as <table id="my-module-table">.
  4344. * @param $action
  4345. * String describing the action to be done on the form item. Either 'match'
  4346. * 'depth', or 'order'. Match is typically used for parent relationships.
  4347. * Order is typically used to set weights on other form elements with the same
  4348. * group. Depth updates the target element with the current indentation.
  4349. * @param $relationship
  4350. * String describing where the $action variable should be performed. Either
  4351. * 'parent', 'sibling', 'group', or 'self'. Parent will only look for fields
  4352. * up the tree. Sibling will look for fields in the same group in rows above
  4353. * and below it. Self affects the dragged row itself. Group affects the
  4354. * dragged row, plus any children below it (the entire dragged group).
  4355. * @param $group
  4356. * A class name applied on all related form elements for this action.
  4357. * @param $subgroup
  4358. * (optional) If the group has several subgroups within it, this string should
  4359. * contain the class name identifying fields in the same subgroup.
  4360. * @param $source
  4361. * (optional) If the $action is 'match', this string should contain the class
  4362. * name identifying what field will be used as the source value when matching
  4363. * the value in $subgroup.
  4364. * @param $hidden
  4365. * (optional) The column containing the field elements may be entirely hidden
  4366. * from view dynamically when the JavaScript is loaded. Set to FALSE if the
  4367. * column should not be hidden.
  4368. * @param $limit
  4369. * (optional) Limit the maximum amount of parenting in this table.
  4370. * @see block-admin-display-form.tpl.php
  4371. * @see theme_menu_overview_form()
  4372. */
  4373. function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) {
  4374. $js_added = &drupal_static(__FUNCTION__, FALSE);
  4375. if (!$js_added) {
  4376. // Add the table drag JavaScript to the page before the module JavaScript
  4377. // to ensure that table drag behaviors are registered before any module
  4378. // uses it.
  4379. drupal_add_library('system', 'jquery.cookie');
  4380. drupal_add_js('misc/tabledrag.js', array('weight' => -1));
  4381. $js_added = TRUE;
  4382. }
  4383. // If a subgroup or source isn't set, assume it is the same as the group.
  4384. $target = isset($subgroup) ? $subgroup : $group;
  4385. $source = isset($source) ? $source : $target;
  4386. $settings['tableDrag'][$table_id][$group][] = array(
  4387. 'target' => $target,
  4388. 'source' => $source,
  4389. 'relationship' => $relationship,
  4390. 'action' => $action,
  4391. 'hidden' => $hidden,
  4392. 'limit' => $limit,
  4393. );
  4394. drupal_add_js($settings, 'setting');
  4395. }
  4396. /**
  4397. * Aggregates JavaScript files into a cache file in the files directory.
  4398. *
  4399. * The file name for the JavaScript cache file is generated from the hash of
  4400. * the aggregated contents of the files in $files. This forces proxies and
  4401. * browsers to download new JavaScript when the JavaScript changes.
  4402. *
  4403. * The cache file name is retrieved on a page load via a lookup variable that
  4404. * contains an associative array. The array key is the hash of the names in
  4405. * $files while the value is the cache file name. The cache file is generated
  4406. * in two cases. First, if there is no file name value for the key, which will
  4407. * happen if a new file name has been added to $files or after the lookup
  4408. * variable is emptied to force a rebuild of the cache. Second, the cache
  4409. * file is generated if it is missing on disk. Old cache files are not deleted
  4410. * immediately when the lookup variable is emptied, but are deleted after a set
  4411. * period by drupal_delete_file_if_stale(). This ensures that files referenced
  4412. * by a cached page will still be available.
  4413. *
  4414. * @param $files
  4415. * An array of JavaScript files to aggregate and compress into one file.
  4416. *
  4417. * @return
  4418. * The URI of the cache file, or FALSE if the file could not be saved.
  4419. */
  4420. function drupal_build_js_cache($files) {
  4421. $contents = '';
  4422. $uri = '';
  4423. $map = variable_get('drupal_js_cache_files', array());
  4424. $key = hash('sha256', serialize($files));
  4425. if (isset($map[$key])) {
  4426. $uri = $map[$key];
  4427. }
  4428. if (empty($uri) || !file_exists($uri)) {
  4429. // Build aggregate JS file.
  4430. foreach ($files as $path => $info) {
  4431. if ($info['preprocess']) {
  4432. // Append a ';' and a newline after each JS file to prevent them from running together.
  4433. $contents .= file_get_contents($path) . ";\n";
  4434. }
  4435. }
  4436. // Prefix filename to prevent blocking by firewalls which reject files
  4437. // starting with "ad*".
  4438. $filename = 'js_' . drupal_hash_base64($contents) . '.js';
  4439. // Create the js/ within the files folder.
  4440. $jspath = 'public://js';
  4441. $uri = $jspath . '/' . $filename;
  4442. // Create the JS file.
  4443. file_prepare_directory($jspath, FILE_CREATE_DIRECTORY);
  4444. if (!file_exists($uri) && !file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) {
  4445. return FALSE;
  4446. }
  4447. // If JS gzip compression is enabled, clean URLs are enabled (which means
  4448. // that rewrite rules are working) and the zlib extension is available then
  4449. // create a gzipped version of this file. This file is served conditionally
  4450. // to browsers that accept gzip using .htaccess rules.
  4451. if (variable_get('js_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
  4452. if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($contents, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) {
  4453. return FALSE;
  4454. }
  4455. }
  4456. $map[$key] = $uri;
  4457. variable_set('drupal_js_cache_files', $map);
  4458. }
  4459. return $uri;
  4460. }
  4461. /**
  4462. * Deletes old cached JavaScript files and variables.
  4463. */
  4464. function drupal_clear_js_cache() {
  4465. variable_del('javascript_parsed');
  4466. variable_del('drupal_js_cache_files');
  4467. file_scan_directory('public://js', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
  4468. }
  4469. /**
  4470. * Converts a PHP variable into its JavaScript equivalent.
  4471. *
  4472. * We use HTML-safe strings, i.e. with <, > and & escaped.
  4473. *
  4474. * @see drupal_json_decode()
  4475. * @ingroup php_wrappers
  4476. */
  4477. function drupal_json_encode($var) {
  4478. // json_encode() does not escape <, > and &, so we do it with str_replace().
  4479. return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), json_encode($var));
  4480. }
  4481. /**
  4482. * Converts an HTML-safe JSON string into its PHP equivalent.
  4483. *
  4484. * @see drupal_json_encode()
  4485. * @ingroup php_wrappers
  4486. */
  4487. function drupal_json_decode($var) {
  4488. return json_decode($var, TRUE);
  4489. }
  4490. /**
  4491. * Return data in JSON format.
  4492. *
  4493. * This function should be used for JavaScript callback functions returning
  4494. * data in JSON format. It sets the header for JavaScript output.
  4495. *
  4496. * @param $var
  4497. * (optional) If set, the variable will be converted to JSON and output.
  4498. */
  4499. function drupal_json_output($var = NULL) {
  4500. // We are returning JSON, so tell the browser.
  4501. drupal_add_http_header('Content-Type', 'application/json');
  4502. if (isset($var)) {
  4503. echo drupal_json_encode($var);
  4504. }
  4505. }
  4506. /**
  4507. * Get a salt useful for hardening against SQL injection.
  4508. *
  4509. * @return
  4510. * A salt based on information in settings.php, not in the database.
  4511. */
  4512. function drupal_get_hash_salt() {
  4513. global $drupal_hash_salt, $databases;
  4514. // If the $drupal_hash_salt variable is empty, a hash of the serialized
  4515. // database credentials is used as a fallback salt.
  4516. return empty($drupal_hash_salt) ? hash('sha256', serialize($databases)) : $drupal_hash_salt;
  4517. }
  4518. /**
  4519. * Ensure the private key variable used to generate tokens is set.
  4520. *
  4521. * @return
  4522. * The private key.
  4523. */
  4524. function drupal_get_private_key() {
  4525. if (!($key = variable_get('drupal_private_key', 0))) {
  4526. $key = drupal_hash_base64(drupal_random_bytes(55));
  4527. variable_set('drupal_private_key', $key);
  4528. }
  4529. return $key;
  4530. }
  4531. /**
  4532. * Generate a token based on $value, the current user session and private key.
  4533. *
  4534. * @param $value
  4535. * An additional value to base the token on.
  4536. */
  4537. function drupal_get_token($value = '') {
  4538. return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt());
  4539. }
  4540. /**
  4541. * Validate a token based on $value, the current user session and private key.
  4542. *
  4543. * @param $token
  4544. * The token to be validated.
  4545. * @param $value
  4546. * An additional value to base the token on.
  4547. * @param $skip_anonymous
  4548. * Set to true to skip token validation for anonymous users.
  4549. * @return
  4550. * True for a valid token, false for an invalid token. When $skip_anonymous
  4551. * is true, the return value will always be true for anonymous users.
  4552. */
  4553. function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
  4554. global $user;
  4555. return (($skip_anonymous && $user->uid == 0) || ($token == drupal_get_token($value)));
  4556. }
  4557. function _drupal_bootstrap_full() {
  4558. static $called = FALSE;
  4559. if ($called) {
  4560. return;
  4561. }
  4562. $called = TRUE;
  4563. require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc');
  4564. require_once DRUPAL_ROOT . '/includes/theme.inc';
  4565. require_once DRUPAL_ROOT . '/includes/pager.inc';
  4566. require_once DRUPAL_ROOT . '/' . variable_get('menu_inc', 'includes/menu.inc');
  4567. require_once DRUPAL_ROOT . '/includes/tablesort.inc';
  4568. require_once DRUPAL_ROOT . '/includes/file.inc';
  4569. require_once DRUPAL_ROOT . '/includes/unicode.inc';
  4570. require_once DRUPAL_ROOT . '/includes/image.inc';
  4571. require_once DRUPAL_ROOT . '/includes/form.inc';
  4572. require_once DRUPAL_ROOT . '/includes/mail.inc';
  4573. require_once DRUPAL_ROOT . '/includes/actions.inc';
  4574. require_once DRUPAL_ROOT . '/includes/ajax.inc';
  4575. require_once DRUPAL_ROOT . '/includes/token.inc';
  4576. require_once DRUPAL_ROOT . '/includes/errors.inc';
  4577. // Detect string handling method
  4578. unicode_check();
  4579. // Undo magic quotes
  4580. fix_gpc_magic();
  4581. // Load all enabled modules
  4582. module_load_all();
  4583. // Make sure all stream wrappers are registered.
  4584. file_get_stream_wrappers();
  4585. $test_info = &$GLOBALS['drupal_test_info'];
  4586. if (!empty($test_info['in_child_site'])) {
  4587. // Running inside the simpletest child site, log fatal errors to test
  4588. // specific file directory.
  4589. ini_set('log_errors', 1);
  4590. ini_set('error_log', 'public://error.log');
  4591. }
  4592. // Initialize $_GET['q'] prior to invoking hook_init().
  4593. drupal_path_initialize();
  4594. // Let all modules take action before the menu system handles the request.
  4595. // We do not want this while running update.php.
  4596. if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
  4597. // Prior to invoking hook_init(), initialize the theme (potentially a custom
  4598. // one for this page), so that:
  4599. // - Modules with hook_init() implementations that call theme() or
  4600. // theme_get_registry() don't initialize the incorrect theme.
  4601. // - The theme can have hook_*_alter() implementations affect page building
  4602. // (e.g., hook_form_alter(), hook_node_view_alter(), hook_page_alter()),
  4603. // ahead of when rendering starts.
  4604. menu_set_custom_theme();
  4605. drupal_theme_initialize();
  4606. module_invoke_all('init');
  4607. }
  4608. }
  4609. /**
  4610. * Store the current page in the cache.
  4611. *
  4612. * If page_compression is enabled, a gzipped version of the page is stored in
  4613. * the cache to avoid compressing the output on each request. The cache entry
  4614. * is unzipped in the relatively rare event that the page is requested by a
  4615. * client without gzip support.
  4616. *
  4617. * Page compression requires the PHP zlib extension
  4618. * (http://php.net/manual/en/ref.zlib.php).
  4619. *
  4620. * @see drupal_page_header()
  4621. */
  4622. function drupal_page_set_cache() {
  4623. global $base_root;
  4624. if (drupal_page_is_cacheable()) {
  4625. $cache = (object) array(
  4626. 'cid' => $base_root . request_uri(),
  4627. 'data' => array(
  4628. 'path' => $_GET['q'],
  4629. 'body' => ob_get_clean(),
  4630. 'title' => drupal_get_title(),
  4631. 'headers' => array(),
  4632. ),
  4633. 'expire' => CACHE_TEMPORARY,
  4634. 'created' => REQUEST_TIME,
  4635. );
  4636. // Restore preferred header names based on the lower-case names returned
  4637. // by drupal_get_http_header().
  4638. $header_names = _drupal_set_preferred_header_name();
  4639. foreach (drupal_get_http_header() as $name_lower => $value) {
  4640. $cache->data['headers'][$header_names[$name_lower]] = $value;
  4641. if ($name_lower == 'expires') {
  4642. // Use the actual timestamp from an Expires header if available.
  4643. $cache->expire = strtotime($value);
  4644. }
  4645. }
  4646. if ($cache->data['body']) {
  4647. if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) {
  4648. $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
  4649. }
  4650. cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire);
  4651. }
  4652. return $cache;
  4653. }
  4654. }
  4655. /**
  4656. * Executes a cron run when called.
  4657. *
  4658. * Do not call this function from test, use $this->cronRun() instead.
  4659. *
  4660. * @return
  4661. * Returns TRUE if ran successfully
  4662. */
  4663. function drupal_cron_run() {
  4664. // Allow execution to continue even if the request gets canceled.
  4665. @ignore_user_abort(TRUE);
  4666. // Prevent session information from being saved while cron is running.
  4667. drupal_save_session(FALSE);
  4668. // Force the current user to anonymous to ensure consistent permissions on
  4669. // cron runs.
  4670. $original_user = $GLOBALS['user'];
  4671. $GLOBALS['user'] = drupal_anonymous_user();
  4672. // Try to allocate enough time to run all the hook_cron implementations.
  4673. drupal_set_time_limit(240);
  4674. $return = FALSE;
  4675. // Grab the defined cron queues.
  4676. $queues = module_invoke_all('cron_queue_info');
  4677. drupal_alter('cron_queue_info', $queues);
  4678. // Try to acquire cron lock.
  4679. if (!lock_acquire('cron', 240.0)) {
  4680. // Cron is still running normally.
  4681. watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING);
  4682. }
  4683. else {
  4684. // Make sure every queue exists. There is no harm in trying to recreate an
  4685. // existing queue.
  4686. foreach ($queues as $queue_name => $info) {
  4687. DrupalQueue::get($queue_name)->createQueue();
  4688. }
  4689. // Register shutdown callback
  4690. drupal_register_shutdown_function('drupal_cron_cleanup');
  4691. // Iterate through the modules calling their cron handlers (if any):
  4692. module_invoke_all('cron');
  4693. // Record cron time
  4694. variable_set('cron_last', REQUEST_TIME);
  4695. watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE);
  4696. // Release cron lock.
  4697. lock_release('cron');
  4698. // Return TRUE so other functions can check if it did run successfully
  4699. $return = TRUE;
  4700. }
  4701. foreach ($queues as $queue_name => $info) {
  4702. $function = $info['worker callback'];
  4703. $end = time() + (isset($info['time']) ? $info['time'] : 15);
  4704. $queue = DrupalQueue::get($queue_name);
  4705. while (time() < $end && ($item = $queue->claimItem())) {
  4706. $function($item->data);
  4707. $queue->deleteItem($item);
  4708. }
  4709. }
  4710. // Restore the user.
  4711. $GLOBALS['user'] = $original_user;
  4712. drupal_save_session(TRUE);
  4713. return $return;
  4714. }
  4715. /**
  4716. * Shutdown function for cron cleanup.
  4717. */
  4718. function drupal_cron_cleanup() {
  4719. // See if the semaphore is still locked.
  4720. if (variable_get('cron_semaphore', FALSE)) {
  4721. watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING);
  4722. // Release cron semaphore
  4723. variable_del('cron_semaphore');
  4724. }
  4725. }
  4726. /**
  4727. * Returns information about system object files (modules, themes, etc.).
  4728. *
  4729. * This function is used to find all or some system object files (module files,
  4730. * theme files, etc.) that exist on the site. It searches in several locations,
  4731. * depending on what type of object you are looking for. For instance, if you
  4732. * are looking for modules and call:
  4733. * @code
  4734. * drupal_system_listing("/\.module$/", "modules", 'name', 0);
  4735. * @endcode
  4736. * this function will search the site-wide modules directory (i.e., /modules/),
  4737. * your install profile's directory (i.e.,
  4738. * /profiles/your_site_profile/modules/), the all-sites directory (i.e.,
  4739. * /sites/all/modules/), and your site-specific directory (i.e.,
  4740. * /sites/your_site_dir/modules/), in that order, and return information about
  4741. * all of the files ending in .module in those directories.
  4742. *
  4743. * The information is returned in an associative array, which can be keyed on
  4744. * the file name ($key = 'filename'), the file name without the extension ($key
  4745. * = 'name'), or the full file stream URI ($key = 'uri'). If you use a key of
  4746. * 'filename' or 'name', files found later in the search will take precedence
  4747. * over files found earlier (unless they belong to a module or theme not
  4748. * compatible with Drupal core); if you choose a key of 'uri', you will get all
  4749. * files found.
  4750. *
  4751. * @param string $mask
  4752. * The preg_match() regular expression for the files to find.
  4753. * @param string $directory
  4754. * The subdirectory name in which the files are found. For example,
  4755. * 'modules' will search in sub-directories of the top-level /modules
  4756. * directory, sub-directories of /sites/all/modules/, etc.
  4757. * @param string $key
  4758. * The key to be used for the associative array returned. Possible values are
  4759. * 'uri', for the file's URI; 'filename', for the basename of the file; and
  4760. * 'name' for the name of the file without the extension. If you choose 'name'
  4761. * or 'filename', only the highest-precedence file will be returned.
  4762. * @param int $min_depth
  4763. * Minimum depth of directories to return files from, relative to each
  4764. * directory searched. For instance, a minimum depth of 2 would find modules
  4765. * inside /modules/node/tests, but not modules directly in /modules/node.
  4766. *
  4767. * @return array
  4768. * An associative array of file objects, keyed on the chosen key. Each element
  4769. * in the array is an object containing file information, with properties:
  4770. * - 'uri': Full URI of the file.
  4771. * - 'filename': File name.
  4772. * - 'name': Name of file without the extension.
  4773. */
  4774. function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) {
  4775. $config = conf_path();
  4776. $profile = drupal_get_profile();
  4777. $searchdir = array($directory);
  4778. $files = array();
  4779. // The 'profiles' directory contains pristine collections of modules and
  4780. // themes as organized by a distribution. It is pristine in the same way
  4781. // that /modules is pristine for core; users should avoid changing anything
  4782. // there in favor of sites/all or sites/<domain> directories.
  4783. if (file_exists("profiles/$profile/$directory")) {
  4784. $searchdir[] = "profiles/$profile/$directory";
  4785. }
  4786. // Always search sites/all/* as well as the global directories
  4787. $searchdir[] = 'sites/all/' . $directory;
  4788. if (file_exists("$config/$directory")) {
  4789. $searchdir[] = "$config/$directory";
  4790. }
  4791. // Get current list of items
  4792. if (!function_exists('file_scan_directory')) {
  4793. require_once DRUPAL_ROOT . '/includes/file.inc';
  4794. }
  4795. foreach ($searchdir as $dir) {
  4796. $files_to_add = file_scan_directory($dir, $mask, array('key' => $key, 'min_depth' => $min_depth));
  4797. // Duplicate files found in later search directories take precedence over
  4798. // earlier ones, so we want them to overwrite keys in our resulting
  4799. // $files array.
  4800. // The exception to this is if the later file is from a module or theme not
  4801. // compatible with Drupal core. This may occur during upgrades of Drupal
  4802. // core when new modules exist in core while older contrib modules with the
  4803. // same name exist in a directory such as sites/all/modules/.
  4804. foreach (array_intersect_key($files_to_add, $files) as $file_key => $file) {
  4805. // If it has no info file, then we just behave liberally and accept the
  4806. // new resource on the list for merging.
  4807. if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info')) {
  4808. // Get the .info file for the module or theme this file belongs to.
  4809. $info = drupal_parse_info_file($info_file);
  4810. // If the module or theme is incompatible with Drupal core, remove it
  4811. // from the array for the current search directory, so it is not
  4812. // overwritten when merged with the $files array.
  4813. if (isset($info['core']) && $info['core'] != DRUPAL_CORE_COMPATIBILITY) {
  4814. unset($files_to_add[$file_key]);
  4815. }
  4816. }
  4817. }
  4818. $files = array_merge($files, $files_to_add);
  4819. }
  4820. return $files;
  4821. }
  4822. /**
  4823. * Set the main page content value for later use.
  4824. *
  4825. * Given the nature of the Drupal page handling, this will be called once with
  4826. * a string or array. We store that and return it later as the block is being
  4827. * displayed.
  4828. *
  4829. * @param $content
  4830. * A string or renderable array representing the body of the page.
  4831. * @return
  4832. * If called without $content, a renderable array representing the body of
  4833. * the page.
  4834. */
  4835. function drupal_set_page_content($content = NULL) {
  4836. $content_block = &drupal_static(__FUNCTION__, NULL);
  4837. $main_content_display = &drupal_static('system_main_content_added', FALSE);
  4838. if (!empty($content)) {
  4839. $content_block = (is_array($content) ? $content : array('main' => array('#markup' => $content)));
  4840. }
  4841. else {
  4842. // Indicate that the main content has been requested. We assume that
  4843. // the module requesting the content will be adding it to the page.
  4844. // A module can indicate that it does not handle the content by setting
  4845. // the static variable back to FALSE after calling this function.
  4846. $main_content_display = TRUE;
  4847. return $content_block;
  4848. }
  4849. }
  4850. /**
  4851. * #pre_render callback to render #browsers into #prefix and #suffix.
  4852. *
  4853. * @param $elements
  4854. * A render array with a '#browsers' property. The '#browsers' property can
  4855. * contain any or all of the following keys:
  4856. * - 'IE': If FALSE, the element is not rendered by Internet Explorer. If
  4857. * TRUE, the element is rendered by Internet Explorer. Can also be a string
  4858. * containing an expression for Internet Explorer to evaluate as part of a
  4859. * conditional comment. For example, this can be set to 'lt IE 7' for the
  4860. * element to be rendered in Internet Explorer 6, but not in Internet
  4861. * Explorer 7 or higher. Defaults to TRUE.
  4862. * - '!IE': If FALSE, the element is not rendered by browsers other than
  4863. * Internet Explorer. If TRUE, the element is rendered by those browsers.
  4864. * Defaults to TRUE.
  4865. * Examples:
  4866. * - To render an element in all browsers, '#browsers' can be left out or set
  4867. * to array('IE' => TRUE, '!IE' => TRUE).
  4868. * - To render an element in Internet Explorer only, '#browsers' can be set
  4869. * to array('!IE' => FALSE).
  4870. * - To render an element in Internet Explorer 6 only, '#browsers' can be set
  4871. * to array('IE' => 'lt IE 7', '!IE' => FALSE).
  4872. * - To render an element in Internet Explorer 8 and higher and in all other
  4873. * browsers, '#browsers' can be set to array('IE' => 'gte IE 8').
  4874. *
  4875. * @return
  4876. * The passed-in element with markup for conditional comments potentially
  4877. * added to '#prefix' and '#suffix'.
  4878. */
  4879. function drupal_pre_render_conditional_comments($elements) {
  4880. $browsers = isset($elements['#browsers']) ? $elements['#browsers'] : array();
  4881. $browsers += array(
  4882. 'IE' => TRUE,
  4883. '!IE' => TRUE,
  4884. );
  4885. // If rendering in all browsers, no need for conditional comments.
  4886. if ($browsers['IE'] === TRUE && $browsers['!IE']) {
  4887. return $elements;
  4888. }
  4889. // Determine the conditional comment expression for Internet Explorer to
  4890. // evaluate.
  4891. if ($browsers['IE'] === TRUE) {
  4892. $expression = 'IE';
  4893. }
  4894. elseif ($browsers['IE'] === FALSE) {
  4895. $expression = '!IE';
  4896. }
  4897. else {
  4898. $expression = $browsers['IE'];
  4899. }
  4900. // Wrap the element's potentially existing #prefix and #suffix properties with
  4901. // conditional comment markup. The conditional comment expression is evaluated
  4902. // by Internet Explorer only. To control the rendering by other browsers,
  4903. // either the "downlevel-hidden" or "downlevel-revealed" technique must be
  4904. // used. See http://en.wikipedia.org/wiki/Conditional_comment for details.
  4905. $elements += array(
  4906. '#prefix' => '',
  4907. '#suffix' => '',
  4908. );
  4909. if (!$browsers['!IE']) {
  4910. // "downlevel-hidden".
  4911. $elements['#prefix'] = "\n<!--[if $expression]>\n" . $elements['#prefix'];
  4912. $elements['#suffix'] .= "<![endif]-->\n";
  4913. }
  4914. else {
  4915. // "downlevel-revealed".
  4916. $elements['#prefix'] = "\n<!--[if $expression]><!-->\n" . $elements['#prefix'];
  4917. $elements['#suffix'] .= "<!--<![endif]-->\n";
  4918. }
  4919. return $elements;
  4920. }
  4921. /**
  4922. * #pre_render callback to render a link into #markup.
  4923. *
  4924. * Doing so during pre_render gives modules a chance to alter the link parts.
  4925. *
  4926. * @param $elements
  4927. * A structured array whose keys form the arguments to l():
  4928. * - #title: The link text to pass as argument to l().
  4929. * - #href: The URL path component to pass as argument to l().
  4930. * - #options: (optional) An array of options to pass to l().
  4931. *
  4932. * @return
  4933. * The passed-in elements containing a rendered link in '#markup'.
  4934. */
  4935. function drupal_pre_render_link($element) {
  4936. // By default, link options to pass to l() are normally set in #options.
  4937. $element += array('#options' => array());
  4938. // However, within the scope of renderable elements, #attributes is a valid
  4939. // way to specify attributes, too. Take them into account, but do not override
  4940. // attributes from #options.
  4941. if (isset($element['#attributes'])) {
  4942. $element['#options'] += array('attributes' => array());
  4943. $element['#options']['attributes'] += $element['#attributes'];
  4944. }
  4945. // This #pre_render callback can be invoked from inside or outside of a Form
  4946. // API context, and depending on that, a HTML ID may be already set in
  4947. // different locations. #options should have precedence over Form API's #id.
  4948. // #attributes have been taken over into #options above already.
  4949. if (isset($element['#options']['attributes']['id'])) {
  4950. $element['#id'] = $element['#options']['attributes']['id'];
  4951. }
  4952. elseif (isset($element['#id'])) {
  4953. $element['#options']['attributes']['id'] = $element['#id'];
  4954. }
  4955. // Conditionally invoke ajax_pre_render_element(), if #ajax is set.
  4956. if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) {
  4957. // If no HTML ID was found above, automatically create one.
  4958. if (!isset($element['#id'])) {
  4959. $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link');
  4960. }
  4961. // If #ajax['path] was not specified, use the href as Ajax request URL.
  4962. if (!isset($element['#ajax']['path'])) {
  4963. $element['#ajax']['path'] = $element['#href'];
  4964. $element['#ajax']['options'] = $element['#options'];
  4965. }
  4966. $element = ajax_pre_render_element($element);
  4967. }
  4968. $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']);
  4969. return $element;
  4970. }
  4971. /**
  4972. * #pre_render callback that collects child links into a single array.
  4973. *
  4974. * This function can be added as a pre_render callback for a renderable array,
  4975. * usually one which will be themed by theme_links(). It iterates through all
  4976. * unrendered children of the element, collects any #links properties it finds,
  4977. * merges them into the parent element's #links array, and prevents those
  4978. * children from being rendered separately.
  4979. *
  4980. * The purpose of this is to allow links to be logically grouped into related
  4981. * categories, so that each child group can be rendered as its own list of
  4982. * links if drupal_render() is called on it, but calling drupal_render() on the
  4983. * parent element will still produce a single list containing all the remaining
  4984. * links, regardless of what group they were in.
  4985. *
  4986. * A typical example comes from node links, which are stored in a renderable
  4987. * array similar to this:
  4988. * @code
  4989. * $node->content['links'] = array(
  4990. * '#theme' => 'links__node',
  4991. * '#pre_render' = array('drupal_pre_render_links'),
  4992. * 'comment' => array(
  4993. * '#theme' => 'links__node__comment',
  4994. * '#links' => array(
  4995. * // An array of links associated with node comments, suitable for
  4996. * // passing in to theme_links().
  4997. * ),
  4998. * ),
  4999. * 'statistics' => array(
  5000. * '#theme' => 'links__node__statistics',
  5001. * '#links' => array(
  5002. * // An array of links associated with node statistics, suitable for
  5003. * // passing in to theme_links().
  5004. * ),
  5005. * ),
  5006. * 'translation' => array(
  5007. * '#theme' => 'links__node__translation',
  5008. * '#links' => array(
  5009. * // An array of links associated with node translation, suitable for
  5010. * // passing in to theme_links().
  5011. * ),
  5012. * ),
  5013. * );
  5014. * @endcode
  5015. *
  5016. * In this example, the links are grouped by functionality, which can be
  5017. * helpful to themers who want to display certain kinds of links independently.
  5018. * For example, adding this code to node.tpl.php will result in the comment
  5019. * links being rendered as a single list:
  5020. * @code
  5021. * print render($content['links']['comment']);
  5022. * @endcode
  5023. *
  5024. * (where $node->content has been transformed into $content before handing
  5025. * control to the node.tpl.php template).
  5026. *
  5027. * The pre_render function defined here allows the above flexibility, but also
  5028. * allows the following code to be used to render all remaining links into a
  5029. * single list, regardless of their group:
  5030. * @code
  5031. * print render($content['links']);
  5032. * @endcode
  5033. *
  5034. * In the above example, this will result in the statistics and translation
  5035. * links being rendered together in a single list (but not the comment links,
  5036. * which were rendered previously on their own).
  5037. *
  5038. * Because of the way this function works, the individual properties of each
  5039. * group (for example, a group-specific #theme property such as
  5040. * 'links__node__comment' in the example above, or any other property such as
  5041. * #attributes or #pre_render that is attached to it) are only used when that
  5042. * group is rendered on its own. When the group is rendered together with other
  5043. * children, these child-specific properties are ignored, and only the overall
  5044. * properties of the parent are used.
  5045. */
  5046. function drupal_pre_render_links($element) {
  5047. $element += array('#links' => array());
  5048. foreach (element_children($element) as $key) {
  5049. $child = &$element[$key];
  5050. // If the child has links which have not been printed yet and the user has
  5051. // access to it, merge its links in to the parent.
  5052. if (isset($child['#links']) && empty($child['#printed']) && (!isset($child['#access']) || $child['#access'])) {
  5053. $element['#links'] += $child['#links'];
  5054. // Mark the child as having been printed already (so that its links
  5055. // cannot be mistakenly rendered twice).
  5056. $child['#printed'] = TRUE;
  5057. }
  5058. }
  5059. return $element;
  5060. }
  5061. /**
  5062. * #pre_render callback to append contents in #markup to #children.
  5063. *
  5064. * This needs to be a #pre_render callback, because eventually assigned
  5065. * #theme_wrappers will expect the element's rendered content in #children.
  5066. * Note that if also a #theme is defined for the element, then the result of
  5067. * the theme callback will override #children.
  5068. *
  5069. * @see drupal_render()
  5070. *
  5071. * @param $elements
  5072. * A structured array using the #markup key.
  5073. *
  5074. * @return
  5075. * The passed-in elements, but #markup appended to #children.
  5076. */
  5077. function drupal_pre_render_markup($elements) {
  5078. $elements['#children'] = $elements['#markup'];
  5079. return $elements;
  5080. }
  5081. /**
  5082. * Renders the page, including all theming.
  5083. *
  5084. * @param $page
  5085. * A string or array representing the content of a page. The array consists of
  5086. * the following keys:
  5087. * - #type: Value is always 'page'. This pushes the theming through page.tpl.php (required).
  5088. * - #show_messages: Suppress drupal_get_message() items. Used by Batch API (optional).
  5089. *
  5090. * @see hook_page_alter()
  5091. * @see element_info()
  5092. */
  5093. function drupal_render_page($page) {
  5094. $main_content_display = &drupal_static('system_main_content_added', FALSE);
  5095. // Allow menu callbacks to return strings or arbitrary arrays to render.
  5096. // If the array returned is not of #type page directly, we need to fill
  5097. // in the page with defaults.
  5098. if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) {
  5099. drupal_set_page_content($page);
  5100. $page = element_info('page');
  5101. }
  5102. // Modules can add elements to $page as needed in hook_page_build().
  5103. foreach (module_implements('page_build') as $module) {
  5104. $function = $module . '_page_build';
  5105. $function($page);
  5106. }
  5107. // Modules alter the $page as needed. Blocks are populated into regions like
  5108. // 'sidebar_first', 'footer', etc.
  5109. drupal_alter('page', $page);
  5110. // If no module has taken care of the main content, add it to the page now.
  5111. // This allows the site to still be usable even if no modules that
  5112. // control page regions (for example, the Block module) are enabled.
  5113. if (!$main_content_display) {
  5114. $page['content']['system_main'] = drupal_set_page_content();
  5115. }
  5116. return drupal_render($page);
  5117. }
  5118. /**
  5119. * Renders HTML given a structured array tree.
  5120. *
  5121. * Recursively iterates over each of the array elements, generating HTML code.
  5122. *
  5123. * HTML generation is controlled by two properties containing theme functions,
  5124. * #theme and #theme_wrappers.
  5125. *
  5126. * #theme is the theme function called first. If it is set and the element has
  5127. * any children, they have to be rendered there. For elements that are not
  5128. * allowed to have any children, e.g. buttons or textfields, it can be used to
  5129. * render the element itself. If #theme is not present and the element has
  5130. * children, they are rendered and concatenated into a string by
  5131. * drupal_render_children().
  5132. *
  5133. * The #theme_wrappers property contains an array of theme functions which will
  5134. * be called, in order, after #theme has run. These can be used to add further
  5135. * markup around the rendered children; e.g., fieldsets add the required markup
  5136. * for a fieldset around their rendered child elements. All wrapper theme
  5137. * functions have to include the element's #children property in their output,
  5138. * as it contains the output of the previous theme functions and the rendered
  5139. * children.
  5140. *
  5141. * For example, for the form element type, by default only the #theme_wrappers
  5142. * property is set, which adds the form markup around the rendered child
  5143. * elements of the form. This allows you to set the #theme property on a
  5144. * specific form to a custom theme function, giving you complete control over
  5145. * the placement of the form's children while not at all having to deal with
  5146. * the form markup itself.
  5147. *
  5148. * drupal_render() can optionally cache the rendered output of elements to
  5149. * improve performance. To use drupal_render() caching, set the element's #cache
  5150. * property to an associative array with one or several of the following keys:
  5151. * - 'keys': An array of one or more keys that identify the element. If 'keys'
  5152. * is set, the cache ID is created automatically from these keys. See
  5153. * drupal_render_cid_create().
  5154. * - 'granularity' (optional): Define the cache granularity using binary
  5155. * combinations of the cache granularity constants, e.g. DRUPAL_CACHE_PER_USER
  5156. * to cache for each user separately or
  5157. * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for each
  5158. * page and role. If not specified the element is cached globally for each
  5159. * theme and language.
  5160. * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is required.
  5161. * If 'cid' is set, 'keys' and 'granularity' are ignored. Use only if you
  5162. * have special requirements.
  5163. * - 'expire': Set to one of the cache lifetime constants.
  5164. * - 'bin': Specify a cache bin to cache the element in. Defaults to 'cache'.
  5165. *
  5166. * This function is usually called from within another function, like
  5167. * drupal_get_form() or a theme function. Elements are sorted internally
  5168. * using uasort(). Since this is expensive, when passing already sorted
  5169. * elements to drupal_render(), for example from a database query, set
  5170. * $elements['#sorted'] = TRUE to avoid sorting them a second time.
  5171. *
  5172. * drupal_render() flags each element with a '#printed' status to indicate that
  5173. * the element has been rendered, which allows individual elements of a given
  5174. * array to be rendered independently and prevents them from being rendered
  5175. * more than once on subsequent calls to drupal_render() (e.g., as part of a
  5176. * larger array). If the same array or array element is passed more than once
  5177. * to drupal_render(), it simply returns a NULL value.
  5178. *
  5179. * @param $elements
  5180. * The structured array describing the data to be rendered.
  5181. * @return
  5182. * The rendered HTML.
  5183. */
  5184. function drupal_render(&$elements) {
  5185. // Early-return nothing if user does not have access.
  5186. if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
  5187. return;
  5188. }
  5189. // Do not print elements twice.
  5190. if (!empty($elements['#printed'])) {
  5191. return;
  5192. }
  5193. // Try to fetch the element's markup from cache and return.
  5194. if (isset($elements['#cache']) && $cached_output = drupal_render_cache_get($elements)) {
  5195. return $cached_output;
  5196. }
  5197. // If #markup is set, ensure #type is set. This allows to specify just #markup
  5198. // on an element without setting #type.
  5199. if (isset($elements['#markup']) && !isset($elements['#type'])) {
  5200. $elements['#type'] = 'markup';
  5201. }
  5202. // If the default values for this element have not been loaded yet, populate
  5203. // them.
  5204. if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
  5205. $elements += element_info($elements['#type']);
  5206. }
  5207. // Make any final changes to the element before it is rendered. This means
  5208. // that the $element or the children can be altered or corrected before the
  5209. // element is rendered into the final text.
  5210. if (isset($elements['#pre_render'])) {
  5211. foreach ($elements['#pre_render'] as $function) {
  5212. if (function_exists($function)) {
  5213. $elements = $function($elements);
  5214. }
  5215. }
  5216. }
  5217. // Allow #pre_render to abort rendering.
  5218. if (!empty($elements['#printed'])) {
  5219. return;
  5220. }
  5221. // Get the children of the element, sorted by weight.
  5222. $children = element_children($elements, TRUE);
  5223. // Initialize this element's #children, unless a #pre_render callback already
  5224. // preset #children.
  5225. if (!isset($elements['#children'])) {
  5226. $elements['#children'] = '';
  5227. }
  5228. // Call the element's #theme function if it is set. Then any children of the
  5229. // element have to be rendered there.
  5230. if (isset($elements['#theme'])) {
  5231. $elements['#children'] = theme($elements['#theme'], $elements);
  5232. }
  5233. // If #theme was not set and the element has children, render them now.
  5234. // This is the same process as drupal_render_children() but is inlined
  5235. // for speed.
  5236. if ($elements['#children'] == '') {
  5237. foreach ($children as $key) {
  5238. $elements['#children'] .= drupal_render($elements[$key]);
  5239. }
  5240. }
  5241. // Let the theme functions in #theme_wrappers add markup around the rendered
  5242. // children.
  5243. if (isset($elements['#theme_wrappers'])) {
  5244. foreach ($elements['#theme_wrappers'] as $theme_wrapper) {
  5245. $elements['#children'] = theme($theme_wrapper, $elements);
  5246. }
  5247. }
  5248. // Filter the outputted content and make any last changes before the
  5249. // content is sent to the browser. The changes are made on $content
  5250. // which allows the output'ed text to be filtered.
  5251. if (isset($elements['#post_render'])) {
  5252. foreach ($elements['#post_render'] as $function) {
  5253. if (function_exists($function)) {
  5254. $elements['#children'] = $function($elements['#children'], $elements);
  5255. }
  5256. }
  5257. }
  5258. // Add any JavaScript state information associated with the element.
  5259. if (!empty($elements['#states'])) {
  5260. drupal_process_states($elements);
  5261. }
  5262. // Add additional libraries, CSS, JavaScript an other custom
  5263. // attached data associated with this element.
  5264. if (!empty($elements['#attached'])) {
  5265. drupal_process_attached($elements);
  5266. }
  5267. $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
  5268. $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
  5269. $output = $prefix . $elements['#children'] . $suffix;
  5270. // Cache the processed element if #cache is set.
  5271. if (isset($elements['#cache'])) {
  5272. drupal_render_cache_set($output, $elements);
  5273. }
  5274. $elements['#printed'] = TRUE;
  5275. return $output;
  5276. }
  5277. /**
  5278. * Render children of an element and concatenate them.
  5279. *
  5280. * This renders all children of an element using drupal_render() and then
  5281. * joins them together into a single string.
  5282. *
  5283. * @param $element
  5284. * The structured array whose children shall be rendered.
  5285. * @param $children_keys
  5286. * If the keys of the element's children are already known, they can be passed
  5287. * in to save another run of element_children().
  5288. */
  5289. function drupal_render_children(&$element, $children_keys = NULL) {
  5290. if ($children_keys === NULL) {
  5291. $children_keys = element_children($element);
  5292. }
  5293. $output = '';
  5294. foreach ($children_keys as $key) {
  5295. if (!empty($element[$key])) {
  5296. $output .= drupal_render($element[$key]);
  5297. }
  5298. }
  5299. return $output;
  5300. }
  5301. /**
  5302. * Render an element.
  5303. *
  5304. * This function renders an element using drupal_render(). The top level
  5305. * element is shown with show() before rendering, so it will always be rendered
  5306. * even if hide() had been previously used on it.
  5307. *
  5308. * @param $element
  5309. * The element to be rendered.
  5310. *
  5311. * @return
  5312. * The rendered element.
  5313. *
  5314. * @see drupal_render()
  5315. * @see show()
  5316. * @see hide()
  5317. */
  5318. function render(&$element) {
  5319. if (is_array($element)) {
  5320. show($element);
  5321. return drupal_render($element);
  5322. }
  5323. else {
  5324. // Safe-guard for inappropriate use of render() on flat variables: return
  5325. // the variable as-is.
  5326. return $element;
  5327. }
  5328. }
  5329. /**
  5330. * Hide an element from later rendering.
  5331. *
  5332. * The first time render() or drupal_render() is called on an element tree,
  5333. * as each element in the tree is rendered, it is marked with a #printed flag
  5334. * and the rendered children of the element are cached. Subsequent calls to
  5335. * render() or drupal_render() will not traverse the child tree of this element
  5336. * again: they will just use the cached children. So if you want to hide an
  5337. * element, be sure to call hide() on the element before its parent tree is
  5338. * rendered for the first time, as it will have no effect on subsequent
  5339. * renderings of the parent tree.
  5340. *
  5341. * @param $element
  5342. * The element to be hidden.
  5343. *
  5344. * @return
  5345. * The element.
  5346. *
  5347. * @see render()
  5348. * @see show()
  5349. */
  5350. function hide(&$element) {
  5351. $element['#printed'] = TRUE;
  5352. return $element;
  5353. }
  5354. /**
  5355. * Show a hidden element for later rendering.
  5356. *
  5357. * You can also use render($element), which shows the element while rendering
  5358. * it.
  5359. *
  5360. * The first time render() or drupal_render() is called on an element tree,
  5361. * as each element in the tree is rendered, it is marked with a #printed flag
  5362. * and the rendered children of the element are cached. Subsequent calls to
  5363. * render() or drupal_render() will not traverse the child tree of this element
  5364. * again: they will just use the cached children. So if you want to show an
  5365. * element, be sure to call show() on the element before its parent tree is
  5366. * rendered for the first time, as it will have no effect on subsequent
  5367. * renderings of the parent tree.
  5368. *
  5369. * @param $element
  5370. * The element to be shown.
  5371. *
  5372. * @return
  5373. * The element.
  5374. *
  5375. * @see render()
  5376. * @see hide()
  5377. */
  5378. function show(&$element) {
  5379. $element['#printed'] = FALSE;
  5380. return $element;
  5381. }
  5382. /**
  5383. * Get the rendered output of a renderable element from cache.
  5384. *
  5385. * @see drupal_render()
  5386. * @see drupal_render_cache_set()
  5387. *
  5388. * @param $elements
  5389. * A renderable array.
  5390. * @return
  5391. * A markup string containing the rendered content of the element, or FALSE
  5392. * if no cached copy of the element is available.
  5393. */
  5394. function drupal_render_cache_get($elements) {
  5395. if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) {
  5396. return FALSE;
  5397. }
  5398. $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
  5399. if (!empty($cid) && $cache = cache_get($cid, $bin)) {
  5400. // Add additional libraries, JavaScript, CSS and other data attached
  5401. // to this element.
  5402. if (isset($cache->data['#attached'])) {
  5403. drupal_process_attached($cache->data);
  5404. }
  5405. // Return the rendered output.
  5406. return $cache->data['#markup'];
  5407. }
  5408. return FALSE;
  5409. }
  5410. /**
  5411. * Cache the rendered output of a renderable element.
  5412. *
  5413. * This is called by drupal_render() if the #cache property is set on an element.
  5414. *
  5415. * @see drupal_render()
  5416. * @see drupal_render_cache_get()
  5417. *
  5418. * @param $markup
  5419. * The rendered output string of $elements.
  5420. * @param $elements
  5421. * A renderable array.
  5422. */
  5423. function drupal_render_cache_set(&$markup, $elements) {
  5424. // Create the cache ID for the element.
  5425. if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) {
  5426. return FALSE;
  5427. }
  5428. // Cache implementations are allowed to modify the markup, to support
  5429. // replacing markup with edge-side include commands. The supporting cache
  5430. // backend will store the markup in some other key (like
  5431. // $data['#real-value']) and return an include command instead. When the
  5432. // ESI command is executed by the content accelerator, the real value can
  5433. // be retrieved and used.
  5434. $data['#markup'] = &$markup;
  5435. // Persist attached data associated with this element.
  5436. $attached = drupal_render_collect_attached($elements, TRUE);
  5437. if ($attached) {
  5438. $data['#attached'] = $attached;
  5439. }
  5440. $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
  5441. $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT;
  5442. cache_set($cid, $data, $bin, $expire);
  5443. }
  5444. /**
  5445. * Collect #attached for an element and all child elements into a single array.
  5446. *
  5447. * When caching elements, it is necessary to collect all libraries, JavaScript
  5448. * and CSS into a single array, from both the element itself and all child
  5449. * elements. This allows drupal_render() to add these back to the page when the
  5450. * element is returned from cache.
  5451. *
  5452. * @param $elements
  5453. * The element to collect #attached from.
  5454. * @param $return
  5455. * Whether to return the attached elements and reset the internal static.
  5456. *
  5457. * @return
  5458. * The #attached array for this element and its descendants.
  5459. */
  5460. function drupal_render_collect_attached($elements, $return = FALSE) {
  5461. $attached = &drupal_static(__FUNCTION__, array());
  5462. // Collect all #attached for this element.
  5463. if (isset($elements['#attached'])) {
  5464. foreach ($elements['#attached'] as $key => $value) {
  5465. if (!isset($attached[$key])) {
  5466. $attached[$key] = array();
  5467. }
  5468. $attached[$key] = array_merge($attached[$key], $value);
  5469. }
  5470. }
  5471. if ($children = element_children($elements)) {
  5472. foreach ($children as $child) {
  5473. drupal_render_collect_attached($elements[$child]);
  5474. }
  5475. }
  5476. // If this was the first call to the function, return all attached elements
  5477. // and reset the static cache.
  5478. if ($return) {
  5479. $return = $attached;
  5480. $attached = array();
  5481. return $return;
  5482. }
  5483. }
  5484. /**
  5485. * Prepare an element for caching based on a query. This smart caching strategy
  5486. * saves Drupal from querying and rendering to HTML when the underlying query is
  5487. * unchanged.
  5488. *
  5489. * Expensive queries should use the query builder to create the query and then
  5490. * call this function. Executing the query and formatting results should happen
  5491. * in a #pre_render callback.
  5492. *
  5493. * @param $query
  5494. * A select query object as returned by db_select().
  5495. * @param $function
  5496. * The name of the function doing this caching. A _pre_render suffix will be
  5497. * added to this string and is also part of the cache key in
  5498. * drupal_render_cache_set() and drupal_render_cache_get().
  5499. * @param $expire
  5500. * The cache expire time, passed eventually to cache_set().
  5501. * @param $granularity
  5502. * One or more granularity constants passed to drupal_render_cid_parts().
  5503. *
  5504. * @return
  5505. * A renderable array with the following keys and values:
  5506. * - #query: The passed-in $query.
  5507. * - #pre_render: $function with a _pre_render suffix.
  5508. * - #cache: An associative array prepared for drupal_render_cache_set().
  5509. */
  5510. function drupal_render_cache_by_query($query, $function, $expire = CACHE_TEMPORARY, $granularity = NULL) {
  5511. $cache_keys = array_merge(array($function), drupal_render_cid_parts($granularity));
  5512. $query->preExecute();
  5513. $cache_keys[] = hash('sha256', serialize(array((string) $query, $query->getArguments())));
  5514. return array(
  5515. '#query' => $query,
  5516. '#pre_render' => array($function . '_pre_render'),
  5517. '#cache' => array(
  5518. 'keys' => $cache_keys,
  5519. 'expire' => $expire,
  5520. ),
  5521. );
  5522. }
  5523. /**
  5524. * Helper function for building cache ids.
  5525. *
  5526. * @param $granularity
  5527. * One or more cache granularity constants, e.g. DRUPAL_CACHE_PER_USER to cache
  5528. * for each user separately or DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to
  5529. * cache separately for each page and role.
  5530. *
  5531. * @return
  5532. * An array of cache ID parts, always containing the active theme. If the
  5533. * locale module is enabled it also contains the active language. If
  5534. * $granularity was passed in, more parts are added.
  5535. */
  5536. function drupal_render_cid_parts($granularity = NULL) {
  5537. global $theme, $base_root, $user;
  5538. $cid_parts[] = $theme;
  5539. // If Locale is enabled but we have only one language we do not need it as cid
  5540. // part.
  5541. if (drupal_multilingual()) {
  5542. foreach (language_types_configurable() as $language_type) {
  5543. $cid_parts[] = $GLOBALS[$language_type]->language;
  5544. }
  5545. }
  5546. if (!empty($granularity)) {
  5547. // 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a
  5548. // resource drag for sites with many users, so when a module is being
  5549. // equivocal, we favor the less expensive 'PER_ROLE' pattern.
  5550. if ($granularity & DRUPAL_CACHE_PER_ROLE) {
  5551. $cid_parts[] = 'r.' . implode(',', array_keys($user->roles));
  5552. }
  5553. elseif ($granularity & DRUPAL_CACHE_PER_USER) {
  5554. $cid_parts[] = "u.$user->uid";
  5555. }
  5556. if ($granularity & DRUPAL_CACHE_PER_PAGE) {
  5557. $cid_parts[] = $base_root . request_uri();
  5558. }
  5559. }
  5560. return $cid_parts;
  5561. }
  5562. /**
  5563. * Create the cache ID for a renderable element.
  5564. *
  5565. * This creates the cache ID string, either by returning the #cache['cid']
  5566. * property if present or by building the cache ID out of the #cache['keys']
  5567. * and, optionally, the #cache['granularity'] properties.
  5568. *
  5569. * @param $elements
  5570. * A renderable array.
  5571. *
  5572. * @return
  5573. * The cache ID string, or FALSE if the element may not be cached.
  5574. */
  5575. function drupal_render_cid_create($elements) {
  5576. if (isset($elements['#cache']['cid'])) {
  5577. return $elements['#cache']['cid'];
  5578. }
  5579. elseif (isset($elements['#cache']['keys'])) {
  5580. $granularity = isset($elements['#cache']['granularity']) ? $elements['#cache']['granularity'] : NULL;
  5581. // Merge in additional cache ID parts based provided by drupal_render_cid_parts().
  5582. $cid_parts = array_merge($elements['#cache']['keys'], drupal_render_cid_parts($granularity));
  5583. return implode(':', $cid_parts);
  5584. }
  5585. return FALSE;
  5586. }
  5587. /**
  5588. * Function used by uasort to sort structured arrays by weight.
  5589. */
  5590. function element_sort($a, $b) {
  5591. $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
  5592. $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
  5593. if ($a_weight == $b_weight) {
  5594. return 0;
  5595. }
  5596. return ($a_weight < $b_weight) ? -1 : 1;
  5597. }
  5598. /**
  5599. * Array sorting callback; sorts elements by title.
  5600. */
  5601. function element_sort_by_title($a, $b) {
  5602. $a_title = (is_array($a) && isset($a['#title'])) ? $a['#title'] : '';
  5603. $b_title = (is_array($b) && isset($b['#title'])) ? $b['#title'] : '';
  5604. return strnatcasecmp($a_title, $b_title);
  5605. }
  5606. /**
  5607. * Retrieve the default properties for the defined element type.
  5608. *
  5609. * @param $type
  5610. * An element type as defined by hook_element_info().
  5611. */
  5612. function element_info($type) {
  5613. // Use the advanced drupal_static() pattern, since this is called very often.
  5614. static $drupal_static_fast;
  5615. if (!isset($drupal_static_fast)) {
  5616. $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
  5617. }
  5618. $cache = &$drupal_static_fast['cache'];
  5619. if (!isset($cache)) {
  5620. $cache = module_invoke_all('element_info');
  5621. foreach ($cache as $element_type => $info) {
  5622. $cache[$element_type]['#type'] = $element_type;
  5623. }
  5624. // Allow modules to alter the element type defaults.
  5625. drupal_alter('element_info', $cache);
  5626. }
  5627. return isset($cache[$type]) ? $cache[$type] : array();
  5628. }
  5629. /**
  5630. * Retrieve a single property for the defined element type.
  5631. *
  5632. * @param $type
  5633. * An element type as defined by hook_element_info().
  5634. * @param $property_name
  5635. * The property within the element type that should be returned.
  5636. * @param $default
  5637. * (Optional) The value to return if the element type does not specify a
  5638. * value for the property. Defaults to NULL.
  5639. */
  5640. function element_info_property($type, $property_name, $default = NULL) {
  5641. return (($info = element_info($type)) && array_key_exists($property_name, $info)) ? $info[$property_name] : $default;
  5642. }
  5643. /**
  5644. * Function used by uasort to sort structured arrays by weight, without the property weight prefix.
  5645. */
  5646. function drupal_sort_weight($a, $b) {
  5647. $a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0;
  5648. $b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0;
  5649. if ($a_weight == $b_weight) {
  5650. return 0;
  5651. }
  5652. return ($a_weight < $b_weight) ? -1 : 1;
  5653. }
  5654. /**
  5655. * Array sorting callback; sorts elements by 'title' key.
  5656. */
  5657. function drupal_sort_title($a, $b) {
  5658. if (!isset($b['title'])) {
  5659. return -1;
  5660. }
  5661. if (!isset($a['title'])) {
  5662. return 1;
  5663. }
  5664. return strcasecmp($a['title'], $b['title']);
  5665. }
  5666. /**
  5667. * Check if the key is a property.
  5668. */
  5669. function element_property($key) {
  5670. return $key[0] == '#';
  5671. }
  5672. /**
  5673. * Get properties of a structured array element. Properties begin with '#'.
  5674. */
  5675. function element_properties($element) {
  5676. return array_filter(array_keys((array) $element), 'element_property');
  5677. }
  5678. /**
  5679. * Check if the key is a child.
  5680. */
  5681. function element_child($key) {
  5682. return !isset($key[0]) || $key[0] != '#';
  5683. }
  5684. /**
  5685. * Return the children of an element, optionally sorted by weight.
  5686. *
  5687. * @param $elements
  5688. * The element to be sorted.
  5689. * @param $sort
  5690. * Boolean to indicate whether the children should be sorted by weight.
  5691. * @return
  5692. * The array keys of the element's children.
  5693. */
  5694. function element_children(&$elements, $sort = FALSE) {
  5695. // Do not attempt to sort elements which have already been sorted.
  5696. $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;
  5697. // Filter out properties from the element, leaving only children.
  5698. $children = array();
  5699. $sortable = FALSE;
  5700. foreach ($elements as $key => $value) {
  5701. if ($key === '' || $key[0] !== '#') {
  5702. $children[$key] = $value;
  5703. if (is_array($value) && isset($value['#weight'])) {
  5704. $sortable = TRUE;
  5705. }
  5706. }
  5707. }
  5708. // Sort the children if necessary.
  5709. if ($sort && $sortable) {
  5710. uasort($children, 'element_sort');
  5711. // Put the sorted children back into $elements in the correct order, to
  5712. // preserve sorting if the same element is passed through
  5713. // element_children() twice.
  5714. foreach ($children as $key => $child) {
  5715. unset($elements[$key]);
  5716. $elements[$key] = $child;
  5717. }
  5718. $elements['#sorted'] = TRUE;
  5719. }
  5720. return array_keys($children);
  5721. }
  5722. /**
  5723. * Return the visibile children of an element.
  5724. *
  5725. * @param $elements
  5726. * The parent element.
  5727. * @return
  5728. * The array keys of the element's visible children.
  5729. */
  5730. function element_get_visible_children(array $elements) {
  5731. $visible_children = array();
  5732. foreach (element_children($elements) as $key) {
  5733. $child = $elements[$key];
  5734. // Skip un-accessible children.
  5735. if (isset($child['#access']) && !$child['#access']) {
  5736. continue;
  5737. }
  5738. // Skip value and hidden elements, since they are not rendered.
  5739. if (isset($child['#type']) && in_array($child['#type'], array('value', 'hidden'))) {
  5740. continue;
  5741. }
  5742. $visible_children[$key] = $child;
  5743. }
  5744. return array_keys($visible_children);
  5745. }
  5746. /**
  5747. * Sets HTML attributes based on element properties.
  5748. *
  5749. * @param $element
  5750. * The renderable element to process.
  5751. * @param $map
  5752. * An associative array whose keys are element property names and whose values
  5753. * are the HTML attribute names to set for corresponding the property; e.g.,
  5754. * array('#propertyname' => 'attributename'). If both names are identical
  5755. * except for the leading '#', then an attribute name value is sufficient and
  5756. * no property name needs to be specified.
  5757. */
  5758. function element_set_attributes(array &$element, array $map) {
  5759. foreach ($map as $property => $attribute) {
  5760. // If the key is numeric, the attribute name needs to be taken over.
  5761. if (is_int($property)) {
  5762. $property = '#' . $attribute;
  5763. }
  5764. // Do not overwrite already existing attributes.
  5765. if (isset($element[$property]) && !isset($element['#attributes'][$attribute])) {
  5766. $element['#attributes'][$attribute] = $element[$property];
  5767. }
  5768. }
  5769. }
  5770. /**
  5771. * Sets a value in a nested array with variable depth.
  5772. *
  5773. * This helper function should be used when the depth of the array element you
  5774. * are changing may vary (that is, the number of parent keys is variable). It
  5775. * is primarily used for form structures and renderable arrays.
  5776. *
  5777. * Example:
  5778. * @code
  5779. * // Assume you have a 'signature' element somewhere in a form. It might be:
  5780. * $form['signature_settings']['signature'] = array(
  5781. * '#type' => 'text_format',
  5782. * '#title' => t('Signature'),
  5783. * );
  5784. * // Or, it might be further nested:
  5785. * $form['signature_settings']['user']['signature'] = array(
  5786. * '#type' => 'text_format',
  5787. * '#title' => t('Signature'),
  5788. * );
  5789. * @endcode
  5790. *
  5791. * To deal with the situation, the code needs to figure out the route to the
  5792. * element, given an array of parents that is either
  5793. * @code array('signature_settings', 'signature') @endcode in the first case or
  5794. * @code array('signature_settings', 'user', 'signature') @endcode in the second
  5795. * case.
  5796. *
  5797. * Without this helper function the only way to set the signature element in one
  5798. * line would be using eval(), which should be avoided:
  5799. * @code
  5800. * // Do not do this! Avoid eval().
  5801. * eval('$form[\'' . implode("']['", $parents) . '\'] = $element;');
  5802. * @endcode
  5803. *
  5804. * Instead, use this helper function:
  5805. * @code
  5806. * drupal_array_set_nested_value($form, $parents, $element);
  5807. * @endcode
  5808. *
  5809. * However if the number of array parent keys is static, the value should always
  5810. * be set directly rather than calling this function. For instance, for the
  5811. * first example we could just do:
  5812. * @code
  5813. * $form['signature_settings']['signature'] = $element;
  5814. * @endcode
  5815. *
  5816. * @param $array
  5817. * A reference to the array to modify.
  5818. * @param $parents
  5819. * An array of parent keys, starting with the outermost key.
  5820. * @param $value
  5821. * The value to set.
  5822. * @param $force
  5823. * (Optional) If TRUE, the value is forced into the structure even if it
  5824. * requires the deletion of an already existing non-array parent value. If
  5825. * FALSE, PHP throws an error if trying to add into a value that is not an
  5826. * array. Defaults to FALSE.
  5827. *
  5828. * @see drupal_array_get_nested_value()
  5829. */
  5830. function drupal_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) {
  5831. $ref = &$array;
  5832. foreach ($parents as $parent) {
  5833. // PHP auto-creates container arrays and NULL entries without error if $ref
  5834. // is NULL, but throws an error if $ref is set, but not an array.
  5835. if ($force && isset($ref) && !is_array($ref)) {
  5836. $ref = array();
  5837. }
  5838. $ref = &$ref[$parent];
  5839. }
  5840. $ref = $value;
  5841. }
  5842. /**
  5843. * Retrieves a value from a nested array with variable depth.
  5844. *
  5845. * This helper function should be used when the depth of the array element being
  5846. * retrieved may vary (that is, the number of parent keys is variable). It is
  5847. * primarily used for form structures and renderable arrays.
  5848. *
  5849. * Without this helper function the only way to get a nested array value with
  5850. * variable depth in one line would be using eval(), which should be avoided:
  5851. * @code
  5852. * // Do not do this! Avoid eval().
  5853. * // May also throw a PHP notice, if the variable array keys do not exist.
  5854. * eval('$value = $array[\'' . implode("']['", $parents) . "'];");
  5855. * @endcode
  5856. *
  5857. * Instead, use this helper function:
  5858. * @code
  5859. * $value = drupal_array_get_nested_value($form, $parents);
  5860. * @endcode
  5861. *
  5862. * The return value will be NULL, regardless of whether the actual value is NULL
  5863. * or whether the requested key does not exist. If it is required to know
  5864. * whether the nested array key actually exists, pass a third argument that is
  5865. * altered by reference:
  5866. * @code
  5867. * $key_exists = NULL;
  5868. * $value = drupal_array_get_nested_value($form, $parents, $key_exists);
  5869. * if ($key_exists) {
  5870. * // ... do something with $value ...
  5871. * }
  5872. * @endcode
  5873. *
  5874. * However if the number of array parent keys is static, the value should always
  5875. * be retrieved directly rather than calling this function. For instance:
  5876. * @code
  5877. * $value = $form['signature_settings']['signature'];
  5878. * @endcode
  5879. *
  5880. * @param $array
  5881. * The array from which to get the value.
  5882. * @param $parents
  5883. * An array of parent keys of the value, starting with the outermost key.
  5884. * @param $key_exists
  5885. * (optional) If given, an already defined variable that is altered by
  5886. * reference.
  5887. *
  5888. * @return
  5889. * The requested nested value. Possibly NULL if the value is NULL or not all
  5890. * nested parent keys exist. $key_exists is altered by reference and is a
  5891. * Boolean that indicates whether all nested parent keys exist (TRUE) or not
  5892. * (FALSE). This allows to distinguish between the two possibilities when NULL
  5893. * is returned.
  5894. *
  5895. * @see drupal_array_set_nested_value()
  5896. */
  5897. function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
  5898. $ref = &$array;
  5899. foreach ($parents as $parent) {
  5900. if (is_array($ref) && array_key_exists($parent, $ref)) {
  5901. $ref = &$ref[$parent];
  5902. }
  5903. else {
  5904. $key_exists = FALSE;
  5905. return NULL;
  5906. }
  5907. }
  5908. $key_exists = TRUE;
  5909. return $ref;
  5910. }
  5911. /**
  5912. * Determines whether a nested array with variable depth contains all of the requested keys.
  5913. *
  5914. * This helper function should be used when the depth of the array element to be
  5915. * checked may vary (that is, the number of parent keys is variable). See
  5916. * drupal_array_set_nested_value() for details. It is primarily used for form
  5917. * structures and renderable arrays.
  5918. *
  5919. * If it is required to also get the value of the checked nested key, use
  5920. * drupal_array_get_nested_value() instead.
  5921. *
  5922. * If the number of array parent keys is static, this helper function is
  5923. * unnecessary and the following code can be used instead:
  5924. * @code
  5925. * $value_exists = isset($form['signature_settings']['signature']);
  5926. * $key_exists = array_key_exists('signature', $form['signature_settings']);
  5927. * @endcode
  5928. *
  5929. * @param $array
  5930. * The array with the value to check for.
  5931. * @param $parents
  5932. * An array of parent keys of the value, starting with the outermost key.
  5933. *
  5934. * @return
  5935. * TRUE if all the parent keys exist, FALSE otherwise.
  5936. *
  5937. * @see drupal_array_get_nested_value()
  5938. */
  5939. function drupal_array_nested_key_exists(array $array, array $parents) {
  5940. // Although this function is similar to PHP's array_key_exists(), its
  5941. // arguments should be consistent with drupal_array_get_nested_value().
  5942. $key_exists = NULL;
  5943. drupal_array_get_nested_value($array, $parents, $key_exists);
  5944. return $key_exists;
  5945. }
  5946. /**
  5947. * Provide theme registration for themes across .inc files.
  5948. */
  5949. function drupal_common_theme() {
  5950. return array(
  5951. // theme.inc
  5952. 'html' => array(
  5953. 'render element' => 'page',
  5954. 'template' => 'html',
  5955. ),
  5956. 'page' => array(
  5957. 'render element' => 'page',
  5958. 'template' => 'page',
  5959. ),
  5960. 'region' => array(
  5961. 'render element' => 'elements',
  5962. 'template' => 'region',
  5963. ),
  5964. 'status_messages' => array(
  5965. 'variables' => array('display' => NULL),
  5966. ),
  5967. 'link' => array(
  5968. 'variables' => array('text' => NULL, 'path' => NULL, 'options' => array()),
  5969. ),
  5970. 'links' => array(
  5971. 'variables' => array('links' => NULL, 'attributes' => array('class' => array('links')), 'heading' => array()),
  5972. ),
  5973. 'image' => array(
  5974. // HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft
  5975. // allows the alt attribute to be omitted in some cases. Therefore,
  5976. // default the alt attribute to an empty string, but allow code calling
  5977. // theme('image') to pass explicit NULL for it to be omitted. Usually,
  5978. // neither omission nor an empty string satisfies accessibility
  5979. // requirements, so it is strongly encouraged for code calling
  5980. // theme('image') to pass a meaningful value for the alt variable.
  5981. // - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
  5982. // - http://www.w3.org/TR/xhtml1/dtds.html
  5983. // - http://dev.w3.org/html5/spec/Overview.html#alt
  5984. // The title attribute is optional in all cases, so it is omitted by
  5985. // default.
  5986. 'variables' => array('path' => NULL, 'width' => NULL, 'height' => NULL, 'alt' => '', 'title' => NULL, 'attributes' => array()),
  5987. ),
  5988. 'breadcrumb' => array(
  5989. 'variables' => array('breadcrumb' => NULL),
  5990. ),
  5991. 'help' => array(
  5992. 'variables' => array(),
  5993. ),
  5994. 'table' => array(
  5995. 'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'empty' => ''),
  5996. ),
  5997. 'tablesort_indicator' => array(
  5998. 'variables' => array('style' => NULL),
  5999. ),
  6000. 'mark' => array(
  6001. 'variables' => array('type' => MARK_NEW),
  6002. ),
  6003. 'item_list' => array(
  6004. 'variables' => array('items' => array(), 'title' => NULL, 'type' => 'ul', 'attributes' => array()),
  6005. ),
  6006. 'more_help_link' => array(
  6007. 'variables' => array('url' => NULL),
  6008. ),
  6009. 'feed_icon' => array(
  6010. 'variables' => array('url' => NULL, 'title' => NULL),
  6011. ),
  6012. 'more_link' => array(
  6013. 'variables' => array('url' => NULL, 'title' => NULL)
  6014. ),
  6015. 'username' => array(
  6016. 'variables' => array('account' => NULL),
  6017. ),
  6018. 'progress_bar' => array(
  6019. 'variables' => array('percent' => NULL, 'message' => NULL),
  6020. ),
  6021. 'indentation' => array(
  6022. 'variables' => array('size' => 1),
  6023. ),
  6024. 'html_tag' => array(
  6025. 'render element' => 'element',
  6026. ),
  6027. // from theme.maintenance.inc
  6028. 'maintenance_page' => array(
  6029. 'variables' => array('content' => NULL, 'show_messages' => TRUE),
  6030. 'template' => 'maintenance-page',
  6031. ),
  6032. 'update_page' => array(
  6033. 'variables' => array('content' => NULL, 'show_messages' => TRUE),
  6034. ),
  6035. 'install_page' => array(
  6036. 'variables' => array('content' => NULL),
  6037. ),
  6038. 'task_list' => array(
  6039. 'variables' => array('items' => NULL, 'active' => NULL),
  6040. ),
  6041. 'authorize_message' => array(
  6042. 'variables' => array('message' => NULL, 'success' => TRUE),
  6043. ),
  6044. 'authorize_report' => array(
  6045. 'variables' => array('messages' => array()),
  6046. ),
  6047. // from pager.inc
  6048. 'pager' => array(
  6049. 'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array(), 'quantity' => 9),
  6050. ),
  6051. 'pager_first' => array(
  6052. 'variables' => array('text' => NULL, 'element' => 0, 'parameters' => array()),
  6053. ),
  6054. 'pager_previous' => array(
  6055. 'variables' => array('text' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
  6056. ),
  6057. 'pager_next' => array(
  6058. 'variables' => array('text' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()),
  6059. ),
  6060. 'pager_last' => array(
  6061. 'variables' => array('text' => NULL, 'element' => 0, 'parameters' => array()),
  6062. ),
  6063. 'pager_link' => array(
  6064. 'variables' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()),
  6065. ),
  6066. // from menu.inc
  6067. 'menu_link' => array(
  6068. 'render element' => 'element',
  6069. ),
  6070. 'menu_tree' => array(
  6071. 'render element' => 'tree',
  6072. ),
  6073. 'menu_local_task' => array(
  6074. 'render element' => 'element',
  6075. ),
  6076. 'menu_local_action' => array(
  6077. 'render element' => 'element',
  6078. ),
  6079. 'menu_local_tasks' => array(
  6080. 'variables' => array('primary' => array(), 'secondary' => array()),
  6081. ),
  6082. // from form.inc
  6083. 'select' => array(
  6084. 'render element' => 'element',
  6085. ),
  6086. 'fieldset' => array(
  6087. 'render element' => 'element',
  6088. ),
  6089. 'radio' => array(
  6090. 'render element' => 'element',
  6091. ),
  6092. 'radios' => array(
  6093. 'render element' => 'element',
  6094. ),
  6095. 'date' => array(
  6096. 'render element' => 'element',
  6097. ),
  6098. 'exposed_filters' => array(
  6099. 'render element' => 'form',
  6100. ),
  6101. 'checkbox' => array(
  6102. 'render element' => 'element',
  6103. ),
  6104. 'checkboxes' => array(
  6105. 'render element' => 'element',
  6106. ),
  6107. 'button' => array(
  6108. 'render element' => 'element',
  6109. ),
  6110. 'image_button' => array(
  6111. 'render element' => 'element',
  6112. ),
  6113. 'hidden' => array(
  6114. 'render element' => 'element',
  6115. ),
  6116. 'textfield' => array(
  6117. 'render element' => 'element',
  6118. ),
  6119. 'form' => array(
  6120. 'render element' => 'element',
  6121. ),
  6122. 'textarea' => array(
  6123. 'render element' => 'element',
  6124. ),
  6125. 'password' => array(
  6126. 'render element' => 'element',
  6127. ),
  6128. 'file' => array(
  6129. 'render element' => 'element',
  6130. ),
  6131. 'tableselect' => array(
  6132. 'render element' => 'element',
  6133. ),
  6134. 'form_element' => array(
  6135. 'render element' => 'element',
  6136. ),
  6137. 'form_required_marker' => array(
  6138. 'render element' => 'element',
  6139. ),
  6140. 'form_element_label' => array(
  6141. 'render element' => 'element',
  6142. ),
  6143. 'vertical_tabs' => array(
  6144. 'render element' => 'element',
  6145. ),
  6146. 'container' => array(
  6147. 'render element' => 'element',
  6148. ),
  6149. );
  6150. }
  6151. /**
  6152. * @ingroup schemaapi
  6153. * @{
  6154. */
  6155. /**
  6156. * Creates all tables in a module's hook_schema() implementation.
  6157. *
  6158. * Note: This function does not pass the module's schema through
  6159. * hook_schema_alter(). The module's tables will be created exactly as the
  6160. * module defines them.
  6161. *
  6162. * @param $module
  6163. * The module for which the tables will be created.
  6164. */
  6165. function drupal_install_schema($module) {
  6166. $schema = drupal_get_schema_unprocessed($module);
  6167. _drupal_schema_initialize($schema, $module, FALSE);
  6168. foreach ($schema as $name => $table) {
  6169. db_create_table($name, $table);
  6170. }
  6171. }
  6172. /**
  6173. * Remove all tables that a module defines in its hook_schema().
  6174. *
  6175. * Note: This function does not pass the module's schema through
  6176. * hook_schema_alter(). The module's tables will be created exactly as the
  6177. * module defines them.
  6178. *
  6179. * @param $module
  6180. * The module for which the tables will be removed.
  6181. * @return
  6182. * An array of arrays with the following key/value pairs:
  6183. * - success: a boolean indicating whether the query succeeded.
  6184. * - query: the SQL query(s) executed, passed through check_plain().
  6185. */
  6186. function drupal_uninstall_schema($module) {
  6187. $schema = drupal_get_schema_unprocessed($module);
  6188. _drupal_schema_initialize($schema, $module, FALSE);
  6189. foreach ($schema as $table) {
  6190. if (db_table_exists($table['name'])) {
  6191. db_drop_table($table['name']);
  6192. }
  6193. }
  6194. }
  6195. /**
  6196. * Returns the unprocessed and unaltered version of a module's schema.
  6197. *
  6198. * Use this function only if you explicitly need the original
  6199. * specification of a schema, as it was defined in a module's
  6200. * hook_schema(). No additional default values will be set,
  6201. * hook_schema_alter() is not invoked and these unprocessed
  6202. * definitions won't be cached.
  6203. *
  6204. * This function can be used to retrieve a schema specification in
  6205. * hook_schema(), so it allows you to derive your tables from existing
  6206. * specifications.
  6207. *
  6208. * It is also used by drupal_install_schema() and
  6209. * drupal_uninstall_schema() to ensure that a module's tables are
  6210. * created exactly as specified without any changes introduced by a
  6211. * module that implements hook_schema_alter().
  6212. *
  6213. * @param $module
  6214. * The module to which the table belongs.
  6215. * @param $table
  6216. * The name of the table. If not given, the module's complete schema
  6217. * is returned.
  6218. */
  6219. function drupal_get_schema_unprocessed($module, $table = NULL) {
  6220. // Load the .install file to get hook_schema.
  6221. module_load_install($module);
  6222. $schema = module_invoke($module, 'schema');
  6223. if (isset($table) && isset($schema[$table])) {
  6224. return $schema[$table];
  6225. }
  6226. elseif (!empty($schema)) {
  6227. return $schema;
  6228. }
  6229. return array();
  6230. }
  6231. /**
  6232. * Fill in required default values for table definitions returned by hook_schema().
  6233. *
  6234. * @param $schema
  6235. * The schema definition array as it was returned by the module's
  6236. * hook_schema().
  6237. * @param $module
  6238. * The module for which hook_schema() was invoked.
  6239. * @param $remove_descriptions
  6240. * (optional) Whether to additionally remove 'description' keys of all tables
  6241. * and fields to improve performance of serialize() and unserialize().
  6242. * Defaults to TRUE.
  6243. */
  6244. function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) {
  6245. // Set the name and module key for all tables.
  6246. foreach ($schema as $name => &$table) {
  6247. if (empty($table['module'])) {
  6248. $table['module'] = $module;
  6249. }
  6250. if (!isset($table['name'])) {
  6251. $table['name'] = $name;
  6252. }
  6253. if ($remove_descriptions) {
  6254. unset($table['description']);
  6255. foreach ($table['fields'] as &$field) {
  6256. unset($field['description']);
  6257. }
  6258. }
  6259. }
  6260. }
  6261. /**
  6262. * Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query.
  6263. *
  6264. * @param $table
  6265. * The name of the table from which to retrieve fields.
  6266. * @param
  6267. * An optional prefix to to all fields.
  6268. *
  6269. * @return An array of fields.
  6270. **/
  6271. function drupal_schema_fields_sql($table, $prefix = NULL) {
  6272. $schema = drupal_get_schema($table);
  6273. $fields = array_keys($schema['fields']);
  6274. if ($prefix) {
  6275. $columns = array();
  6276. foreach ($fields as $field) {
  6277. $columns[] = "$prefix.$field";
  6278. }
  6279. return $columns;
  6280. }
  6281. else {
  6282. return $fields;
  6283. }
  6284. }
  6285. /**
  6286. * Saves (inserts or updates) a record to the database based upon the schema.
  6287. *
  6288. * @param $table
  6289. * The name of the table; this must be defined by a hook_schema()
  6290. * implementation.
  6291. * @param $record
  6292. * An object or array representing the record to write, passed in by
  6293. * reference. If inserting a new record, values not provided in $record will
  6294. * be populated in $record and in the database with the default values from
  6295. * the schema, as well as a single serial (auto-increment) field (if present).
  6296. * If updating an existing record, only provided values are updated in the
  6297. * database, and $record is not modified.
  6298. * @param $primary_keys
  6299. * To indicate that this is a new record to be inserted, omit this argument.
  6300. * If this is an update, this argument specifies the primary keys' field
  6301. * names. If there is only 1 field in the key, you may pass in a string; if
  6302. * there are multiple fields in the key, pass in an array.
  6303. *
  6304. * @return
  6305. * If the record insert or update failed, returns FALSE. If it succeeded,
  6306. * returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
  6307. */
  6308. function drupal_write_record($table, &$record, $primary_keys = array()) {
  6309. // Standardize $primary_keys to an array.
  6310. if (is_string($primary_keys)) {
  6311. $primary_keys = array($primary_keys);
  6312. }
  6313. $schema = drupal_get_schema($table);
  6314. if (empty($schema)) {
  6315. return FALSE;
  6316. }
  6317. $object = (object) $record;
  6318. $fields = array();
  6319. // Go through the schema to determine fields to write.
  6320. foreach ($schema['fields'] as $field => $info) {
  6321. if ($info['type'] == 'serial') {
  6322. // Skip serial types if we are updating.
  6323. if (!empty($primary_keys)) {
  6324. continue;
  6325. }
  6326. // Track serial field so we can helpfully populate them after the query.
  6327. // NOTE: Each table should come with one serial field only.
  6328. $serial = $field;
  6329. }
  6330. // Skip field if it is in $primary_keys as it is unnecessary to update a
  6331. // field to the value it is already set to.
  6332. if (in_array($field, $primary_keys)) {
  6333. continue;
  6334. }
  6335. if (!property_exists($object, $field)) {
  6336. // Skip fields that are not provided, default values are already known
  6337. // by the database.
  6338. continue;
  6339. }
  6340. // Build array of fields to update or insert.
  6341. if (empty($info['serialize'])) {
  6342. $fields[$field] = $object->$field;
  6343. }
  6344. else {
  6345. $fields[$field] = serialize($object->$field);
  6346. }
  6347. // Type cast to proper datatype, except when the value is NULL and the
  6348. // column allows this.
  6349. //
  6350. // MySQL PDO silently casts e.g. FALSE and '' to 0 when inserting the value
  6351. // into an integer column, but PostgreSQL PDO does not. Also type cast NULL
  6352. // when the column does not allow this.
  6353. if (isset($object->$field) || !empty($info['not null'])) {
  6354. if ($info['type'] == 'int' || $info['type'] == 'serial') {
  6355. $fields[$field] = (int) $fields[$field];
  6356. }
  6357. elseif ($info['type'] == 'float') {
  6358. $fields[$field] = (float) $fields[$field];
  6359. }
  6360. else {
  6361. $fields[$field] = (string) $fields[$field];
  6362. }
  6363. }
  6364. }
  6365. if (empty($fields)) {
  6366. return;
  6367. }
  6368. // Build the SQL.
  6369. if (empty($primary_keys)) {
  6370. // We are doing an insert.
  6371. $options = array('return' => Database::RETURN_INSERT_ID);
  6372. if (isset($serial) && isset($fields[$serial])) {
  6373. // If the serial column has been explicitly set with an ID, then we don't
  6374. // require the database to return the last insert id.
  6375. if ($fields[$serial]) {
  6376. $options['return'] = Database::RETURN_AFFECTED;
  6377. }
  6378. // If a serial column does exist with no value (i.e. 0) then remove it as
  6379. // the database will insert the correct value for us.
  6380. else {
  6381. unset($fields[$serial]);
  6382. }
  6383. }
  6384. $query = db_insert($table, $options)->fields($fields);
  6385. $return = SAVED_NEW;
  6386. }
  6387. else {
  6388. $query = db_update($table)->fields($fields);
  6389. foreach ($primary_keys as $key) {
  6390. $query->condition($key, $object->$key);
  6391. }
  6392. $return = SAVED_UPDATED;
  6393. }
  6394. // Execute the SQL.
  6395. if ($query_return = $query->execute()) {
  6396. if (isset($serial)) {
  6397. // If the database was not told to return the last insert id, it will be
  6398. // because we already know it.
  6399. if (isset($options) && $options['return'] != Database::RETURN_INSERT_ID) {
  6400. $object->$serial = $fields[$serial];
  6401. }
  6402. else {
  6403. $object->$serial = $query_return;
  6404. }
  6405. }
  6406. }
  6407. // If we have a single-field primary key but got no insert ID, the
  6408. // query failed. Note that we explicitly check for FALSE, because
  6409. // a valid update query which doesn't change any values will return
  6410. // zero (0) affected rows.
  6411. elseif ($query_return === FALSE && count($primary_keys) == 1) {
  6412. $return = FALSE;
  6413. }
  6414. // If we are inserting, populate empty fields with default values.
  6415. if (empty($primary_keys)) {
  6416. foreach ($schema['fields'] as $field => $info) {
  6417. if (isset($info['default']) && !property_exists($object, $field)) {
  6418. $object->$field = $info['default'];
  6419. }
  6420. }
  6421. }
  6422. // If we began with an array, convert back.
  6423. if (is_array($record)) {
  6424. $record = (array) $object;
  6425. }
  6426. return $return;
  6427. }
  6428. /**
  6429. * @} End of "ingroup schemaapi".
  6430. */
  6431. /**
  6432. * Parses Drupal module and theme .info files.
  6433. *
  6434. * Info files are NOT for placing arbitrary theme and module-specific settings.
  6435. * Use variable_get() and variable_set() for that.
  6436. *
  6437. * Information stored in a module .info file:
  6438. * - name: The real name of the module for display purposes.
  6439. * - description: A brief description of the module.
  6440. * - dependencies: An array of shortnames of other modules this module requires.
  6441. * - package: The name of the package of modules this module belongs to.
  6442. *
  6443. * See forum.info for an example of a module .info file.
  6444. *
  6445. * Information stored in a theme .info file:
  6446. * - name: The real name of the theme for display purposes.
  6447. * - description: Brief description.
  6448. * - screenshot: Path to screenshot relative to the theme's .info file.
  6449. * - engine: Theme engine; typically phptemplate.
  6450. * - base: Name of a base theme, if applicable; e.g., base = zen.
  6451. * - regions: Listed regions; e.g., region[left] = Left sidebar.
  6452. * - features: Features available; e.g., features[] = logo.
  6453. * - stylesheets: Theme stylesheets; e.g., stylesheets[all][] = my-style.css.
  6454. * - scripts: Theme scripts; e.g., scripts[] = my-script.js.
  6455. *
  6456. * See bartik.info for an example of a theme .info file.
  6457. *
  6458. * @param $filename
  6459. * The file we are parsing. Accepts file with relative or absolute path.
  6460. *
  6461. * @return
  6462. * The info array.
  6463. *
  6464. * @see drupal_parse_info_format()
  6465. */
  6466. function drupal_parse_info_file($filename) {
  6467. $info = &drupal_static(__FUNCTION__, array());
  6468. if (!isset($info[$filename])) {
  6469. if (!file_exists($filename)) {
  6470. $info[$filename] = array();
  6471. }
  6472. else {
  6473. $data = file_get_contents($filename);
  6474. $info[$filename] = drupal_parse_info_format($data);
  6475. }
  6476. }
  6477. return $info[$filename];
  6478. }
  6479. /**
  6480. * Parse data in Drupal's .info format.
  6481. *
  6482. * Data should be in an .ini-like format to specify values. White-space
  6483. * generally doesn't matter, except inside values:
  6484. * @code
  6485. * key = value
  6486. * key = "value"
  6487. * key = 'value'
  6488. * key = "multi-line
  6489. * value"
  6490. * key = 'multi-line
  6491. * value'
  6492. * key
  6493. * =
  6494. * 'value'
  6495. * @endcode
  6496. *
  6497. * Arrays are created using a HTTP GET alike syntax:
  6498. * @code
  6499. * key[] = "numeric array"
  6500. * key[index] = "associative array"
  6501. * key[index][] = "nested numeric array"
  6502. * key[index][index] = "nested associative array"
  6503. * @endcode
  6504. *
  6505. * PHP constants are substituted in, but only when used as the entire value.
  6506. * Comments should start with a semi-colon at the beginning of a line.
  6507. *
  6508. * @param $data
  6509. * A string to parse.
  6510. * @return
  6511. * The info array.
  6512. *
  6513. * @see drupal_parse_info_file()
  6514. */
  6515. function drupal_parse_info_format($data) {
  6516. $info = array();
  6517. $constants = get_defined_constants();
  6518. if (preg_match_all('
  6519. @^\s* # Start at the beginning of a line, ignoring leading whitespace
  6520. ((?:
  6521. [^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets,
  6522. \[[^\[\]]*\] # unless they are balanced and not nested
  6523. )+?)
  6524. \s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space)
  6525. (?:
  6526. ("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes
  6527. (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes
  6528. ([^\r\n]*?) # Non-quoted string
  6529. )\s*$ # Stop at the next end of a line, ignoring trailing whitespace
  6530. @msx', $data, $matches, PREG_SET_ORDER)) {
  6531. foreach ($matches as $match) {
  6532. // Fetch the key and value string
  6533. $i = 0;
  6534. foreach (array('key', 'value1', 'value2', 'value3') as $var) {
  6535. $$var = isset($match[++$i]) ? $match[$i] : '';
  6536. }
  6537. $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
  6538. // Parse array syntax
  6539. $keys = preg_split('/\]?\[/', rtrim($key, ']'));
  6540. $last = array_pop($keys);
  6541. $parent = &$info;
  6542. // Create nested arrays
  6543. foreach ($keys as $key) {
  6544. if ($key == '') {
  6545. $key = count($parent);
  6546. }
  6547. if (!isset($parent[$key]) || !is_array($parent[$key])) {
  6548. $parent[$key] = array();
  6549. }
  6550. $parent = &$parent[$key];
  6551. }
  6552. // Handle PHP constants.
  6553. if (isset($constants[$value])) {
  6554. $value = $constants[$value];
  6555. }
  6556. // Insert actual value
  6557. if ($last == '') {
  6558. $last = count($parent);
  6559. }
  6560. $parent[$last] = $value;
  6561. }
  6562. }
  6563. return $info;
  6564. }
  6565. /**
  6566. * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt.
  6567. *
  6568. * @return
  6569. * Array of the possible severity levels for log messages.
  6570. *
  6571. * @see watchdog()
  6572. * @ingroup logging_severity_levels
  6573. */
  6574. function watchdog_severity_levels() {
  6575. return array(
  6576. WATCHDOG_EMERGENCY => t('emergency'),
  6577. WATCHDOG_ALERT => t('alert'),
  6578. WATCHDOG_CRITICAL => t('critical'),
  6579. WATCHDOG_ERROR => t('error'),
  6580. WATCHDOG_WARNING => t('warning'),
  6581. WATCHDOG_NOTICE => t('notice'),
  6582. WATCHDOG_INFO => t('info'),
  6583. WATCHDOG_DEBUG => t('debug'),
  6584. );
  6585. }
  6586. /**
  6587. * Explode a string of given tags into an array.
  6588. *
  6589. * @see drupal_implode_tags()
  6590. */
  6591. function drupal_explode_tags($tags) {
  6592. // This regexp allows the following types of user input:
  6593. // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
  6594. $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
  6595. preg_match_all($regexp, $tags, $matches);
  6596. $typed_tags = array_unique($matches[1]);
  6597. $tags = array();
  6598. foreach ($typed_tags as $tag) {
  6599. // If a user has escaped a term (to demonstrate that it is a group,
  6600. // or includes a comma or quote character), we remove the escape
  6601. // formatting so to save the term into the database as the user intends.
  6602. $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
  6603. if ($tag != "") {
  6604. $tags[] = $tag;
  6605. }
  6606. }
  6607. return $tags;
  6608. }
  6609. /**
  6610. * Implode an array of tags into a string.
  6611. *
  6612. * @see drupal_explode_tags()
  6613. */
  6614. function drupal_implode_tags($tags) {
  6615. $encoded_tags = array();
  6616. foreach ($tags as $tag) {
  6617. // Commas and quotes in tag names are special cases, so encode them.
  6618. if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) {
  6619. $tag = '"' . str_replace('"', '""', $tag) . '"';
  6620. }
  6621. $encoded_tags[] = $tag;
  6622. }
  6623. return implode(', ', $encoded_tags);
  6624. }
  6625. /**
  6626. * Flush all cached data on the site.
  6627. *
  6628. * Empties cache tables, rebuilds the menu cache and theme registries, and
  6629. * invokes a hook so that other modules' cache data can be cleared as well.
  6630. */
  6631. function drupal_flush_all_caches() {
  6632. // Change query-strings on css/js files to enforce reload for all users.
  6633. _drupal_flush_css_js();
  6634. registry_rebuild();
  6635. drupal_clear_css_cache();
  6636. drupal_clear_js_cache();
  6637. // Rebuild the theme data. Note that the module data is rebuilt above, as
  6638. // part of registry_rebuild().
  6639. system_rebuild_theme_data();
  6640. drupal_theme_rebuild();
  6641. node_types_rebuild();
  6642. // node_menu() defines menu items based on node types so it needs to come
  6643. // after node types are rebuilt.
  6644. menu_rebuild();
  6645. // Synchronize to catch any actions that were added or removed.
  6646. actions_synchronize();
  6647. // Don't clear cache_form - in-progress form submissions may break.
  6648. // Ordered so clearing the page cache will always be the last action.
  6649. $core = array('cache', 'cache_filter', 'cache_bootstrap', 'cache_page');
  6650. $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
  6651. foreach ($cache_tables as $table) {
  6652. cache_clear_all('*', $table, TRUE);
  6653. }
  6654. // Rebuild the bootstrap module list. We do this here so that developers
  6655. // can get new hook_boot() implementations registered without having to
  6656. // write a hook_update_N() function.
  6657. _system_update_bootstrap_status();
  6658. }
  6659. /**
  6660. * Helper function to change query-strings on css/js files.
  6661. *
  6662. * Changes the character added to all css/js files as dummy query-string, so
  6663. * that all browsers are forced to reload fresh files.
  6664. */
  6665. function _drupal_flush_css_js() {
  6666. // The timestamp is converted to base 36 in order to make it more compact.
  6667. variable_set('css_js_query_string', base_convert(REQUEST_TIME, 10, 36));
  6668. }
  6669. /**
  6670. * Debug function used for outputting debug information.
  6671. *
  6672. * The debug information is passed on to trigger_error() after being converted
  6673. * to a string using _drupal_debug_message().
  6674. *
  6675. * @param $data
  6676. * Data to be output.
  6677. * @param $label
  6678. * Label to prefix the data.
  6679. * @param $print_r
  6680. * Flag to switch between print_r() and var_export() for data conversion to
  6681. * string. Set $print_r to TRUE when dealing with a recursive data structure
  6682. * as var_export() will generate an error.
  6683. */
  6684. function debug($data, $label = NULL, $print_r = FALSE) {
  6685. // Print $data contents to string.
  6686. $string = check_plain($print_r ? print_r($data, TRUE) : var_export($data, TRUE));
  6687. // Display values with pre-formatting to increase readability.
  6688. $string = '<pre>' . $string . '</pre>';
  6689. trigger_error(trim($label ? "$label: $string" : $string));
  6690. }
  6691. /**
  6692. * Parse a dependency for comparison by drupal_check_incompatibility().
  6693. *
  6694. * @param $dependency
  6695. * A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'.
  6696. * @return
  6697. * An associative array with three keys:
  6698. * - 'name' includes the name of the thing to depend on (e.g. 'foo').
  6699. * - 'original_version' contains the original version string (which can be
  6700. * used in the UI for reporting incompatibilities).
  6701. * - 'versions' is a list of associative arrays, each containing the keys
  6702. * 'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<',
  6703. * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
  6704. * Callers should pass this structure to drupal_check_incompatibility().
  6705. *
  6706. * @see drupal_check_incompatibility()
  6707. */
  6708. function drupal_parse_dependency($dependency) {
  6709. // We use named subpatterns and support every op that version_compare
  6710. // supports. Also, op is optional and defaults to equals.
  6711. $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?';
  6712. // Core version is always optional: 7.x-2.x and 2.x is treated the same.
  6713. $p_core = '(?:' . preg_quote(DRUPAL_CORE_COMPATIBILITY) . '-)?';
  6714. $p_major = '(?P<major>\d+)';
  6715. // By setting the minor version to x, branches can be matched.
  6716. $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)';
  6717. $value = array();
  6718. $parts = explode('(', $dependency, 2);
  6719. $value['name'] = trim($parts[0]);
  6720. if (isset($parts[1])) {
  6721. $value['original_version'] = ' (' . $parts[1];
  6722. foreach (explode(',', $parts[1]) as $version) {
  6723. if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) {
  6724. $op = !empty($matches['operation']) ? $matches['operation'] : '=';
  6725. if ($matches['minor'] == 'x') {
  6726. // Drupal considers "2.x" to mean any version that begins with
  6727. // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(),
  6728. // on the other hand, treats "x" as a string; so to
  6729. // version_compare(), "2.x" is considered less than 2.0. This
  6730. // means that >=2.x and <2.x are handled by version_compare()
  6731. // as we need, but > and <= are not.
  6732. if ($op == '>' || $op == '<=') {
  6733. $matches['major']++;
  6734. }
  6735. // Equivalence can be checked by adding two restrictions.
  6736. if ($op == '=' || $op == '==') {
  6737. $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x');
  6738. $op = '>=';
  6739. }
  6740. }
  6741. $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']);
  6742. }
  6743. }
  6744. }
  6745. return $value;
  6746. }
  6747. /**
  6748. * Check whether a version is compatible with a given dependency.
  6749. *
  6750. * @param $v
  6751. * The parsed dependency structure from drupal_parse_dependency().
  6752. * @param $current_version
  6753. * The version to check against (like 4.2).
  6754. * @return
  6755. * NULL if compatible, otherwise the original dependency version string that
  6756. * caused the incompatibility.
  6757. *
  6758. * @see drupal_parse_dependency()
  6759. */
  6760. function drupal_check_incompatibility($v, $current_version) {
  6761. if (!empty($v['versions'])) {
  6762. foreach ($v['versions'] as $required_version) {
  6763. if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op']))) {
  6764. return $v['original_version'];
  6765. }
  6766. }
  6767. }
  6768. }
  6769. /**
  6770. * Get the entity info array of an entity type.
  6771. *
  6772. * @see hook_entity_info()
  6773. * @see hook_entity_info_alter()
  6774. *
  6775. * @param $entity_type
  6776. * The entity type, e.g. node, for which the info shall be returned, or NULL
  6777. * to return an array with info about all types.
  6778. */
  6779. function entity_get_info($entity_type = NULL) {
  6780. global $language;
  6781. // Use the advanced drupal_static() pattern, since this is called very often.
  6782. static $drupal_static_fast;
  6783. if (!isset($drupal_static_fast)) {
  6784. $drupal_static_fast['entity_info'] = &drupal_static(__FUNCTION__);
  6785. }
  6786. $entity_info = &$drupal_static_fast['entity_info'];
  6787. // hook_entity_info() includes translated strings, so each language is cached
  6788. // separately.
  6789. $langcode = $language->language;
  6790. if (empty($entity_info)) {
  6791. if ($cache = cache_get("entity_info:$langcode")) {
  6792. $entity_info = $cache->data;
  6793. }
  6794. else {
  6795. $entity_info = module_invoke_all('entity_info');
  6796. // Merge in default values.
  6797. foreach ($entity_info as $name => $data) {
  6798. $entity_info[$name] += array(
  6799. 'fieldable' => FALSE,
  6800. 'controller class' => 'DrupalDefaultEntityController',
  6801. 'static cache' => TRUE,
  6802. 'field cache' => TRUE,
  6803. 'load hook' => $name . '_load',
  6804. 'bundles' => array(),
  6805. 'view modes' => array(),
  6806. 'entity keys' => array(),
  6807. 'translation' => array(),
  6808. );
  6809. $entity_info[$name]['entity keys'] += array(
  6810. 'revision' => '',
  6811. 'bundle' => '',
  6812. );
  6813. foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) {
  6814. $entity_info[$name]['view modes'][$view_mode] += array(
  6815. 'custom settings' => FALSE,
  6816. );
  6817. }
  6818. // If no bundle key is provided, assume a single bundle, named after
  6819. // the entity type.
  6820. if (empty($entity_info[$name]['entity keys']['bundle']) && empty($entity_info[$name]['bundles'])) {
  6821. $entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
  6822. }
  6823. // Prepare entity schema fields SQL info for
  6824. // DrupalEntityControllerInterface::buildQuery().
  6825. if (isset($entity_info[$name]['base table'])) {
  6826. $entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
  6827. if (isset($entity_info[$name]['revision table'])) {
  6828. $entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']);
  6829. }
  6830. }
  6831. }
  6832. // Let other modules alter the entity info.
  6833. drupal_alter('entity_info', $entity_info);
  6834. cache_set("entity_info:$langcode", $entity_info);
  6835. }
  6836. }
  6837. if (empty($entity_type)) {
  6838. return $entity_info;
  6839. }
  6840. elseif (isset($entity_info[$entity_type])) {
  6841. return $entity_info[$entity_type];
  6842. }
  6843. }
  6844. /**
  6845. * Resets the cached information about entity types.
  6846. */
  6847. function entity_info_cache_clear() {
  6848. drupal_static_reset('entity_get_info');
  6849. // Clear all languages.
  6850. cache_clear_all('entity_info:', 'cache', TRUE);
  6851. }
  6852. /**
  6853. * Helper function to extract id, vid, and bundle name from an entity.
  6854. *
  6855. * @param $entity_type
  6856. * The entity type; e.g. 'node' or 'user'.
  6857. * @param $entity
  6858. * The entity from which to extract values.
  6859. * @return
  6860. * A numerically indexed array (not a hash table) containing these
  6861. * elements:
  6862. * 0: primary id of the entity
  6863. * 1: revision id of the entity, or NULL if $entity_type is not versioned
  6864. * 2: bundle name of the entity
  6865. */
  6866. function entity_extract_ids($entity_type, $entity) {
  6867. $info = entity_get_info($entity_type);
  6868. // Objects being created might not have id/vid yet.
  6869. $id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL;
  6870. $vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL;
  6871. if (!empty($info['entity keys']['bundle'])) {
  6872. // Explicitly fail for malformed entities missing the bundle property.
  6873. if (!isset($entity->{$info['entity keys']['bundle']}) || $entity->{$info['entity keys']['bundle']} === '') {
  6874. throw new EntityMalformedException(t('Missing bundle property on entity of type @entity_type.', array('@entity_type' => $entity_type)));
  6875. }
  6876. $bundle = $entity->{$info['entity keys']['bundle']};
  6877. }
  6878. else {
  6879. // The entity type provides no bundle key: assume a single bundle, named
  6880. // after the entity type.
  6881. $bundle = $entity_type;
  6882. }
  6883. return array($id, $vid, $bundle);
  6884. }
  6885. /**
  6886. * Helper function to assemble an object structure with initial ids.
  6887. *
  6888. * This function can be seen as reciprocal to entity_extract_ids().
  6889. *
  6890. * @param $entity_type
  6891. * The entity type; e.g. 'node' or 'user'.
  6892. * @param $ids
  6893. * A numerically indexed array, as returned by entity_extract_ids(),
  6894. * containing these elements:
  6895. * 0: primary id of the entity
  6896. * 1: revision id of the entity, or NULL if $entity_type is not versioned
  6897. * 2: bundle name of the entity, or NULL if $entity_type has no bundles
  6898. * @return
  6899. * An entity structure, initialized with the ids provided.
  6900. */
  6901. function entity_create_stub_entity($entity_type, $ids) {
  6902. $entity = new stdClass();
  6903. $info = entity_get_info($entity_type);
  6904. $entity->{$info['entity keys']['id']} = $ids[0];
  6905. if (!empty($info['entity keys']['revision']) && isset($ids[1])) {
  6906. $entity->{$info['entity keys']['revision']} = $ids[1];
  6907. }
  6908. if (!empty($info['entity keys']['bundle']) && isset($ids[2])) {
  6909. $entity->{$info['entity keys']['bundle']} = $ids[2];
  6910. }
  6911. return $entity;
  6912. }
  6913. /**
  6914. * Load entities from the database.
  6915. *
  6916. * The entities are stored in a static memory cache, and will not require
  6917. * database access if loaded again during the same page request.
  6918. *
  6919. * The actual loading is done through a class that has to implement the
  6920. * DrupalEntityControllerInterface interface. By default,
  6921. * DrupalDefaultEntityController is used. Entity types can specify that a
  6922. * different class should be used by setting the 'controller class' key in
  6923. * hook_entity_info(). These classes can either implement the
  6924. * DrupalEntityControllerInterface interface, or, most commonly, extend the
  6925. * DrupalDefaultEntityController class. See node_entity_info() and the
  6926. * NodeController in node.module as an example.
  6927. *
  6928. * @see hook_entity_info()
  6929. * @see DrupalEntityControllerInterface
  6930. * @see DrupalDefaultEntityController
  6931. * @see EntityFieldQuery
  6932. *
  6933. * @param $entity_type
  6934. * The entity type to load, e.g. node or user.
  6935. * @param $ids
  6936. * An array of entity IDs, or FALSE to load all entities.
  6937. * @param $conditions
  6938. * (deprecated) An associative array of conditions on the base table, where
  6939. * the keys are the database fields and the values are the values those
  6940. * fields must have. Instead, it is preferable to use EntityFieldQuery to
  6941. * retrieve a list of entity IDs loadable by this function.
  6942. * @param $reset
  6943. * Whether to reset the internal cache for the requested entity type.
  6944. *
  6945. * @return
  6946. * An array of entity objects indexed by their ids. When no results are
  6947. * found, an empty array is returned.
  6948. *
  6949. * @todo Remove $conditions in Drupal 8.
  6950. */
  6951. function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) {
  6952. if ($reset) {
  6953. entity_get_controller($entity_type)->resetCache();
  6954. }
  6955. return entity_get_controller($entity_type)->load($ids, $conditions);
  6956. }
  6957. /**
  6958. * Loads the unchanged, i.e. not modified, entity from the database.
  6959. *
  6960. * Unlike entity_load() this function ensures the entity is directly loaded from
  6961. * the database, thus bypassing any static cache. In particular, this function
  6962. * is useful to determine changes by comparing the entity being saved to the
  6963. * stored entity.
  6964. *
  6965. * @param $entity_type
  6966. * The entity type to load, e.g. node or user.
  6967. * @param $id
  6968. * The id of the entity to load.
  6969. *
  6970. * @return
  6971. * The unchanged entity, or FALSE if the entity cannot be loaded.
  6972. */
  6973. function entity_load_unchanged($entity_type, $id) {
  6974. entity_get_controller($entity_type)->resetCache(array($id));
  6975. $result = entity_get_controller($entity_type)->load(array($id));
  6976. return reset($result);
  6977. }
  6978. /**
  6979. * Get the entity controller class for an entity type.
  6980. */
  6981. function entity_get_controller($entity_type) {
  6982. $controllers = &drupal_static(__FUNCTION__, array());
  6983. if (!isset($controllers[$entity_type])) {
  6984. $type_info = entity_get_info($entity_type);
  6985. $class = $type_info['controller class'];
  6986. $controllers[$entity_type] = new $class($entity_type);
  6987. }
  6988. return $controllers[$entity_type];
  6989. }
  6990. /**
  6991. * Invoke hook_entity_prepare_view().
  6992. *
  6993. * If adding a new entity similar to nodes, comments or users, you should
  6994. * invoke this function during the ENTITY_build_content() or
  6995. * ENTITY_view_multiple() phases of rendering to allow other modules to alter
  6996. * the objects during this phase. This is needed for situations where
  6997. * information needs to be loaded outside of ENTITY_load() - particularly
  6998. * when loading entities into one another - i.e. a user object into a node, due
  6999. * to the potential for unwanted side-effects such as caching and infinite
  7000. * recursion. By convention, entity_prepare_view() is called after
  7001. * field_attach_prepare_view() to allow entity level hooks to act on content
  7002. * loaded by field API.
  7003. * @see hook_entity_prepare_view()
  7004. *
  7005. * @param $entity_type
  7006. * The type of entity, i.e. 'node', 'user'.
  7007. * @param $entities
  7008. * The entity objects which are being prepared for view, keyed by object ID.
  7009. * @param $langcode
  7010. * (optional) A language code to be used for rendering. Defaults to the global
  7011. * content language of the current request.
  7012. */
  7013. function entity_prepare_view($entity_type, $entities, $langcode = NULL) {
  7014. if (!isset($langcode)) {
  7015. $langcode = $GLOBALS['language_content']->language;
  7016. }
  7017. // To ensure hooks are only run once per entity, check for an
  7018. // entity_view_prepared flag and only process items without it.
  7019. // @todo: resolve this more generally for both entity and field level hooks.
  7020. $prepare = array();
  7021. foreach ($entities as $id => $entity) {
  7022. if (empty($entity->entity_view_prepared)) {
  7023. // Add this entity to the items to be prepared.
  7024. $prepare[$id] = $entity;
  7025. // Mark this item as prepared.
  7026. $entity->entity_view_prepared = TRUE;
  7027. }
  7028. }
  7029. if (!empty($prepare)) {
  7030. module_invoke_all('entity_prepare_view', $prepare, $entity_type, $langcode);
  7031. }
  7032. }
  7033. /**
  7034. * Returns the uri elements of an entity.
  7035. *
  7036. * @param $entity_type
  7037. * The entity type; e.g. 'node' or 'user'.
  7038. * @param $entity
  7039. * The entity for which to generate a path.
  7040. * @return
  7041. * An array containing the 'path' and 'options' keys used to build the uri of
  7042. * the entity, and matching the signature of url(). NULL if the entity has no
  7043. * uri of its own.
  7044. */
  7045. function entity_uri($entity_type, $entity) {
  7046. // This check enables the URI of an entity to be easily overridden from what
  7047. // the callback for the entity type or bundle would return, and it helps
  7048. // minimize performance overhead when entity_uri() is called multiple times
  7049. // for the same entity.
  7050. if (!isset($entity->uri)) {
  7051. $info = entity_get_info($entity_type);
  7052. list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
  7053. // A bundle-specific callback takes precedence over the generic one for the
  7054. // entity type.
  7055. if (isset($info['bundles'][$bundle]['uri callback'])) {
  7056. $uri_callback = $info['bundles'][$bundle]['uri callback'];
  7057. }
  7058. elseif (isset($info['uri callback'])) {
  7059. $uri_callback = $info['uri callback'];
  7060. }
  7061. else {
  7062. $uri_callback = NULL;
  7063. }
  7064. // Invoke the callback to get the URI. If there is no callback, set the
  7065. // entity's 'uri' property to FALSE to indicate that it is known to not have
  7066. // a URI.
  7067. if (isset($uri_callback) && function_exists($uri_callback)) {
  7068. $entity->uri = $uri_callback($entity);
  7069. if (!isset($entity->uri['options'])) {
  7070. $entity->uri['options'] = array();
  7071. }
  7072. // Pass the entity data to url() so that alter functions do not need to
  7073. // lookup this entity again.
  7074. $entity->uri['options']['entity_type'] = $entity_type;
  7075. $entity->uri['options']['entity'] = $entity;
  7076. }
  7077. else {
  7078. $entity->uri = FALSE;
  7079. }
  7080. }
  7081. return $entity->uri ? $entity->uri : NULL;
  7082. }
  7083. /**
  7084. * Returns the label of an entity.
  7085. *
  7086. * See the 'label callback' component of the hook_entity_info() return value
  7087. * for more information.
  7088. *
  7089. * @param $entity_type
  7090. * The entity type; e.g., 'node' or 'user'.
  7091. * @param $entity
  7092. * The entity for which to generate the label.
  7093. *
  7094. * @return
  7095. * The entity label, or FALSE if not found.
  7096. */
  7097. function entity_label($entity_type, $entity) {
  7098. $label = FALSE;
  7099. $info = entity_get_info($entity_type);
  7100. if (isset($info['label callback']) && function_exists($info['label callback'])) {
  7101. $label = $info['label callback']($entity, $entity_type);
  7102. }
  7103. elseif (!empty($info['entity keys']['label']) && isset($entity->{$info['entity keys']['label']})) {
  7104. $label = $entity->{$info['entity keys']['label']};
  7105. }
  7106. return $label;
  7107. }
  7108. /**
  7109. * Helper function for attaching field API validation to entity forms.
  7110. */
  7111. function entity_form_field_validate($entity_type, $form, &$form_state) {
  7112. // All field attach API functions act on an entity object, but during form
  7113. // validation, we don't have one. $form_state contains the entity as it was
  7114. // prior to processing the current form submission, and we must not update it
  7115. // until we have fully validated the submitted input. Therefore, for
  7116. // validation, act on a pseudo entity created out of the form values.
  7117. $pseudo_entity = (object) $form_state['values'];
  7118. field_attach_form_validate($entity_type, $pseudo_entity, $form, $form_state);
  7119. }
  7120. /**
  7121. * Helper function for copying submitted values to entity properties for simple entity forms.
  7122. *
  7123. * During the submission handling of an entity form's "Save", "Preview", and
  7124. * possibly other buttons, the form state's entity needs to be updated with the
  7125. * submitted form values. Each entity form implements its own builder function
  7126. * for doing this, appropriate for the particular entity and form, whereas
  7127. * modules may specify additional builder functions in $form['#entity_builders']
  7128. * for copying the form values of added form elements to entity properties.
  7129. * Many of the main entity builder functions can call this helper function to
  7130. * re-use its logic of copying $form_state['values'][PROPERTY] values to
  7131. * $entity->PROPERTY for all entries in $form_state['values'] that are not field
  7132. * data, and calling field_attach_submit() to copy field data. Apart from that
  7133. * this helper invokes any additional builder functions that have been specified
  7134. * in $form['#entity_builders'].
  7135. *
  7136. * For some entity forms (e.g., forms with complex non-field data and forms that
  7137. * simultaneously edit multiple entities), this behavior may be inappropriate,
  7138. * so the builder function for such forms needs to implement the required
  7139. * functionality instead of calling this function.
  7140. */
  7141. function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_state) {
  7142. $info = entity_get_info($entity_type);
  7143. list(, , $bundle) = entity_extract_ids($entity_type, $entity);
  7144. // Copy top-level form values that are not for fields to entity properties,
  7145. // without changing existing entity properties that are not being edited by
  7146. // this form. Copying field values must be done using field_attach_submit().
  7147. $values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $bundle)) : $form_state['values'];
  7148. foreach ($values_excluding_fields as $key => $value) {
  7149. $entity->$key = $value;
  7150. }
  7151. // Invoke all specified builders for copying form values to entity properties.
  7152. if (isset($form['#entity_builders'])) {
  7153. foreach ($form['#entity_builders'] as $function) {
  7154. $function($entity_type, $entity, $form, $form_state);
  7155. }
  7156. }
  7157. // Copy field values to the entity.
  7158. if ($info['fieldable']) {
  7159. field_attach_submit($entity_type, $entity, $form, $form_state);
  7160. }
  7161. }
  7162. /**
  7163. * Performs one or more XML-RPC request(s).
  7164. *
  7165. * Usage example:
  7166. * @code
  7167. * $result = xmlrpc('http://example.com/xmlrpc.php', array(
  7168. * 'service.methodName' => array($parameter, $second, $third),
  7169. * ));
  7170. * @endcode
  7171. *
  7172. * @param $url
  7173. * An absolute URL of the XML-RPC endpoint.
  7174. * @param $args
  7175. * An associative array whose keys are the methods to call and whose values
  7176. * are the arguments to pass to the respective method. If multiple methods
  7177. * are specified, a system.multicall is performed.
  7178. * @param $options
  7179. * (optional) An array of options to pass along to drupal_http_request().
  7180. *
  7181. * @return
  7182. * For one request:
  7183. * Either the return value of the method on success, or FALSE.
  7184. * If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg().
  7185. * For multiple requests:
  7186. * An array of results. Each result will either be the result
  7187. * returned by the method called, or an xmlrpc_error object if the call
  7188. * failed. See xmlrpc_error().
  7189. */
  7190. function xmlrpc($url, $args, $options = array()) {
  7191. require_once DRUPAL_ROOT . '/includes/xmlrpc.inc';
  7192. return _xmlrpc($url, $args, $options);
  7193. }
  7194. /**
  7195. * Retrieves a list of all available archivers.
  7196. *
  7197. * @see hook_archiver_info()
  7198. * @see hook_archiver_info_alter()
  7199. */
  7200. function archiver_get_info() {
  7201. $archiver_info = &drupal_static(__FUNCTION__, array());
  7202. if (empty($archiver_info)) {
  7203. $cache = cache_get('archiver_info');
  7204. if ($cache === FALSE) {
  7205. // Rebuild the cache and save it.
  7206. $archiver_info = module_invoke_all('archiver_info');
  7207. drupal_alter('archiver_info', $archiver_info);
  7208. uasort($archiver_info, 'drupal_sort_weight');
  7209. cache_set('archiver_info', $archiver_info);
  7210. }
  7211. else {
  7212. $archiver_info = $cache->data;
  7213. }
  7214. }
  7215. return $archiver_info;
  7216. }
  7217. /**
  7218. * Returns a string of supported archive extensions.
  7219. *
  7220. * @return
  7221. * A space-separated string of extensions suitable for use by the file
  7222. * validation system.
  7223. */
  7224. function archiver_get_extensions() {
  7225. $valid_extensions = array();
  7226. foreach (archiver_get_info() as $archive) {
  7227. foreach ($archive['extensions'] as $extension) {
  7228. foreach (explode('.', $extension) as $part) {
  7229. if (!in_array($part, $valid_extensions)) {
  7230. $valid_extensions[] = $part;
  7231. }
  7232. }
  7233. }
  7234. }
  7235. return implode(' ', $valid_extensions);
  7236. }
  7237. /**
  7238. * Create the appropriate archiver for the specified file.
  7239. *
  7240. * @param $file
  7241. * The full path of the archive file. Note that stream wrapper
  7242. * paths are supported, but not remote ones.
  7243. * @return
  7244. * A newly created instance of the archiver class appropriate
  7245. * for the specified file, already bound to that file.
  7246. * If no appropriate archiver class was found, will return FALSE.
  7247. */
  7248. function archiver_get_archiver($file) {
  7249. // Archivers can only work on local paths
  7250. $filepath = drupal_realpath($file);
  7251. if (!is_file($filepath)) {
  7252. throw new Exception(t('Archivers can only operate on local files: %file not supported', array('%file' => $file)));
  7253. }
  7254. $archiver_info = archiver_get_info();
  7255. foreach ($archiver_info as $implementation) {
  7256. foreach ($implementation['extensions'] as $extension) {
  7257. // Because extensions may be multi-part, such as .tar.gz,
  7258. // we cannot use simpler approaches like substr() or pathinfo().
  7259. // This method isn't quite as clean but gets the job done.
  7260. // Also note that the file may not yet exist, so we cannot rely
  7261. // on fileinfo() or other disk-level utilities.
  7262. if (strrpos($filepath, '.' . $extension) === strlen($filepath) - strlen('.' . $extension)) {
  7263. return new $implementation['class']($filepath);
  7264. }
  7265. }
  7266. }
  7267. }
  7268. /**
  7269. * Drupal Updater registry.
  7270. *
  7271. * An Updater is a class that knows how to update various parts of the Drupal
  7272. * file system, for example to update modules that have newer releases, or to
  7273. * install a new theme.
  7274. *
  7275. * @return
  7276. * Returns the Drupal Updater class registry.
  7277. *
  7278. * @see hook_updater_info()
  7279. * @see hook_updater_info_alter()
  7280. */
  7281. function drupal_get_updaters() {
  7282. $updaters = &drupal_static(__FUNCTION__);
  7283. if (!isset($updaters)) {
  7284. $updaters = module_invoke_all('updater_info');
  7285. drupal_alter('updater_info', $updaters);
  7286. uasort($updaters, 'drupal_sort_weight');
  7287. }
  7288. return $updaters;
  7289. }
  7290. /**
  7291. * Drupal FileTransfer registry.
  7292. *
  7293. * @return
  7294. * Returns the Drupal FileTransfer class registry.
  7295. *
  7296. * @see FileTransfer
  7297. * @see hook_filetransfer_info()
  7298. * @see hook_filetransfer_info_alter()
  7299. */
  7300. function drupal_get_filetransfer_info() {
  7301. $info = &drupal_static(__FUNCTION__);
  7302. if (!isset($info)) {
  7303. // Since we have to manually set the 'file path' default for each
  7304. // module separately, we can't use module_invoke_all().
  7305. $info = array();
  7306. foreach (module_implements('filetransfer_info') as $module) {
  7307. $function = $module . '_filetransfer_info';
  7308. if (function_exists($function)) {
  7309. $result = $function();
  7310. if (isset($result) && is_array($result)) {
  7311. foreach ($result as &$values) {
  7312. if (empty($values['file path'])) {
  7313. $values['file path'] = drupal_get_path('module', $module);
  7314. }
  7315. }
  7316. $info = array_merge_recursive($info, $result);
  7317. }
  7318. }
  7319. }
  7320. drupal_alter('filetransfer_info', $info);
  7321. uasort($info, 'drupal_sort_weight');
  7322. }
  7323. return $info;
  7324. }