diff options
Diffstat (limited to 'includes')
40 files changed, 1486 insertions, 718 deletions
diff --git a/includes/actions.inc b/includes/actions.inc index 760de8300..ed43af4fd 100644 --- a/includes/actions.inc +++ b/includes/actions.inc @@ -22,7 +22,7 @@ * - $a1, $a2: Optional additional information, which can be passed into * actions_do() and will be passed along to the action function. * - * @} End of "defgroup actions". + * @} */ /** @@ -48,6 +48,7 @@ * Passed along to the callback. * @param $a2 * Passed along to the callback. + * * @return * An associative array containing the results of the functions that * perform the actions, keyed on action ID. @@ -149,6 +150,7 @@ function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a * * @param $reset * Reset the action info static cache. + * * @return * An associative array keyed on action function name, with the same format * as the return value of hook_action_info(), containing all @@ -176,9 +178,9 @@ function actions_list($reset = FALSE) { * function and the actions returned by actions_list() are partially * synchronized. Non-configurable actions from hook_action_info() * implementations are put into the database when actions_synchronize() is - * called, which happens when admin/config/system/actions is visited. Configurable - * actions are not added to the database until they are configured in the - * user interface, in which case a database row is created for each + * called, which happens when admin/config/system/actions is visited. + * Configurable actions are not added to the database until they are configured + * in the user interface, in which case a database row is created for each * configuration of each action. * * @return @@ -205,6 +207,7 @@ function actions_get_all_actions() { * An associative array with function names or action IDs as keys * and associative arrays with keys 'label', 'type', etc. as values. * This is usually the output of actions_list() or actions_get_all_actions(). + * * @return * An associative array whose keys are hashes of the input array keys, and * whose corresponding values are associative arrays with components @@ -223,7 +226,7 @@ function actions_actions_map($actions) { } /** - * Given a hash of an action array key, returns the key (function or ID). + * Returns an action array key (function or ID), given its hash. * * Faster than actions_actions_map() when you only need the function name or ID. * @@ -231,6 +234,7 @@ function actions_actions_map($actions) { * Hash of a function name or action ID array key. The array key * is a key into the return value of actions_list() (array key is the action * function name) or actions_get_all_actions() (array key is the action ID). + * * @return * The corresponding array key, or FALSE if no match is found. */ @@ -332,6 +336,7 @@ function actions_synchronize($delete_orphans = FALSE) { * to Jim'. * @param $aid * The ID of this action. If omitted, a new action is created. + * * @return * The ID of the action. */ @@ -361,6 +366,7 @@ function actions_save($function, $type, $params, $label, $aid = NULL) { * * @param $aid * The ID of the action to retrieve. + * * @return * The appropriate action row from the database as an object. */ @@ -380,4 +386,3 @@ function actions_delete($aid) { ->execute(); module_invoke_all('actions_delete', $aid); } - diff --git a/includes/ajax.inc b/includes/ajax.inc index d70808efe..fb07477d6 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -24,7 +24,8 @@ * ajax_form_callback() and a defined #ajax['callback'] function. * However, you may optionally specify a different path to request or a * different callback function to invoke, which can return updated HTML or can - * also return a richer set of @link ajax_commands Ajax framework commands @endlink. + * also return a richer set of + * @link ajax_commands Ajax framework commands @endlink. * * Standard form handling is as follows: * - A form element has a #ajax property that includes #ajax['callback'] and @@ -101,7 +102,7 @@ * In the above example, the 'changethis' element is Ajax-enabled. The default * #ajax['event'] is 'change', so when the 'changethis' element changes, * an Ajax call is made. The form is submitted and reprocessed, and then the - * callback is called. In this case, the form has been automatically + * callback is called. In this case, the form has been automatically * built changing $form['replace_textfield']['#description'], so the callback * just returns that part of the form. * @@ -188,11 +189,11 @@ * be converted to a JSON object and returned to the client, which will then * iterate over the array and process it like a macro language. * - * Each command item is an associative array which will be converted to a command - * object on the JavaScript side. $command_item['command'] is the type of - * command, e.g. 'alert' or 'replace', and will correspond to a method in the - * Drupal.ajax[command] space. The command array may contain any other data - * that the command needs to process, e.g. 'method', 'selector', 'settings', etc. + * Each command item is an associative array which will be converted to a + * command object on the JavaScript side. $command_item['command'] is the type + * of command, e.g. 'alert' or 'replace', and will correspond to a method in the + * Drupal.ajax[command] space. The command array may contain any other data that + * the command needs to process, e.g. 'method', 'selector', 'settings', etc. * * Commands are usually created with a couple of helper functions, so they * look like this: @@ -222,7 +223,7 @@ */ /** - * Render a commands array into JSON. + * Renders a commands array into JSON. * * @param $commands * A list of macro commands generated by the use of ajax_command_*() @@ -262,19 +263,13 @@ function ajax_render($commands = array()) { } } - // Settings are handled separately, later in this function, so that changes to - // the ajaxPageState setting that occur during drupal_get_css() and - // drupal_get_js() get included, and because the jQuery.extend() code produced - // by drupal_get_js() for adding settings isn't appropriate during an Ajax - // response, because it does not pass TRUE for the "deep" parameter, and - // therefore, can clobber existing settings on the page. + // Render the HTML to load these files, and add AJAX commands to insert this + // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the + // data from being altered again, as we already altered it above. Settings are + // handled separately, afterwards. if (isset($items['js']['settings'])) { unset($items['js']['settings']); } - - // Render the HTML to load these files, and add Ajax commands to insert this - // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the - // data from being altered again, as we already altered it above. $styles = drupal_get_css($items['css'], TRUE); $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); $scripts_header = drupal_get_js('header', $items['js'], TRUE); @@ -293,11 +288,10 @@ function ajax_render($commands = array()) { $commands = array_merge($extra_commands, $commands); } + // Now add a command to merge changes and additions to Drupal.settings. $scripts = drupal_add_js(); if (!empty($scripts['settings'])) { $settings = $scripts['settings']; - // Automatically extract any settings added via drupal_add_js() and make - // them the first command. array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE)); } @@ -308,7 +302,7 @@ function ajax_render($commands = array()) { } /** - * Get a form submitted via #ajax during an Ajax callback. + * Gets a form submitted via #ajax during an Ajax callback. * * This will load a form from the form cache used during Ajax operations. It * pulls the form info from $_POST. @@ -368,6 +362,8 @@ function ajax_get_form() { * #ajax['path']. If processing is required that cannot be accomplished with * a callback, re-implement this function and set #ajax['path'] to the * enhanced function. + * + * @see system_menu() */ function ajax_form_callback() { list($form, $form_state) = ajax_get_form(); @@ -403,6 +399,9 @@ function ajax_form_callback() { * of the page. Therefore, system_menu() sets the 'theme callback' for * 'system/ajax' to this function, and it is recommended that modules * implementing other generic Ajax paths do the same. + * + * @see system_menu() + * @see file_menu() */ function ajax_base_page_theme() { if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) { @@ -421,7 +420,7 @@ function ajax_base_page_theme() { } /** - * Package and send the result of a page callback to the browser as an Ajax response. + * Packages and sends the result of a page callback as an Ajax response. * * This function is the equivalent of drupal_deliver_html_page(), but for Ajax * requests. Like that function, it: @@ -554,7 +553,7 @@ function ajax_prepare_response($page_callback_result) { } /** - * Perform end-of-Ajax-request tasks. + * Performs end-of-Ajax-request tasks. * * This function is the equivalent of drupal_page_footer(), but for Ajax * requests. @@ -577,7 +576,7 @@ function ajax_footer() { } /** - * Form element process callback to handle #ajax. + * Form element processing handler for the #ajax form property. * * @param $element * An associative array containing the properties of the element. @@ -596,7 +595,7 @@ function ajax_process_form($element, &$form_state) { } /** - * Add Ajax information about an element to the page to communicate with JavaScript. + * Adds Ajax information about an element to communicate with JavaScript. * * If #ajax['path'] is set on an element, this additional JavaScript is added * to the page header to attach the Ajax behaviors. See ajax.js for more @@ -1210,4 +1209,3 @@ function ajax_command_restripe($selector) { 'selector' => $selector, ); } - diff --git a/includes/archiver.inc b/includes/archiver.inc index fec053be6..3ce117390 100644 --- a/includes/archiver.inc +++ b/includes/archiver.inc @@ -6,61 +6,63 @@ */ /** - * Common interface for all Archiver classes. + * Defines the common interface for all Archiver classes. */ interface ArchiverInterface { /** - * Constructor for a new archiver instance. + * Constructs a new archiver instance. * * @param $file_path - * The full system path of the archive to manipulate. Only local files - * are supported. If the file does not yet exist, it will be created if + * The full system path of the archive to manipulate. Only local files + * are supported. If the file does not yet exist, it will be created if * appropriate. */ public function __construct($file_path); /** - * Add the specified file or directory to the archive. + * Adds the specified file or directory to the archive. * * @param $file_path * The full system path of the file or directory to add. Only local files * and directories are supported. + * * @return ArchiverInterface * The called object. */ public function add($file_path); /** - * Remove the specified file from the archive. + * Removes the specified file from the archive. * * @param $path * The file name relative to the root of the archive to remove. + * * @return ArchiverInterface * The called object. */ public function remove($path); /** - * Extract multiple files in the archive to the specified path. + * Extracts multiple files in the archive to the specified path. * * @param $path * A full system path of the directory to which to extract files. * @param $files * Optionally specify a list of files to be extracted. Files are * relative to the root of the archive. If not specified, all files - * in the archive will be extracted + * in the archive will be extracted. + * * @return ArchiverInterface * The called object. */ - public function extract($path, Array $files = array()); + public function extract($path, array $files = array()); /** - * List all files in the archive. + * Lists all files in the archive. * * @return * An array of file names relative to the root of the archive. */ public function listContents(); } - diff --git a/includes/authorize.inc b/includes/authorize.inc index 852860413..da6918ca7 100644 --- a/includes/authorize.inc +++ b/includes/authorize.inc @@ -6,7 +6,13 @@ */ /** - * Build the form for choosing a FileTransfer type and supplying credentials. + * Form constructor for the file transfer authorization form. + * + * Allows the user to choose a FileTransfer type and supply credentials. + * + * @see authorize_filetransfer_form_validate() + * @see authorize_filetransfer_form_submit() + * @ingroup forms */ function authorize_filetransfer_form($form, &$form_state) { global $base_url, $is_https; @@ -127,10 +133,11 @@ function authorize_filetransfer_form($form, &$form_state) { } /** - * Generate the Form API array for the settings for a given connection backend. + * Generates the Form API array for a given connection backend's settings. * * @param $backend * The name of the backend (e.g. 'ftp', 'ssh', etc). + * * @return * Form API array of connection settings for the given backend. * @@ -151,7 +158,7 @@ function _authorize_filetransfer_connection_settings($backend) { } /** - * Recursively fill in the default settings on a file transfer connection form. + * Sets the default settings on a file transfer connection form recursively. * * The default settings for the file transfer connection forms are saved in * the database. The settings are stored as a nested array in the case of a @@ -165,8 +172,6 @@ function _authorize_filetransfer_connection_settings($backend) { * The key for our current form element, if any. * @param array $defaults * The default settings for the file transfer backend we're operating on. - * @return - * Nothing, this function just sets $element['#default_value'] if needed. */ function _authorize_filetransfer_connection_settings_set_defaults(&$element, $key, array $defaults) { // If we're operating on a form element which isn't a fieldset, and we have @@ -186,9 +191,10 @@ function _authorize_filetransfer_connection_settings_set_defaults(&$element, $ke } /** - * Validate callback for the filetransfer authorization form. + * Form validation handler for authorize_filetransfer_form(). * * @see authorize_filetransfer_form() + * @see authorize_filetransfer_submit() */ function authorize_filetransfer_form_validate($form, &$form_state) { // Only validate the form if we have collected all of the user input and are @@ -218,9 +224,10 @@ function authorize_filetransfer_form_validate($form, &$form_state) { } /** - * Submit callback when a file transfer is being authorized. + * Form submission handler for authorize_filetransfer_form(). * * @see authorize_filetransfer_form() + * @see authorize_filetransfer_validate() */ function authorize_filetransfer_form_submit($form, &$form_state) { global $base_url; @@ -280,7 +287,7 @@ function authorize_filetransfer_form_submit($form, &$form_state) { } /** - * Run the operation specified in $_SESSION['authorize_operation'] + * Runs the operation specified in $_SESSION['authorize_operation']. * * @param $filetransfer * The FileTransfer object to use for running the operation. @@ -298,12 +305,13 @@ function authorize_run_operation($filetransfer) { } /** - * Get a FileTransfer class for a specific transfer method and settings. + * Gets a FileTransfer class for a specific transfer method and settings. * * @param $backend * The FileTransfer backend to get the class for. * @param $settings * Array of settings for the FileTransfer. + * * @return * An instantiated FileTransfer object for the requested method and settings, * or FALSE if there was an error finding or instantiating it. diff --git a/includes/batch.inc b/includes/batch.inc index 727c62560..061acd4c6 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -1,6 +1,5 @@ <?php - /** * @file * Batch processing API for processes to run in multiple HTTP requests. @@ -21,6 +20,7 @@ * @param $id * The ID of the batch to load. When a progressive batch is being processed, * the relevant ID is found in $_REQUEST['id']. + * * @return * An array representing the batch, or FALSE if no batch was found. */ @@ -36,7 +36,7 @@ function batch_load($id) { } /** - * State-based dispatcher for the batch processing page. + * Renders the batch processing page based on the current state of the batch. * * @see _batch_shutdown() */ @@ -94,7 +94,7 @@ function _batch_page() { } /** - * Initialize the batch processing. + * Initializes the batch processing. * * JavaScript-enabled clients are identified by the 'has_js' cookie set in * drupal.js. If no JavaScript-enabled page has been visited during the current @@ -110,7 +110,7 @@ function _batch_start() { } /** - * Output a batch processing page with JavaScript support. + * Outputs a batch processing page with JavaScript support. * * This initializes the batch and error messages. Note that in JavaScript-based * processing, the batch processing page is displayed only once and updated via @@ -144,7 +144,7 @@ function _batch_progress_page_js() { } /** - * Do one execution pass in JavaScript-mode and return progress to the browser. + * Does one execution pass with JavaScript and returns progress to the browser. * * @see _batch_progress_page_js() * @see _batch_process() @@ -164,7 +164,7 @@ function _batch_do() { } /** - * Output a batch processing page without JavaScript support. + * Outputs a batch processing page without JavaScript support. * * @see _batch_process() */ @@ -228,7 +228,7 @@ function _batch_progress_page_nojs() { } /** - * Process sets in a batch. + * Processes sets in a batch. * * If the batch was marked for progressive execution (default), this executes as * many operations in batch sets until an execution time of 1 second has been @@ -370,7 +370,7 @@ function _batch_process() { } /** - * Helper function for _batch_process(): returns the formatted percentage. + * Formats the percent completion for a batch set. * * @param $total * The total number of operations. @@ -379,11 +379,14 @@ function _batch_process() { * rather than an integer in the case of a multi-step operation that is not * yet complete; in that case, the fractional part of $current represents the * fraction of the operation that has been completed. + * * @return * The properly formatted percentage, as a string. We output percentages * using the correct number of decimal places so that we never print "100%" * until we are finished, but we also never print more decimal places than * are meaningful. + * + * @see _batch_process() */ function _batch_api_percentage($total, $current) { if (!$total || $total == $current) { @@ -410,7 +413,7 @@ function _batch_api_percentage($total, $current) { } /** - * Return the batch set being currently processed. + * Returns the batch set being currently processed. */ function &_batch_current_set() { $batch = &batch_get(); @@ -418,7 +421,7 @@ function &_batch_current_set() { } /** - * Retrieve the next set in a batch. + * Retrieves the next set in a batch. * * If there is a subsequent set in this batch, assign it as the new set to * process and execute its form submit handler (if defined), which may add @@ -442,7 +445,7 @@ function _batch_next_set() { } /** - * End the batch processing. + * Ends the batch processing. * * Call the 'finished' callback of each batch set to allow custom handling of * the results and resolve page redirection. @@ -521,7 +524,10 @@ function _batch_finished() { } /** - * Shutdown function; store the current batch data for the next request. + * Shutdown function: Stores the current batch data for the next request. + * + * @see _batch_page() + * @see drupal_register_shutdown_function() */ function _batch_shutdown() { if ($batch = batch_get()) { @@ -531,4 +537,3 @@ function _batch_shutdown() { ->execute(); } } - diff --git a/includes/batch.queue.inc b/includes/batch.queue.inc index 846483698..ed290ee70 100644 --- a/includes/batch.queue.inc +++ b/includes/batch.queue.inc @@ -1,24 +1,30 @@ <?php - /** * @file * Queue handlers used by the Batch API. * - * Those implementations: - * - ensure FIFO ordering, - * - let an item be repeatedly claimed until it is actually deleted (no notion - * of lease time or 'expire' date), to allow multipass operations. + * These implementations: + * - Ensure FIFO ordering. + * - Allow an item to be repeatedly claimed until it is actually deleted (no + * notion of lease time or 'expire' date), to allow multipass operations. */ /** - * Batch queue implementation. + * Defines a batch queue. * * Stale items from failed batches are cleaned from the {queue} table on cron * using the 'created' date. */ class BatchQueue extends SystemQueue { + /** + * Overrides SystemQueue::claimItem(). + * + * Unlike SystemQueue::claimItem(), this method provides a default lease + * time of 0 (no expiration) instead of 30. This allows the item to be + * claimed repeatedly until it is deleted. + */ public function claimItem($lease_time = 0) { $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject(); if ($item) { @@ -29,9 +35,9 @@ class BatchQueue extends SystemQueue { } /** - * Retrieve all remaining items in the queue. + * Retrieves all remaining items in the queue. * - * This is specific to Batch API and is not part of the DrupalQueueInterface, + * This is specific to Batch API and is not part of the DrupalQueueInterface. */ public function getAllItems() { $result = array(); @@ -44,10 +50,17 @@ class BatchQueue extends SystemQueue { } /** - * Batch queue implementation used for non-progressive batches. + * Defines a batch queue for non-progressive batches. */ class BatchMemoryQueue extends MemoryQueue { + /** + * Overrides MemoryQueue::claimItem(). + * + * Unlike MemoryQueue::claimItem(), this method provides a default lease + * time of 0 (no expiration) instead of 30. This allows the item to be + * claimed repeatedly until it is deleted. + */ public function claimItem($lease_time = 0) { if (!empty($this->queue)) { reset($this->queue); @@ -57,9 +70,9 @@ class BatchMemoryQueue extends MemoryQueue { } /** - * Retrieve all remaining items in the queue. + * Retrieves all remaining items in the queue. * - * This is specific to Batch API and is not part of the DrupalQueueInterface, + * This is specific to Batch API and is not part of the DrupalQueueInterface. */ public function getAllItems() { $result = array(); diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 292b8bb87..c32c05d5f 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.11'); +define('VERSION', '7.12'); /** * Core API compatibility. @@ -43,9 +43,9 @@ define('CACHE_TEMPORARY', -1); * Logging severity levels as defined in RFC 3164. * * The WATCHDOG_* constant definitions correspond to the logging severity levels - * defined in RFC 3164, section 4.1.1. PHP supplies predefined LOG_* constants + * defined in RFC 3164, section 4.1.1. PHP supplies predefined LOG_* constants * for use in the syslog() function, but their values on Windows builds do not - * correspond to RFC 3164. The associated PHP bug report was closed with the + * correspond to RFC 3164. The associated PHP bug report was closed with the * comment, "And it's also not a bug, as Windows just have less log levels," * and "So the behavior you're seeing is perfectly normal." * @@ -137,8 +137,7 @@ define('DRUPAL_BOOTSTRAP_PAGE_HEADER', 5); define('DRUPAL_BOOTSTRAP_LANGUAGE', 6); /** - * Final bootstrap phase: Drupal is fully loaded; validate and fix - * input data. + * Final bootstrap phase: Drupal is fully loaded; validate and fix input data. */ define('DRUPAL_BOOTSTRAP_FULL', 7); @@ -153,8 +152,9 @@ define('DRUPAL_ANONYMOUS_RID', 1); define('DRUPAL_AUTHENTICATED_RID', 2); /** - * The number of bytes in a kilobyte. For more information, visit - * http://en.wikipedia.org/wiki/Kilobyte. + * The number of bytes in a kilobyte. + * + * For more information, visit http://en.wikipedia.org/wiki/Kilobyte. */ define('DRUPAL_KILOBYTE', 1024); @@ -191,10 +191,14 @@ define('LANGUAGE_LTR', 0); define('LANGUAGE_RTL', 1); /** - * For convenience, define a short form of the request time global. + * Time of the current request in seconds elapsed since the Unix Epoch. + * + * This differs from $_SERVER['REQUEST_TIME'], which is stored as a float + * since PHP 5.4.0. Float timestamps confuse most PHP functions + * (including date_create()). * - * REQUEST_TIME is a float with microseconds since PHP 5.4.0, but float - * timestamps confuses most of the PHP functions (including date_create()). + * @see http://php.net/manual/reserved.variables.server.php + * @see http://php.net/manual/function.time.php */ define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']); @@ -285,12 +289,12 @@ abstract class DrupalCacheArray implements ArrayAccess { /** * A cid to pass to cache_set() and cache_get(). */ - private $cid; + protected $cid; /** * A bin to pass to cache_set() and cache_get(). */ - private $bin; + protected $bin; /** * An array of keys to add to the cache at the end of the request. @@ -303,7 +307,7 @@ abstract class DrupalCacheArray implements ArrayAccess { protected $storage = array(); /** - * Constructor. + * Constructs a DrupalCacheArray object. * * @param $cid * The cid for the array being cached. @@ -319,10 +323,16 @@ abstract class DrupalCacheArray implements ArrayAccess { } } + /** + * Implements ArrayAccess::offsetExists(). + */ public function offsetExists($offset) { return $this->offsetGet($offset) !== NULL; } + /** + * Implements ArrayAccess::offsetGet(). + */ public function offsetGet($offset) { if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) { return $this->storage[$offset]; @@ -332,10 +342,16 @@ abstract class DrupalCacheArray implements ArrayAccess { } } + /** + * Implements ArrayAccess::offsetSet(). + */ public function offsetSet($offset, $value) { $this->storage[$offset] = $value; } + /** + * Implements ArrayAccess::offsetUnset(). + */ public function offsetUnset($offset) { unset($this->storage[$offset]); } @@ -375,32 +391,31 @@ abstract class DrupalCacheArray implements ArrayAccess { abstract protected function resolveCacheMiss($offset); /** - * Immediately write a value to the persistent cache. + * Writes a value to the persistent cache immediately. * - * @param $cid - * The cache ID. - * @param $bin - * The cache bin. * @param $data * The data to write to the persistent cache. * @param $lock * Whether to acquire a lock before writing to cache. */ - protected function set($cid, $data, $bin, $lock = TRUE) { + protected function set($data, $lock = TRUE) { // Lock cache writes to help avoid stampedes. // To implement locking for cache misses, override __construct(). - $lock_name = $cid . ':' . $bin; + $lock_name = $this->cid . ':' . $this->bin; if (!$lock || lock_acquire($lock_name)) { - if ($cached = cache_get($cid, $bin)) { + if ($cached = cache_get($this->cid, $this->bin)) { $data = $cached->data + $data; } - cache_set($cid, $data, $bin); + cache_set($this->cid, $data, $this->bin); if ($lock) { lock_release($lock_name); } } } + /** + * Destructs the DrupalCacheArray object. + */ public function __destruct() { $data = array(); foreach ($this->keysToPersist as $offset => $persist) { @@ -409,14 +424,16 @@ abstract class DrupalCacheArray implements ArrayAccess { } } if (!empty($data)) { - $this->set($this->cid, $data, $this->bin); + $this->set($data); } } } /** - * Start the timer with the specified name. If you start and stop the same - * timer multiple times, the measured intervals will be accumulated. + * Starts the timer with the specified name. + * + * If you start and stop the same timer multiple times, the measured intervals + * will be accumulated. * * @param $name * The name of the timer. @@ -429,7 +446,7 @@ function timer_start($name) { } /** - * Read the current timer value without stopping the timer. + * Reads the current timer value without stopping the timer. * * @param $name * The name of the timer. @@ -453,7 +470,7 @@ function timer_read($name) { } /** - * Stop the timer with the specified name. + * Stops the timer with the specified name. * * @param $name * The name of the timer. @@ -578,7 +595,7 @@ function conf_path($require_settings = TRUE, $reset = FALSE) { } /** - * Set appropriate server variables needed for command line scripts to work. + * Sets appropriate server variables needed for command line scripts to work. * * This function can be called by command line scripts before bootstrapping * Drupal, to ensure that the page loads with the desired server parameters. @@ -640,7 +657,7 @@ function drupal_override_server_variables($variables = array()) { } /** - * Initialize PHP environment. + * Initializes the PHP environment. */ function drupal_environment_initialize() { if (!isset($_SERVER['HTTP_REFERER'])) { @@ -699,7 +716,7 @@ function drupal_environment_initialize() { } /** - * Validate that a hostname (for example $_SERVER['HTTP_HOST']) is safe. + * Validates that a hostname (for example $_SERVER['HTTP_HOST']) is safe. * * @return * TRUE if only containing valid characters, or FALSE otherwise. @@ -709,8 +726,7 @@ function drupal_valid_http_host($host) { } /** - * Loads the configuration and sets the base URL, cookie domain, and - * session name correctly. + * Sets the base URL, cookie domain, and session name from configuration. */ function drupal_settings_initialize() { global $base_url, $base_path, $base_root; @@ -796,9 +812,10 @@ function drupal_settings_initialize() { } /** - * Returns and optionally sets the filename for a system item (module, - * theme, etc.). The filename, whether provided, cached, or retrieved - * from the database, is only returned if the file exists. + * Returns and optionally sets the filename for a system resource. + * + * The filename, whether provided, cached, or retrieved from the database, is + * only returned if the file exists. * * This function plays a key role in allowing Drupal's resources (modules * and themes) to be located in different places depending on a site's @@ -828,6 +845,11 @@ function drupal_get_filename($type, $name, $filename = NULL) { // drupal_static(). static $files = array(), $dirs = array(); + // Profiles are a special case: they have a fixed location and naming. + if ($type == 'profile') { + $profile_filename = "profiles/$name/$name.profile"; + $files[$type][$name] = file_exists($profile_filename) ? $profile_filename : FALSE; + } if (!isset($files[$type])) { $files[$type] = array(); } @@ -895,11 +917,11 @@ function drupal_get_filename($type, $name, $filename = NULL) { } /** - * Load the persistent variable table. + * Loads the persistent variable table. * * The variable table is composed of values that have been saved in the table - * with variable_set() as well as those explicitly specified in the configuration - * file. + * with variable_set() as well as those explicitly specified in the + * configuration file. */ function variable_initialize($conf = array()) { // NOTE: caching the variables improves performance by 20% when serving @@ -1006,7 +1028,7 @@ function variable_del($name) { } /** - * Retrieve the current page from the cache. + * Retrieves the current page from the cache. * * Note: we do not serve cached pages to authenticated users, or to anonymous * users when $_SESSION is non-empty. $_SESSION may contain status messages @@ -1038,7 +1060,7 @@ function drupal_page_get_cache($check_only = FALSE) { } /** - * Determine the cacheability of the current page. + * Determines the cacheability of the current page. * * @param $allow_caching * Set to FALSE if you want to prevent this page to get cached. @@ -1057,7 +1079,7 @@ function drupal_page_is_cacheable($allow_caching = NULL) { } /** - * Invoke a bootstrap hook in all bootstrap modules that implement it. + * Invokes a bootstrap hook in all bootstrap modules that implement it. * * @param $hook * The name of the bootstrap hook to invoke. @@ -1079,8 +1101,9 @@ function bootstrap_invoke_all($hook) { } /** - * Includes a file with the provided type and name. This prevents - * including a theme, engine, module, etc., more than once. + * Includes a file with the provided type and name. + * + * This prevents including a theme, engine, module, etc., more than once. * * @param $type * The type of item to load (i.e. theme, theme_engine, module). @@ -1112,7 +1135,7 @@ function drupal_load($type, $name) { } /** - * Set an HTTP response header for the current page. + * Sets an HTTP response header for the current page. * * Note: When sending a Content-Type header, always include a 'charset' type, * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS). @@ -1148,11 +1171,12 @@ function drupal_add_http_header($name, $value, $append = FALSE) { } /** - * Get the HTTP response headers for the current page. + * Gets the HTTP response headers for the current page. * * @param $name * An HTTP header name. If omitted, all headers are returned as name/value * pairs. If an array value is FALSE, the header has been unset. + * * @return * A string containing the header value, or FALSE if the header has been set, * or NULL if the header has not been set. @@ -1169,6 +1193,8 @@ function drupal_get_http_header($name = NULL) { } /** + * Sets the preferred name for the HTTP header. + * * Header names are case-insensitive, but for maximum compatibility they should * follow "common form" (see RFC 2617, section 4.2). */ @@ -1182,9 +1208,10 @@ function _drupal_set_preferred_header_name($name = NULL) { } /** - * Send the HTTP response headers previously set using drupal_add_http_header(). - * Add default headers, unless they have been replaced or unset using - * drupal_add_http_header(). + * Sends the HTTP response headers that were previously set, adding defaults. + * + * Headers are set in drupal_add_http_header(). Default headers are not set + * if they have been replaced or unset using drupal_add_http_header(). * * @param $default_headers * An array of headers as name/value pairs. @@ -1219,7 +1246,7 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE) } /** - * Set HTTP headers in preparation for a page response. + * Sets HTTP headers in preparation for a page response. * * Authenticated users are always given a 'no-cache' header, and will fetch a * fresh page on every request. This prevents authenticated users from seeing @@ -1262,7 +1289,7 @@ function drupal_page_header() { } /** - * Set HTTP headers in preparation for a cached page response. + * Sets HTTP headers in preparation for a cached page response. * * The headers allow as much as possible in proxies and browsers without any * particular knowledge about the pages. Modules can override these headers @@ -1365,7 +1392,7 @@ function drupal_serve_page_from_cache(stdClass $cache) { } /** - * Define the critical hooks that force modules to always be loaded. + * Defines the critical hooks that force modules to always be loaded. */ function bootstrap_hooks() { return array('boot', 'exit', 'watchdog', 'language_init'); @@ -1418,10 +1445,10 @@ function drupal_unpack($obj, $field = 'data') { * $text = t("@name's blog", array('@name' => format_username($account))); * @endcode * Basically, you can put variables like @name into your string, and t() will - * substitute their sanitized values at translation time (see $args below or - * the Localization API pages referenced above for details). Translators can - * then rearrange the string as necessary for the language (e.g., in Spanish, - * it might be "blog de @name"). + * substitute their sanitized values at translation time. (See the + * Localization API pages referenced above and the documentation of + * format_string() for details.) Translators can then rearrange the string as + * necessary for the language (e.g., in Spanish, it might be "blog de @name"). * * During the Drupal installation phase, some resources used by t() wil not be * available to code that needs localization. See st() and get_t() for @@ -1430,8 +1457,9 @@ function drupal_unpack($obj, $field = 'data') { * @param $string * A string containing the English string to translate. * @param $args - * An associative array of replacements to make after translation. - * See format_string(). + * An associative array of replacements to make after translation. Based + * on the first character of the key, the value is escaped and/or themed. + * See format_string() for details. * @param $options * An associative array of additional options, with the following elements: * - 'langcode' (defaults to the current language): The language code to @@ -1444,6 +1472,7 @@ function drupal_unpack($obj, $field = 'data') { * * @see st() * @see get_t() + * @see format_string() * @ingroup sanitization */ function t($string, array $args = array(), array $options = array()) { @@ -1482,7 +1511,7 @@ function t($string, array $args = array(), array $options = array()) { } /** - * Replace placeholders with sanitized values in a string. + * Replaces placeholders with sanitized values in a string. * * @param $string * A string containing placeholders. @@ -1524,7 +1553,7 @@ function format_string($string, array $args = array()) { } /** - * Encode special characters in a plain-text string for display as HTML. + * Encodes special characters in a plain-text string for display as HTML. * * Also validates strings as UTF-8 to prevent cross site scripting attacks on * Internet Explorer 6. @@ -1563,6 +1592,7 @@ function check_plain($text) { * * @param $text * The text to check. + * * @return * TRUE if the text is valid UTF-8, FALSE if not. */ @@ -1604,7 +1634,7 @@ function request_uri() { } /** - * Log an exception. + * Logs an exception. * * This is a wrapper function for watchdog() which automatically decodes an * exception. @@ -1645,7 +1675,7 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia } /** - * Log a system message. + * Logs a system message. * * @param $type * The category to which this message belongs. Can be any string, but the @@ -1705,7 +1735,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO } /** - * Set a message which reflects the status of the performed operation. + * Sets a message which reflects the status of the performed operation. * * If the function is called with no arguments, this function returns all set * messages without clearing them. @@ -1741,12 +1771,13 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { } /** - * Return all messages that have been set. + * Returns all messages that have been set. * * @param $type * (optional) Only return messages of this type. * @param $clear_queue * (optional) Set to FALSE if you do not want to clear the messages queue + * * @return * An associative array, the key is the message type, the value an array * of messages. If the $type parameter is passed, you get only that type, @@ -1774,7 +1805,9 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) { } /** - * Get the title of the current page, for display on the page and in the title bar. + * Gets the title of the current page. + * + * The title is displayed on the page and in the title bar. * * @return * The current page's title. @@ -1791,7 +1824,9 @@ function drupal_get_title() { } /** - * Set the title of the current page, for display on the page and in the title bar. + * Sets the title of the current page. + * + * The title is displayed on the page and in the title bar. * * @param $title * Optional string value to assign to the page title; or if set to NULL @@ -1816,7 +1851,7 @@ function drupal_set_title($title = NULL, $output = CHECK_PLAIN) { } /** - * Check to see if an IP address has been blocked. + * Checks to see if an IP address has been blocked. * * Blocked IP addresses are stored in the database by default. However for * performance reasons we allow an override in settings.php. This allows us @@ -1825,6 +1860,7 @@ function drupal_set_title($title = NULL, $output = CHECK_PLAIN) { * * @param $ip * IP address to check. + * * @return bool * TRUE if access is denied, FALSE if access is allowed. */ @@ -1850,7 +1886,7 @@ function drupal_is_denied($ip) { } /** - * Handle denied users. + * Handles denied users. * * @param $ip * IP address to check. Prints a message and exits if access is denied. @@ -1869,7 +1905,8 @@ function drupal_block_denied($ip) { * * This function is better than simply calling mt_rand() or any other built-in * PHP function because it can return a long string of bytes (compared to < 4 - * bytes normally from mt_rand()) and uses the best available pseudo-random source. + * bytes normally from mt_rand()) and uses the best available pseudo-random + * source. * * @param $count * The number of characters (bytes) to return in the string. @@ -1916,7 +1953,7 @@ function drupal_random_bytes($count) { } /** - * Calculate a base-64 encoded, URL-safe sha-256 hmac. + * Calculates a base-64 encoded, URL-safe sha-256 hmac. * * @param $data * String to be validated with the hmac. @@ -1934,7 +1971,7 @@ function drupal_hmac_base64($data, $key) { } /** - * Calculate a base-64 encoded, URL-safe sha-256 hash. + * Calculates a base-64 encoded, URL-safe sha-256 hash. * * @param $data * String to be hashed. @@ -1977,7 +2014,8 @@ function drupal_hash_base64($data) { * @see drupal_array_merge_deep_array() */ function drupal_array_merge_deep() { - return drupal_array_merge_deep_array(func_get_args()); + $args = func_get_args(); + return drupal_array_merge_deep_array($args); } /** @@ -2038,20 +2076,22 @@ function drupal_anonymous_user() { } /** - * A string describing a phase of Drupal to load. Each phase adds to the - * previous one, so invoking a later phase automatically runs the earlier - * phases too. The most important usage is that if you want to access the - * Drupal database from a script without loading anything else, you can - * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE). + * Ensures Drupal is bootstrapped to the specified phase. + * + * The bootstrap phase is an integer constant identifying a phase of Drupal + * to load. Each phase adds to the previous one, so invoking a later phase + * automatically runs the earlier phases as well. To access the Drupal + * database from a script without loading anything else, include bootstrap.inc + * and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE). * * @param $phase * A constant. Allowed values are the DRUPAL_BOOTSTRAP_* constants. * @param $new_phase * A boolean, set to FALSE if calling drupal_bootstrap from inside a * function called from drupal_bootstrap (recursion). + * * @return * The most recently completed phase. - * */ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) { // Not drupal_static(), because does not depend on any run-time information. @@ -2130,7 +2170,7 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) { } /** - * Return the time zone of the current user. + * Returns the time zone of the current user. */ function drupal_get_user_timezone() { global $user; @@ -2145,7 +2185,7 @@ function drupal_get_user_timezone() { } /** - * Custom PHP error handler. + * Provides custom PHP error handling. * * @param $error_level * The level of the error raised. @@ -2156,7 +2196,8 @@ function drupal_get_user_timezone() { * @param $line * The line number the error was raised at. * @param $context - * An array that points to the active symbol table at the point the error occurred. + * An array that points to the active symbol table at the point the error + * occurred. */ function _drupal_error_handler($error_level, $message, $filename, $line, $context) { require_once DRUPAL_ROOT . '/includes/errors.inc'; @@ -2164,7 +2205,7 @@ function _drupal_error_handler($error_level, $message, $filename, $line, $contex } /** - * Custom PHP exception handler. + * Provides custom PHP exception handling. * * Uncaught exceptions are those not enclosed in a try/catch block. They are * always fatal: the execution of the script will stop as soon as the exception @@ -2192,7 +2233,7 @@ function _drupal_exception_handler($exception) { } /** - * Bootstrap configuration: Setup script environment and load settings.php. + * Sets up the script environment and loads settings.php. */ function _drupal_bootstrap_configuration() { // Set the Drupal custom error handler. @@ -2207,7 +2248,7 @@ function _drupal_bootstrap_configuration() { } /** - * Bootstrap page cache: Try to serve a page from cache. + * Attempts to serve a page from the cache. */ function _drupal_bootstrap_page_cache() { global $user; @@ -2263,7 +2304,7 @@ function _drupal_bootstrap_page_cache() { } /** - * Bootstrap database: Initialize database system and register autoload functions. + * Initializes the database system and registers autoload functions. */ function _drupal_bootstrap_database() { // Redirect the user to the installation script if Drupal has not been @@ -2315,7 +2356,7 @@ function _drupal_bootstrap_database() { } /** - * Bootstrap variables: Load system variables and all enabled bootstrap modules. + * Loads system variables and all enabled bootstrap modules. */ function _drupal_bootstrap_variables() { global $conf; @@ -2332,7 +2373,7 @@ function _drupal_bootstrap_variables() { } /** - * Bootstrap page header: Invoke hook_boot(), initialize locking system, and send default HTTP headers. + * Invokes hook_boot(), initializes locking system, and sends HTTP headers. */ function _drupal_bootstrap_page_header() { bootstrap_invoke_all('boot'); @@ -2355,8 +2396,7 @@ function drupal_get_bootstrap_phase() { } /** - * Checks the current User-Agent string to see if this is an internal request - * from SimpleTest. If so, returns the test prefix for this test. + * Returns the test prefix if this is an internal request from SimpleTest. * * @return * Either the simpletest prefix (the string "simpletest" followed by any @@ -2392,7 +2432,7 @@ function drupal_valid_test_ua() { } /** - * Generate a user agent string with a HMAC and timestamp for simpletest. + * Generates a user agent string with a HMAC and timestamp for simpletest. */ function drupal_generate_test_ua($prefix) { global $drupal_hash_salt; @@ -2452,7 +2492,7 @@ function drupal_fast_404() { } /** - * Return TRUE if a Drupal installation is currently being attempted. + * Returns TRUE if a Drupal installation is currently being attempted. */ function drupal_installation_attempted() { return defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install'; @@ -2495,10 +2535,9 @@ function get_t() { } /** - * Initialize all the defined language types. + * Initializes all the defined language types. */ function drupal_language_initialize() { - global $language; $types = language_types(); // Ensure the language is correctly returned, even without multilanguage @@ -2518,13 +2557,10 @@ function drupal_language_initialize() { // environments. bootstrap_invoke_all('language_init'); } - - // Send appropriate HTTP-Header for browsers and search engines. - header('Content-Language: ' . $language->language); } /** - * The built-in language types. + * Returns a list of the built-in language types. * * @return * An array of key-values pairs where the key is the language type and the @@ -2539,7 +2575,7 @@ function drupal_language_types() { } /** - * Return true if there is more than one language enabled. + * Returns TRUE if there is more than one language enabled. */ function drupal_multilingual() { // The "language_count" variable stores the number of enabled languages to @@ -2549,7 +2585,7 @@ function drupal_multilingual() { } /** - * Return an array of the available language types. + * Returns an array of the available language types. */ function language_types() { return array_keys(variable_get('language_types', drupal_language_types())); @@ -2606,7 +2642,7 @@ function language_list($field = 'language') { } /** - * Default language used on the site + * Returns the default language used on the site * * @param $property * Optional property of the language object to return @@ -2676,16 +2712,16 @@ function request_path() { } /** - * Return a component of the current Drupal path. + * Returns a component of the current Drupal path. * * When viewing a page at the path "admin/structure/types", for example, arg(0) * returns "admin", arg(1) returns "structure", and arg(2) returns "types". * - * Avoid use of this function where possible, as resulting code is hard to read. - * In menu callback functions, attempt to use named arguments. See the explanation - * in menu.inc for how to construct callbacks that take arguments. When attempting - * to use this function to load an element from the current path, e.g. loading the - * node on a node page, please use menu_get_object() instead. + * Avoid use of this function where possible, as resulting code is hard to + * read. In menu callback functions, attempt to use named arguments. See the + * explanation in menu.inc for how to construct callbacks that take arguments. + * When attempting to use this function to load an element from the current + * path, e.g. loading the node on a node page, use menu_get_object() instead. * * @param $index * The index of the component, where each component is separated by a '/' @@ -2725,6 +2761,8 @@ function arg($index = NULL, $path = NULL) { } /** + * Returns the IP address of the client machine. + * * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of * the proxy server, and not the client's. The actual header name can be @@ -2774,7 +2812,7 @@ function ip_address() { */ /** - * Get the schema definition of a table, or the whole database schema. + * Gets the schema definition of a table, or the whole database schema. * * The returned schema will include any modifications made by any * module that implements hook_schema_alter(). @@ -2810,11 +2848,17 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) { */ class SchemaCache extends DrupalCacheArray { + /** + * Constructs a SchemaCache object. + */ public function __construct() { // Cache by request method. parent::__construct('schema:runtime:' . ($_SERVER['REQUEST_METHOD'] == 'GET'), 'cache'); } + /** + * Overrides DrupalCacheArray::resolveCacheMiss(). + */ protected function resolveCacheMiss($offset) { $complete_schema = drupal_get_complete_schema(); $value = isset($complete_schema[$offset]) ? $complete_schema[$offset] : NULL; @@ -2825,7 +2869,7 @@ class SchemaCache extends DrupalCacheArray { } /** - * Get the whole database schema. + * Gets the whole database schema. * * The returned schema will include any modifications made by any * module that implements hook_schema_alter(). @@ -2895,13 +2939,14 @@ function drupal_get_complete_schema($rebuild = FALSE) { */ /** - * Confirm that an interface is available. + * Confirms that an interface is available. * * This function is rarely called directly. Instead, it is registered as an * spl_autoload() handler, and PHP calls it for us when necessary. * * @param $interface * The name of the interface to check or load. + * * @return * TRUE if the interface is currently available, FALSE otherwise. */ @@ -2910,13 +2955,14 @@ function drupal_autoload_interface($interface) { } /** - * Confirm that a class is available. + * Confirms that a class is available. * * This function is rarely called directly. Instead, it is registered as an * spl_autoload() handler, and PHP calls it for us when necessary. * * @param $class * The name of the class to check or load. + * * @return * TRUE if the class is currently available, FALSE otherwise. */ @@ -2925,7 +2971,7 @@ function drupal_autoload_class($class) { } /** - * Helper to check for a resource in the registry. + * Checks for a resource in the registry. * * @param $type * The type of resource we are looking up, or one of the constants @@ -2934,6 +2980,7 @@ function drupal_autoload_class($class) { * @param $name * The name of the resource, or NULL if either of the REGISTRY_* constants * is passed in. + * * @return * TRUE if the resource was found, FALSE if not. * NULL if either of the REGISTRY_* constants is passed in as $type. @@ -3005,7 +3052,7 @@ function _registry_check_code($type, $name = NULL) { } /** - * Rescan all enabled modules and rebuild the registry. + * Rescans all enabled modules and rebuilds the registry. * * Rescans all code in modules or includes directories, storing the location of * each interface or class in the database. @@ -3016,7 +3063,7 @@ function registry_rebuild() { } /** - * Update the registry based on the latest files listed in the database. + * Updates the registry based on the latest files listed in the database. * * This function should be used when system_rebuild_module_data() does not need * to be called, because it is already known that the list of files in the @@ -3034,7 +3081,7 @@ function registry_update() { */ /** - * Central static variable storage. + * Provides central static variable storage. * * All functions requiring a static variable to persist or cache data within * a single page request are encouraged to use this function unless it is @@ -3185,7 +3232,7 @@ function &drupal_static($name, $default_value = NULL, $reset = FALSE) { } /** - * Reset one or all centrally stored static variable(s). + * Resets one or all centrally stored static variable(s). * * @param $name * Name of the static variable to reset. Omit to reset all variables. @@ -3195,7 +3242,7 @@ function drupal_static_reset($name = NULL) { } /** - * Detect whether the current script is running in a command-line environment. + * Detects whether the current script is running in a command-line environment. */ function drupal_is_cli() { return (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0))); @@ -3203,7 +3250,8 @@ function drupal_is_cli() { /** * Formats text for emphasized display in a placeholder inside a sentence. - * Used automatically by t(). + * + * Used automatically by format_string(). * * @param $text * The text to format (plain-text). @@ -3216,7 +3264,7 @@ function drupal_placeholder($text) { } /** - * Register a function for execution on shutdown. + * Registers a function for execution on shutdown. * * Wrapper for register_shutdown_function() that catches thrown exceptions to * avoid "Exception thrown without a stack frame in Unknown". @@ -3251,7 +3299,7 @@ function &drupal_register_shutdown_function($callback = NULL) { } /** - * Internal function used to execute registered shutdown functions. + * Executes registered shutdown functions. */ function _drupal_shutdown_function() { $callbacks = &drupal_register_shutdown_function(); diff --git a/includes/cache-install.inc b/includes/cache-install.inc index d9bb0f92e..9e0dd01de 100644 --- a/includes/cache-install.inc +++ b/includes/cache-install.inc @@ -6,7 +6,7 @@ */ /** - * A stub cache implementation to be used during the installation process. + * Defines a stub cache implementation to be used during installation. * * The stub implementation is needed when database access is not yet available. * Because Drupal's caching system never requires that cached data be present, @@ -15,17 +15,30 @@ * normal operations would have a negative impact on performance. */ class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterface { + + /** + * Overrides DrupalDatabaseCache::get(). + */ function get($cid) { return FALSE; } + /** + * Overrides DrupalDatabaseCache::getMultiple(). + */ function getMultiple(&$cids) { return array(); } + /** + * Overrides DrupalDatabaseCache::set(). + */ function set($cid, $data, $expire = CACHE_PERMANENT) { } + /** + * Overrides DrupalDatabaseCache::clear(). + */ function clear($cid = NULL, $wildcard = FALSE) { // If there is a database cache, attempt to clear it whenever possible. The // reason for doing this is that the database cache can accumulate data @@ -52,6 +65,9 @@ class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterfac } } + /** + * Overrides DrupalDatabaseCache::isEmpty(). + */ function isEmpty() { return TRUE; } diff --git a/includes/cache.inc b/includes/cache.inc index 8666874ac..eb7f09040 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -1,18 +1,23 @@ <?php /** - * Get the cache object for a cache bin. + * @file + * Functions and interfaces for cache handling. + */ + +/** + * Gets the cache object for a cache bin. * * By default, this returns an instance of the DrupalDatabaseCache class. * Classes implementing DrupalCacheInterface can register themselves both as a * default implementation and for specific bins. * - * @see DrupalCacheInterface - * * @param $bin * The cache bin for which the cache object should be returned. * @return DrupalCacheInterface * The cache object associated with the specified bin. + * + * @see DrupalCacheInterface */ function _cache_get_object($bin) { // We do not use drupal_static() here because we do not want to change the @@ -29,7 +34,7 @@ function _cache_get_object($bin) { } /** - * Return data from the persistent cache + * Returns data from the persistent cache. * * Data may be stored as either plain text or as serialized data. cache_get * will automatically return unserialized objects and arrays. @@ -44,19 +49,22 @@ function _cache_get_object($bin) { * * @return * The cache or FALSE on failure. + * + * @see cache_set() */ function cache_get($cid, $bin = 'cache') { return _cache_get_object($bin)->get($cid); } /** - * Return data from the persistent cache when given an array of cache IDs. + * Returns data from the persistent cache when given an array of cache IDs. * * @param $cids * An array of cache IDs for the data to retrieve. This is passed by * reference, and will have the IDs successfully returned from cache removed. * @param $bin * The cache bin where the data is stored. + * * @return * An array of the items successfully returned from cache indexed by cid. */ @@ -65,7 +73,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { } /** - * Store data in the persistent cache. + * Stores data in the persistent cache. * * The persistent cache is split up into several cache bins. In the default * cache implementation, each cache bin corresponds to a database table by the @@ -132,13 +140,15 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * general cache wipe. * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. + * + * @see cache_get() */ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { return _cache_get_object($bin)->set($cid, $data, $expire); } /** - * Expire data from the cache. + * Expires data from the cache. * * If called without arguments, expirable entries will be cleared from the * cache_page and cache_block bins. @@ -146,15 +156,12 @@ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { * @param $cid * If set, the cache ID to delete. Otherwise, all cache entries that can * expire are deleted. - * * @param $bin - * If set, the bin $bin to delete from. Mandatory - * argument if $cid is set. - * + * If set, the cache bin to delete from. Mandatory argument if $cid is set. * @param $wildcard - * If $wildcard is TRUE, cache IDs starting with $cid are deleted in - * addition to the exact cache ID specified by $cid. If $wildcard is - * TRUE and $cid is '*' then the entire bin $bin is emptied. + * If TRUE, cache IDs starting with $cid are deleted in addition to the + * exact cache ID specified by $cid. If $wildcard is TRUE and $cid is '*', + * the entire cache bin is emptied. */ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) { if (!isset($cid) && !isset($bin)) { @@ -170,13 +177,14 @@ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) { } /** - * Check if a cache bin is empty. + * Checks if a cache bin is empty. * * A cache bin is considered empty if it does not contain any valid data for any * cache ID. * * @param $bin * The cache bin to check. + * * @return * TRUE if the cache bin specified is empty. */ @@ -185,7 +193,7 @@ function cache_is_empty($bin) { } /** - * Interface for cache implementations. + * Defines an interface for cache implementations. * * All cache implementations have to implement this interface. * DrupalDatabaseCache provides the default implementation, which can be @@ -223,7 +231,7 @@ function cache_is_empty($bin) { */ interface DrupalCacheInterface { /** - * Constructor. + * Constructs a new cache interface. * * @param $bin * The cache bin for which the object is created. @@ -231,31 +239,34 @@ interface DrupalCacheInterface { function __construct($bin); /** - * Return data from the persistent cache. Data may be stored as either plain - * text or as serialized data. cache_get will automatically return - * unserialized objects and arrays. + * Returns data from the persistent cache. + * + * Data may be stored as either plain text or as serialized data. cache_get() + * will automatically return unserialized objects and arrays. * * @param $cid * The cache ID of the data to retrieve. + * * @return * The cache or FALSE on failure. */ function get($cid); /** - * Return data from the persistent cache when given an array of cache IDs. + * Returns data from the persistent cache when given an array of cache IDs. * * @param $cids * An array of cache IDs for the data to retrieve. This is passed by * reference, and will have the IDs successfully returned from cache * removed. + * * @return * An array of the items successfully returned from cache indexed by cid. */ function getMultiple(&$cids); /** - * Store data in the persistent cache. + * Stores data in the persistent cache. * * @param $cid * The cache ID of the data to store. @@ -276,8 +287,10 @@ interface DrupalCacheInterface { /** - * Expire data from the cache. If called without arguments, expirable - * entries will be cleared from the cache_page and cache_block bins. + * Expires data from the cache. + * + * If called without arguments, expirable entries will be cleared from the + * cache_page and cache_block bins. * * @param $cid * If set, the cache ID to delete. Otherwise, all cache entries that can @@ -290,7 +303,7 @@ interface DrupalCacheInterface { function clear($cid = NULL, $wildcard = FALSE); /** - * Check if a cache bin is empty. + * Checks if a cache bin is empty. * * A cache bin is considered empty if it does not contain any valid data for * any cache ID. @@ -302,7 +315,7 @@ interface DrupalCacheInterface { } /** - * Default cache implementation. + * Defines a default cache implementation. * * This is Drupal's default cache implementation. It uses the database to store * cached data. Each cache bin corresponds to a database table by the same name. @@ -310,16 +323,25 @@ interface DrupalCacheInterface { class DrupalDatabaseCache implements DrupalCacheInterface { protected $bin; + /** + * Constructs a new DrupalDatabaseCache object. + */ function __construct($bin) { $this->bin = $bin; } + /** + * Implements DrupalCacheInterface::get(). + */ function get($cid) { $cids = array($cid); $cache = $this->getMultiple($cids); return reset($cache); } + /** + * Implements DrupalCacheInterface::getMultiple(). + */ function getMultiple(&$cids) { try { // Garbage collection necessary when enforcing a minimum cache lifetime. @@ -373,13 +395,14 @@ class DrupalDatabaseCache implements DrupalCacheInterface { } /** - * Prepare a cached item. + * Prepares a cached item. * * Checks that items are either permanent or did not expire, and unserializes * data as appropriate. * * @param $cache * An item loaded from cache_get() or cache_get_multiple(). + * * @return * The item with data unserialized as appropriate or FALSE if there is no * valid item to load. @@ -408,6 +431,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface { return $cache; } + /** + * Implements DrupalCacheInterface::set(). + */ function set($cid, $data, $expire = CACHE_PERMANENT) { $fields = array( 'serialized' => 0, @@ -434,6 +460,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface { } } + /** + * Implements DrupalCacheInterface::clear(). + */ function clear($cid = NULL, $wildcard = FALSE) { global $user; @@ -496,6 +525,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface { } } + /** + * Implements DrupalCacheInterface::isEmpty(). + */ function isEmpty() { $this->garbageCollection(); $query = db_select($this->bin); diff --git a/includes/common.inc b/includes/common.inc index 3a3d27473..43e211813 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -70,8 +70,7 @@ define('CSS_DEFAULT', 0); define('CSS_THEME', 100); /** - * The default group for JavaScript libraries, settings or jQuery plugins added - * to the page. + * The default group for JavaScript and jQuery libraries added to the page. */ define('JS_LIBRARY', -100); @@ -86,8 +85,9 @@ define('JS_DEFAULT', 0); define('JS_THEME', 100); /** - * Error code indicating that the request made by drupal_http_request() exceeded - * the specified timeout. + * Error code indicating that the request exceeded the specified timeout. + * + * @see drupal_http_request() */ define('HTTP_REQUEST_TIMEOUT', -1); @@ -110,31 +110,36 @@ define('HTTP_REQUEST_TIMEOUT', -1); */ /** - * The block should not get cached. This setting should be used: - * - for simple blocks (notably those that do not perform any db query), - * where querying the db cache would be more expensive than directly generating - * the content. - * - for blocks that change too frequently. + * The block should not get cached. + * + * This setting should be used: + * - For simple blocks (notably those that do not perform any db query), where + * querying the db cache would be more expensive than directly generating the + * content. + * - For blocks that change too frequently. */ define('DRUPAL_NO_CACHE', -1); /** - * The block is handling its own caching in its hook_block_view(). From the - * perspective of the block cache system, this is equivalent to DRUPAL_NO_CACHE. - * Useful when time based expiration is needed or a site uses a node access - * which invalidates standard block cache. + * The block is handling its own caching in its hook_block_view(). + * + * From the perspective of the block cache system, this is equivalent to + * DRUPAL_NO_CACHE. Useful when time based expiration is needed or a site uses + * a node access which invalidates standard block cache. */ define('DRUPAL_CACHE_CUSTOM', -2); /** - * The block or element can change depending on the roles the user viewing the - * page belongs to. This is the default setting for blocks, used when the block - * does not specify anything. + * The block or element can change depending on the user's roles. + * + * This is the default setting for blocks, used when the block does not specify + * anything. */ define('DRUPAL_CACHE_PER_ROLE', 0x0001); /** - * The block or element can change depending on the user viewing the page. + * The block or element can change depending on the user. + * * 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. */ @@ -146,12 +151,12 @@ define('DRUPAL_CACHE_PER_USER', 0x0002); define('DRUPAL_CACHE_PER_PAGE', 0x0004); /** - * The block or element is the same for every user on every page where it is visible. + * The block or element is the same for every user and page that it is visible. */ define('DRUPAL_CACHE_GLOBAL', 0x0008); /** - * Add content to a specified region. + * Adds content to a specified region. * * @param $region * Page region the content is added to. @@ -168,7 +173,7 @@ function drupal_add_region_content($region = NULL, $data = NULL) { } /** - * Get assigned content for a given region. + * Gets assigned content for a given region. * * @param $region * A specified region to fetch content for. If NULL, all regions will be @@ -194,13 +199,13 @@ function drupal_get_region_content($region = NULL, $delimiter = ' ') { } /** - * Get the name of the currently active install profile. + * Gets the name of the currently active install profile. * * When this function is called during Drupal's initial installation process, * the name of the profile that's about to be installed is stored in the global * installation state. At all other times, the standard Drupal systems variable - * table contains the name of the current profile, and we can call variable_get() - * to determine what one is active. + * table contains the name of the current profile, and we can call + * variable_get() to determine what one is active. * * @return $profile * The name of the install profile. @@ -220,7 +225,7 @@ function drupal_get_profile() { /** - * Set the breadcrumb trail for the current page. + * Sets the breadcrumb trail for the current page. * * @param $breadcrumb * Array of links, starting with "home" and proceeding up to but not including @@ -236,7 +241,7 @@ function drupal_set_breadcrumb($breadcrumb = NULL) { } /** - * Get the breadcrumb trail for the current page. + * Gets the breadcrumb trail for the current page. */ function drupal_get_breadcrumb() { $breadcrumb = drupal_set_breadcrumb(); @@ -265,7 +270,7 @@ function drupal_get_rdf_namespaces() { } /** - * Add output to the head tag of the HTML page. + * Adds output to the HEAD tag of the HTML page. * * This function can be called as long the headers aren't sent. Pass no * arguments (or NULL for both) to retrieve the currently stored elements. @@ -333,7 +338,7 @@ function _drupal_default_html_head() { } /** - * Retrieve output to be displayed in the HEAD tag of the HTML page. + * Retrieves output to be displayed in the HEAD tag of the HTML page. */ function drupal_get_html_head() { $elements = drupal_add_html_head(); @@ -342,7 +347,7 @@ function drupal_get_html_head() { } /** - * Add a feed URL for the current page. + * Adds a feed URL for the current page. * * This function can be called as long the HTML header hasn't been sent. * @@ -370,7 +375,7 @@ function drupal_add_feed($url = NULL, $title = '') { } /** - * Get the feed URLs for the current page. + * Gets the feed URLs for the current page. * * @param $delimiter * A delimiter to split feeds by. @@ -387,7 +392,7 @@ function drupal_get_feeds($delimiter = "\n") { */ /** - * Process a URL query parameter array to remove unwanted elements. + * Processes a URL query parameter array to remove unwanted elements. * * @param $query * (optional) An array to be processed. Defaults to $_GET. @@ -432,7 +437,7 @@ function drupal_get_query_parameters(array $query = NULL, array $exclude = array } /** - * Split an URL-encoded query string into an array. + * Splits a URL-encoded query string into an array. * * @param $query * The query string to split. @@ -452,7 +457,7 @@ function drupal_get_query_array($query) { } /** - * Parse an array into a valid, rawurlencoded query string. + * Parses an array into a valid, rawurlencoded query string. * * This differs from http_build_query() as we need to rawurlencode() (instead of * urlencode()) all query parameters. @@ -493,7 +498,7 @@ function drupal_http_build_query(array $query, $parent = '') { } /** - * Prepare a 'destination' URL query parameter for use in combination with drupal_goto(). + * Prepares a 'destination' URL query parameter for use with drupal_goto(). * * Used to direct the user back to the referring page after completing a form. * By default the current URL is returned. If a destination exists in the @@ -524,7 +529,7 @@ function drupal_get_destination() { } /** - * Wrapper around parse_url() to parse a system URL string into an associative array, suitable for url(). + * Parses a system URL string into an associative array suitable for url(). * * This function should only be used for URLs that have been generated by the * system, resp. url(). It should not be used for URLs that come from external @@ -621,7 +626,7 @@ function drupal_encode_path($path) { } /** - * Send the user to a different Drupal page. + * Sends the user to a different Drupal page. * * This issues an on-site HTTP redirect. The function makes sure the redirected * URL is formatted correctly. @@ -686,7 +691,7 @@ function drupal_goto($path = '', array $options = array(), $http_response_code = } /** - * Deliver a "site is under maintenance" message to the browser. + * Delivers a "site is under maintenance" message to the browser. * * Page callback functions wanting to report a "site offline" message should * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However, @@ -698,7 +703,7 @@ function drupal_site_offline() { } /** - * Deliver a "page not found" error to the browser. + * Delivers a "page not found" error to the browser. * * Page callback functions wanting to report a "page not found" message should * return MENU_NOT_FOUND instead of calling drupal_not_found(). However, @@ -710,19 +715,20 @@ function drupal_not_found() { } /** - * Deliver a "access denied" error to the browser. + * Delivers an "access denied" error to the browser. * * Page callback functions wanting to report an "access denied" message should * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However, * functions that are invoked in contexts where that return value might not - * bubble up to menu_execute_active_handler() should call drupal_access_denied(). + * bubble up to menu_execute_active_handler() should call + * drupal_access_denied(). */ function drupal_access_denied() { drupal_deliver_page(MENU_ACCESS_DENIED); } /** - * Perform an HTTP request. + * Performs an HTTP request. * * This is a flexible and powerful HTTP client implementation. Correctly * handles GET, POST, PUT or any other HTTP requests. Handles redirects. @@ -832,7 +838,7 @@ function drupal_http_request($url, array $options = array()) { // Mark that this request failed. This will trigger a check of the web // server's ability to make outgoing HTTP requests the next time that // requirements checking is performed. - // See system_requirements() + // See system_requirements(). variable_set('drupal_http_request_fails', TRUE); return $result; @@ -1023,6 +1029,14 @@ function drupal_http_request($url, array $options = array()) { * @} End of "HTTP handling". */ +/** + * Strips slashes from a string or array of strings. + * + * Callback for array_walk() within fix_gpx_magic(). + * + * @param $item + * An individual string or array of strings from superglobals. + */ function _fix_gpc_magic(&$item) { if (is_array($item)) { array_walk($item, '_fix_gpc_magic'); @@ -1033,11 +1047,19 @@ function _fix_gpc_magic(&$item) { } /** - * Helper function to strip slashes from $_FILES skipping over the tmp_name keys - * since PHP generates single backslashes for file paths on Windows systems. + * Strips slashes from $_FILES items. + * + * Callback for array_walk() within fix_gpc_magic(). * - * tmp_name does not have backslashes added see - * http://php.net/manual/en/features.file-upload.php#42280 + * The tmp_name key is skipped keys since PHP generates single backslashes for + * file paths on Windows systems. + * + * @param $item + * An item from $_FILES. + * @param $key + * The key for the item within $_FILES. + * + * @see http://php.net/manual/en/features.file-upload.php#42280 */ function _fix_gpc_magic_files(&$item, $key) { if ($key != 'tmp_name') { @@ -1051,7 +1073,10 @@ function _fix_gpc_magic_files(&$item, $key) { } /** - * Fix double-escaping problems caused by "magic quotes" in some PHP installations. + * Fixes double-escaping caused by "magic quotes" in some PHP installations. + * + * @see _fix_gpc_magic() + * @see _fix_gpc_magic_files() */ function fix_gpc_magic() { static $fixed = FALSE; @@ -1072,12 +1097,13 @@ function fix_gpc_magic() { */ /** - * Verify the syntax of the given e-mail address. + * Verifies the syntax of the given e-mail address. * * Empty e-mail addresses are allowed. See RFC 2822 for details. * * @param $mail * A string containing an e-mail address. + * * @return * TRUE if the address is in a valid format. */ @@ -1086,7 +1112,7 @@ function valid_email_address($mail) { } /** - * Verify the syntax of the given URL. + * Verifies the syntax of the given URL. * * This function should only be used on actual URLs. It should not be used for * Drupal menu paths, which can contain arbitrary characters. @@ -1095,6 +1121,7 @@ function valid_email_address($mail) { * The URL to verify. * @param $absolute * Whether the URL is absolute (beginning with a scheme such as "http:"). + * * @return * TRUE if the URL is in a valid format. */ @@ -1127,7 +1154,7 @@ function valid_url($url, $absolute = FALSE) { */ /** - * Register an event for the current visitor to the flood control mechanism. + * Registers an event for the current visitor to the flood control mechanism. * * @param $name * The name of an event. @@ -1154,7 +1181,7 @@ function flood_register_event($name, $window = 3600, $identifier = NULL) { } /** - * Make the flood control mechanism forget about an event for the current visitor. + * Makes the flood control mechanism forget an event for the current visitor. * * @param $name * The name of an event. @@ -1172,7 +1199,7 @@ function flood_clear_event($name, $identifier = NULL) { } /** - * Checks whether user is allowed to proceed with the specified event. + * Checks whether a user is allowed to proceed with the specified event. * * Events can have thresholds saying that each user can only do that event * a certain number of times in a time window. This function verifies that the @@ -1266,7 +1293,7 @@ function drupal_strip_dangerous_protocols($uri) { } /** - * Strips dangerous protocols (e.g. 'javascript:') from a URI and encodes it for output to an HTML attribute value. + * Strips dangerous protocols from a URI and encodes it for output to HTML. * * @param $uri * A plain-text URI that might contain dangerous protocols. @@ -1286,7 +1313,7 @@ function check_url($uri) { } /** - * Very permissive XSS/HTML filter for admin-only use. + * Applies a very permissive XSS/HTML filter for admin-only use. * * Use only for fields where it is impractical to use the * whole filter system, but where some (mainly inline) mark-up @@ -1300,7 +1327,7 @@ function filter_xss_admin($string) { } /** - * Filters an HTML string to prevent cross-site-scripting (XSS) vulnerabilities. + * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities. * * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html. @@ -1331,21 +1358,21 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', if (!drupal_validate_utf8($string)) { return ''; } - // Store the text format + // Store the text format. _filter_xss_split($allowed_tags, TRUE); - // Remove NULL characters (ignored by some browsers) + // Remove NULL characters (ignored by some browsers). $string = str_replace(chr(0), '', $string); - // Remove Netscape 4 JS entities + // Remove Netscape 4 JS entities. $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string); - // Defuse all HTML entities + // Defuse all HTML entities. $string = str_replace('&', '&', $string); - // Change back only well-formed entities in our whitelist - // Decimal numeric entities + // Change back only well-formed entities in our whitelist: + // Decimal numeric entities. $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string); - // Hexadecimal numeric entities + // Hexadecimal numeric entities. $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string); - // Named entities + // Named entities. $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string); return preg_replace_callback('% @@ -1369,6 +1396,7 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', * If $store is FALSE then the array has one element, the HTML tag to process. * @param $store * Whether to store $m. + * * @return * If the element isn't allowed, an empty string. Otherwise, the cleaned up * version of the HTML element. @@ -1384,16 +1412,16 @@ function _filter_xss_split($m, $store = FALSE) { $string = $m[1]; if (substr($string, 0, 1) != '<') { - // We matched a lone ">" character + // We matched a lone ">" character. return '>'; } elseif (strlen($string) == 1) { - // We matched a lone "<" character + // We matched a lone "<" character. return '<'; } if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) { - // Seriously malformed + // Seriously malformed. return ''; } @@ -1407,7 +1435,7 @@ function _filter_xss_split($m, $store = FALSE) { } if (!isset($allowed_html[strtolower($elem)])) { - // Disallowed HTML element + // Disallowed HTML element. return ''; } @@ -1423,7 +1451,7 @@ function _filter_xss_split($m, $store = FALSE) { $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count); $xhtml_slash = $count ? ' /' : ''; - // Clean up attributes + // Clean up attributes. $attr2 = implode(' ', _filter_xss_attributes($attrlist)); $attr2 = preg_replace('/[<>]/', '', $attr2); $attr2 = strlen($attr2) ? ' ' . $attr2 : ''; @@ -1448,7 +1476,7 @@ function _filter_xss_attributes($attr) { switch ($mode) { case 0: - // Attribute name, href for instance + // Attribute name, href for instance. if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { $attrname = strtolower($match[1]); $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); @@ -1458,7 +1486,7 @@ function _filter_xss_attributes($attr) { break; case 1: - // Equals sign or valueless ("selected") + // Equals sign or valueless ("selected"). if (preg_match('/^\s*=\s*/', $attr)) { $working = 1; $mode = 2; $attr = preg_replace('/^\s*=\s*/', '', $attr); @@ -1475,7 +1503,7 @@ function _filter_xss_attributes($attr) { break; case 2: - // Attribute value, a URL after href= for instance + // Attribute value, a URL after href= for instance. if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) { $thisval = filter_xss_bad_protocol($match[1]); @@ -1512,7 +1540,7 @@ function _filter_xss_attributes($attr) { } if ($working == 0) { - // not well formed, remove and try again + // Not well formed; remove and try again. $attr = preg_replace('/ ^ ( @@ -1536,15 +1564,16 @@ function _filter_xss_attributes($attr) { } /** - * Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:). + * Processes an HTML attribute value and strips dangerous protocols from URLs. * * @param $string * The string with the attribute value. * @param $decode - * (Deprecated) Whether to decode entities in the $string. Set to FALSE if the + * (deprecated) Whether to decode entities in the $string. Set to FALSE if the * $string is in plain text, TRUE otherwise. Defaults to TRUE. This parameter * is deprecated and will be removed in Drupal 8. To process a plain-text URI, * call drupal_strip_dangerous_protocols() or check_url() instead. + * * @return * Cleaned up and HTML-escaped version of $string. */ @@ -1598,7 +1627,7 @@ function format_rss_channel($title, $link, $description, $items, $langcode = NUL } /** - * Format a single RSS item. + * Formats a single RSS item. * * Arbitrary elements may be added using the $args associative array. */ @@ -1614,7 +1643,7 @@ function format_rss_item($title, $link, $description, $args = array()) { } /** - * Format XML elements. + * Formats XML elements. * * @param $array * An array where each item represents an element and is either a: @@ -1653,7 +1682,7 @@ function format_xml_elements($array) { } /** - * Format a string containing a count of items. + * Formats a string containing a count of items. * * This function ensures that the string is pluralized correctly. Since t() is * called by this function, make sure not to pass already-localized strings to @@ -1675,31 +1704,27 @@ function format_xml_elements($array) { * @param $count * The item count to display. * @param $singular - * The string for the singular case. Please make sure it is clear this is - * singular, to ease translation (e.g. use "1 new comment" instead of "1 new"). - * Do not use @count in the singular string. + * The string for the singular case. Make sure it is clear this is singular, + * to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not + * use @count in the singular string. * @param $plural - * The string for the plural case. Please make sure it is clear this is plural, - * to ease translation. Use @count in place of the item count, as in "@count - * new comments". + * The string for the plural case. Make sure it is clear this is plural, to + * ease translation. Use @count in place of the item count, as in + * "@count new comments". * @param $args - * An associative array of replacements to make after translation. Incidences + * An associative array of replacements to make after translation. Instances * of any key in this array are replaced with the corresponding value. - * Based on the first character of the key, the value is escaped and/or themed: - * - !variable: inserted as is - * - @variable: escape plain text to HTML (check_plain) - * - %variable: escape text and theme as a placeholder for user-submitted - * content (check_plain + drupal_placeholder) - * Note that you do not need to include @count in this array. - * This replacement is done automatically for the plural case. + * Based on the first character of the key, the value is escaped and/or + * themed. See format_string(). Note that you do not need to include @count + * in this array; this replacement is done automatically for the plural case. * @param $options - * An associative array of additional options, with the following keys: - * - 'langcode' (default to the current language) The language code to - * translate to a language other than what is used to display the page. - * - 'context' (default to the empty context) The context the source string - * belongs to. + * An associative array of additional options. See t() for allowed keys. + * * @return * A translated string. + * + * @see t() + * @see format_string() */ function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) { $args['@count'] = $count; @@ -1728,11 +1753,12 @@ function format_plural($count, $singular, $plural, array $args = array(), array } /** - * Parse a given byte count. + * Parses a given byte count. * * @param $size * A size expressed as a number of bytes with optional SI or IEC binary unit * prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes). + * * @return * An integer representation of the size in bytes. */ @@ -1749,13 +1775,14 @@ function parse_size($size) { } /** - * Generate a string representation for the given byte count. + * Generates a string representation for the given byte count. * * @param $size * A size in bytes. * @param $langcode * Optional language code to translate to a language other than what is used * to display the page. + * * @return * A translated string representation of the size. */ @@ -1788,19 +1815,20 @@ function format_size($size, $langcode = NULL) { } /** - * Format a time interval with the requested granularity. + * Formats a time interval with the requested granularity. * - * @param $timestamp + * @param $interval * The length of the interval in seconds. * @param $granularity * How many different units to display in the string. * @param $langcode * Optional language code to translate to a language other than * what is used to display the page. + * * @return * A translated string representation of the interval. */ -function format_interval($timestamp, $granularity = 2, $langcode = NULL) { +function format_interval($interval, $granularity = 2, $langcode = NULL) { $units = array( '1 year|@count years' => 31536000, '1 month|@count months' => 2592000, @@ -1813,9 +1841,9 @@ function format_interval($timestamp, $granularity = 2, $langcode = NULL) { $output = ''; foreach ($units as $key => $value) { $key = explode('|', $key); - if ($timestamp >= $value) { - $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode)); - $timestamp %= $value; + if ($interval >= $value) { + $output .= ($output ? ' ' : '') . format_plural(floor($interval / $value), $key[0], $key[1], array(), array('langcode' => $langcode)); + $interval %= $value; $granularity--; } @@ -1928,10 +1956,11 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL /** * Returns an ISO8601 formatted date based on the given date. * - * Can be used as a callback for RDF mappings. + * Callback for use within hook_rdf_mapping() implementations. * * @param $date * A UNIX timestamp. + * * @return string * An ISO8601 formatted date. */ @@ -1942,7 +1971,9 @@ function date_iso8601($date) { } /** - * Callback function for preg_replace_callback(). + * Translates a formatted date string. + * + * Callback for preg_replace_callback() within format_date(). */ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { // We cache translations to avoid redundant and rather costly calls to t(). @@ -2057,8 +2088,8 @@ function format_username($account) { * Drupal on a web server that cannot be configured to automatically find * index.php, then hook_url_outbound_alter() can be implemented to force * this value to 'index.php'. - * - 'entity_type': The entity type of the object that called url(). Only set if - * url() is invoked by entity_uri(). + * - 'entity_type': The entity type of the object that called url(). Only + * set if url() is invoked by entity_uri(). * - 'entity': The entity object (such as a node) for which the URL is being * generated. Only set if url() is invoked by entity_uri(). * @@ -2183,7 +2214,7 @@ function url($path = NULL, array $options = array()) { } /** - * Return TRUE if a path is external to Drupal (e.g. http://example.com). + * Returns TRUE if a path is external to Drupal (e.g. http://example.com). * * If a path cannot be assessed by Drupal's menu handler, then we must * treat it as potentially insecure. @@ -2191,6 +2222,7 @@ function url($path = NULL, array $options = array()) { * @param $path * The internal path or external URL being linked to, such as "node/34" or * "http://example.com/foo". + * * @return * Boolean TRUE or FALSE, where TRUE indicates an external path. */ @@ -2203,7 +2235,7 @@ function url_is_external($path) { } /** - * Format an attribute string for a HTTP header. + * Formats an attribute string for an HTTP header. * * @param $attributes * An associative array of attributes such as 'rel'. @@ -2225,7 +2257,7 @@ function drupal_http_header_attributes(array $attributes = array()) { } /** - * Converts an associative array to an attribute string for use in XML/HTML tags. + * Converts an associative array to an XML/HTML tag attribute string. * * Each array key and its value will be formatted into an attribute string. * If a value is itself an array, then its elements are concatenated to a single @@ -2338,7 +2370,7 @@ function l($text, $path, array $options = array()) { // rendering. if (variable_get('theme_link', TRUE)) { drupal_theme_initialize(); - $registry = theme_get_registry(); + $registry = theme_get_registry(FALSE); // We don't want to duplicate functionality that's in theme(), so any // hint of a module or theme doing anything at all special with the 'link' // theme hook should simply result in theme() being called. This includes @@ -2446,7 +2478,7 @@ function drupal_deliver_page($page_callback_result, $default_delivery_callback = } /** - * Package and send the result of a page callback to the browser as HTML. + * Packages and sends the result of a page callback to the browser as HTML. * * @param $page_callback_result * The result of a page callback. Can be one of: @@ -2466,6 +2498,10 @@ function drupal_deliver_html_page($page_callback_result) { drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); } + // Send appropriate HTTP-Header for browsers and search engines. + global $language; + drupal_add_http_header('Content-Language', $language->language); + // Menu status constants are integers; page content is a string or array. if (is_int($page_callback_result)) { // @todo: Break these up into separate functions? @@ -2551,7 +2587,7 @@ function drupal_deliver_html_page($page_callback_result) { } /** - * Perform end-of-request tasks. + * Performs end-of-request tasks. * * This function sets the page cache if appropriate, and allows modules to * react to the closing of the page by calling hook_exit(). @@ -2578,7 +2614,7 @@ function drupal_page_footer() { } /** - * Perform end-of-request tasks. + * Performs end-of-request tasks. * * In some cases page requests need to end without calling drupal_page_footer(). * In these cases, call drupal_exit() instead. There should rarely be a reason @@ -2600,7 +2636,7 @@ function drupal_exit($destination = NULL) { } /** - * Form an associative array from a linear array. + * Forms an associative array from a linear array. * * This function walks through the provided array and constructs an associative * array out of it. The keys of the resulting array will be the values of the @@ -2676,10 +2712,10 @@ function drupal_get_path($type, $name) { } /** - * Return the base URL path (i.e., directory) of the Drupal installation. + * Returns the base URL path (i.e., directory) of the Drupal installation. * - * base_path() prefixes and suffixes a "/" onto the returned path if the path is - * not empty. At the very least, this will return "/". + * base_path() adds a "/" to the beginning and end of the returned path if the + * path is not empty. At the very least, this will return "/". * * Examples: * - http://example.com returns "/" because the path is empty. @@ -2690,12 +2726,12 @@ function base_path() { } /** - * Add a LINK tag with a distinct 'rel' attribute to the page's HEAD. + * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD. * - * This function can be called as long the HTML header hasn't been sent, - * which on normal pages is up through the preprocess step of theme('html'). - * Adding a link will overwrite a prior link with the exact same 'rel' and - * 'href' attributes. + * This function can be called as long the HTML header hasn't been sent, which + * on normal pages is up through the preprocess step of theme('html'). Adding + * a link will overwrite a prior link with the exact same 'rel' and 'href' + * attributes. * * @param $attributes * Associative array of element attributes including 'href' and 'rel'. @@ -2759,8 +2795,8 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { * See drupal_get_css() where the overrides are performed. Also, if the * direction of the current language is right-to-left (Hebrew, Arabic, * etc.), the function will also look for an RTL CSS file and append it to - * the list. The name of this file should have an '-rtl.css' suffix. For - * example a CSS file called 'mymodule-name.css' will have a + * the list. The name of this file should have an '-rtl.css' suffix. For + * example, a CSS file called 'mymodule-name.css' will have a * 'mymodule-name-rtl.css' file added to the list, if exists in the same * directory. This CSS file should contain overrides for properties which * should be reversed or otherwise different in a right-to-left display. @@ -2892,7 +2928,7 @@ function drupal_add_css($data = NULL, $options = NULL) { } /** - * Returns a themed representation of all stylesheets that should be attached to the page. + * Returns a themed representation of all stylesheets to attach to the page. * * It loads the CSS in order, with 'module' first, then 'theme' afterwards. * This ensures proper cascading of styles so themes can easily override @@ -2938,7 +2974,7 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) { foreach ($css as $key => $item) { if ($item['type'] == 'file') { // If defined, force a unique basename for this file. - $basename = isset($item['basename']) ? $item['basename'] : basename($item['data']); + $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']); if (isset($previous_item[$basename])) { // Remove the previous item that shared the same base name. unset($css[$previous_item[$basename]]); @@ -2962,11 +2998,24 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) { } /** - * Function used by uasort to sort the array structures returned by drupal_add_css() and drupal_add_js(). + * Sorts CSS and JavaScript resources. + * + * Callback for uasort() within: + * - drupal_get_css() + * - drupal_get_js() * * This sort order helps optimize front-end performance while providing modules * and themes with the necessary control for ordering the CSS and JavaScript * appearing on a page. + * + * @param $a + * First item for comparison. The compared items should be associative arrays + * of member items from drupal_add_css() or drupal_add_js(). + * @param $b + * Second item for comparison. + * + * @see drupal_add_css() + * @see drupal_add_js() */ function drupal_sort_css_js($a, $b) { // First order by group, so that, for example, all items in the CSS_SYSTEM @@ -3033,6 +3082,7 @@ function drupal_sort_css_js($a, $b) { * 'items' key, which is the subset of items from $css that are in the group. * * @see drupal_pre_render_styles() + * @see system_element_info() */ function drupal_group_css($css) { $groups = array(); @@ -3115,6 +3165,7 @@ function drupal_group_css($css) { * * @see drupal_group_css() * @see drupal_pre_render_styles() + * @see system_element_info() */ function drupal_aggregate_css(&$css_groups) { $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); @@ -3376,8 +3427,8 @@ function drupal_pre_render_styles($elements) { * in $css while the value is the cache file name. The cache file is generated * in two cases. First, if there is no file name value for the key, which will * happen if a new file name has been added to $css or after the lookup - * variable is emptied to force a rebuild of the cache. Second, the cache - * file is generated if it is missing on disk. Old cache files are not deleted + * variable is emptied to force a rebuild of the cache. Second, the cache file + * is generated if it is missing on disk. Old cache files are not deleted * immediately when the lookup variable is emptied, but are deleted after a set * period by drupal_delete_file_if_stale(). This ensures that files referenced * by a cached page will still be available. @@ -3455,9 +3506,7 @@ function drupal_build_css_cache($css) { } /** - * Helper function for drupal_build_css_cache(). - * - * This function will prefix all paths within a CSS file. + * Prefixes all paths within a CSS file for drupal_build_css_cache(). */ function _drupal_build_css_path($matches, $base = NULL) { $_base = &drupal_static(__FUNCTION__); @@ -3528,13 +3577,14 @@ function drupal_load_stylesheet($file, $optimize = NULL, $reset_basepath = TRUE) } /** - * Process the contents of a stylesheet for aggregation. + * Processes the contents of a stylesheet for aggregation. * * @param $contents * The contents of the stylesheet. * @param $optimize * (optional) Boolean whether CSS contents should be minified. Defaults to * FALSE. + * * @return * Contents of the stylesheet including the imported stylesheets. */ @@ -3630,7 +3680,7 @@ function drupal_delete_file_if_stale($uri) { } /** - * Prepare a string for use as a valid CSS identifier (element, class or ID name). + * Prepares a string for use as a CSS identifier (element, class, or ID name). * * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid * CSS identifiers (including element names, classes, and IDs in selectors.) @@ -3639,6 +3689,7 @@ function drupal_delete_file_if_stale($uri) { * The identifier to clean. * @param $filter * An array of string replacements to use on the identifier. + * * @return * The cleaned identifier. */ @@ -3660,13 +3711,14 @@ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_ } /** - * Prepare a string for use as a valid class name. + * Prepares a string for use as a valid class name. * * Do not pass one string containing multiple classes as they will be * incorrectly concatenated with dashes, i.e. "one two" will become "one-two". * * @param $class * The class name to clean. + * * @return * The cleaned class name. */ @@ -3675,7 +3727,7 @@ function drupal_html_class($class) { } /** - * Prepare a string for use as a valid HTML ID and guarantee uniqueness. + * Prepares a string for use as a valid HTML ID and guarantees uniqueness. * * This function ensures that each passed HTML ID value only exists once on the * page. By tracking the already returned ids, this function enables forms, @@ -3806,7 +3858,7 @@ function drupal_region_class($region) { * to tell the user that a new message arrived, by opening a pop up, alert * box, etc.). This should only be used for JavaScript that cannot be executed * from a file. When adding inline code, make sure that you are not relying on - * $() being the jQuery function. Wrap your code in + * $() being the jQuery function. Wrap your code in * @code (function ($) {... })(jQuery); @endcode * or use jQuery() instead of $(). * - Add external JavaScript ('external'): Allows the inclusion of external @@ -3926,7 +3978,7 @@ function drupal_region_class($region) { * happened later in the page request gets added to the page after one for * which drupal_add_js() happened earlier in the page request. * - defer: If set to TRUE, the defer attribute is set on the <script> - * tag. Defaults to FALSE. + * tag. Defaults to FALSE. * - cache: If set to FALSE, the JavaScript file is loaded anew on every page * call; in other words, it is not cached. Used only when 'type' references * a JavaScript file. Defaults to TRUE. @@ -4023,6 +4075,7 @@ function drupal_add_js($data = NULL, $options = NULL) { * * @param $data * (optional) The default data parameter for the JavaScript item array. + * * @see drupal_get_js() * @see drupal_add_js() */ @@ -4066,8 +4119,10 @@ function drupal_js_defaults($data = NULL) { * (optional) If set to TRUE, this function skips calling drupal_alter() on * $javascript, useful when the calling function passes a $javascript array * that has already been altered. + * * @return * All JavaScript code segments and includes for the scope as HTML tags. + * * @see drupal_add_js() * @see locale_js_alter() * @see drupal_js_defaults() @@ -4239,7 +4294,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS * ); * @endcode * - * 'js', 'css', and 'library' are types that get special handling. For any + * 'js', 'css', and 'library' are types that get special handling. For any * other kind of attached data, the array key must be the full name of the * callback function and each value an array of arguments. For example: * @code @@ -4590,16 +4645,16 @@ function drupal_get_library($module, $name = NULL) { } /** - * Assist in adding the tableDrag JavaScript behavior to a themed table. + * Assists in adding the tableDrag JavaScript behavior to a themed table. * * Draggable tables should be used wherever an outline or list of sortable items * needs to be arranged by an end-user. Draggable tables are very flexible and * can manipulate the value of form elements placed within individual columns. * - * To set up a table to use drag and drop in place of weight select-lists or - * in place of a form that contains parent relationships, the form must be - * themed into a table. The table must have an id attribute set. If using - * theme_table(), the id may be set as such: + * To set up a table to use drag and drop in place of weight select-lists or in + * place of a form that contains parent relationships, the form must be themed + * into a table. The table must have an ID attribute set. If using + * theme_table(), the ID may be set as follows: * @code * $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'my-module-table'))); * return $output; @@ -4614,8 +4669,8 @@ function drupal_get_library($module, $name = NULL) { * $form['my_elements'][$delta]['weight']['#attributes']['class'] = array('my-elements-weight'); * @endcode * - * Each row of the table must also have a class of "draggable" in order to enable the - * drag handles: + * Each row of the table must also have a class of "draggable" in order to + * enable the drag handles: * @code * $row = array(...); * $rows[] = array( @@ -4635,8 +4690,8 @@ function drupal_get_library($module, $name = NULL) { * @endcode * * In a more complex case where there are several groups in one column (such as - * the block regions on the admin/structure/block page), a separate subgroup class - * must also be added to differentiate the groups. + * the block regions on the admin/structure/block page), a separate subgroup + * class must also be added to differentiate the groups. * @code * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = array('my-elements-weight', 'my-elements-weight-' . $region); * @endcode @@ -4653,14 +4708,14 @@ function drupal_get_library($module, $name = NULL) { * * In a situation where tree relationships are present, adding multiple * subgroups is not necessary, because the table will contain indentations that - * provide enough information about the sibling and parent relationships. - * See theme_menu_overview_form() for an example creating a table containing - * parent relationships. - * - * Please note that this function should be called from the theme layer, such as - * in a .tpl.php file, theme_ function, or in a template_preprocess function, - * not in a form declaration. Though the same JavaScript could be added to the - * page using drupal_add_js() directly, this function helps keep template files + * provide enough information about the sibling and parent relationships. See + * theme_menu_overview_form() for an example creating a table containing parent + * relationships. + * + * Note that this function should be called from the theme layer, such as in a + * .tpl.php file, theme_ function, or in a template_preprocess function, not in + * a form declaration. Though the same JavaScript could be added to the page + * using drupal_add_js() directly, this function helps keep template files * clean and readable. It also prevents tabledrag.js from being added twice * accidentally. * @@ -4733,8 +4788,8 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro * $files while the value is the cache file name. The cache file is generated * in two cases. First, if there is no file name value for the key, which will * happen if a new file name has been added to $files or after the lookup - * variable is emptied to force a rebuild of the cache. Second, the cache - * file is generated if it is missing on disk. Old cache files are not deleted + * variable is emptied to force a rebuild of the cache. Second, the cache file + * is generated if it is missing on disk. Old cache files are not deleted * immediately when the lookup variable is emptied, but are deleted after a set * period by drupal_delete_file_if_stale(). This ensures that files referenced * by a cached page will still be available. @@ -4800,14 +4855,29 @@ function drupal_clear_js_cache() { /** * Converts a PHP variable into its JavaScript equivalent. * - * We use HTML-safe strings, i.e. with <, > and & escaped. + * We use HTML-safe strings, with several characters escaped. * * @see drupal_json_decode() + * @see drupal_json_encode_helper() * @ingroup php_wrappers */ function drupal_json_encode($var) { - // json_encode() does not escape <, > and &, so we do it with str_replace(). - return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), json_encode($var)); + // The PHP version cannot change within a request. + static $php530; + + if (!isset($php530)) { + $php530 = version_compare(PHP_VERSION, '5.3.0', '>='); + } + + if ($php530) { + // Encode <, >, ', &, and " using the json_encode() options parameter. + return json_encode($var, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); + } + + // json_encode() escapes <, >, ', &, and " using its options parameter, but + // does not support this parameter prior to PHP 5.3.0. Use a helper instead. + include_once DRUPAL_ROOT . '/includes/json-encode.inc'; + return drupal_json_encode_helper($var); } /** @@ -4821,7 +4891,7 @@ function drupal_json_decode($var) { } /** - * Return data in JSON format. + * Returns data in JSON format. * * This function should be used for JavaScript callback functions returning * data in JSON format. It sets the header for JavaScript output. @@ -4839,7 +4909,7 @@ function drupal_json_output($var = NULL) { } /** - * Get a salt useful for hardening against SQL injection. + * Gets a salt useful for hardening against SQL injection. * * @return * A salt based on information in settings.php, not in the database. @@ -4852,7 +4922,7 @@ function drupal_get_hash_salt() { } /** - * Ensure the private key variable used to generate tokens is set. + * Ensures the private key variable used to generate tokens is set. * * @return * The private key. @@ -4866,7 +4936,7 @@ function drupal_get_private_key() { } /** - * Generate a token based on $value, the current user session and private key. + * Generates a token based on $value, the user session, and the private key. * * @param $value * An additional value to base the token on. @@ -4876,7 +4946,7 @@ function drupal_get_token($value = '') { } /** - * Validate a token based on $value, the current user session and private key. + * Validates a token based on $value, the user session, and the private key. * * @param $token * The token to be validated. @@ -4884,6 +4954,7 @@ function drupal_get_token($value = '') { * An additional value to base the token on. * @param $skip_anonymous * Set to true to skip token validation for anonymous users. + * * @return * True for a valid token, false for an invalid token. When $skip_anonymous * is true, the return value will always be true for anonymous users. @@ -4952,7 +5023,7 @@ function _drupal_bootstrap_full() { } /** - * Store the current page in the cache. + * Stores the current page in the cache. * * If page_compression is enabled, a gzipped version of the page is stored in * the cache to avoid compressing the output on each request. The cache entry @@ -5004,10 +5075,10 @@ function drupal_page_set_cache() { /** * Executes a cron run when called. * - * Do not call this function from test, use $this->cronRun() instead. + * Do not call this function from a test. Use $this->cronRun() instead. * * @return - * Returns TRUE if ran successfully + * TRUE if cron ran successfully. */ function drupal_cron_run() { // Allow execution to continue even if the request gets canceled. @@ -5040,7 +5111,7 @@ function drupal_cron_run() { foreach ($queues as $queue_name => $info) { DrupalQueue::get($queue_name)->createQueue(); } - // Register shutdown callback + // Register shutdown callback. drupal_register_shutdown_function('drupal_cron_cleanup'); // Iterate through the modules calling their cron handlers (if any): @@ -5054,7 +5125,7 @@ function drupal_cron_run() { } } - // Record cron time + // Record cron time. variable_set('cron_last', REQUEST_TIME); watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE); @@ -5082,14 +5153,17 @@ function drupal_cron_run() { } /** - * Shutdown function for cron cleanup. + * Shutdown function: Performs cron cleanup. + * + * @see drupal_cron_run() + * @see drupal_register_shutdown_function() */ function drupal_cron_cleanup() { // See if the semaphore is still locked. if (variable_get('cron_semaphore', FALSE)) { watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING); - // Release cron semaphore + // Release cron semaphore. variable_del('cron_semaphore'); } } @@ -5158,14 +5232,14 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) $searchdir[] = "profiles/$profile/$directory"; } - // Always search sites/all/* as well as the global directories + // Always search sites/all/* as well as the global directories. $searchdir[] = 'sites/all/' . $directory; if (file_exists("$config/$directory")) { $searchdir[] = "$config/$directory"; } - // Get current list of items + // Get current list of items. if (!function_exists('file_scan_directory')) { require_once DRUPAL_ROOT . '/includes/file.inc'; } @@ -5201,7 +5275,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) } /** - * Set the main page content value for later use. + * Sets the main page content value for later use. * * Given the nature of the Drupal page handling, this will be called once with * a string or array. We store that and return it later as the block is being @@ -5209,6 +5283,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) * * @param $content * A string or renderable array representing the body of the page. + * * @return * If called without $content, a renderable array representing the body of * the page. @@ -5459,13 +5534,13 @@ function drupal_pre_render_links($element) { * Note that if also a #theme is defined for the element, then the result of * the theme callback will override #children. * - * @see drupal_render() - * * @param $elements * A structured array using the #markup key. * * @return * The passed-in elements, but #markup appended to #children. + * + * @see drupal_render() */ function drupal_pre_render_markup($elements) { $elements['#children'] = $elements['#markup']; @@ -5478,8 +5553,10 @@ function drupal_pre_render_markup($elements) { * @param $page * A string or array representing the content of a page. The array consists of * the following keys: - * - #type: Value is always 'page'. This pushes the theming through page.tpl.php (required). - * - #show_messages: Suppress drupal_get_message() items. Used by Batch API (optional). + * - #type: Value is always 'page'. This pushes the theming through + * page.tpl.php (required). + * - #show_messages: Suppress drupal_get_message() items. Used by Batch + * API (optional). * * @see hook_page_alter() * @see element_info() @@ -5556,20 +5633,20 @@ function drupal_render_page($page) { * drupal_render() can optionally cache the rendered output of elements to * improve performance. To use drupal_render() caching, set the element's #cache * property to an associative array with one or several of the following keys: - * - 'keys': An array of one or more keys that identify the element. If 'keys' - * is set, the cache ID is created automatically from these keys. See - * drupal_render_cid_create(). - * - 'granularity' (optional): Define the cache granularity using binary - * combinations of the cache granularity constants, e.g. DRUPAL_CACHE_PER_USER - * to cache for each user separately or - * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for each - * page and role. If not specified the element is cached globally for each - * theme and language. - * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is required. - * If 'cid' is set, 'keys' and 'granularity' are ignored. Use only if you - * have special requirements. - * - 'expire': Set to one of the cache lifetime constants. - * - 'bin': Specify a cache bin to cache the element in. Defaults to 'cache'. + * - 'keys': An array of one or more keys that identify the element. If 'keys' + * is set, the cache ID is created automatically from these keys. See + * drupal_render_cid_create(). + * - 'granularity' (optional): Define the cache granularity using binary + * combinations of the cache granularity constants, e.g. + * DRUPAL_CACHE_PER_USER to cache for each user separately or + * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for each + * page and role. If not specified the element is cached globally for each + * theme and language. + * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is required. + * If 'cid' is set, 'keys' and 'granularity' are ignored. Use only if you + * have special requirements. + * - 'expire': Set to one of the cache lifetime constants. + * - 'bin': Specify a cache bin to cache the element in. Defaults to 'cache'. * * This function is usually called from within another function, like * drupal_get_form() or a theme function. Elements are sorted internally @@ -5586,6 +5663,7 @@ function drupal_render_page($page) { * * @param $elements * The structured array describing the data to be rendered. + * * @return * The rendered HTML. */ @@ -5601,8 +5679,11 @@ function drupal_render(&$elements) { } // Try to fetch the element's markup from cache and return. - if (isset($elements['#cache']) && $cached_output = drupal_render_cache_get($elements)) { - return $cached_output; + if (isset($elements['#cache'])) { + $cached_output = drupal_render_cache_get($elements); + if ($cached_output !== FALSE) { + return $cached_output; + } } // If #markup is set, ensure #type is set. This allows to specify just #markup @@ -5699,7 +5780,7 @@ function drupal_render(&$elements) { } /** - * Render children of an element and concatenate them. + * Renders children of an element and concatenates them. * * This renders all children of an element using drupal_render() and then * joins them together into a single string. @@ -5724,7 +5805,7 @@ function drupal_render_children(&$element, $children_keys = NULL) { } /** - * Render an element. + * Renders an element. * * This function renders an element using drupal_render(). The top level * element is shown with show() before rendering, so it will always be rendered @@ -5753,7 +5834,7 @@ function render(&$element) { } /** - * Hide an element from later rendering. + * Hides an element from later rendering. * * The first time render() or drupal_render() is called on an element tree, * as each element in the tree is rendered, it is marked with a #printed flag @@ -5779,7 +5860,7 @@ function hide(&$element) { } /** - * Show a hidden element for later rendering. + * Shows a hidden element for later rendering. * * You can also use render($element), which shows the element while rendering * it. @@ -5808,16 +5889,17 @@ function show(&$element) { } /** - * Get the rendered output of a renderable element from cache. - * - * @see drupal_render() - * @see drupal_render_cache_set() + * Gets the rendered output of a renderable element from the cache. * * @param $elements * A renderable array. + * * @return * A markup string containing the rendered content of the element, or FALSE * if no cached copy of the element is available. + * + * @see drupal_render() + * @see drupal_render_cache_set() */ function drupal_render_cache_get($elements) { if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) { @@ -5838,17 +5920,17 @@ function drupal_render_cache_get($elements) { } /** - * Cache the rendered output of a renderable element. - * - * This is called by drupal_render() if the #cache property is set on an element. + * Caches the rendered output of a renderable element. * - * @see drupal_render() - * @see drupal_render_cache_get() + * This is called by drupal_render() if the #cache property is set on an + * element. * * @param $markup * The rendered output string of $elements. * @param $elements * A renderable array. + * + * @see drupal_render_cache_get() */ function drupal_render_cache_set(&$markup, $elements) { // Create the cache ID for the element. @@ -5874,7 +5956,7 @@ function drupal_render_cache_set(&$markup, $elements) { } /** - * Collect #attached for an element and all child elements into a single array. + * Collects #attached for an element and its children into a single array. * * When caching elements, it is necessary to collect all libraries, JavaScript * and CSS into a single array, from both the element itself and all child @@ -5917,9 +5999,10 @@ function drupal_render_collect_attached($elements, $return = FALSE) { } /** - * Prepare an element for caching based on a query. This smart caching strategy - * saves Drupal from querying and rendering to HTML when the underlying query is - * unchanged. + * Prepares an element for caching based on a query. + * + * This smart caching strategy saves Drupal from querying and rendering to HTML + * when the underlying query is unchanged. * * Expensive queries should use the query builder to create the query and then * call this function. Executing the query and formatting results should happen @@ -5957,12 +6040,15 @@ function drupal_render_cache_by_query($query, $function, $expire = CACHE_TEMPORA } /** - * Helper function for building cache ids. + * Returns cache ID parts for building a cache ID. * * @param $granularity - * One or more cache granularity constants, e.g. DRUPAL_CACHE_PER_USER to cache - * for each user separately or DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to - * cache separately for each page and role. + * One or more cache granularity constants. For example, to cache separately + * for each user, use DRUPAL_CACHE_PER_USER. To cache separately for each + * page and role, use the expression: + * @code + * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE + * @endcode * * @return * An array of cache ID parts, always containing the active theme. If the @@ -6001,7 +6087,7 @@ function drupal_render_cid_parts($granularity = NULL) { } /** - * Create the cache ID for a renderable element. + * Creates the cache ID for a renderable element. * * This creates the cache ID string, either by returning the #cache['cid'] * property if present or by building the cache ID out of the #cache['keys'] @@ -6048,7 +6134,7 @@ function element_sort_by_title($a, $b) { } /** - * Retrieve the default properties for the defined element type. + * Retrieves the default properties for the defined element type. * * @param $type * An element type as defined by hook_element_info(). @@ -6074,7 +6160,7 @@ function element_info($type) { } /** - * Retrieve a single property for the defined element type. + * Retrieves a single property for the defined element type. * * @param $type * An element type as defined by hook_element_info(). @@ -6114,21 +6200,21 @@ function drupal_sort_title($a, $b) { } /** - * Check if the key is a property. + * Checks if the key is a property. */ function element_property($key) { return $key[0] == '#'; } /** - * Get properties of a structured array element. Properties begin with '#'. + * Gets properties of a structured array element (keys beginning with '#'). */ function element_properties($element) { return array_filter(array_keys((array) $element), 'element_property'); } /** - * Check if the key is a child. + * Checks if the key is a child. */ function element_child($key) { return !isset($key[0]) || $key[0] != '#'; @@ -6144,6 +6230,7 @@ function element_child($key) { * The element array whose children are to be identified. * @param $sort * Boolean to indicate whether the children should be sorted by weight. + * * @return * The array keys of the element's children. */ @@ -6183,6 +6270,7 @@ function element_children(&$elements, $sort = FALSE) { * * @param $elements * The parent element. + * * @return * The array keys of the element's visible children. */ @@ -6377,7 +6465,7 @@ function drupal_array_get_nested_value(array &$array, array $parents, &$key_exis } /** - * Determines whether a nested array with variable depth contains all of the requested keys. + * Determines whether a nested array contains the requested keys. * * This helper function should be used when the depth of the array element to be * checked may vary (that is, the number of parent keys is variable). See @@ -6413,11 +6501,11 @@ function drupal_array_nested_key_exists(array $array, array $parents) { } /** - * Provide theme registration for themes across .inc files. + * Provides theme registration for themes across .inc files. */ function drupal_common_theme() { return array( - // theme.inc + // From theme.inc. 'html' => array( 'render element' => 'page', 'template' => 'html', @@ -6493,7 +6581,7 @@ function drupal_common_theme() { 'html_tag' => array( 'render element' => 'element', ), - // from theme.maintenance.inc + // From theme.maintenance.inc. 'maintenance_page' => array( 'variables' => array('content' => NULL, 'show_messages' => TRUE), 'template' => 'maintenance-page', @@ -6513,7 +6601,7 @@ function drupal_common_theme() { 'authorize_report' => array( 'variables' => array('messages' => array()), ), - // from pager.inc + // From pager.inc. 'pager' => array( 'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array(), 'quantity' => 9), ), @@ -6532,7 +6620,7 @@ function drupal_common_theme() { 'pager_link' => array( 'variables' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()), ), - // from menu.inc + // From menu.inc. 'menu_link' => array( 'render element' => 'element', ), @@ -6548,7 +6636,7 @@ function drupal_common_theme() { 'menu_local_tasks' => array( 'variables' => array('primary' => array(), 'secondary' => array()), ), - // from form.inc + // From form.inc. 'select' => array( 'render element' => 'element', ), @@ -6624,7 +6712,7 @@ function drupal_common_theme() { */ /** - * Creates all tables in a module's hook_schema() implementation. + * Creates all tables defined in a module's hook_schema(). * * Note: This function does not pass the module's schema through * hook_schema_alter(). The module's tables will be created exactly as the @@ -6643,7 +6731,7 @@ function drupal_install_schema($module) { } /** - * Remove all tables that a module defines in its hook_schema(). + * Removes all tables defined in a module's hook_schema(). * * Note: This function does not pass the module's schema through * hook_schema_alter(). The module's tables will be created exactly as the @@ -6651,6 +6739,7 @@ function drupal_install_schema($module) { * * @param $module * The module for which the tables will be removed. + * * @return * An array of arrays with the following key/value pairs: * - success: a boolean indicating whether the query succeeded. @@ -6706,7 +6795,7 @@ function drupal_get_schema_unprocessed($module, $table = NULL) { } /** - * Fill in required default values for table definitions returned by hook_schema(). + * Fills in required default values for table definitions from hook_schema(). * * @param $schema * The schema definition array as it was returned by the module's @@ -6737,7 +6826,9 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU } /** - * Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query. + * Retrieves a list of fields from a table schema. + * + * The returned list is suitable for use in an SQL query. * * @param $table * The name of the table from which to retrieve fields. @@ -6745,7 +6836,7 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU * An optional prefix to to all fields. * * @return An array of fields. - **/ + */ function drupal_schema_fields_sql($table, $prefix = NULL) { $schema = drupal_get_schema($table); $fields = array_keys($schema['fields']); @@ -6973,7 +7064,7 @@ function drupal_parse_info_file($filename) { } /** - * Parse data in Drupal's .info format. + * Parses data in Drupal's .info format. * * Data should be in an .ini-like format to specify values. White-space * generally doesn't matter, except inside values: @@ -7003,6 +7094,7 @@ function drupal_parse_info_file($filename) { * * @param $data * A string to parse. + * * @return * The info array. * @@ -7026,19 +7118,19 @@ function drupal_parse_info_format($data) { )\s*$ # Stop at the next end of a line, ignoring trailing whitespace @msx', $data, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { - // Fetch the key and value string + // Fetch the key and value string. $i = 0; foreach (array('key', 'value1', 'value2', 'value3') as $var) { $$var = isset($match[++$i]) ? $match[$i] : ''; } $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3; - // Parse array syntax + // Parse array syntax. $keys = preg_split('/\]?\[/', rtrim($key, ']')); $last = array_pop($keys); $parent = &$info; - // Create nested arrays + // Create nested arrays. foreach ($keys as $key) { if ($key == '') { $key = count($parent); @@ -7054,7 +7146,7 @@ function drupal_parse_info_format($data) { $value = $constants[$value]; } - // Insert actual value + // Insert actual value. if ($last == '') { $last = count($parent); } @@ -7066,11 +7158,12 @@ function drupal_parse_info_format($data) { } /** - * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. + * Returns a list of severity levels, as defined in RFC 3164. * * @return * Array of the possible severity levels for log messages. * + * @see http://www.ietf.org/rfc/rfc3164.txt * @see watchdog() * @ingroup logging_severity_levels */ @@ -7089,7 +7182,7 @@ function watchdog_severity_levels() { /** - * Explode a string of given tags into an array. + * Explodes a string of tags into an array. * * @see drupal_implode_tags() */ @@ -7115,7 +7208,7 @@ function drupal_explode_tags($tags) { } /** - * Implode an array of tags into a string. + * Implodes an array of tags into a string. * * @see drupal_explode_tags() */ @@ -7133,7 +7226,7 @@ function drupal_implode_tags($tags) { } /** - * Flush all cached data on the site. + * Flushes all cached data on the site. * * Empties cache tables, rebuilds the menu cache and theme registries, and * invokes a hook so that other modules' cache data can be cleared as well. @@ -7151,6 +7244,7 @@ function drupal_flush_all_caches() { system_rebuild_theme_data(); drupal_theme_rebuild(); + entity_info_cache_clear(); node_types_rebuild(); // node_menu() defines menu items based on node types so it needs to come // after node types are rebuilt. @@ -7174,10 +7268,10 @@ function drupal_flush_all_caches() { } /** - * Helper function to change query-strings on css/js files. + * Changes the dummy query string added to all CSS and JavaScript files. * - * Changes the character added to all css/js files as dummy query-string, so - * that all browsers are forced to reload fresh files. + * Changing the dummy query string appended to CSS and JavaScript files forces + * all browsers to reload fresh files. */ function _drupal_flush_css_js() { // The timestamp is converted to base 36 in order to make it more compact. @@ -7185,7 +7279,7 @@ function _drupal_flush_css_js() { } /** - * Debug function used for outputting debug information. + * Outputs debug information. * * The debug information is passed on to trigger_error() after being converted * to a string using _drupal_debug_message(). @@ -7210,10 +7304,11 @@ function debug($data, $label = NULL, $print_r = FALSE) { } /** - * Parse a dependency for comparison by drupal_check_incompatibility(). + * Parses a dependency for comparison by drupal_check_incompatibility(). * * @param $dependency * A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'. + * * @return * An associative array with three keys: * - 'name' includes the name of the thing to depend on (e.g. 'foo'). @@ -7267,12 +7362,13 @@ function drupal_parse_dependency($dependency) { } /** - * Check whether a version is compatible with a given dependency. + * Checks whether a version is compatible with a given dependency. * * @param $v * The parsed dependency structure from drupal_parse_dependency(). * @param $current_version * The version to check against (like 4.2). + * * @return * NULL if compatible, otherwise the original dependency version string that * caused the incompatibility. @@ -7777,11 +7873,12 @@ function archiver_get_extensions() { } /** - * Create the appropriate archiver for the specified file. + * Creates the appropriate archiver for the specified file. * * @param $file - * The full path of the archive file. Note that stream wrapper - * paths are supported, but not remote ones. + * The full path of the archive file. Note that stream wrapper paths are + * supported, but not remote ones. + * * @return * A newly created instance of the archiver class appropriate * for the specified file, already bound to that file. @@ -7810,14 +7907,14 @@ function archiver_get_archiver($file) { } /** - * Drupal Updater registry. + * Assembles the Drupal Updater registry. * * An Updater is a class that knows how to update various parts of the Drupal * file system, for example to update modules that have newer releases, or to * install a new theme. * * @return - * Returns the Drupal Updater class registry. + * The Drupal Updater class registry. * * @see hook_updater_info() * @see hook_updater_info_alter() @@ -7833,10 +7930,10 @@ function drupal_get_updaters() { } /** - * Drupal FileTransfer registry. + * Assembles the Drupal FileTransfer registry. * * @return - * Returns the Drupal FileTransfer class registry. + * The Drupal FileTransfer class registry. * * @see FileTransfer * @see hook_filetransfer_info() diff --git a/includes/database/database.inc b/includes/database/database.inc index 33000aa70..6e40b2765 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1016,9 +1016,9 @@ abstract class DatabaseConnection extends PDO { throw new DatabaseTransactionNoActiveException(); } // A previous rollback to an earlier savepoint may mean that the savepoint - // in question has already been rolled back. - if (!in_array($savepoint_name, $this->transactionLayers)) { - return; + // in question has already been accidentally committed. + if (!isset($this->transactionLayers[$savepoint_name])) { + throw new DatabaseTransactionNoActiveException(); } // We need to find the point we're rolling back to, all other savepoints @@ -1096,8 +1096,12 @@ abstract class DatabaseConnection extends PDO { if (!$this->supportsTransactions()) { return; } + // The transaction has already been committed earlier. There is nothing we + // need to do. If this transaction was part of an earlier out-of-order + // rollback, an exception would already have been thrown by + // Database::rollback(). if (!isset($this->transactionLayers[$name])) { - throw new DatabaseTransactionNoActiveException(); + return; } // Mark this layer as committable. diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index 7d5d85998..e024a7f39 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -37,14 +37,20 @@ class DatabaseConnection_mysql extends DatabaseConnection { $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']); } $dsn .= ';dbname=' . $connection_options['database']; - parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( + // Allow PDO options to be overridden. + $connection_options += array( + 'pdo' => array(), + ); + $connection_options['pdo'] += array( // So we don't have to mess around with cursors and unbuffered queries by default. PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, // Because MySQL's prepared statements skip the query cache, because it's dumb. PDO::ATTR_EMULATE_PREPARES => TRUE, // Force column names to lower case. PDO::ATTR_CASE => PDO::CASE_LOWER, - )); + ); + + parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); // Force MySQL to use the UTF-8 character set. Also set the collation, if a // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci' @@ -56,14 +62,22 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->exec('SET NAMES utf8'); } - // Force MySQL's behavior to conform more closely to SQL standards. - // This allows Drupal to run almost seamlessly on many different - // kinds of database systems. These settings force MySQL to behave - // the same as postgresql, or sqlite in regards to syntax interpretation - // and invalid data handling. See http://drupal.org/node/344575 for - // further discussion. Also, as MySQL 5.5 changed the meaning of - // TRADITIONAL we need to spell out the modes one by one. - $this->exec("SET sql_mode='ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'"); + // Set MySQL init_commands if not already defined. Default Drupal's MySQL + // behavior to conform more closely to SQL standards. This allows Drupal + // to run almost seamlessly on many different kinds of database systems. + // These settings force MySQL to behave the same as postgresql, or sqlite + // in regards to syntax interpretation and invalid data handling. See + // http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5 + // changed the meaning of TRADITIONAL we need to spell out the modes one by + // one. + $connection_options += array( + 'init_commands' => array(), + ); + $connection_options['init_commands'] += array( + 'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'", + ); + // Set connection options. + $this->exec(implode('; ', $connection_options['init_commands'])); } public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { @@ -169,8 +183,11 @@ class DatabaseConnection_mysql extends DatabaseConnection { // succeed for MySQL error code 1305 ("SAVEPOINT does not exist"). if ($e->errorInfo[1] == '1305') { // If one SAVEPOINT was released automatically, then all were. - // Therefore, we keep just the topmost transaction. - $this->transactionLayers = array('drupal_transaction' => 'drupal_transaction'); + // Therefore, clean the transaction stack. + $this->transactionLayers = array(); + // We also have to explain to PDO that the transaction stack has + // been cleaned-up. + PDO::commit(); } else { throw $e; diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index 39b4e9b69..d42a1cc3c 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -47,7 +47,12 @@ class DatabaseConnection_pgsql extends DatabaseConnection { $this->connectionOptions = $connection_options; $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port']; - parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( + + // Allow PDO options to be overridden. + $connection_options += array( + 'pdo' => array(), + ); + $connection_options['pdo'] += array( // Prepared statements are most effective for performance when queries // are recycled (used several times). However, if they are not re-used, // prepared statements become ineffecient. Since most of Drupal's @@ -59,10 +64,16 @@ class DatabaseConnection_pgsql extends DatabaseConnection { PDO::ATTR_STRINGIFY_FETCHES => TRUE, // Force column names to lower case. PDO::ATTR_CASE => PDO::CASE_LOWER, - )); + ); + parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); // Force PostgreSQL to use the UTF-8 character set by default. $this->exec("SET NAMES 'UTF8'"); + + // Execute PostgreSQL init_commands. + if (isset($connection_options['init_commands'])) { + $this->exec(implode('; ', $connection_options['init_commands'])); + } } public function query($query, array $args = array(), $options = array()) { diff --git a/includes/database/query.inc b/includes/database/query.inc index c77968767..6020b0ea5 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -22,6 +22,9 @@ interface QueryConditionInterface { * parameters, they are taken as $field and $value with $operator having a * value of IN if $value is an array and = otherwise. * + * Do not use this method to test for NULL values. Instead, use + * QueryConditionInterface::isNull() or QueryConditionInterface::isNotNull(). + * * @param $field * The name of the field to check. If you would like to add a more complex * condition involving operators or functions, use where(). @@ -36,6 +39,9 @@ interface QueryConditionInterface { * * @return QueryConditionInterface * The called object. + * + * @see QueryConditionInterface::isNull() + * @see QueryConditionInterface::isNotNull() */ public function condition($field, $value = NULL, $operator = NULL); diff --git a/includes/database/select.inc b/includes/database/select.inc index 9b587aebe..7e2af85e7 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -590,11 +590,13 @@ class SelectQueryExtender implements SelectQueryInterface { } public function hasAllTags() { - return call_user_func_array(array($this->query, 'hasAllTags'), func_get_args()); + $args = func_get_args(); + return call_user_func_array(array($this->query, 'hasAllTags'), $args); } public function hasAnyTag() { - return call_user_func_array(array($this->query, 'hasAnyTags'), func_get_args()); + $args = func_get_args(); + return call_user_func_array(array($this->query, 'hasAnyTags'), $args); } public function addMetaData($key, $object) { @@ -637,16 +639,16 @@ class SelectQueryExtender implements SelectQueryInterface { /* Implementations of QueryConditionInterface for the HAVING clause. */ public function havingCondition($field, $value = NULL, $operator = '=') { - $this->query->condition($field, $value, $operator, $num_args); + $this->query->havingCondition($field, $value, $operator); return $this; } public function &havingConditions() { - return $this->having->conditions(); + return $this->query->havingConditions(); } public function havingArguments() { - return $this->having->arguments(); + return $this->query->havingArguments(); } public function having($snippet, $args = array()) { @@ -790,31 +792,7 @@ class SelectQueryExtender implements SelectQueryInterface { } public function countQuery() { - // Create our new query object that we will mutate into a count query. - $count = clone($this); - - // Zero-out existing fields and expressions. - $fields =& $count->getFields(); - $fields = array(); - $expressions =& $count->getExpressions(); - $expressions = array(); - - // Also remove 'all_fields' statements, which are expanded into tablename.* - // when the query is executed. - $tables = &$count->getTables(); - foreach ($tables as $alias => &$table) { - unset($table['all_fields']); - } - - // Ordering a count query is a waste of cycles, and breaks on some - // databases anyway. - $orders = &$count->getOrderBy(); - $orders = array(); - - // COUNT() is an expression, so we add that back in. - $count->addExpression('COUNT(*)'); - - return $count; + return $this->query->countQuery(); } function isNull($field) { @@ -836,7 +814,7 @@ class SelectQueryExtender implements SelectQueryInterface { $this->query->notExists($select); return $this; } - + public function __toString() { return (string) $this->query; } @@ -1005,11 +983,13 @@ class SelectQuery extends Query implements SelectQueryInterface { } public function hasAllTags() { - return !(boolean)array_diff(func_get_args(), array_keys($this->alterTags)); + $args = func_get_args(); + return !(boolean)array_diff($args, array_keys($this->alterTags)); } public function hasAnyTag() { - return (boolean)array_intersect(func_get_args(), array_keys($this->alterTags)); + $args = func_get_args(); + return (boolean)array_intersect($args, array_keys($this->alterTags)); } public function addMetaData($key, $object) { @@ -1088,7 +1068,7 @@ class SelectQuery extends Query implements SelectQueryInterface { $this->where->notExists($select); return $this; } - + public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) { $this->where->compile($connection, $queryPlaceholder); $this->having->compile($connection, $queryPlaceholder); @@ -1172,17 +1152,17 @@ class SelectQuery extends Query implements SelectQueryInterface { $this->having->isNotNull($field); return $this; } - + public function havingExists(SelectQueryInterface $select) { $this->having->exists($select); return $this; } - + public function havingNotExists(SelectQueryInterface $select) { $this->having->notExists($select); return $this; } - + public function forUpdate($set = TRUE) { if (isset($set)) { $this->forUpdate = $set; @@ -1451,17 +1431,20 @@ class SelectQuery extends Query implements SelectQueryInterface { $count = clone($this); $group_by = $count->getGroupBy(); + $having = $count->havingConditions(); - if (!$count->distinct) { + if (!$count->distinct && !isset($having[0])) { // When not executing a distinct query, we can zero-out existing fields - // and expressions that are not used by a GROUP BY. Fields listed in - // the GROUP BY clause need to be present in the query. + // and expressions that are not used by a GROUP BY or HAVING. Fields + // listed in a GROUP BY or HAVING clause need to be present in the + // query. $fields =& $count->getFields(); foreach (array_keys($fields) as $field) { if (empty($group_by[$field])) { unset($fields[$field]); } } + $expressions =& $count->getExpressions(); foreach (array_keys($expressions) as $field) { if (empty($group_by[$field])) { diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index 3e2490b00..b4e41b527 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -59,16 +59,21 @@ class DatabaseConnection_sqlite extends DatabaseConnection { $this->statementClass = NULL; // This driver defaults to transaction support, except if explicitly passed FALSE. - $this->transactionSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE; + $this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE; $this->connectionOptions = $connection_options; - parent::__construct('sqlite:' . $connection_options['database'], '', '', array( + // Allow PDO options to be overridden. + $connection_options += array( + 'pdo' => array(), + ); + $connection_options['pdo'] += array( // Force column names to lower case. PDO::ATTR_CASE => PDO::CASE_LOWER, // Convert numeric values to strings when fetching. PDO::ATTR_STRINGIFY_FETCHES => TRUE, - )); + ); + parent::__construct('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']); // Attach one database for each registered prefix. $prefixes = $this->prefixes; @@ -103,6 +108,11 @@ class DatabaseConnection_sqlite extends DatabaseConnection { $this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3); $this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3); $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand')); + + // Execute sqlite init_commands. + if (isset($connection_options['init_commands'])) { + $this->exec(implode('; ', $connection_options['init_commands'])); + } } /** diff --git a/includes/file.inc b/includes/file.inc index 40e8349a0..7fd6c71c9 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -770,7 +770,7 @@ function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS $file = clone $source; $file->fid = NULL; $file->uri = $uri; - $file->filename = basename($uri); + $file->filename = drupal_basename($uri); // If we are replacing an existing file re-use its database record. if ($replace == FILE_EXISTS_REPLACE) { $existing_files = file_load_multiple(array(), array('uri' => $uri)); @@ -783,7 +783,7 @@ function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS // If we are renaming around an existing file (rather than a directory), // use its basename for the filename. elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { - $file->filename = basename($destination); + $file->filename = drupal_basename($destination); } $file = file_save($file); @@ -828,6 +828,10 @@ function file_valid_uri($uri) { * is reported. * - If file already exists in $destination either the call will error out, * replace the file or rename the file based on the $replace parameter. + * - Provides a fallback using realpaths if the move fails using stream + * wrappers. This can occur because PHP's copy() function does not properly + * support streams if safe_mode or open_basedir are enabled. See + * https://bugs.php.net/bug.php?id=60456 * * @param $source * A string specifying the filepath or URI of the source file. @@ -867,14 +871,14 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST // Build a destination URI if necessary. if (!isset($destination)) { - $destination = file_build_uri(basename($source)); + $destination = file_build_uri(drupal_basename($source)); } // Prepare the destination directory. if (file_prepare_directory($destination)) { // The destination is already a directory, so append the source basename. - $destination = file_stream_wrapper_uri_normalize($destination . '/' . basename($source)); + $destination = file_stream_wrapper_uri_normalize($destination . '/' . drupal_basename($source)); } else { // Perhaps $destination is a dir/file? @@ -907,8 +911,12 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST file_ensure_htaccess(); // Perform the copy operation. if (!@copy($source, $destination)) { - watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR); - return FALSE; + // If the copy failed and realpaths exist, retry the operation using them + // instead. + if ($real_source === FALSE || $real_destination === FALSE || !@copy($real_source, $real_destination)) { + watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR); + return FALSE; + } } // Set the permissions on the new file. @@ -950,7 +958,7 @@ function file_destination($destination, $replace) { break; case FILE_EXISTS_RENAME: - $basename = basename($destination); + $basename = drupal_basename($destination); $directory = drupal_dirname($destination); $destination = file_create_filename($basename, $directory); break; @@ -1025,7 +1033,7 @@ function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS // If we are renaming around an existing file (rather than a directory), // use its basename for the filename. elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { - $file->filename = basename($destination); + $file->filename = drupal_basename($destination); } $file = file_save($file); @@ -1138,7 +1146,7 @@ function file_munge_filename($filename, $extensions, $alerts = TRUE) { } /** - * Undo the effect of upload_munge_filename(). + * Undo the effect of file_munge_filename(). * * @param $filename * String with the filename to be unmunged. @@ -1443,7 +1451,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, $file = new stdClass(); $file->uid = $user->uid; $file->status = 0; - $file->filename = trim(basename($_FILES['files']['name'][$source]), '.'); + $file->filename = trim(drupal_basename($_FILES['files']['name'][$source]), '.'); $file->uri = $_FILES['files']['tmp_name'][$source]; $file->filemime = file_get_mimetype($file->filename); $file->filesize = $_FILES['files']['size'][$source]; @@ -1841,7 +1849,7 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM $file = new stdClass(); $file->fid = NULL; $file->uri = $uri; - $file->filename = basename($uri); + $file->filename = drupal_basename($uri); $file->filemime = file_get_mimetype($file->uri); $file->uid = $user->uid; $file->status = FILE_STATUS_PERMANENT; @@ -1857,7 +1865,7 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM // If we are renaming around an existing file (rather than a directory), // use its basename for the filename. elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { - $file->filename = basename($destination); + $file->filename = drupal_basename($destination); } return file_save($file); @@ -2267,6 +2275,35 @@ function drupal_dirname($uri) { } /** + * Gets the filename from a given path. + * + * PHP's basename() does not properly support streams or filenames beginning + * with a non-US-ASCII character. + * + * @see http://bugs.php.net/bug.php?id=37738 + * @see basename() + * + * @ingroup php_wrappers + */ +function drupal_basename($uri, $suffix = NULL) { + $separators = '/'; + if (DIRECTORY_SEPARATOR != '/') { + // For Windows OS add special separator. + $separators .= DIRECTORY_SEPARATOR; + } + // Remove right-most slashes when $uri points to directory. + $uri = rtrim($uri, $separators); + // Returns the trailing part of the $uri starting after one of the directory + // separators. + $filename = preg_match('@[^' . preg_quote($separators, '@') . ']+$@', $uri, $matches) ? $matches[0] : ''; + // Cuts off a suffix from the filename. + if ($suffix) { + $filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename); + } + return $filename; +} + +/** * Creates a directory using Drupal's default mode. * * PHP's mkdir() does not respect Drupal's default permissions mode. If a mode @@ -2362,7 +2399,7 @@ function drupal_tempnam($directory, $prefix) { $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) { - return $scheme . '://' . basename($filename); + return $scheme . '://' . drupal_basename($filename); } else { return FALSE; diff --git a/includes/filetransfer/filetransfer.inc b/includes/filetransfer/filetransfer.inc index 2083da9d3..bd2057cdd 100644 --- a/includes/filetransfer/filetransfer.inc +++ b/includes/filetransfer/filetransfer.inc @@ -211,7 +211,7 @@ abstract class FileTransfer { */ protected function copyDirectoryJailed($source, $destination) { if ($this->isDirectory($destination)) { - $destination = $destination . '/' . basename($source); + $destination = $destination . '/' . drupal_basename($source); } $this->createDirectory($destination); foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { @@ -302,9 +302,9 @@ abstract class FileTransfer { $chroot = ''; while (count($parts)) { $check = implode($parts, '/'); - if ($this->isFile($check . '/' . basename(__FILE__))) { + if ($this->isFile($check . '/' . drupal_basename(__FILE__))) { // Remove the trailing slash. - return substr($chroot,0,-1); + return substr($chroot, 0, -1); } $chroot .= array_shift($parts) . '/'; } diff --git a/includes/form.inc b/includes/form.inc index e0bc9cba0..3d5f6f22e 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -16,7 +16,7 @@ * \@see user_pass_validate(). * \@see user_pass_submit(). * - * @} End of "defgroup forms". + * @} */ /** @@ -156,6 +156,8 @@ function drupal_get_form($form_id) { * automatically loaded by form_get_cache(). By default the current menu * router item's 'file' definition is added, if any. Use * form_load_include() to add include files from a form constructor. + * - base_form_id: Identification for a base form, as declared in a + * hook_forms() implementation. * - rebuild_info: Internal. Similar to 'build_info', but pertaining to * drupal_rebuild_form(). * - rebuild: Normally, after the entire form processing is completed and @@ -3349,6 +3351,13 @@ function form_process_machine_name($element, &$form_state) { 'replace' => '_', ); + // By default, machine names are restricted to Latin alphanumeric characters. + // So, default to LTR directionality. + if (!isset($element['#attributes'])) { + $element['#attributes'] = array(); + } + $element['#attributes'] += array('dir' => 'ltr'); + // The source element defaults to array('name'), but may have been overidden. if (empty($element['#machine_name']['source'])) { return $element; @@ -3786,13 +3795,27 @@ function theme_password($variables) { * Expand weight elements into selects. */ function form_process_weight($element) { - for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { - $weights[$n] = $n; - } - $element['#options'] = $weights; - $element['#type'] = 'select'; $element['#is_weight'] = TRUE; - $element += element_info('select'); + + // If the number of options is small enough, use a select field. + $max_elements = variable_get('drupal_weight_select_max', DRUPAL_WEIGHT_SELECT_MAX); + if ($element['#delta'] <= $max_elements) { + $element['#type'] = 'select'; + for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { + $weights[$n] = $n; + } + $element['#options'] = $weights; + $element += element_info('select'); + } + // Otherwise, use a text field. + else { + $element['#type'] = 'textfield'; + // Use a field big enough to fit most weights. + $element['#size'] = 10; + $element['#element_validate'] = array('element_validate_integer'); + $element += element_info('textfield'); + } + return $element; } @@ -3976,7 +3999,7 @@ function theme_form_element_label($variables) { $t = get_t(); // If title and required marker are both empty, output no label. - if (empty($element['#title']) && empty($element['#required'])) { + if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) { return ''; } diff --git a/includes/graph.inc b/includes/graph.inc index 416fad6df..7fcc57a45 100644 --- a/includes/graph.inc +++ b/includes/graph.inc @@ -143,4 +143,3 @@ function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL // topological order if the graph is acyclic. $state['last_visit_order'][] = $start; } - diff --git a/includes/image.inc b/includes/image.inc index 8dc36b995..f6ae7f19b 100644 --- a/includes/image.inc +++ b/includes/image.inc @@ -186,14 +186,14 @@ function image_scale_and_crop(stdClass $image, $width, $height) { * Dimensions to be modified - an array with components width and height, in * pixels. * @param $width - * The target width, in pixels. This value is omitted then the scaling will + * The target width, in pixels. If this value is NULL then the scaling will be * based only on the height value. * @param $height - * The target height, in pixels. This value is omitted then the scaling will - * based only on the width value. + * The target height, in pixels. If this value is NULL then the scaling will + * be based only on the width value. * @param $upscale - * Boolean indicating that files smaller than the dimensions will be scaled - * up. This generally results in a low quality image. + * Boolean indicating that images smaller than the target dimensions will be + * scaled up. This generally results in a low quality image. * * @return * TRUE if $dimensions was modified, FALSE otherwise. @@ -203,31 +203,25 @@ function image_scale_and_crop(stdClass $image, $width, $height) { function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) { $aspect = $dimensions['height'] / $dimensions['width']; - if ($upscale) { - // Set width/height according to aspect ratio if either is empty. - $width = !empty($width) ? $width : $height / $aspect; - $height = !empty($height) ? $height : $width / $aspect; + // Calculate one of the dimensions from the other target dimension, + // ensuring the same aspect ratio as the source dimensions. If one of the + // target dimensions is missing, that is the one that is calculated. If both + // are specified then the dimension calculated is the one that would not be + // calculated to be bigger than its target. + if (($width && !$height) || ($width && $height && $aspect < $height / $width)) { + $height = (int) round($width * $aspect); } else { - // Set impossibly large values if the width and height aren't set. - $width = !empty($width) ? $width : 9999999; - $height = !empty($height) ? $height : 9999999; - - // Don't scale up. - if (round($width) >= $dimensions['width'] && round($height) >= $dimensions['height']) { - return FALSE; - } + $width = (int) round($height / $aspect); } - if ($aspect < $height / $width) { - $dimensions['width'] = $width; - $dimensions['height'] = (int) round($width * $aspect); - } - else { - $dimensions['width'] = (int) round($height / $aspect); - $dimensions['height'] = $height; + // Don't upscale if the option isn't enabled. + if (!$upscale && ($width >= $dimensions['width'] || $height >= $dimensions['height'])) { + return FALSE; } + $dimensions['width'] = $width; + $dimensions['height'] = $height; return TRUE; } diff --git a/includes/install.core.inc b/includes/install.core.inc index a74dfdf0f..ec3a8539b 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -570,6 +570,12 @@ function install_tasks($install_state) { // Now add any tasks defined by the installation profile. if (!empty($install_state['parameters']['profile'])) { + // Load the profile install file, because it is not always loaded when + // hook_install_tasks() is invoked (e.g. batch processing). + $profile_install_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.install'; + if (file_exists($profile_install_file)) { + include_once $profile_install_file; + } $function = $install_state['parameters']['profile'] . '_install_tasks'; if (function_exists($function)) { $result = $function($install_state); @@ -595,7 +601,7 @@ function install_tasks($install_state) { // Allow the installation profile to modify the full list of tasks. if (!empty($install_state['parameters']['profile'])) { $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; - if (is_file($profile_file)) { + if (file_exists($profile_file)) { include_once $profile_file; $function = $install_state['parameters']['profile'] . '_install_tasks_alter'; if (function_exists($function)) { @@ -710,8 +716,10 @@ function install_display_output($output, $install_state) { * * @return * A themed status report, or an exception if there are requirement errors. - * Otherwise, no output is returned, so that the next task can be run - * in the same page request. + * If there are only requirement warnings, a themed status report is shown + * initially, but the user is allowed to bypass it by providing 'continue=1' + * in the URL. Otherwise, no output is returned, so that the next task can be + * run in the same page request. */ function install_verify_requirements(&$install_state) { // Check the installation requirements for Drupal and this profile. @@ -723,22 +731,30 @@ function install_verify_requirements(&$install_state) { // Check the severity of the requirements reported. $severity = drupal_requirements_severity($requirements); - if ($severity == REQUIREMENT_ERROR) { + // If there are errors, always display them. If there are only warnings, skip + // them if the user has provided a URL parameter acknowledging the warnings + // and indicating a desire to continue anyway. See drupal_requirements_url(). + if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($install_state['parameters']['continue']))) { if ($install_state['interactive']) { drupal_set_title(st('Requirements problem')); $status_report = theme('status_report', array('requirements' => $requirements)); - $status_report .= st('Check the error messages and <a href="!url">proceed with the installation</a>.', array('!url' => check_url(request_uri()))); + $status_report .= st('Check the error messages and <a href="!url">proceed with the installation</a>.', array('!url' => check_url(drupal_requirements_url($severity)))); return $status_report; } else { - // Throw an exception showing all unmet requirements. + // Throw an exception showing any unmet requirements. $failures = array(); foreach ($requirements as $requirement) { + // Skip warnings altogether for non-interactive installations; these + // proceed in a single request so there is no good opportunity (and no + // good method) to warn the user anyway. if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) { $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description']; } } - throw new Exception(implode("\n\n", $failures)); + if (!empty($failures)) { + throw new Exception(implode("\n\n", $failures)); + } } } } @@ -1290,7 +1306,7 @@ function install_already_done_error() { */ function install_load_profile(&$install_state) { $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; - if (is_file($profile_file)) { + if (file_exists($profile_file)) { include_once $profile_file; $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['locale']); } @@ -1408,13 +1424,6 @@ function install_import_locales(&$install_state) { * The form API definition for the site configuration form. */ function install_configure_form($form, &$form_state, &$install_state) { - if (variable_get('site_name', FALSE) || variable_get('site_mail', FALSE)) { - // Site already configured: This should never happen, means re-running the - // installer, possibly by an attacker after the 'install_task' variable got - // accidentally blown somewhere. Stop it now. - throw new Exception(install_already_done_error()); - } - drupal_set_title(st('Configure site')); // Warn about settings.php permissions risk @@ -1816,7 +1825,7 @@ function install_configure_form_submit($form, &$form_state) { // We precreated user 1 with placeholder values. Let's save the real values. $account = user_load(1); - $merge_data = array('init' => $form_state['values']['account']['mail'], 'roles' => !empty($account->roles) ? $account->roles : array(), 'status' => 1); + $merge_data = array('init' => $form_state['values']['account']['mail'], 'roles' => !empty($account->roles) ? $account->roles : array(), 'status' => 1, 'timezone' => $form_state['values']['date_default_timezone']); user_save($account, array_merge($form_state['values']['account'], $merge_data)); // Load global $user and perform final login tasks. $user = user_load(1); diff --git a/includes/install.inc b/includes/install.inc index 516e14618..6411f8f19 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -999,7 +999,6 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) { } } - /** * Send the user to a different installer page. * @@ -1017,6 +1016,68 @@ function install_goto($path) { } /** + * Returns the URL of the current script, with modified query parameters. + * + * This function can be called by low-level scripts (such as install.php and + * update.php) and returns the URL of the current script. Existing query + * parameters are preserved by default, but new ones can optionally be merged + * in. + * + * This function is used when the script must maintain certain query parameters + * over multiple page requests in order to work correctly. In such cases (for + * example, update.php, which requires the 'continue=1' parameter to remain in + * the URL throughout the update process if there are any requirement warnings + * that need to be bypassed), using this function to generate the URL for links + * to the next steps of the script ensures that the links will work correctly. + * + * @param $query + * (optional) An array of query parameters to merge in to the existing ones. + * + * @return + * The URL of the current script, with query parameters modified by the + * passed-in $query. The URL is not sanitized, so it still needs to be run + * through check_url() if it will be used as an HTML attribute value. + * + * @see drupal_requirements_url() + */ +function drupal_current_script_url($query = array()) { + $uri = $_SERVER['SCRIPT_NAME']; + $query = array_merge(drupal_get_query_parameters(), $query); + if (!empty($query)) { + $uri .= '?' . drupal_http_build_query($query); + } + return $uri; +} + +/** + * Returns a URL for proceeding to the next page after a requirements problem. + * + * This function can be called by low-level scripts (such as install.php and + * update.php) and returns a URL that can be used to attempt to proceed to the + * next step of the script. + * + * @param $severity + * The severity of the requirements problem, as returned by + * drupal_requirements_severity(). + * + * @return + * A URL for attempting to proceed to the next step of the script. The URL is + * not sanitized, so it still needs to be run through check_url() if it will + * be used as an HTML attribute value. + * + * @see drupal_current_script_url() + */ +function drupal_requirements_url($severity) { + $query = array(); + // If there are no errors, only warnings, append 'continue=1' to the URL so + // the user can bypass this screen on the next page load. + if ($severity == REQUIREMENT_WARNING) { + $query['continue'] = 1; + } + return drupal_current_script_url($query); +} + +/** * Functional equivalent of t(), used when some systems are not available. * * Used during the install process, when database, theme, and localization diff --git a/includes/iso.inc b/includes/iso.inc index dabbefdd5..a88de57e9 100644 --- a/includes/iso.inc +++ b/includes/iso.inc @@ -74,6 +74,7 @@ function _country_get_predefined_list() { 'CO' => $t('Colombia'), 'CR' => $t('Costa Rica'), 'CU' => $t('Cuba'), + 'CW' => $t('Curaçao'), 'CV' => $t('Cape Verde'), 'CX' => $t('Christmas Island'), 'CY' => $t('Cyprus'), diff --git a/includes/json-encode.inc b/includes/json-encode.inc new file mode 100644 index 000000000..1efd6ddbe --- /dev/null +++ b/includes/json-encode.inc @@ -0,0 +1,102 @@ +<?php + +/** + * @file + * Provides a helper to properly encode HTML-safe JSON prior to PHP 5.3.0. + */ + +/** + * Encodes a PHP variable to HTML-safe JSON for PHP versions below 5.3.0. + * + * @see drupal_json_encode() + */ +function drupal_json_encode_helper($var) { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; // Lowercase necessary! + + case 'integer': + case 'double': + return $var; + + case 'resource': + case 'string': + // Always use Unicode escape sequences (\u0022) over JSON escape + // sequences (\") to prevent browsers interpreting these as + // special characters. + $replace_pairs = array( + // ", \ and U+0000 - U+001F must be escaped according to RFC 4627. + '\\' => '\u005C', + '"' => '\u0022', + "\x00" => '\u0000', + "\x01" => '\u0001', + "\x02" => '\u0002', + "\x03" => '\u0003', + "\x04" => '\u0004', + "\x05" => '\u0005', + "\x06" => '\u0006', + "\x07" => '\u0007', + "\x08" => '\u0008', + "\x09" => '\u0009', + "\x0a" => '\u000A', + "\x0b" => '\u000B', + "\x0c" => '\u000C', + "\x0d" => '\u000D', + "\x0e" => '\u000E', + "\x0f" => '\u000F', + "\x10" => '\u0010', + "\x11" => '\u0011', + "\x12" => '\u0012', + "\x13" => '\u0013', + "\x14" => '\u0014', + "\x15" => '\u0015', + "\x16" => '\u0016', + "\x17" => '\u0017', + "\x18" => '\u0018', + "\x19" => '\u0019', + "\x1a" => '\u001A', + "\x1b" => '\u001B', + "\x1c" => '\u001C', + "\x1d" => '\u001D', + "\x1e" => '\u001E', + "\x1f" => '\u001F', + // Prevent browsers from interpreting these as as special. + "'" => '\u0027', + '<' => '\u003C', + '>' => '\u003E', + '&' => '\u0026', + // Prevent browsers from interpreting the solidus as special and + // non-compliant JSON parsers from interpreting // as a comment. + '/' => '\u002F', + // While these are allowed unescaped according to ECMA-262, section + // 15.12.2, they cause problems in some JSON parsers. + "\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator. + "\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator. + ); + + return '"' . strtr($var, $replace_pairs) . '"'; + + case 'array': + // Arrays in JSON can't be associative. If the array is empty or if it + // has sequential whole number keys starting with 0, it's not associative + // so we can go ahead and convert it as an array. + if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) { + $output = array(); + foreach ($var as $v) { + $output[] = drupal_json_encode_helper($v); + } + return '[ ' . implode(', ', $output) . ' ]'; + } + // Otherwise, fall through to convert the array as an object. + + case 'object': + $output = array(); + foreach ($var as $k => $v) { + $output[] = drupal_json_encode_helper(strval($k)) . ':' . drupal_json_encode_helper($v); + } + return '{' . implode(', ', $output) . '}'; + + default: + return 'null'; + } +} diff --git a/includes/locale.inc b/includes/locale.inc index 6ebb8972c..0db4d4a07 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -43,6 +43,36 @@ define('LOCALE_LANGUAGE_NEGOTIATION_SESSION', 'locale-session'); define('LOCALE_JS_STRING', '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+'); /** + * Regular expression pattern used to match simple JS object literal. + * + * This pattern matches a basic JS object, but will fail on an object with + * nested objects. Used in JS file parsing for string arg processing. + */ +define('LOCALE_JS_OBJECT', '\{.*?\}'); + +/** + * Regular expression to match an object containing a key 'context'. + * + * Pattern to match a JS object containing a 'context key' with a string value, + * which is captured. Will fail if there are nested objects. + */ +define('LOCALE_JS_OBJECT_CONTEXT', ' + \{ # match object literal start + .*? # match anything, non-greedy + (?: # match a form of "context" + \'context\' + | + "context" + | + context + ) + \s*:\s* # match key-value separator ":" + (' . LOCALE_JS_STRING . ') # match context string + .*? # match anything, non-greedy + \} # match end of object literal +'); + +/** * Translation import mode overwriting all existing translations * if new translated version available. */ @@ -1447,6 +1477,9 @@ function _locale_parse_js_file($filepath) { [^\w]Drupal\s*\.\s*t\s* # match "Drupal.t" with whitespace \(\s* # match "(" argument list start (' . LOCALE_JS_STRING . ')\s* # capture string argument + (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture str args + (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*) # optionally capture context + ?)? # close optional args [,\)] # match ")" or "," to finish ~sx', $file, $t_matches); @@ -1474,55 +1507,73 @@ function _locale_parse_js_file($filepath) { (?:\s*\+\s*)? # match "+" with possible whitespace, for str concat )+ # match multiple because we supports concatenating strs )\s* # end capturing of plural string argument + (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args + (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)? # optionally capture context + )? [,\)] ~sx', $file, $plural_matches); + $matches = array(); - // Loop through all matches and process them. - $all_matches = array_merge($plural_matches[1], $t_matches[1]); - foreach ($all_matches as $key => $string) { - $strings = array($string); + // Add strings from Drupal.t(). + foreach ($t_matches[1] as $key => $string) { + $matches[] = array( + 'string' => $string, + 'context' => $t_matches[2][$key], + ); + } + + // Add string from Drupal.formatPlural(). + foreach ($plural_matches[1] as $key => $string) { + $matches[] = array( + 'string' => $string, + 'context' => $plural_matches[3][$key], + ); // If there is also a plural version of this string, add it to the strings array. if (isset($plural_matches[2][$key])) { - $strings[] = $plural_matches[2][$key]; - } - - foreach ($strings as $key => $string) { - // Remove the quotes and string concatenations from the string. - $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($string, 1, -1))); - - $source = db_query("SELECT lid, location FROM {locales_source} WHERE source = :source AND textgroup = 'default'", array(':source' => $string))->fetchObject(); - if ($source) { - // We already have this source string and now have to add the location - // to the location column, if this file is not yet present in there. - $locations = preg_split('~\s*;\s*~', $source->location); - - if (!in_array($filepath, $locations)) { - $locations[] = $filepath; - $locations = implode('; ', $locations); - - // Save the new locations string to the database. - db_update('locales_source') - ->fields(array( - 'location' => $locations, - )) - ->condition('lid', $source->lid) - ->execute(); - } - } - else { - // We don't have the source string yet, thus we insert it into the database. - db_insert('locales_source') + $matches[] = array( + 'string' => $plural_matches[2][$key], + 'context' => $plural_matches[3][$key], + ); + } + } + + foreach ($matches as $key => $match) { + // Remove the quotes and string concatenations from the string. + $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['string'], 1, -1))); + $context = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['context'], 1, -1))); + + $source = db_query("SELECT lid, location FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = 'default'", array(':source' => $string, ':context' => $context))->fetchObject(); + if ($source) { + // We already have this source string and now have to add the location + // to the location column, if this file is not yet present in there. + $locations = preg_split('~\s*;\s*~', $source->location); + + if (!in_array($filepath, $locations)) { + $locations[] = $filepath; + $locations = implode('; ', $locations); + + // Save the new locations string to the database. + db_update('locales_source') ->fields(array( - 'location' => $filepath, - 'source' => $string, - 'context' => '', - 'textgroup' => 'default', + 'location' => $locations, )) + ->condition('lid', $source->lid) ->execute(); } } + else { + // We don't have the source string yet, thus we insert it into the database. + db_insert('locales_source') + ->fields(array( + 'location' => $filepath, + 'source' => $string, + 'context' => $context, + 'textgroup' => 'default', + )) + ->execute(); + } } } @@ -1942,11 +1993,11 @@ function _locale_rebuild_js($langcode = NULL) { // Construct the array for JavaScript translations. // Only add strings with a translation to the translations array. - $result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup", array(':language' => $language->language, ':textgroup' => 'default')); + $result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup", array(':language' => $language->language, ':textgroup' => 'default')); $translations = array(); foreach ($result as $data) { - $translations[$data->source] = $data->translation; + $translations[$data->context][$data->source] = $data->translation; } // Construct the JavaScript file, if there are translations. @@ -2242,7 +2293,7 @@ function _locale_batch_import($filepath, &$context) { // The filename is either {langcode}.po or {prefix}.{langcode}.po, so // we can extract the language code to use for the import from the end. if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) { - $file = (object) array('filename' => basename($filepath), 'uri' => $filepath); + $file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath); _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]); $context['results'][] = $filepath; } diff --git a/includes/mail.inc b/includes/mail.inc index 7272df972..13a6f4643 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -57,6 +57,12 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * user_mail_tokens($variables, $data, $options); * switch($key) { * case 'notice': + * // If the recipient can receive such notices by instant-message, do + * // not send by email. + * if (example_im_send($key, $message, $params)) { + * $message['send'] = FALSE; + * break; + * } * $langcode = $message['language']->language; * $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode)); * $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode)); @@ -65,6 +71,19 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * } * @endcode * + * Another example, which uses drupal_mail() to format a message for sending + * later: + * + * @code + * $params = array('current_conditions' => $data); + * $to = 'user@example.com'; + * $message = drupal_mail('example', 'notice', $to, $language, $params, FALSE); + * // Only add to the spool if sending was not canceled. + * if ($message['send']) { + * example_spool_message($message); + * } + * @endcode + * * @param $module * A module name to invoke hook_mail() on. The {$module}_mail() hook will be * called to complete the $message structure which will already contain common @@ -86,8 +105,10 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * @param $from * Sets From to this value, if given. * @param $send - * Send the message directly, without calling drupal_mail_system()->mail() - * manually. + * If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver + * the message, and store the result in $message['result']. Modules + * implementing hook_mail_alter() may cancel sending by setting + * $message['send'] to FALSE. * * @return * The $message array structure containing all details of the @@ -108,6 +129,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N 'from' => isset($from) ? $from : $default_from, 'language' => $language, 'params' => $params, + 'send' => TRUE, 'subject' => '', 'body' => array() ); @@ -148,12 +170,20 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N // Optionally send e-mail. if ($send) { - $message['result'] = $system->mail($message); - - // Log errors - if (!$message['result']) { - watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR); - drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error'); + // The original caller requested sending. Sending was canceled by one or + // more hook_mail_alter() implementations. We set 'result' to NULL, because + // FALSE indicates an error in sending. + if (empty($message['send'])) { + $message['result'] = NULL; + } + // Sending was originally requested and was not canceled. + else { + $message['result'] = $system->mail($message); + // Log errors. + if (!$message['result']) { + watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR); + drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error'); + } } } diff --git a/includes/menu.inc b/includes/menu.inc index dad65d727..25a87af12 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -275,6 +275,20 @@ define('MENU_MAX_DEPTH', 9); */ /** + * Reserved key to identify the most specific menu link for a given path. + * + * The value of this constant is a hash of the constant name. We use the hash + * so that the reserved key is over 32 characters in length and will not + * collide with allowed menu names: + * @code + * sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91 + * @endcode + * + * @see menu_link_get_preferred() + */ +define('MENU_PREFERRED_LINK', '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91'); + +/** * Returns the ancestors (and relevant placeholders) for any given path. * * For example, the ancestors of node/12345/edit are: @@ -1241,7 +1255,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = if ($item['access']) { // Find a menu link corresponding to the current path. If $active_path // is NULL, let menu_link_get_preferred() determine the path. - if ($active_link = menu_link_get_preferred($active_path)) { + if ($active_link = menu_link_get_preferred($active_path, $menu_name)) { // The active link may only be taken into account to build the // active trail, if it resides in the requested menu. Otherwise, // we'd needlessly re-run _menu_build_tree() queries for every menu @@ -1325,6 +1339,8 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = * Defaults to 1, which is the default to build a whole tree for a menu, i.e. * excluding menu container itself. * - max_depth: The maximum depth of menu links in the resulting tree. + * - conditions: An associative array of custom database select query + * condition key/value pairs; see _menu_build_tree() for the actual query. * * @return * A fully built menu tree. @@ -1408,6 +1424,12 @@ function _menu_build_tree($menu_name, array $parameters = array()) { if (isset($parameters['max_depth'])) { $query->condition('ml.depth', $parameters['max_depth'], '<='); } + // Add custom query conditions, if any were passed. + if (isset($parameters['conditions'])) { + foreach ($parameters['conditions'] as $column => $value) { + $query->condition($column, $value); + } + } // Build an ordered array of links using the query result object. $links = array(); @@ -2260,6 +2282,13 @@ function theme_menu_local_tasks(&$variables) { /** * Set (or get) the active menu for the current page - determines the active trail. + * + * @return + * An array of menu machine names, in order of preference. The + * 'menu_default_active_menus' variable may be used to assert a menu order + * different from the order of creation, or to prevent a particular menu from + * being used at all in the active trail. + * E.g., $conf['menu_default_active_menus'] = array('navigation', 'main-menu') */ function menu_set_active_menu_names($menu_names = NULL) { $active = &drupal_static(__FUNCTION__); @@ -2390,23 +2419,30 @@ function menu_set_active_trail($new_trail = NULL) { * @param $path * The path, for example 'node/5'. The function will find the corresponding * menu link ('node/5' if it exists, or fallback to 'node/%'). + * @param $selected_menu + * The name of a menu used to restrict the search for a preferred menu link. + * If not specified, all the menus returned by menu_get_active_menu_names() + * will be used. * * @return - * A fully translated menu link, or NULL if no matching menu link was + * A fully translated menu link, or FALSE if no matching menu link was * found. The most specific menu link ('node/5' preferred over 'node/%') in * the most preferred menu (as defined by menu_get_active_menu_names()) is * returned. */ -function menu_link_get_preferred($path = NULL) { +function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { $preferred_links = &drupal_static(__FUNCTION__); if (!isset($path)) { $path = $_GET['q']; } - if (!isset($preferred_links[$path])) { - $preferred_links[$path] = FALSE; + if (empty($selected_menu)) { + // Use an illegal menu name as the key for the preferred menu link. + $selected_menu = MENU_PREFERRED_LINK; + } + if (!isset($preferred_links[$path])) { // Look for the correct menu link by building a list of candidate paths, // which are ordered by priority (translated hrefs are preferred over // untranslated paths). Afterwards, the most relevant path is picked from @@ -2428,6 +2464,8 @@ function menu_link_get_preferred($path = NULL) { // Retrieve a list of menu names, ordered by preference. $menu_names = menu_get_active_menu_names(); + // Put the selected menu at the front of the list. + array_unshift($menu_names, $selected_menu); $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); @@ -2435,7 +2473,6 @@ function menu_link_get_preferred($path = NULL) { // Weight must be taken from {menu_links}, not {menu_router}. $query->addField('ml', 'weight', 'link_weight'); $query->fields('m'); - $query->condition('ml.menu_name', $menu_names, 'IN'); $query->condition('ml.link_path', $path_candidates, 'IN'); // Sort candidates by link path and menu name. @@ -2443,29 +2480,35 @@ function menu_link_get_preferred($path = NULL) { foreach ($query->execute() as $candidate) { $candidate['weight'] = $candidate['link_weight']; $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; + // Add any menus not already in the menu name search list. + if (!in_array($candidate['menu_name'], $menu_names)) { + $menu_names[] = $candidate['menu_name']; + } } - // Pick the most specific link, in the most preferred menu. + // Store the most specific link for each menu. Also save the most specific + // link of the most preferred menu in $preferred_link. foreach ($path_candidates as $link_path) { - if (!isset($candidates[$link_path])) { - continue; - } - foreach ($menu_names as $menu_name) { - if (!isset($candidates[$link_path][$menu_name])) { - continue; - } - $candidate_item = $candidates[$link_path][$menu_name]; - $map = explode('/', $path); - _menu_translate($candidate_item, $map); - if ($candidate_item['access']) { - $preferred_links[$path] = $candidate_item; + if (isset($candidates[$link_path])) { + foreach ($menu_names as $menu_name) { + if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) { + $candidate_item = $candidates[$link_path][$menu_name]; + $map = explode('/', $path); + _menu_translate($candidate_item, $map); + if ($candidate_item['access']) { + $preferred_links[$path][$menu_name] = $candidate_item; + if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) { + // Store the most specific link. + $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item; + } + } + } } - break 2; } } } - return $preferred_links[$path]; + return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE; } /** diff --git a/includes/module.inc b/includes/module.inc index 66c77f577..77e35b7b0 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -539,6 +539,7 @@ function module_disable($module_list, $disable_dependents = TRUE) { system_list_reset(); module_list(TRUE); module_implements('', FALSE, TRUE); + entity_info_cache_clear(); // Invoke hook_modules_disabled before disabling modules, // so we can still call module hooks to get information. module_invoke_all('modules_disabled', $invoke_modules); @@ -952,10 +953,24 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { } // If any modules implement one of the extra hooks that do not implement // the primary hook, we need to add them to the $modules array in their - // appropriate order. + // appropriate order. module_implements() can only return ordered + // implementations of a single hook. To get the ordered implementations + // of multiple hooks, we mimic the module_implements() logic of first + // ordering by module_list(), and then calling + // drupal_alter('module_implements'). if (array_diff($extra_modules, $modules)) { - // Order the modules by the order returned by module_list(). + // Merge the arrays and order by module_list(). $modules = array_intersect(module_list(), array_merge($modules, $extra_modules)); + // Since module_implements() already took care of loading the necessary + // include files, we can safely pass FALSE for the array values. + $implementations = array_fill_keys($modules, FALSE); + // Let modules adjust the order solely based on the primary hook. This + // ensures the same module order regardless of whether this if block + // runs. Calling drupal_alter() recursively in this way does not result + // in an infinite loop, because this call is for a single $type, so we + // won't end up in this code block again. + drupal_alter('module_implements', $implementations, $hook); + $modules = array_keys($implementations); } foreach ($modules as $module) { // Since $modules is a merged array, for any given module, we do not @@ -1003,4 +1018,3 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { $function($data, $context1, $context2); } } - diff --git a/includes/password.inc b/includes/password.inc index c0761da69..d4f5f738a 100644 --- a/includes/password.inc +++ b/includes/password.inc @@ -285,4 +285,3 @@ function user_needs_new_hash($account) { // Check whether the iteration count used differs from the standard number. return (_password_get_count_log2($account->pass) !== $count_log2); } - diff --git a/includes/path.inc b/includes/path.inc index db605370c..ed5b639fb 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -13,12 +13,12 @@ * Initialize the $_GET['q'] variable to the proper normal path. */ function drupal_path_initialize() { - if (!empty($_GET['q'])) { - $_GET['q'] = drupal_get_normal_path($_GET['q']); - } - else { - $_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node')); + // Ensure $_GET['q'] is set before calling drupal_normal_path(), to support + // path caching with hook_url_inbound_alter(). + if (empty($_GET['q'])) { + $_GET['q'] = variable_get('site_frontpage', 'node'); } + $_GET['q'] = drupal_get_normal_path($_GET['q']); } /** diff --git a/includes/registry.inc b/includes/registry.inc index 3fb14fb31..c7b8702c4 100644 --- a/includes/registry.inc +++ b/includes/registry.inc @@ -183,4 +183,3 @@ function _registry_parse_file($filename, $contents, $module = '', $weight = 0) { /** * @} End of "defgroup registry". */ - diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc index e47668e3a..2af8c9e91 100644 --- a/includes/stream_wrappers.inc +++ b/includes/stream_wrappers.inc @@ -317,7 +317,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface } $extension = ''; - $file_parts = explode('.', basename($uri)); + $file_parts = explode('.', drupal_basename($uri)); // Remove the first part: a full filename should not match an extension. array_shift($file_parts); @@ -377,7 +377,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface $realpath = realpath($path); if (!$realpath) { // This file does not yet exist. - $realpath = realpath(dirname($path)) . '/' . basename($path); + $realpath = realpath(dirname($path)) . '/' . drupal_basename($path); } $directory = realpath($this->getDirectoryPath()); if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) { diff --git a/includes/theme.inc b/includes/theme.inc index 252eec081..da4200e56 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -237,18 +237,33 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb /** * Get the theme registry. * + * @param $complete + * Optional boolean to indicate whether to return the complete theme registry + * array or an instance of the ThemeRegistry class. If TRUE, the complete + * theme registry array will be returned. This is useful if you want to + * foreach over the whole registry, use array_* functions or inspect it in a + * debugger. If FALSE, an instance of the ThemeRegistry class will be + * returned, this provides an ArrayObject which allows it to be accessed + * with array syntax and isset(), and should be more lightweight + * than the full registry. Defaults to TRUE. + * * @return - * The theme registry array if it has been stored in memory, NULL otherwise. + * The complete theme registry array, or an instance of the ThemeRegistry + * class. */ -function theme_get_registry() { - static $theme_registry = NULL; +function theme_get_registry($complete = TRUE) { + static $theme_registry = array(); + $key = (int) $complete; - if (!isset($theme_registry)) { + if (!isset($theme_registry[$key])) { list($callback, $arguments) = _theme_registry_callback(); - $theme_registry = call_user_func_array($callback, $arguments); + if (!$complete) { + $arguments[] = FALSE; + } + $theme_registry[$key] = call_user_func_array($callback, $arguments); } - return $theme_registry; + return $theme_registry[$key]; } /** @@ -268,7 +283,7 @@ function _theme_registry_callback($callback = NULL, array $arguments = array()) } /** - * Get the theme_registry cache from the database; if it doesn't exist, build it. + * Get the theme_registry cache; if it doesn't exist, build it. * * @param $theme * The loaded $theme object as returned by list_themes(). @@ -277,23 +292,34 @@ function _theme_registry_callback($callback = NULL, array $arguments = array()) * oldest first order. * @param $theme_engine * The name of the theme engine. + * @param $complete + * Whether to load the complete theme registry or an instance of the + * ThemeRegistry class. + * + * @return + * The theme registry array, or an instance of the ThemeRegistry class. */ -function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL) { - // Check the theme registry cache; if it exists, use it. - $cache = cache_get("theme_registry:$theme->name", 'cache'); - if (isset($cache->data)) { - $registry = $cache->data; +function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) { + if ($complete) { + // Check the theme registry cache; if it exists, use it. + $cached = cache_get("theme_registry:$theme->name"); + if (isset($cached->data)) { + $registry = $cached->data; + } + else { + // If not, build one and cache it. + $registry = _theme_build_registry($theme, $base_theme, $theme_engine); + // Only persist this registry if all modules are loaded. This assures a + // complete set of theme hooks. + if (module_load_all(NULL)) { + _theme_save_registry($theme, $registry); + } + } + return $registry; } else { - // If not, build one and cache it. - $registry = _theme_build_registry($theme, $base_theme, $theme_engine); - // Only persist this registry if all modules are loaded. This assures a - // complete set of theme hooks. - if (module_load_all(NULL)) { - _theme_save_registry($theme, $registry); - } + return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache'); } - return $registry; } /** @@ -313,6 +339,116 @@ function drupal_theme_rebuild() { } /** + * Builds the run-time theme registry. + * + * Extends DrupalCacheArray to allow the theme registry to be accessed as a + * complete registry, while internally caching only the parts of the registry + * that are actually in use on the site. On cache misses the complete + * theme registry is loaded and used to update the run-time cache. + */ +class ThemeRegistry Extends DrupalCacheArray { + + /** + * Whether the partial registry can be persisted to the cache. + * + * This is only allowed if all modules and the request method is GET. theme() + * should be very rarely called on POST requests and this avoids polluting + * the runtime cache. + */ + protected $persistable; + + /** + * The complete theme registry array. + */ + protected $completeRegistry; + + function __construct($cid, $bin) { + $this->cid = $cid; + $this->bin = $bin; + $this->persistable = module_load_all(NULL) && $_SERVER['REQUEST_METHOD'] == 'GET'; + + $data = array(); + if ($this->persistable && $cached = cache_get($this->cid, $this->bin)) { + $data = $cached->data; + } + else { + // If there is no runtime cache stored, fetch the full theme registry, + // but then initialize each value to NULL. This allows offsetExists() + // to function correctly on non-registered theme hooks without triggering + // a call to resolveCacheMiss(). + $data = $this->initializeRegistry(); + if ($this->persistable) { + $this->set($data); + } + } + $this->storage = $data; + } + + /** + * Initializes the full theme registry. + * + * @return + * An array with the keys of the full theme registry, but the values + * initialized to NULL. + */ + function initializeRegistry() { + $this->completeRegistry = theme_get_registry(); + + return array_fill_keys(array_keys($this->completeRegistry), NULL); + } + + public function offsetExists($offset) { + // Since the theme registry allows for theme hooks to be requested that + // are not registered, just check the existence of the key in the registry. + // Use array_key_exists() here since a NULL value indicates that the theme + // hook exists but has not yet been requested. + return array_key_exists($offset, $this->storage); + } + + public function offsetGet($offset) { + // If the offset is set but empty, it is a registered theme hook that has + // not yet been requested. Offsets that do not exist at all were not + // registered in hook_theme(). + if (isset($this->storage[$offset])) { + return $this->storage[$offset]; + } + elseif (array_key_exists($offset, $this->storage)) { + return $this->resolveCacheMiss($offset); + } + } + + public function resolveCacheMiss($offset) { + if (!isset($this->completeRegistry)) { + $this->completeRegistry = theme_get_registry(); + } + $this->storage[$offset] = $this->completeRegistry[$offset]; + if ($this->persistable) { + $this->persist($offset); + } + return $this->storage[$offset]; + } + + public function set($data, $lock = TRUE) { + $lock_name = $this->cid . ':' . $this->bin; + if (!$lock || lock_acquire($lock_name)) { + if ($cached = cache_get($this->cid, $this->bin)) { + // Use array merge instead of union so that filled in values in $data + // overwrite empty values in the current cache. + $data = array_merge($cached->data, $data); + } + else { + $registry = $this->initializeRegistry(); + $data = array_merge($registry, $data); + } + cache_set($this->cid, $data, $this->bin); + if ($lock) { + lock_release($lock_name); + } + } + } +} + +/** * Process a single implementation of hook_theme(). * * @param $cache @@ -657,8 +793,9 @@ function list_themes($refresh = FALSE) { * Generates themed output. * * All requests for themed output must go through this function. It examines - * the request and routes it to the appropriate theme function or template, by - * checking the theme registry. + * the request and routes it to the appropriate + * @link themeable theme function or template @endlink, by checking the theme + * registry. * * The first argument to this function is the name of the theme hook. For * instance, to theme a table, the theme hook name is 'table'. By default, this @@ -758,6 +895,8 @@ function list_themes($refresh = FALSE) { * * @return * An HTML string representing the themed output. + * + * @see themeable */ function theme($hook, $variables = array()) { static $hooks = NULL; @@ -771,7 +910,7 @@ function theme($hook, $variables = array()) { if (!isset($hooks)) { drupal_theme_initialize(); - $hooks = theme_get_registry(); + $hooks = theme_get_registry(FALSE); } // If an array of hook candidates were passed, use the first one that has an @@ -1391,7 +1530,7 @@ function theme_link($variables) { * @param $variables * An associative array containing: * - links: An associative array of links to be themed. The key for each link - * is used as its css class. Each link should be itself an array, with the + * is used as its CSS class. Each link should be itself an array, with the * following elements: * - title: The link text. * - href: The link URL. If omitted, the 'title' is shown as a plain text @@ -1981,6 +2120,8 @@ function theme_username($variables) { /** * Returns HTML for a progress bar. * + * Note that the core Batch API uses this only for non-JavaScript batch jobs. + * * @param $variables * An associative array containing: * - percent: The percentage of the progress. diff --git a/includes/token.inc b/includes/token.inc index edc8a962f..0b05c68f4 100644 --- a/includes/token.inc +++ b/includes/token.inc @@ -77,8 +77,13 @@ * Text with tokens replaced. */ function token_replace($text, array $data = array(), array $options = array()) { + $text_tokens = token_scan($text); + if (empty($text_tokens)) { + return $text; + } + $replacements = array(); - foreach (token_scan($text) as $type => $tokens) { + foreach ($text_tokens as $type => $tokens) { $replacements += token_generate($type, $tokens, $data, $options); if (!empty($options['clear'])) { $replacements += array_fill_keys($tokens, ''); diff --git a/includes/update.inc b/includes/update.inc index 3a131083d..41f33f402 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -746,7 +746,7 @@ function update_fix_d7_requirements() { // Rename 'site_offline_message' variable to 'maintenance_mode_message' // and 'site_offline' variable to 'maintenance_mode'. // Old variable is removed in update for system.module, see - // system_update_7036(). + // system_update_7072(). if ($message = variable_get('site_offline_message', NULL)) { variable_set('maintenance_mode_message', $message); } diff --git a/includes/updater.inc b/includes/updater.inc index 363c6ebff..9801629b1 100644 --- a/includes/updater.inc +++ b/includes/updater.inc @@ -141,7 +141,7 @@ class Updater { return FALSE; } foreach ($info_files as $info_file) { - if (drupal_substr($info_file->filename, 0, -5) == basename($directory)) { + if (drupal_substr($info_file->filename, 0, -5) == drupal_basename($directory)) { // Info file Has the same name as the directory, return it. return $info_file->uri; } @@ -163,7 +163,7 @@ class Updater { * The name of the project. */ public static function getProjectName($directory) { - return basename($directory); + return drupal_basename($directory); } /** diff --git a/includes/utility.inc b/includes/utility.inc index df6c48fb9..d195bff74 100644 --- a/includes/utility.inc +++ b/includes/utility.inc @@ -46,6 +46,13 @@ function drupal_var_export($var, $prefix = '') { $output = "'" . $var . "'"; } } + elseif (is_object($var) && get_class($var) === 'stdClass') { + // var_export() will export stdClass objects using an undefined + // magic method __set_state() leaving the export broken. This + // workaround avoids this by casting the object as an array for + // export and casting it back to an object when evaluated. + $output .= '(object) ' . drupal_var_export((array) $var, $prefix); + } else { $output = var_export($var, TRUE); } diff --git a/includes/xmlrpc.inc b/includes/xmlrpc.inc index 92e5d14f0..b1c6f39c6 100644 --- a/includes/xmlrpc.inc +++ b/includes/xmlrpc.inc @@ -622,4 +622,3 @@ function xmlrpc_error_msg() { function xmlrpc_clear_error() { xmlrpc_error(NULL, NULL, TRUE); } - diff --git a/includes/xmlrpcs.inc b/includes/xmlrpcs.inc index 70c7cdac3..118f652d2 100644 --- a/includes/xmlrpcs.inc +++ b/includes/xmlrpcs.inc @@ -382,4 +382,3 @@ function xmlrpc_server_method_help($method) { $xmlrpc_server = xmlrpc_server_get(); return $xmlrpc_server->help[$method]; } - |