+Drupal 7.13 xxxx-xx-xx (development version)
+----------------------
+
+Drupal 7.12, 2012-02-01
+----------------------
+- Fixed bug preventing custom menus from receiving an active trail.
+- Fixed hook_field_delete() no longer invoked during field_purge_data().
+- Fixed bug causing entity info cache to not be cleared with the rest of caches.
+- Fixed file_unmanaged_copy() fails with Drupal 7.7+ and safe_mode() or
+ open_basedir().
+- Fixed Nested transactions throw exceptions when they got out of scope.
+- Fixed bugs with the Return-Path when sending mail on both Windows and
+ non-Windows systems.
+- Fixed bug with DrupalCacheArray property visibility preventing others from
+ extending it (API change: http://drupal.org/node/1422264).
+- Fixed bug with handling of non-ASCII characters in file names (API change:
+ http://drupal.org/node/1424840).
+- Reconciled field maximum length with database column size in image and
+ aggregator modules.
+- Fixes to various core JavaScript files to allow for minification and
+ aggregation.
+- Fixed Prevent tests from deleting main installation's tables when
+ parent::setUp() is not called.
+- Fixed several Poll module bugs.
+- Fixed several Shortcut module bugs.
+- Added new hook_system_theme_info() to provide ability for contributed modules
+ to test theme functionality.
+- Added ability to cancel mail sending from hook_mail_alter().
+- Added support for configurable PDO connection options, enabling master-master
+ database replication.
+- Numerous improvements to tests and test runner to pave the way for faster test
+ runs.
+- Expanded test coverage.
+- Numerous API documentation improvements.
+- Numerous performance improvements, including token replacement and render
+ cache.
+
Drupal 7.11, 2012-02-01
----------------------
- Fixed security issues (Multiple vulnerabilities), see SA-CORE-2012-001.
mysql -u username -p
Again, you will be asked for the 'username' database password. At the MySQL
-prompt, enter following command:
+prompt, enter the following command:
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
ON databasename.*
-Drupal core is maintained by the community. To participate, go to
-
- http://drupal.org/contribute
-
-The people listed here have agreed to do more quality assurance work for
-particular areas of Drupal. All of them are subject to change.
-
+Drupal core is built and maintained by the Drupal project community. Everyone is
+encouraged to submit issues and changes (patches) to improve Drupal, and to
+contribute in other ways -- see http://drupal.org/contribute to find out how.
Branch maintainers
------------------
-Drupal 7
+The Drupal Core branch maintainers oversee the development of Drupal as a whole.
+The branch maintainers for Drupal 7 are:
+
- Dries Buytaert 'dries' <http://drupal.org/user/1>
- Angela Byron 'webchick' <http://drupal.org/user/24967>
Component maintainers
---------------------
+The Drupal Core component maintainers oversee the development of Drupal
+subsystems. See http://drupal.org/contribute/core-maintainers for more
+information on their responsibilities, and to find out how to become a component
+maintainer. Current component maintainers for Drupal 7:
+
Ajax system
- Alex Bronstein 'effulgentsia' <http://drupal.org/user/78040>
- Randy Fay 'rfay' <http://drupal.org/user/30906>
- Brandon Bowersox 'brandonojc' <http://drupal.org/user/186415>
Documentation
-- Ariane Khachatourians 'arianek' <http://drupal.org/user/158886>
- Jennifer Hodgdon 'jhodgdon' <http://drupal.org/user/155601>
Security
- ?
Taxonomy module
+- Jess Myrbo 'xjm' <http://drupal.org/user/65776>
- Nathaniel Catchpole 'catch' <http://drupal.org/user/35733>
- Benjamin Doherty 'bangpound' <http://drupal.org/user/100456>
download Drupal 6.x and follow the instructions in its UPGRADE.txt. This
document only applies for upgrades from 6.x to 7.x.
-3. Log in as user ID 1 (the site maintenance user).
+3. In addition to updating to the latest available version of Drupal 7.x core,
+ you must also upgrade all of your contributed modules for Drupal to their
+ latest Drupal 6.x versions.
-4. Go to Administer > Site configuration > Site maintenance. Select
+4. Log in as user ID 1 (the site maintenance user).
+
+5. Go to Administer > Site configuration > Site maintenance. Select
"Off-line" and save the configuration.
-5. Go to Administer > Site building > Themes. Enable "Garland" and select it as
+6. Go to Administer > Site building > Themes. Enable "Garland" and select it as
the default theme.
-6. Go to Administer > Site building > Modules. Disable all modules that are not
+7. Go to Administer > Site building > Modules. Disable all modules that are not
listed under "Core - required" or "Core - optional". It is possible that some
modules cannot be disabled, because others depend on them. Repeat this step
until all non-core modules are disabled.
no longer need their data, then you can uninstall them under the Uninstall
tab after disabling them.
-7. On the command line or in your FTP client, remove the file
+8. On the command line or in your FTP client, remove the file
sites/default/default.settings.php
-8. Remove all old core files and directories, except for the 'sites' directory
+9. Remove all old core files and directories, except for the 'sites' directory
and any custom files you added elsewhere.
If you made modifications to files like .htaccess or robots.txt, you will
need to re-apply them from your backup, after the new files are in place.
-9. If you uninstalled any modules, remove them from the sites/all/modules and
+10. If you uninstalled any modules, remove them from the sites/all/modules and
other sites/*/modules directories. Leave other modules in place, even though
they are incompatible with Drupal 7.x.
-10. Download the latest Drupal 7.x release from http://drupal.org to a
+11. Download the latest Drupal 7.x release from http://drupal.org to a
directory outside of your web root. Extract the archive and copy the files
into your Drupal directory.
from http://drupal.org using your web browser, extract it, and then use an
FTP client to upload the files to your web root.
-11. Re-apply any modifications to files such as .htaccess or robots.txt.
+12. Re-apply any modifications to files such as .htaccess or robots.txt.
-12. Make your settings.php file writeable, so that the update process can
+13. Make your settings.php file writeable, so that the update process can
convert it to the format of Drupal 7.x. settings.php is usually located in
sites/default/settings.php
-13. Run update.php by visiting http://www.example.com/update.php (replace
+14. Run update.php by visiting http://www.example.com/update.php (replace
www.example.com with your domain name). This will update the core database
tables.
- Once the upgrade is done, $update_free_access must be reverted to FALSE.
-14. Backup your database after the core upgrade has run.
+15. Backup your database after the core upgrade has run.
-15. Replace and update your non-core modules and themes, following the
+16. Replace and update your non-core modules and themes, following the
procedures at http://drupal.org/node/948216
-16. Go to Administration > Reports > Status report. Verify that everything is
+17. Go to Administration > Reports > Status report. Verify that everything is
working as expected.
-17. Ensure that $update_free_access is FALSE in settings.php.
+18. Ensure that $update_free_access is FALSE in settings.php.
-18. Go to Administration > Configuration > Development > Maintenance mode.
+19. Go to Administration > Configuration > Development > Maintenance mode.
Disable the "Put site into maintenance mode" checkbox and save the
configuration.
* - $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".
+ * @}
*/
/**
* 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.
*
* @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
* 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
* 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
}
/**
- * 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.
*
* 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.
*/
* to Jim'.
* @param $aid
* The ID of this action. If omitted, a new action is created.
+ *
* @return
* The ID of the action.
*/
*
* @param $aid
* The ID of the action to retrieve.
+ *
* @return
* The appropriate action row from the database as an object.
*/
->execute();
module_invoke_all('actions_delete', $aid);
}
-
* 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
* 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.
*
* 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:
*/
/**
- * 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_*()
}
}
- // 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);
$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));
}
}
/**
- * 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.
* #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();
* 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'])) {
}
/**
- * 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:
}
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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
'selector' => $selector,
);
}
-
*/
/**
- * 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();
}
-
*/
/**
- * 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;
}
/**
- * 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.
*
}
/**
- * 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
* 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
}
/**
- * 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
}
/**
- * 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;
}
/**
- * 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.
}
/**
- * 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.
<?php
-
/**
* @file
* Batch processing API for processes to run in multiple HTTP requests.
* @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.
*/
}
/**
- * State-based dispatcher for the batch processing page.
+ * Renders the batch processing page based on the current state of the batch.
*
* @see _batch_shutdown()
*/
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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()
}
/**
- * Output a batch processing page without JavaScript support.
+ * Outputs a batch processing page without JavaScript support.
*
* @see _batch_process()
*/
}
/**
- * 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
}
/**
- * Helper function for _batch_process(): returns the formatted percentage.
+ * Formats the percent completion for a batch set.
*
* @param $total
* The total number of operations.
* 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) {
}
/**
- * Return the batch set being currently processed.
+ * Returns the batch set being currently processed.
*/
function &_batch_current_set() {
$batch = &batch_get();
}
/**
- * 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
}
/**
- * 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.
}
/**
- * 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()) {
->execute();
}
}
-
<?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) {
}
/**
- * 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();
}
/**
- * 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);
}
/**
- * 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();
/**
* The current system version.
*/
-define('VERSION', '7.11');
+define('VERSION', '7.12');
/**
* Core API compatibility.
* 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."
*
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);
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);
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']);
/**
* 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.
protected $storage = array();
/**
- * Constructor.
+ * Constructs a DrupalCacheArray object.
*
* @param $cid
* The cid for the array being cached.
}
}
+ /**
+ * 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];
}
}
+ /**
+ * Implements ArrayAccess::offsetSet().
+ */
public function offsetSet($offset, $value) {
$this->storage[$offset] = $value;
}
+ /**
+ * Implements ArrayAccess::offsetUnset().
+ */
public function offsetUnset($offset) {
unset($this->storage[$offset]);
}
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) {
}
}
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.
}
/**
- * 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.
}
/**
- * Stop the timer with the specified name.
+ * Stops the timer with the specified name.
*
* @param $name
* The name of the timer.
}
/**
- * 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.
}
/**
- * Initialize PHP environment.
+ * Initializes the PHP environment.
*/
function drupal_environment_initialize() {
if (!isset($_SERVER['HTTP_REFERER'])) {
}
/**
- * 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.
}
/**
- * 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;
}
/**
- * 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
// 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();
}
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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).
}
/**
- * 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).
}
/**
- * 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.
}
/**
+ * 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).
*/
}
/**
- * 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.
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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');
* $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
* @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
*
* @see st()
* @see get_t()
+ * @see format_string()
* @ingroup sanitization
*/
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.
}
/**
- * 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.
*
* @param $text
* The text to check.
+ *
* @return
* TRUE if the text is valid UTF-8, FALSE if not.
*/
}
/**
- * Log an exception.
+ * Logs an exception.
*
* This is a wrapper function for watchdog() which automatically decodes an
* exception.
}
/**
- * Log a system message.
+ * Logs a system message.
*
* @param $type
* The category to which this message belongs. Can be any string, but the
}
/**
- * 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.
}
/**
- * 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,
}
/**
- * 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.
}
/**
- * 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
}
/**
- * 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
*
* @param $ip
* IP address to check.
+ *
* @return bool
* TRUE if access is denied, FALSE if access is allowed.
*/
}
/**
- * Handle denied users.
+ * Handles denied users.
*
* @param $ip
* IP address to check. Prints a message and exits if access is denied.
*
* 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.
}
/**
- * 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.
}
/**
- * 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.
* @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);
}
/**
}
/**
- * 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.
}
/**
- * Return the time zone of the current user.
+ * Returns the time zone of the current user.
*/
function drupal_get_user_timezone() {
global $user;
}
/**
- * Custom PHP error handler.
+ * Provides custom PHP error handling.
*
* @param $error_level
* The level of the error raised.
* @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';
}
/**
- * 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
}
/**
- * 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.
}
/**
- * 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;
}
/**
- * 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
}
/**
- * Bootstrap variables: Load system variables and all enabled bootstrap modules.
+ * Loads system variables and all enabled bootstrap modules.
*/
function _drupal_bootstrap_variables() {
global $conf;
}
/**
- * 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');
}
/**
- * 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
}
/**
- * 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;
}
/**
- * 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';
}
/**
- * 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
// 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
}
/**
- * 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
}
/**
- * 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()));
}
/**
- * Default language used on the site
+ * Returns the default language used on the site
*
* @param $property
* Optional property of the language object to return
}
/**
- * 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 '/'
}
/**
+ * 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
*/
/**
- * 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().
*/
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;
}
/**
- * 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().
*/
/**
- * 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.
*/
}
/**
- * 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.
*/
}
/**
- * 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
* @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.
}
/**
- * 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.
}
/**
- * 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
*/
/**
- * 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
}
/**
- * 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.
}
/**
- * 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)));
/**
* 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).
}
/**
- * 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".
}
/**
- * Internal function used to execute registered shutdown functions.
+ * Executes registered shutdown functions.
*/
function _drupal_shutdown_function() {
$callbacks = &drupal_register_shutdown_function();
*/
/**
- * 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,
* 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
}
}
+ /**
+ * Overrides DrupalDatabaseCache::isEmpty().
+ */
function isEmpty() {
return TRUE;
}
<?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
}
/**
- * 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.
*
* @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.
*/
}
/**
- * 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
* 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.
* @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)) {
}
/**
- * 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.
*/
}
/**
- * 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
*/
interface DrupalCacheInterface {
/**
- * Constructor.
+ * Constructs a new cache interface.
*
* @param $bin
* The cache bin for which the object is created.
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.
/**
- * 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
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.
}
/**
- * 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.
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.
}
/**
- * 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.
return $cache;
}
+ /**
+ * Implements DrupalCacheInterface::set().
+ */
function set($cid, $data, $expire = CACHE_PERMANENT) {
$fields = array(
'serialized' => 0,
}
}
+ /**
+ * Implements DrupalCacheInterface::clear().
+ */
function clear($cid = NULL, $wildcard = FALSE) {
global $user;
}
}
+ /**
+ * Implements DrupalCacheInterface::isEmpty().
+ */
function isEmpty() {
$this->garbageCollection();
$query = db_select($this->bin);
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);
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);
*/
/**
- * 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.
*/
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.
}
/**
- * 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
}
/**
- * 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.
/**
- * 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
}
/**
- * Get the breadcrumb trail for the current page.
+ * Gets the breadcrumb trail for the current page.
*/
function drupal_get_breadcrumb() {
$breadcrumb = drupal_set_breadcrumb();
}
/**
- * 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.
}
/**
- * 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();
}
/**
- * 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.
*
}
/**
- * Get the feed URLs for the current page.
+ * Gets the feed URLs for the current page.
*
* @param $delimiter
* A delimiter to split feeds by.
*/
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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.
}
/**
- * 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,
}
/**
- * 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,
}
/**
- * 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.
// 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;
* @} 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');
}
/**
- * 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') {
}
/**
- * 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;
*/
/**
- * 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.
*/
}
/**
- * 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.
* 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.
*/
*/
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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
}
/**
- * 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.
}
/**
- * 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
}
/**
- * 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.
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('%
* 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.
$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 '';
}
}
if (!isset($allowed_html[strtolower($elem)])) {
- // Disallowed HTML element
+ // Disallowed HTML element.
return '';
}
$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 : '';
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');
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);
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]);
}
if ($working == 0) {
- // not well formed, remove and try again
+ // Not well formed; remove and try again.
$attr = preg_replace('/
^
(
}
/**
- * 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.
*/
}
/**
- * Format a single RSS item.
+ * Formats a single RSS item.
*
* Arbitrary elements may be added using the $args associative array.
*/
}
/**
- * Format XML elements.
+ * Formats XML elements.
*
* @param $array
* An array where each item represents an element and is either a:
}
/**
- * 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
* @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;
}
/**
- * 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.
*/
}
/**
- * 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.
*/
}
/**
- * 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,
$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--;
}
/**
* 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.
*/
}
/**
- * 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().
* 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().
*
}
/**
- * 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.
* @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.
*/
}
/**
- * 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'.
}
/**
- * 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
// 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
}
/**
- * 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:
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?
}
/**
- * 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().
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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.
}
/**
- * 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'.
* 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.
}
/**
- * 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
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]]);
}
/**
- * 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
* '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();
*
* @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'));
* 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.
}
/**
- * 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__);
}
/**
- * 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.
*/
}
/**
- * 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.)
* The identifier to clean.
* @param $filter
* An array of string replacements to use on the identifier.
+ *
* @return
* The cleaned identifier.
*/
}
/**
- * 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.
*/
}
/**
- * 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,
* 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
* 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.
*
* @param $data
* (optional) The default data parameter for the JavaScript item array.
+ *
* @see drupal_get_js()
* @see drupal_add_js()
*/
* (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()
* );
* @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
}
/**
- * 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;
* $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(
* @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
*
* 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.
*
* $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.
/**
* 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);
}
/**
}
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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.
* 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.
}
/**
- * 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
/**
* 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.
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):
}
}
- // Record cron time
+ // Record cron time.
variable_set('cron_last', REQUEST_TIME);
watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE);
}
/**
- * 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');
}
}
$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';
}
}
/**
- * 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
*
* @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.
* 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'];
* @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()
* 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
*
* @param $elements
* The structured array describing the data to be rendered.
+ *
* @return
* The rendered HTML.
*/
}
// 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
}
/**
- * 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.
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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.
}
/**
- * 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)) {
}
/**
- * 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.
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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
}
/**
- * 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']
}
/**
- * 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().
}
/**
- * 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().
}
/**
- * 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] != '#';
* 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.
*/
*
* @param $elements
* The parent element.
+ *
* @return
* The array keys of the element's visible children.
*/
}
/**
- * 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
}
/**
- * 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',
'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',
'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),
),
'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',
),
'menu_local_tasks' => array(
'variables' => array('primary' => array(), 'secondary' => array()),
),
- // from form.inc
+ // From form.inc.
'select' => array(
'render element' => 'element',
),
*/
/**
- * 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
}
/**
- * 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
*
* @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.
}
/**
- * 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
}
/**
- * 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.
* 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']);
}
/**
- * 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:
*
* @param $data
* A string to parse.
+ *
* @return
* The info array.
*
)\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);
$value = $constants[$value];
}
- // Insert actual value
+ // Insert actual value.
if ($last == '') {
$last = count($parent);
}
}
/**
- * 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
*/
/**
- * Explode a string of given tags into an array.
+ * Explodes a string of tags into an array.
*
* @see drupal_implode_tags()
*/
}
/**
- * Implode an array of tags into a string.
+ * Implodes an array of tags into a string.
*
* @see drupal_explode_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.
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.
}
/**
- * 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.
}
/**
- * 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().
}
/**
- * 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').
}
/**
- * 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.
}
/**
- * 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.
}
/**
- * 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()
}
/**
- * 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()
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
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.
$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'
$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()) {
// 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;
$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
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()) {
* 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().
*
* @return QueryConditionInterface
* The called object.
+ *
+ * @see QueryConditionInterface::isNull()
+ * @see QueryConditionInterface::isNotNull()
*/
public function condition($field, $value = NULL, $operator = NULL);
}
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) {
/* 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()) {
}
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) {
$this->query->notExists($select);
return $this;
}
-
+
public function __toString() {
return (string) $this->query;
}
}
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) {
$this->where->notExists($select);
return $this;
}
-
+
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
$this->where->compile($connection, $queryPlaceholder);
$this->having->compile($connection, $queryPlaceholder);
$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;
$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])) {
$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;
$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']));
+ }
}
/**
$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));
// 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);
* 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.
// 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?
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.
break;
case FILE_EXISTS_RENAME:
- $basename = basename($destination);
+ $basename = drupal_basename($destination);
$directory = drupal_dirname($destination);
$destination = file_create_filename($basename, $directory);
break;
// 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);
}
/**
- * Undo the effect of upload_munge_filename().
+ * Undo the effect of file_munge_filename().
*
* @param $filename
* String with the filename to be unmunged.
$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];
$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;
// 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);
}
/**
+ * 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
$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;
*/
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) {
$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) . '/';
}
* \@see user_pass_validate().
* \@see user_pass_submit().
*
- * @} End of "defgroup forms".
+ * @}
*/
/**
* 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
'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;
* 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;
}
$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 '';
}
// topological order if the graph is acyclic.
$state['last_visit_order'][] = $start;
}
-
* 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.
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;
}
// 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);
// 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)) {
*
* @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.
// 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));
+ }
}
}
}
*/
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']);
}
* 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
// 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);
}
}
-
/**
* Send the user to a different installer page.
*
}
/**
+ * 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
'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'),
--- /dev/null
+<?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';
+ }
+}
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.
*/
[^\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);
(?:\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();
+ }
}
}
// 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.
// 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;
}
* 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));
* }
* @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
* @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
'from' => isset($from) ? $from : $default_from,
'language' => $language,
'params' => $params,
+ 'send' => TRUE,
'subject' => '',
'body' => array()
);
// 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');
+ }
}
}
*/
/**
+ * 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:
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
* 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.
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();
/**
* 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__);
* @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
// 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');
// 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.
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;
}
/**
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);
}
// 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
$function($data, $context1, $context2);
}
}
-
// Check whether the iteration count used differs from the standard number.
return (_password_get_count_log2($account->pass) !== $count_log2);
}
-
* 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']);
}
/**
/**
* @} End of "defgroup registry".
*/
-
}
$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);
$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) {
/**
* 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];
}
/**
}
/**
- * 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().
* 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;
}
/**
}
/**
+ * 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
* 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
*
* @return
* An HTML string representing the themed output.
+ *
+ * @see themeable
*/
function theme($hook, $variables = array()) {
static $hooks = NULL;
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
* @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
/**
* 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.
* 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, '');
// 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);
}
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;
}
* The name of the project.
*/
public static function getProjectName($directory) {
- return basename($directory);
+ return drupal_basename($directory);
}
/**
$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);
}
function xmlrpc_clear_error() {
xmlrpc_error(NULL, NULL, TRUE);
}
-
$xmlrpc_server = xmlrpc_server_get();
return $xmlrpc_server->help[$method];
}
-
Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
// This function is left empty to make it simple to override for modules
// that wish to add functionality here.
-}
+};
/**
* Prepare the Ajax request before it is sent.
// Removes the float on the select box (used for non-JS interface).
if ($('.connection-settings-update-filetransfer-default-wrapper').length > 0) {
- console.log($('.connection-settings-update-filetransfer-default-wrapper'));
$('.connection-settings-update-filetransfer-default-wrapper').css('float', 'none');
}
// Hides the submit button for non-js users.
Drupal.jsAC = function ($input, db) {
var ac = this;
this.input = $input[0];
- this.ariaLive = $('#' + $input.attr('id') + '-autocomplete-aria-live');
+ this.ariaLive = $('#' + this.input.id + '-autocomplete-aria-live');
this.db = db;
$input
str = str.replace(key, args[key]);
}
return str;
-}
+};
/**
* Translate strings to the page language or a given language.
* An object of replacements pairs to make after translation. Incidences
* of any key in this array are replaced with the corresponding value.
* See Drupal.formatString().
+ *
+ * @param options
+ * - 'context' (defaults to the empty context): The context the source string
+ * belongs to.
+ *
* @return
* The translated string.
*/
-Drupal.t = function (str, args) {
+Drupal.t = function (str, args, options) {
+ options = options || {};
+ options.context = options.context || '';
+
// Fetch the localized version of the string.
- if (Drupal.locale.strings && Drupal.locale.strings[str]) {
- str = Drupal.locale.strings[str];
+ if (Drupal.locale.strings && Drupal.locale.strings[options.context] && Drupal.locale.strings[options.context][str]) {
+ str = Drupal.locale.strings[options.context][str];
}
if (args) {
* See Drupal.formatString().
* Note that you do not need to include @count in this array.
* This replacement is done automatically for the plural case.
+ * @param options
+ * The options to pass to the Drupal.t() function.
* @return
* A translated string.
*/
-Drupal.formatPlural = function (count, singular, plural, args) {
+Drupal.formatPlural = function (count, singular, plural, args, options) {
var args = args || {};
args['@count'] = count;
// Determine the index of the plural form.
var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1);
if (index == 0) {
- return Drupal.t(singular, args);
+ return Drupal.t(singular, args, options);
}
else if (index == 1) {
- return Drupal.t(plural, args);
+ return Drupal.t(plural, args, options);
}
else {
args['@count[' + index + ']'] = args['@count'];
delete args['@count'];
- return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args);
+ return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args, options);
}
};
*
* @see template_preprocess()
* @see template_preprocess_aggregator_feed_source()
+ *
+ * @ingroup themeable
*/
?>
<div class="feed-source">
*
* @see template_preprocess()
* @see template_preprocess_aggregator_item()
+ *
+ * @ingroup themeable
*/
?>
<div class="feed-item">
*
* @see template_preprocess()
* @see template_preprocess_aggregator_summary_item()
+ *
+ * @ingroup themeable
*/
?>
<a href="<?php print $feed_url; ?>"><?php print $feed_title; ?></a>
*
* @see template_preprocess()
* @see template_preprocess_aggregator_summary_items()
+ *
+ * @ingroup themeable
*/
?>
<h3><?php print $title; ?></h3>
*
* @see template_preprocess()
* @see template_preprocess_aggregator_wrapper()
+ *
+ * @ingroup themeable
*/
?>
<div id="aggregator">
}
/**
- * Form builder; Generate a form to add/edit feed sources.
+ * Form constructor for adding and editing feed sources.
+ *
+ * @param $feed
+ * If editing a feed, the feed to edit as a PHP stdClass value; if adding a
+ * new feed, NULL.
*
* @ingroup forms
* @see aggregator_form_feed_validate()
}
/**
- * Validate aggregator_form_feed() form submissions.
+ * Form validation handler for aggregator_form_feed().
+ *
+ * @see aggregator_form_feed_submit()
*/
function aggregator_form_feed_validate($form, &$form_state) {
if ($form_state['values']['op'] == t('Save')) {
}
/**
- * Process aggregator_form_feed() form submissions.
+ * Form submission handler for aggregator_form_feed().
*
+ * @see aggregator_form_feed_validate()
* @todo Add delete confirmation dialog.
*/
function aggregator_form_feed_submit($form, &$form_state) {
}
}
+/**
+ * Deletes a feed.
+ *
+ * @param $feed
+ * An associative array describing the feed to be cleared.
+ *
+ * @see aggregator_admin_remove_feed_submit()
+ */
function aggregator_admin_remove_feed($form, $form_state, $feed) {
return confirm_form(
array(
}
/**
- * Remove all items from a feed and redirect to the overview page.
+ * Form submission handler for aggregator_admin_remove_feed().
*
- * @param $feed
- * An associative array describing the feed to be cleared.
+ * Removes all items from a feed and redirects to the overview page.
*/
function aggregator_admin_remove_feed_submit($form, &$form_state) {
aggregator_remove($form_state['values']['feed']);
}
/**
- * Form builder; Generate a form to import feeds from OPML.
+ * Form constructor for importing feeds from OPML.
*
* @ingroup forms
* @see aggregator_form_opml_validate()
}
/**
- * Validate aggregator_form_opml form submissions.
+ * Form validation handler for aggregator_form_opml().
+ *
+ * @see aggregator_form_opml_submit()
*/
function aggregator_form_opml_validate($form, &$form_state) {
// If both fields are empty or filled, cancel.
}
/**
- * Process aggregator_form_opml form submissions.
+ * Form submission handler for aggregator_form_opml().
+ *
+ * @see aggregator_form_opml_validate()
*/
function aggregator_form_opml_submit($form, &$form_state) {
$data = '';
}
/**
- * Parse an OPML file.
+ * Parses an OPML file.
*
* Feeds are recognized as <outline> elements with the attributes "text" and
* "xmlurl" set.
*
* @return
* An array of feeds, each an associative array with a "title" and a "url"
- * element, or NULL if the OPML document failed to be parsed. An empty
- * array will be returned if the document is valid but contains no feeds, as
- * some OPML documents do.
+ * element, or NULL if the OPML document failed to be parsed. An empty array
+ * will be returned if the document is valid but contains no feeds, as some
+ * OPML documents do.
*/
function _aggregator_parse_opml($opml) {
$feeds = array();
}
/**
- * Form builder; Configure the aggregator system.
+ * Form constructor for the aggregator system settings.
*
+ * @see aggregator_admin_form_submit()
* @ingroup forms
*/
function aggregator_admin_form($form, $form_state) {
return $form;
}
+/**
+ * Form submission handler for aggregator_admin_form().
+ */
function aggregator_admin_form_submit($form, &$form_state) {
if (isset($form_state['values']['aggregator_processors'])) {
$form_state['values']['aggregator_processors'] = array_filter($form_state['values']['aggregator_processors']);
}
/**
- * Form builder; Generate a form to add/edit/delete aggregator categories.
+ * Form constructor to add/edit/delete aggregator categories.
+ *
+ * @param $edit
+ * An associative array containing:
+ * - title: A string to use for the category title.
+ * - description: A string to use for the category description.
+ * - cid: The category ID.
*
* @ingroup forms
* @see aggregator_form_category_validate()
}
/**
- * Validate aggregator_form_feed form submissions.
+ * Form validation handler for aggregator_form_category().
+ *
+ * @see aggregator_form_category_submit()
*/
function aggregator_form_category_validate($form, &$form_state) {
if ($form_state['values']['op'] == t('Save')) {
}
/**
- * Process aggregator_form_category form submissions.
+ * Form submission handler for aggregator_form_category().
*
+ * @see aggregator_form_category_validate()
* @todo Add delete confirmation dialog.
*/
function aggregator_form_category_submit($form, &$form_state) {
*/
/**
- * Implement this hook to create an alternative fetcher for aggregator module.
+ * Create an alternative fetcher for aggregator.module.
*
- * A fetcher downloads feed data to a Drupal site. The fetcher is called
- * at the first of the three aggregation stages: data is downloaded by the
- * active fetcher, it is converted to a common format by the active parser and
- * finally, it is passed to all active processors which manipulate or store the
- * data.
+ * A fetcher downloads feed data to a Drupal site. The fetcher is called at the
+ * first of the three aggregation stages: first, data is downloaded by the
+ * active fetcher; second, it is converted to a common format by the active
+ * parser; and finally, it is passed to all active processors, which manipulate
+ * or store the data.
*
* Modules that define this hook can be set as active fetcher on
* admin/config/services/aggregator. Only one fetcher can be active at a time.
*
* @param $feed
- * The $feed object that describes the resource to be downloaded.
- * $feed->url contains the link to the feed. Download the data at the URL
- * and expose it to other modules by attaching it to $feed->source_string.
+ * A feed object representing the resource to be downloaded. $feed->url
+ * contains the link to the feed. Download the data at the URL and expose it
+ * to other modules by attaching it to $feed->source_string.
*
* @return
* TRUE if fetching was successful, FALSE otherwise.
}
/**
- * Implement this hook to expose the title and a short description of your
- * fetcher.
+ * Specify the title and short description of your fetcher.
*
* The title and the description provided are shown on
* admin/config/services/aggregator among other places. Use as title the human
}
/**
- * Implement this hook to create an alternative parser for aggregator module.
+ * Create an alternative parser for aggregator module.
*
* A parser converts feed item data to a common format. The parser is called
- * at the second of the three aggregation stages: data is downloaded by the
- * active fetcher, it is converted to a common format by the active parser and
- * finally, it is passed to all active processors which manipulate or store the
- * data.
+ * at the second of the three aggregation stages: first, data is downloaded
+ * by the active fetcher; second, it is converted to a common format by the
+ * active parser; and finally, it is passed to all active processors which
+ * manipulate or store the data.
*
- * Modules that define this hook can be set as active parser on
+ * Modules that define this hook can be set as the active parser on
* admin/config/services/aggregator. Only one parser can be active at a time.
*
* @param $feed
- * The $feed object that describes the resource to be parsed.
- * $feed->source_string contains the raw feed data as a string. Parse data
- * from $feed->source_string and expose it to other modules as an array of
- * data items on $feed->items.
- *
- * Feed format:
- * - $feed->description (string) - description of the feed
- * - $feed->image (string) - image for the feed
- * - $feed->etag (string) - value of feed's entity tag header field
- * - $feed->modified (UNIX timestamp) - value of feed's last modified header
- * field
- * - $feed->items (Array) - array of feed items.
- *
- * By convention, the common format for a single feed item is:
- * $item[key-name] = value;
- *
- * Recognized keys:
- * TITLE (string) - the title of a feed item
- * DESCRIPTION (string) - the description (body text) of a feed item
- * TIMESTAMP (UNIX timestamp) - the feed item's published time as UNIX timestamp
- * AUTHOR (string) - the feed item's author
- * GUID (string) - RSS/Atom global unique identifier
- * LINK (string) - the feed item's URL
+ * An object describing the resource to be parsed. $feed->source_string
+ * contains the raw feed data. The hook implementation should parse this data
+ * and add the following properties to the $feed object:
+ * - description: The human-readable description of the feed.
+ * - link: A full URL that directly relates to the feed.
+ * - image: An image URL used to display an image of the feed.
+ * - etag: An entity tag from the HTTP header used for cache validation to
+ * determine if the content has been changed.
+ * - modified: The UNIX timestamp when the feed was last modified.
+ * - items: An array of feed items. The common format for a single feed item
+ * is an associative array containing:
+ * - title: The human-readable title of the feed item.
+ * - description: The full body text of the item or a summary.
+ * - timestamp: The UNIX timestamp when the feed item was last published.
+ * - author: The author of the feed item.
+ * - guid: The global unique identifier (GUID) string that uniquely
+ * identifies the item. If not available, the link is used to identify
+ * the item.
+ * - link: A full URL to the individual feed item.
*
* @return
* TRUE if parsing was successful, FALSE otherwise.
}
/**
- * Implement this hook to expose the title and a short description of your
- * parser.
+ * Specify the title and short description of your parser.
*
* The title and the description provided are shown on
* admin/config/services/aggregator among other places. Use as title the human
}
/**
- * Implement this hook to create a processor for aggregator module.
+ * Create a processor for aggregator.module.
*
* A processor acts on parsed feed data. Active processors are called at the
- * third and last of the aggregation stages: data is downloaded by the active
- * fetcher, it is converted to a common format by the active parser and
- * finally, it is passed to all active processors which manipulate or store the
- * data.
+ * third and last of the aggregation stages: first, data is downloaded by the
+ * active fetcher; second, it is converted to a common format by the active
+ * parser; and finally, it is passed to all active processors that manipulate or
+ * store the data.
*
* Modules that define this hook can be activated as processor on
* admin/config/services/aggregator.
*
* @param $feed
- * The $feed object that describes the resource to be processed. $feed->items
- * contains an array of feed items downloaded and parsed at the parsing
- * stage. See hook_aggregator_parse() for the basic format of a single item
- * in the $feed->items array. For the exact format refer to the particular
- * parser in use.
+ * A feed object representing the resource to be processed. $feed->items
+ * contains an array of feed items downloaded and parsed at the parsing stage.
+ * See hook_aggregator_parse() for the basic format of a single item in the
+ * $feed->items array. For the exact format refer to the particular parser in
+ * use.
*
* @see hook_aggregator_process_info()
* @see hook_aggregator_fetch()
}
/**
- * Implement this hook to expose the title and a short description of your
- * processor.
+ * Specify the title and short description of your processor.
*
* The title and the description provided are shown most importantly on
* admin/config/services/aggregator. Use as title the natural name of the
- * processor and as description a brief (40 to 80 characters) explanation of
- * the functionality.
+ * processor and as description a brief (40 to 80 characters) explanation of the
+ * functionality.
*
- * This hook is only called if your module implements
- * hook_aggregator_process(). If this hook is not implemented aggregator
- * will use your module's file name as title and there will be no description.
+ * This hook is only called if your module implements hook_aggregator_process().
+ * If this hook is not implemented aggregator will use your module's file name
+ * as title and there will be no description.
*
* @return
* An associative array defining a title and a description string.
}
/**
- * Implement this hook to remove stored data if a feed is being deleted or a
- * feed's items are being removed.
+ * Remove stored feed data.
*
* Aggregator calls this hook if either a feed is deleted or a user clicks on
* "remove items".
}
/**
- * Title callback for aggregatory category pages.
+ * Title callback: Returns a title for aggregatory category pages.
+ *
+ * @param $category
+ * An aggregator category.
*
* @return
* An aggregator category title.
}
/**
- * Find out whether there are any aggregator categories.
+ * Determines whether there are any aggregator categories.
*
* @return
- * TRUE if there is at least one category and the user has access to them, FALSE
- * otherwise.
+ * TRUE if there is at least one category and the user has access to them;
+ * FALSE otherwise.
*/
function _aggregator_has_categories() {
return user_access('access news feeds') && (bool) db_query_range('SELECT 1 FROM {aggregator_category}', 0, 1)->fetchField();
}
/**
- * Add/edit/delete aggregator categories.
+ * Adds/edits/deletes aggregator categories.
*
* @param $edit
* An associative array describing the category to be added/edited/deleted.
->condition('cid', $edit['cid'])
->execute();
// Make sure there is no active block for this category.
- db_delete('block')
- ->condition('module', 'aggregator')
- ->condition('delta', 'category-' . $edit['cid'])
- ->execute();
+ if (module_exists('block')) {
+ db_delete('block')
+ ->condition('module', 'aggregator')
+ ->condition('delta', 'category-' . $edit['cid'])
+ ->execute();
+ }
$edit['title'] = '';
$op = 'delete';
}
->condition('fid', $edit['fid'])
->execute();
// Make sure there is no active block for this feed.
- db_delete('block')
- ->condition('module', 'aggregator')
- ->condition('delta', 'feed-' . $edit['fid'])
- ->execute();
+ if (module_exists('block')) {
+ db_delete('block')
+ ->condition('module', 'aggregator')
+ ->condition('delta', 'feed-' . $edit['fid'])
+ ->execute();
+ }
}
elseif (!empty($edit['title'])) {
$edit['fid'] = db_insert('aggregator_feed')
->execute();
}
+/**
+ * Gets the fetcher, parser, and processors.
+ *
+ * @return
+ * An array containing the fetcher, parser, and processors.
+ */
function _aggregator_get_variables() {
// Fetch the feed.
$fetcher = variable_get('aggregator_fetcher', 'aggregator');
}
/**
- * Load an aggregator feed.
+ * Loads an aggregator feed.
*
* @param $fid
* The feed id.
+ *
* @return
* An object describing the feed.
*/
}
/**
- * Load an aggregator category.
+ * Loads an aggregator category.
*
* @param $cid
* The category id.
+ *
* @return
* An associative array describing the category.
*/
}
/**
- * Safely render HTML content, as allowed.
+ * Safely renders HTML content, as allowed.
*
* @param $value
* The content to be filtered.
+ *
* @return
* The filtered content.
*/
}
/**
- * Check and sanitize aggregator configuration.
+ * Checks and sanitizes the aggregator configuration.
*
- * Goes through all fetchers, parsers and processors and checks whether they are
- * available.
- * If one is missing resets to standard configuration.
+ * Goes through all fetchers, parsers and processors and checks whether they
+ * are available. If one is missing resets to standard configuration.
*
* @return
- * TRUE if this function reset the configuration FALSE if not.
+ * TRUE if this function resets the configuration; FALSE if not.
*/
function aggregator_sanitize_configuration() {
$reset = FALSE;
*
* @param $count
* Items count.
+ *
* @return
* Plural-formatted "@count items"
*/
* @param $items
* The items to be listed.
* @param $op
- * Which form should be added to the items. Only 'categorize' is now recognized.
+ * Which form should be added to the items. Only 'categorize' is now
+ * recognized.
* @param $feed_source
* The feed source URL.
+ *
* @return
- * The items HTML.
+ * The rendered list of items for a feed.
*/
function _aggregator_page_list($items, $op, $feed_source = '') {
if (user_access('administer news feeds') && ($op == 'categorize')) {
}
/**
- * Form builder; build the page list form.
+ * Form constructor to build the page list form.
*
* @param $items
* An array of the feed items.
* @param $feed_source
* The feed source URL.
- * @return
- * The form structure.
+ *
* @ingroup forms
* @see aggregator_categorize_items_submit()
*/
}
/**
- * Process aggregator_categorize_items() form submissions.
+ * Form submission handler for aggregator_categorize_items().
*/
function aggregator_categorize_items_submit($form, &$form_state) {
if (!empty($form_state['values']['categories'])) {
}
/**
- * Process variables for aggregator-wrapper.tpl.php.
+ * Processes variables for aggregator-wrapper.tpl.php.
*
* @see aggregator-wrapper.tpl.php
*/
}
/**
- * Process variables for aggregator-item.tpl.php.
+ * Processes variables for aggregator-item.tpl.php.
*
* @see aggregator-item.tpl.php
*/
}
/**
- * Process variables for aggregator-summary-items.tpl.php.
+ * Processes variables for aggregator-summary-items.tpl.php.
*
* @see aggregator-summary-items.tpl.php
*/
}
/**
- * Process variables for aggregator-summary-item.tpl.php.
+ * Processes variables for aggregator-summary-item.tpl.php.
*
* @see aggregator-summary-item.tpl.php
*/
}
/**
- * Process variables for aggregator-feed-source.tpl.php.
+ * Processes variables for aggregator-feed-source.tpl.php.
*
* @see aggregator-feed-source.tpl.php
*/
}
/**
- * Parse a feed and store its items.
+ * Parses a feed and stores its items.
*
* @param $data
* The feed data.
* @param $feed
* An object describing the feed to be parsed.
+ *
* @return
* FALSE on error, TRUE otherwise.
*/
}
/**
- * Callback function used by the XML parser.
+ * Performs an action when an opening tag is encountered.
+ *
+ * Callback function used by xml_parse() within aggregator_parse_feed().
*/
function aggregator_element_start($parser, $name, $attributes) {
global $item, $element, $tag, $items, $channel;
}
/**
- * Call-back function used by the XML parser.
+ * Performs an action when a closing tag is encountered.
+ *
+ * Callback function used by xml_parse() within aggregator_parse_feed().
*/
function aggregator_element_end($parser, $name) {
global $element;
}
/**
- * Callback function used by the XML parser.
+ * Performs an action when data is encountered.
+ *
+ * Callback function used by xml_parse() within aggregator_parse_feed().
*/
function aggregator_element_data($parser, $data) {
global $channel, $element, $items, $item, $image, $tag;
}
/**
- * Parse the W3C date/time format, a subset of ISO 8601.
+ * Parses the W3C date/time format, a subset of ISO 8601.
*
- * PHP date parsing functions do not handle this format.
- * See http://www.w3.org/TR/NOTE-datetime for more information.
- * Originally from MagpieRSS (http://magpierss.sourceforge.net/).
+ * PHP date parsing functions do not handle this format. See
+ * http://www.w3.org/TR/NOTE-datetime for more information. Originally from
+ * MagpieRSS (http://magpierss.sourceforge.net/).
*
* @param $date_str
* A string with a potentially W3C DTF date.
+ *
* @return
* A timestamp if parsed successfully or FALSE if not.
*/
$form['modules']['aggregator']['aggregator_teaser_length'] = array(
'#type' => 'select',
'#title' => t('Length of trimmed description'),
- '#default_value' => 600,
+ '#default_value' => variable_get('aggregator_teaser_length', 600),
'#options' => drupal_map_assoc(array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000), '_aggregator_characters'),
'#description' => t("The maximum number of characters used in the trimmed version of content.")
);
}
/**
- * Helper function for teaser length choices.
+ * Creates display text for teaser length option values.
+ *
+ * Callback for drupal_map_assoc() within
+ * aggregator_form_aggregator_admin_form_alter().
*/
function _aggregator_characters($length) {
return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters');
}
/**
- * Add/edit/delete an aggregator item.
+ * Adds/edits/deletes an aggregator item.
*
* @param $edit
* An associative array describing the item to be added/edited/deleted.
}
/**
- * Expire feed items on $feed that are older than aggregator_clear.
+ * Expires items from a feed depending on expiration settings.
*
* @param $feed
* Object describing feed.
}
}
+/**
+ * Tests aggregator configuration settings.
+ */
+class AggregatorConfigurationTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Aggregator configuration',
+ 'description' => 'Test aggregator settings page.',
+ 'group' => 'Aggregator',
+ );
+ }
+
+ /**
+ * Tests the settings form to ensure the correct default values are used.
+ */
+ function testSettingsPage() {
+ $edit = array(
+ 'aggregator_allowed_html_tags' => '<a>',
+ 'aggregator_summary_items' => 10,
+ 'aggregator_clear' => 3600,
+ 'aggregator_category_selector' => 'select',
+ 'aggregator_teaser_length' => 200,
+