Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • develop
  • issue-424
  • master
  • master-no-logins
  • svn-staging
  • svn-testing
  • svn-trunk
  • topics/add-contribution-info
  • 2010-11-11
  • 2010-12-15
  • 2011-01-11
  • 2011-01-18
  • 2011-01-26
  • 2011-02-10
  • 2011-02-23
  • 2011-03-09
  • 2011-03-15
  • 2011-03-30
  • 2011-04-05
  • 2011-05-03
  • 2011-05-12
  • 2011-06-16
  • 2011-06-21
  • 2011-06-23
  • 2011-06-29
  • 2011-06-30
  • 2011-07-11
  • 2011-07-18
  • 2011-07-20
  • 2011-07-21
  • 2011-07-28
  • 2011-08-03
  • 2011-08-05
  • 2011-08-15
  • 2011-08-17
  • 2011-08-29
  • 2011-08-30
  • 2011-09-19
  • 2011-10-03
  • 2011-10-06
  • 2011-10-27
  • 2011-11-01
  • 2011-11-08
  • 2011-11-08.2
  • 2011-11-14
  • 2011-11-17
  • 2011-12-05
  • 2011-12-16
  • 2012-01-12
  • 2012-01-13
  • 2012-02-07
  • 2012-03-01
  • 2012-04-02
  • 2012-04-03
  • 2012-04-18
  • 20120207
  • 7.1
  • 7.10
  • 7.11
  • 7.12
  • 7.13
  • 7.14
  • 7.15
  • 7.16
  • 7.16.1
  • 7.17
  • 7.18
  • 7.19
  • 7.2
  • 7.3
  • 7.3.1
  • 7.4
  • 7.5
  • 7.5.1
  • 7.6
  • 7.6.1
  • 7.7
  • 7.7.1
  • 7.7.2
  • 7.7.3
  • 7.8
  • 7.9
82 results

Target

Select target project
  • rklusman2/UNL-CMS
  • yzha1/UNL-CMS
  • pear/UNL-CMS
  • bbieber2/UNL-CMS
  • tsteiner2/UNL-CMS
  • erasmussen2/UNL-CMS
  • UNL-Information-Services/UNL-CMS
7 results
Select Git revision
  • develop
  • issue-424
  • issue-525
  • master
  • master-no-logins
  • svn-staging
  • svn-testing
  • svn-trunk
  • topics/add-contribution-info
  • 2010-11-11
  • 2010-12-15
  • 2011-01-11
  • 2011-01-18
  • 2011-01-26
  • 2011-02-10
  • 2011-02-23
  • 2011-03-09
  • 2011-03-15
  • 2011-03-30
  • 2011-04-05
  • 2011-05-03
  • 2011-05-12
  • 2011-06-16
  • 2011-06-21
  • 2011-06-23
  • 2011-06-29
  • 2011-06-30
  • 2011-07-11
  • 2011-07-18
  • 2011-07-20
  • 2011-07-21
  • 2011-07-28
  • 2011-08-03
  • 2011-08-05
  • 2011-08-15
  • 2011-08-17
  • 2011-08-29
  • 2011-08-30
  • 2011-09-19
  • 2011-10-03
  • 2011-10-06
  • 2011-10-27
  • 2011-11-01
  • 2011-11-08
  • 2011-11-08.2
  • 2011-11-14
  • 2011-11-17
  • 2011-12-05
  • 2011-12-16
  • 2012-01-12
  • 2012-01-13
  • 2012-02-07
  • 2012-03-01
  • 2012-04-02
  • 2012-04-03
  • 2012-04-18
  • 20120207
  • 7.1
  • 7.2
  • 7.3
  • 7.3.1
  • 7.4
  • 7.5
  • 7.5.1
  • 7.6
  • 7.6.1
66 results
Show changes
Showing
with 2855 additions and 1094 deletions
<?php
// $Id: install.inc,v 1.142 2010/08/22 15:31:18 dries Exp $
/**
* @file
* API functions for installing modules and themes.
*/
/**
* Indicates that a module has not been installed yet.
......@@ -72,7 +76,7 @@ define('FILE_NOT_WRITABLE', 64);
define('FILE_NOT_EXECUTABLE', 128);
/**
* Initialize the update system by loading all installed module's .install files.
* Loads .install files for installed modules to initialize the update system.
*/
function drupal_load_updates() {
foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
......@@ -175,14 +179,17 @@ function drupal_set_installed_schema_version($module, $version) {
->fields(array('schema_version' => $version))
->condition('name', $module)
->execute();
// Reset the static cache of module schema versions.
drupal_get_installed_schema_version(NULL, TRUE);
}
/**
* Loads the install profile, extracting its defined distribution name.
* Loads the installation profile, extracting its defined distribution name.
*
* @return
* The distribution name defined in the profile's .info file. Defaults to
* "Drupal" if none is explicitly provided by the install profile.
* "Drupal" if none is explicitly provided by the installation profile.
*
* @see install_profile_info()
*/
......@@ -196,17 +203,18 @@ function drupal_install_profile_distribution_name() {
// At all other times, we load the profile via standard methods.
else {
$profile = drupal_get_profile();
$info = install_profile_info($profile);
$info = system_get_info('module', $profile);
return $info['distribution_name'];
}
}
/**
* Auto detect the base_url with PHP predefined variables.
* Detects the base URL using the PHP $_SERVER variables.
*
* @param $file
* The name of the file calling this function so we can strip it out of
* the URI when generating the base_url.
*
* @return
* The auto-detected $base_url that should be configured in settings.php
*/
......@@ -221,12 +229,28 @@ function drupal_detect_baseurl($file = 'install.php') {
}
/**
* Detect all supported databases that are compiled into PHP.
* Detects all supported databases that are compiled into PHP.
*
* @return
* An array of database types compiled into PHP.
*/
function drupal_detect_database_types() {
$databases = drupal_get_database_types();
foreach ($databases as $driver => $installer) {
$databases[$driver] = $installer->name();
}
return $databases;
}
/**
* Returns all supported database installer objects that are compiled into PHP.
*
* @return
* An array of database installer objects compiled into PHP.
*/
function drupal_get_database_types() {
$databases = array();
// We define a driver as a directory in /includes/database that in turn
......@@ -235,7 +259,6 @@ function drupal_detect_database_types() {
// Because we have no registry yet, we need to also include the install.inc
// file for the driver explicitly.
require_once DRUPAL_ROOT . '/includes/database/database.inc';
spl_autoload_register('db_autoload');
foreach (file_scan_directory(DRUPAL_ROOT . '/includes/database', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) {
if (file_exists($file->uri . '/database.inc') && file_exists($file->uri . '/install.inc')) {
$drivers[$file->filename] = $file->uri;
......@@ -243,10 +266,9 @@ function drupal_detect_database_types() {
}
foreach ($drivers as $driver => $file) {
$class = 'DatabaseTasks_' . $driver;
$installer = new $class();
$installer = db_installer_object($driver);
if ($installer->installable()) {
$databases[$driver] = $installer->name();
$databases[$driver] = $installer;
}
}
......@@ -276,9 +298,13 @@ abstract class DatabaseTasks {
* to call (optional) and any arguments to be passed to the function.
*/
protected $tasks = array(
array(
'function' => 'checkEngineVersion',
'arguments' => array(),
),
array(
'arguments' => array(
'CREATE TABLE drupal_install_test (id int NULL)',
'CREATE TABLE {drupal_install_test} (id int NULL)',
'Drupal can use CREATE TABLE database commands.',
'Failed to <strong>CREATE</strong> a test table on your database server with the command %query. The server reports the following message: %error.<p>Are you sure the configured username has the necessary permissions to create tables in the database?</p>',
TRUE,
......@@ -286,33 +312,34 @@ abstract class DatabaseTasks {
),
array(
'arguments' => array(
'INSERT INTO drupal_install_test (id) VALUES (1)',
'INSERT INTO {drupal_install_test} (id) VALUES (1)',
'Drupal can use INSERT database commands.',
'Failed to <strong>INSERT</strong> a value into a test table on your database server. We tried inserting a value with the command %query and the server reported the following error: %error.',
),
),
array(
'arguments' => array(
'UPDATE drupal_install_test SET id = 2',
'UPDATE {drupal_install_test} SET id = 2',
'Drupal can use UPDATE database commands.',
'Failed to <strong>UPDATE</strong> a value in a test table on your database server. We tried updating a value with the command %query and the server reported the following error: %error.',
),
),
array(
'arguments' => array(
'DELETE FROM drupal_install_test',
'DELETE FROM {drupal_install_test}',
'Drupal can use DELETE database commands.',
'Failed to <strong>DELETE</strong> a value from a test table on your database server. We tried deleting a value with the command %query and the server reported the following error: %error.',
),
),
array(
'arguments' => array(
'DROP TABLE drupal_install_test',
'DROP TABLE {drupal_install_test}',
'Drupal can use DROP TABLE database commands.',
'Failed to <strong>DROP</strong> a test table from your database server. We tried dropping a table with the command %query and the server reported the following error %error.',
),
),
);
/**
* Results from tasks.
*
......@@ -348,8 +375,22 @@ abstract class DatabaseTasks {
return $this->hasPdoDriver() && empty($this->error);
}
/**
* Return the human-readable name of the driver.
*/
abstract public function name();
/**
* Return the minimum required version of the engine.
*
* @return
* A version string. If not NULL, it will be checked against the version
* reported by the Database engine using version_compare().
*/
public function minimumVersion() {
return NULL;
}
/**
* Run database tasks and tests to see if Drupal can run on the database.
*/
......@@ -415,15 +456,133 @@ abstract class DatabaseTasks {
return !$fatal;
}
}
/**
* Check the engine version.
*/
protected function checkEngineVersion() {
if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) {
$this->fail(st("The database version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion())));
}
}
/**
* Return driver specific configuration options.
*
* @param $database
* An array of driver specific configuration options.
*
* @return
* The options form array.
*/
public function getFormOptions($database) {
$form['database'] = array(
'#type' => 'textfield',
'#title' => st('Database name'),
'#default_value' => empty($database['database']) ? '' : $database['database'],
'#size' => 45,
'#required' => TRUE,
'#description' => st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_distribution_name())),
);
$form['username'] = array(
'#type' => 'textfield',
'#title' => st('Database username'),
'#default_value' => empty($database['username']) ? '' : $database['username'],
'#required' => TRUE,
'#size' => 45,
);
$form['password'] = array(
'#type' => 'password',
'#title' => st('Database password'),
'#default_value' => empty($database['password']) ? '' : $database['password'],
'#required' => FALSE,
'#size' => 45,
);
$form['advanced_options'] = array(
'#type' => 'fieldset',
'#title' => st('Advanced options'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => st("These options are only necessary for some sites. If you're not sure what you should enter here, leave the default settings or check with your hosting provider."),
'#weight' => 10,
);
$profile = drupal_get_profile();
$db_prefix = ($profile == 'standard') ? 'drupal_' : $profile . '_';
$form['advanced_options']['db_prefix'] = array(
'#type' => 'textfield',
'#title' => st('Table prefix'),
'#default_value' => '',
'#size' => 45,
'#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_distribution_name(), '%prefix' => $db_prefix)),
'#weight' => 10,
);
$form['advanced_options']['host'] = array(
'#type' => 'textfield',
'#title' => st('Database host'),
'#default_value' => empty($database['host']) ? 'localhost' : $database['host'],
'#size' => 45,
// Hostnames can be 255 characters long.
'#maxlength' => 255,
'#required' => TRUE,
'#description' => st('If your database is located on a different server, change this.'),
);
$form['advanced_options']['port'] = array(
'#type' => 'textfield',
'#title' => st('Database port'),
'#default_value' => empty($database['port']) ? '' : $database['port'],
'#size' => 45,
// The maximum port number is 65536, 5 digits.
'#maxlength' => 5,
'#description' => st('If your database server is listening to a non-standard port, enter its number.'),
);
return $form;
}
/**
* Validates driver specific configuration settings.
*
* Checks to ensure correct basic database settings and that a proper
* connection to the database can be established.
*
* @param $database
* An array of driver specific configuration options.
*
* @return
* An array of driver configuration errors, keyed by form element name.
*/
public function validateDatabaseSettings($database) {
$errors = array();
// Verify the table prefix.
if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) {
$errors[$database['driver'] . '][advanced_options][db_prefix'] = st('The database table prefix you have entered, %prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%prefix' => $database['prefix']));
}
// Verify the database port.
if (!empty($database['port']) && !is_numeric($database['port'])) {
$errors[$database['driver'] . '][advanced_options][port'] = st('Database port must be a number.');
}
return $errors;
}
}
/**
* @class Exception class used to throw error if the DatabaseInstaller fails.
* Exception thrown if the database installer fails.
*/
class DatabaseTaskException extends Exception {
}
/**
* Replace values in settings.php with values in the submitted array.
* Replaces values in settings.php with values in the submitted array.
*
* @param $settings
* An array of settings that need to be updated.
......@@ -501,10 +660,11 @@ function drupal_rewrite_settings($settings = array(), $prefix = '') {
}
/**
* Verify an install profile for installation.
* Verifies an installation profile for installation.
*
* @param $install_state
* An array of information about the current installation state.
*
* @return
* The list of modules to install.
*/
......@@ -524,12 +684,12 @@ function drupal_verify_profile($install_state) {
// Get a list of modules that exist in Drupal's assorted subdirectories.
$present_modules = array();
foreach (drupal_system_listing('/\.module$/', 'modules', 'name', 0) as $present_module) {
foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0) as $present_module) {
$present_modules[] = $present_module->name;
}
// The install profile is also a module, which needs to be installed after all the other dependencies
// have been installed.
// The installation profile is also a module, which needs to be installed
// after all the other dependencies have been installed.
$present_modules[] = drupal_get_profile();
// Verify that all of the profile's required modules are present.
......@@ -553,7 +713,7 @@ function drupal_verify_profile($install_state) {
}
/**
* Callback to install the system module.
* Installs the system module.
*
* Separated from the installation of other modules so core system
* functions can be made available while other modules are installed.
......@@ -581,51 +741,60 @@ function drupal_install_system() {
}
/**
* Calls the uninstall function and updates the system table for a given module.
* Uninstalls a given list of modules.
*
* @param $module_list
* The modules to uninstall.
* @param $uninstall_dependents
* If TRUE, the function will check that all modules which depend on the
* passed-in module list either are already uninstalled or contained in the
* list, and it will ensure that the modules are uninstalled in the correct
* order. This incurs a significant performance cost, so use FALSE if you
* know $module_list is already complete and in the correct order.
*
* @return
* FALSE if one or more dependent modules are missing from the list, TRUE
* otherwise.
*/
function drupal_uninstall_modules($module_list = array()) {
foreach ($module_list as $module) {
// First, retrieve all the module's menu paths from db.
drupal_load('module', $module);
$paths = module_invoke($module, 'menu');
function drupal_uninstall_modules($module_list = array(), $uninstall_dependents = TRUE) {
if ($uninstall_dependents) {
// Get all module data so we can find dependents and sort.
$module_data = system_rebuild_module_data();
// Create an associative array with weights as values.
$module_list = array_flip(array_values($module_list));
// Uninstall the module.
module_load_install($module);
module_invoke($module, 'uninstall');
drupal_uninstall_schema($module);
watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
// Now remove the menu links for all paths declared by this module.
if (!empty($paths)) {
$paths = array_keys($paths);
// Clean out the names of load functions.
foreach ($paths as $index => $path) {
$parts = explode('/', $path, MENU_MAX_PARTS);
foreach ($parts as $k => $part) {
if (preg_match('/^%[a-z_]*$/', $part)) {
$parts[$k] = '%';
$profile = drupal_get_profile();
while (list($module) = each($module_list)) {
if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) {
// This module doesn't exist or is already uninstalled, skip it.
unset($module_list[$module]);
continue;
}
$module_list[$module] = $module_data[$module]->sort;
// If the module has any dependents which are not already uninstalled and
// not included in the passed-in list, abort. It is not safe to uninstall
// them automatically because uninstalling a module is a destructive
// operation.
foreach (array_keys($module_data[$module]->required_by) as $dependent) {
if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && $dependent != $profile) {
return FALSE;
}
}
$paths[$index] = implode('/', $parts);
}
$result = db_select('menu_links')
->fields('menu_links')
->condition('router_path', $paths, 'IN')
->condition('external', 0)
->orderBy('depth')
->execute();
// Remove all such items. Starting from those with the greatest depth will
// minimize the amount of re-parenting done by menu_link_delete().
foreach ($result as $item) {
_menu_delete_item($item, TRUE);
}
// Sort the module list by pre-calculated weights.
asort($module_list);
$module_list = array_keys($module_list);
}
foreach ($module_list as $module) {
// Uninstall the module.
module_load_install($module);
module_invoke($module, 'uninstall');
drupal_uninstall_schema($module);
watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
}
......@@ -633,10 +802,12 @@ function drupal_uninstall_modules($module_list = array()) {
// Call hook_module_uninstall to let other modules act
module_invoke_all('modules_uninstalled', $module_list);
}
return TRUE;
}
/**
* Verify the state of the specified file.
* Verifies the state of the specified file.
*
* @param $file
* The file to check for.
......@@ -644,6 +815,7 @@ function drupal_uninstall_modules($module_list = array()) {
* An optional bitmask created from various FILE_* constants.
* @param $type
* The type of file. Can be file (default), dir, or link.
*
* @return
* TRUE on success or FALSE on failure. A message is set for the latter.
*/
......@@ -715,7 +887,7 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
}
/**
* Create a directory with specified permissions.
* Creates a directory with the specified permissions.
*
* @param $file
* The name of the directory to create;
......@@ -723,6 +895,7 @@ function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
* The permissions of the directory to create.
* @param $message
* (optional) Whether to output messages. Defaults to TRUE.
*
* @return
* TRUE/FALSE whether or not the directory was successfully created.
*/
......@@ -754,7 +927,7 @@ function drupal_install_mkdir($file, $mask, $message = TRUE) {
}
/**
* Attempt to fix file permissions.
* Attempts to fix file permissions.
*
* The general approach here is that, because we do not know the security
* setup of the webserver, we apply our permission changes to all three
......@@ -771,6 +944,7 @@ function drupal_install_mkdir($file, $mask, $message = TRUE) {
* The desired permissions for the file.
* @param $message
* (optional) Whether to output messages. Defaults to TRUE.
*
* @return
* TRUE/FALSE whether or not we were able to fix the file's permissions.
*/
......@@ -835,9 +1009,8 @@ function drupal_install_fix_file($file, $mask, $message = TRUE) {
}
}
/**
* Send the user to a different installer page.
* Sends the user to a different installer page.
*
* This issues an on-site HTTP redirect. Messages (and errors) are erased.
*
......@@ -853,12 +1026,79 @@ function install_goto($path) {
}
/**
* Functional equivalent of t(), used when some systems are not available.
* 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);
}
/**
* Translates a string when some systems are not available.
*
* Used during the install process, when database, theme, and localization
* system is possibly not yet available.
*
* Use t() if your code will never run during the Drupal installation phase.
* Use st() if your code will only run during installation and never any other
* time. Use get_t() if your code could run in either circumstance.
*
* @see t()
* @see get_t()
* @ingroup sanitization
*/
function st($string, array $args = array(), array $options = array()) {
......@@ -872,11 +1112,16 @@ function st($string, array $args = array(), array $options = array()) {
if (!isset($locale_strings)) {
$locale_strings = array();
if (isset($install_state['parameters']['profile']) && isset($install_state['parameters']['locale'])) {
$filename = 'profiles/' . $install_state['parameters']['profile'] . '/translations/' . $install_state['parameters']['locale'] . '.po';
if (file_exists(DRUPAL_ROOT . '/' . $filename)) {
// If the given locale was selected, there should be at least one .po file
// with its name ending in {$install_state['parameters']['locale']}.po
// This might or might not be the entire filename. It is also possible
// that multiple files end with the same extension, even if unlikely.
$po_files = file_scan_directory('./profiles/' . $install_state['parameters']['profile'] . '/translations', '/'. $install_state['parameters']['locale'] .'\.po$/', array('recurse' => FALSE));
if (count($po_files)) {
require_once DRUPAL_ROOT . '/includes/locale.inc';
$file = (object) array('uri' => $filename);
_locale_import_read_po('mem-store', $file);
foreach ($po_files as $po_file) {
_locale_import_read_po('mem-store', $po_file);
}
$locale_strings = _locale_import_one_string('mem-report');
}
}
......@@ -903,12 +1148,12 @@ function st($string, array $args = array(), array $options = array()) {
}
/**
* Check an install profile's requirements.
* Checks an installation profile's requirements.
*
* @param $profile
* Name of install profile to check.
* Name of installation profile to check.
* @return
* Array of the install profile's requirements.
* Array of the installation profile's requirements.
*/
function drupal_check_profile($profile) {
include_once DRUPAL_ROOT . '/includes/file.inc';
......@@ -934,11 +1179,12 @@ function drupal_check_profile($profile) {
}
/**
* Extract highest severity from requirements array.
* Extracts the highest severity from the requirements array.
*
* @param $requirements
* An array of requirements, in the same format as is returned by
* hook_requirements().
*
* @return
* The highest severity in the array.
*/
......@@ -953,12 +1199,13 @@ function drupal_requirements_severity(&$requirements) {
}
/**
* Check a module's requirements.
* Checks a module's requirements.
*
* @param $module
* Machine name of module to check.
*
* @return
* TRUE/FALSE depending on the requirements are in place.
* TRUE or FALSE, depending on whether the requirements are met.
*/
function drupal_check_module($module) {
module_load_install($module);
......@@ -983,19 +1230,30 @@ function drupal_check_module($module) {
}
/**
* Retrieve info about an install profile from its .info file.
* Retrieves information about an installation profile from its .info file.
*
* The information stored in a profile .info file is similar to that stored in
* a normal Drupal module .info file. For example:
* - name: The real name of the install profile for display purposes.
* - name: The real name of the installation profile for display purposes.
* - description: A brief description of the profile.
* - dependencies: An array of shortnames of other modules this install profile requires.
* - dependencies: An array of shortnames of other modules that this install
* profile requires.
*
* Additional, less commonly-used information that can appear in a profile.info
* file but not in a normal Drupal module .info file includes:
* - distribution_name: The name of the Drupal distribution that is being
* installed, to be shown throughout the installation process. Defaults to
* 'Drupal'.
* - exclusive: If the install profile is intended to be the only eligible
* choice in a distribution, setting exclusive = TRUE will auto-select it
* during installation, and the install profile selection screen will be
* skipped. If more than one profile is found where exclusive = TRUE then
* this property will have no effect and the profile selection screen will
* be shown as normal with all available profiles shown.
*
* Note that this function does an expensive file system scan to get info file
* information for dependencies. If you only need information from the info
* file itself, use system_get_info().
*
* Example of .info file:
* @code
......@@ -1005,10 +1263,11 @@ function drupal_check_module($module) {
* dependencies[] = dblog
* @endcode
*
* @param profile
* @param $profile
* Name of profile.
* @param locale
* @param $locale
* Name of locale used (if any).
*
* @return
* The info array.
*/
......@@ -1050,8 +1309,18 @@ function install_profile_info($profile, $locale = 'en') {
* encoding.
*/
function db_run_tasks($driver) {
$task_class = 'DatabaseTasks_' . $driver;
$DatabaseTasks = new $task_class();
$DatabaseTasks->runTasks();
db_installer_object($driver)->runTasks();
return TRUE;
}
/**
* Returns a database installer object.
*
* @param $driver
* The name of the driver.
*/
function db_installer_object($driver) {
Database::loadDriverFile($driver, array('install.inc'));
$task_class = 'DatabaseTasks_' . $driver;
return new $task_class();
}
<?php
// $Id: iso.inc,v 1.12 2010/10/09 18:20:43 webchick Exp $
/**
* @file
......@@ -75,6 +74,7 @@ function _country_get_predefined_list() {
'CO' => $t('Colombia'),
'CR' => $t('Costa Rica'),
'CU' => $t('Cuba'),
'CW' => $t('Curaçao'),
'CV' => $t('Cape Verde'),
'CX' => $t('Christmas Island'),
'CY' => $t('Cyprus'),
......@@ -241,7 +241,7 @@ function _country_get_predefined_list() {
'TH' => $t('Thailand'),
'TJ' => $t('Tajikistan'),
'TK' => $t('Tokelau'),
'TL' => $t('East Timor'),
'TL' => $t('Timor-Leste'),
'TM' => $t('Turkmenistan'),
'TN' => $t('Tunisia'),
'TO' => $t('Tonga'),
......@@ -461,7 +461,7 @@ function _locale_get_predefined_list() {
'tt' => array('Tatar', 'Tatarça'),
'tw' => array('Twi'),
'ty' => array('Tahitian'),
'ug' => array('Uighur'),
'ug' => array('Uyghur'),
'uk' => array('Ukrainian', 'Українська'),
'ur' => array('Urdu', /* Left-to-right marker "" */ 'اردو', LANGUAGE_RTL),
'uz' => array('Uzbek', "o'zbek"),
......
<?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';
}
}
<?php
// $Id: language.inc,v 1.30 2010/05/12 08:26:14 dries Exp $
/**
* @file
* Multiple language handling functionality.
* Language Negotiation API.
*
* @see http://drupal.org/node/1497272
*/
/**
......@@ -12,7 +13,96 @@
define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default');
/**
* Return all the defined language types.
* @defgroup language_negotiation Language Negotiation API functionality
* @{
* Functions to customize the language types and the negotiation process.
*
* The language negotiation API is based on two major concepts:
* - Language types: types of translatable data (the types of data that a user
* can view or request).
* - Language negotiation providers: functions for determining which language to
* use to present a particular piece of data to the user.
* Both language types and language negotiation providers are customizable.
*
* Drupal defines three built-in language types:
* - Interface language: The page's main language, used to present translated
* user interface elements such as titles, labels, help text, and messages.
* - Content language: The language used to present content that is available
* in more than one language (see
* @link field_language Field Language API @endlink for details).
* - URL language: The language associated with URLs. When generating a URL,
* this value will be used by url() as a default if no explicit preference is
* provided.
* Modules can define additional language types through
* hook_language_types_info(), and alter existing language type definitions
* through hook_language_types_info_alter().
*
* Language types may be configurable or fixed. The language negotiation
* providers associated with a configurable language type can be explicitly
* set through the user interface. A fixed language type has predetermined
* (module-defined) language negotiation settings and, thus, does not appear in
* the configuration page. Here is a code snippet that makes the content
* language (which by default inherits the interface language's values)
* configurable:
* @code
* function mymodule_language_types_info_alter(&$language_types) {
* unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
* }
* @endcode
*
* Every language type can have a different set of language negotiation
* providers assigned to it. Different language types often share the same
* language negotiation settings, but they can have independent settings if
* needed. If two language types are configured the same way, their language
* switcher configuration will be functionally identical and the same settings
* will act on both language types.
*
* Drupal defines the following built-in language negotiation providers:
* - URL: Determine the language from the URL (path prefix or domain).
* - Session: Determine the language from a request/session parameter.
* - User: Follow the user's language preference.
* - Browser: Determine the language from the browser's language settings.
* - Default language: Use the default site language.
* Language negotiation providers are simple callback functions that implement a
* particular logic to return a language code. For instance, the URL provider
* searches for a valid path prefix or domain name in the current request URL.
* If a language negotiation provider does not return a valid language code, the
* next provider associated to the language type (based on provider weight) is
* invoked.
*
* Modules can define additional language negotiation providers through
* hook_language_negotiation_info(), and alter existing providers through
* hook_language_negotiation_info_alter(). Here is an example snippet that lets
* path prefixes be ignored for administrative paths:
* @code
* function mymodule_language_negotiation_info_alter(&$negotiation_info) {
* // Replace the core function with our own function.
* module_load_include('language', 'inc', 'language.negotiation');
* $negotiation_info[LANGUAGE_NEGOTIATION_URL]['callbacks']['negotiation'] = 'mymodule_from_url';
* $negotiation_info[LANGUAGE_NEGOTIATION_URL]['file'] = drupal_get_path('module', 'mymodule') . '/mymodule.module';
* }
*
* function mymodule_from_url($languages) {
* // Use the core URL language negotiation provider to get a valid language
* // code.
* module_load_include('language', 'inc', 'language.negotiation');
* $langcode = language_from_url($languages);
*
* // If we are on an administrative path, override with the default language.
* if (isset($_GET['q']) && strtok($_GET['q'], '/') == 'admin') {
* return language_default()->langcode;
* }
* return $langcode;
* }
* ?>
* @endcode
*
* For more information, see
* @link http://drupal.org/node/1497272 Language Negotiation API @endlink
*/
/**
* Returns all the defined language types.
*
* @return
* An array of language type names. The name will be used as the global
......@@ -31,11 +121,11 @@ function language_types_info() {
}
/**
* Return only the configurable language types.
* Returns only the configurable language types.
*
* A language type maybe configurable or fixed. A fixed language type is a type
* whose negotiation values are unchangeable and defined while defining the
* language type itself.
* whose language negotiation providers are module-defined and not altered
* through the user interface.
*
* @param $stored
* Optional. By default retrieves values from the 'language_types' variable to
......@@ -69,7 +159,7 @@ function language_types_configurable($stored = TRUE) {
}
/**
* Disable the given language types.
* Disables the given language types.
*
* @param $types
* An array of language types.
......@@ -85,16 +175,55 @@ function language_types_disable($types) {
}
/**
* Check if a language provider is enabled.
* Updates the language type configuration.
*/
function language_types_set() {
// Ensure that we are getting the defined language negotiation information. An
// invocation of module_enable() or module_disable() could outdate the cached
// information.
drupal_static_reset('language_types_info');
drupal_static_reset('language_negotiation_info');
// Determine which language types are configurable and which not by checking
// whether the 'fixed' key is defined. Non-configurable (fixed) language types
// have their language negotiation settings stored there.
$defined_providers = language_negotiation_info();
foreach (language_types_info() as $type => $info) {
if (isset($info['fixed'])) {
$language_types[$type] = FALSE;
$negotiation = array();
foreach ($info['fixed'] as $weight => $id) {
if (isset($defined_providers[$id])) {
$negotiation[$id] = $weight;
}
}
language_negotiation_set($type, $negotiation);
}
else {
$language_types[$type] = TRUE;
}
}
// Save language types.
variable_set('language_types', $language_types);
// Ensure that subsequent calls of language_types_configurable() return the
// updated language type information.
drupal_static_reset('language_types_configurable');
}
/**
* Checks whether a language negotiation provider is enabled for a language type.
*
* This has two possible behaviors:
* - If $provider_id is given return its ID if enabled, FALSE otherwise.
* - If no ID is passed the first enabled language provider is returned.
* - If no ID is passed the first enabled language negotiation provider is
* returned.
*
* @param $type
* The language negotiation type.
* The language negotiation provider type.
* @param $provider_id
* The language provider ID.
* The language negotiation provider ID.
*
* @return
* The provider ID if it is enabled, FALSE otherwise.
......@@ -118,14 +247,13 @@ function language_negotiation_get($type, $provider_id = NULL) {
}
/**
* Check if the given language provider is enabled for any configurable language
* type.
* Checks if the language negotiation provider is enabled for any language type.
*
* @param $provider_id
* The language provider ID.
* The language negotiation provider ID.
*
* @return
* TRUE if there is at least one language type for which the give language
* TRUE if there is at least one language type for which the given language
* provider is enabled, FALSE otherwise.
*/
function language_negotiation_get_any($provider_id) {
......@@ -139,7 +267,7 @@ function language_negotiation_get_any($provider_id) {
}
/**
* Return the language switch links for the given language.
* Returns the language switch links for the given language.
*
* @param $type
* The language negotiation type.
......@@ -153,6 +281,11 @@ function language_negotiation_get_switch_links($type, $path) {
$links = FALSE;
$negotiation = variable_get("language_negotiation_$type", array());
// Only get the languages if we have more than one.
if (count(language_list()) >= 2) {
$language = language_initialize($type);
}
foreach ($negotiation as $id => $provider) {
if (isset($provider['callbacks']['switcher'])) {
if (isset($provider['file'])) {
......@@ -162,6 +295,12 @@ function language_negotiation_get_switch_links($type, $path) {
$callback = $provider['callbacks']['switcher'];
$result = $callback($type, $path);
// Add support for WCAG 2.0's Language of Parts to add language identifiers.
// http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html
foreach ($result as $langcode => $link) {
$result[$langcode]['attributes']['lang'] = $langcode;
}
if (!empty($result)) {
// Allow modules to provide translations for specific links.
drupal_alter('language_switch_links', $result, $type, $path);
......@@ -174,14 +313,37 @@ function language_negotiation_get_switch_links($type, $path) {
return $links;
}
/**
* Removes any unused language negotation providers from the configuration.
*/
function language_negotiation_purge() {
// Ensure that we are getting the defined language negotiation information. An
// invocation of module_enable() or module_disable() could outdate the cached
// information.
drupal_static_reset('language_negotiation_info');
drupal_static_reset('language_types_info');
$defined_providers = language_negotiation_info();
foreach (language_types_info() as $type => $type_info) {
$weight = 0;
$negotiation = array();
foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) {
if (isset($defined_providers[$id])) {
$negotiation[$id] = $weight++;
}
}
language_negotiation_set($type, $negotiation);
}
}
/**
* Save a list of language providers.
* Saves a list of language negotiation providers.
*
* @param $type
* The language negotiation type.
* @param $language_providers
* An array of language provider ids.
* An array of language negotiation provider weights keyed by provider ID.
* @see language_provider_weight()
*/
function language_negotiation_set($type, $language_providers) {
// Save only the necessary fields.
......@@ -190,7 +352,7 @@ function language_negotiation_set($type, $language_providers) {
$negotiation = array();
$providers_weight = array();
$defined_providers = language_negotiation_info();
$default_types = language_types_configurable();
$default_types = language_types_configurable(FALSE);
// Initialize the providers weight list.
foreach ($language_providers as $id => $provider) {
......@@ -206,7 +368,7 @@ function language_negotiation_set($type, $language_providers) {
// If the provider does not express any preference about types, make it
// available for any configurable type.
$types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types);
// Check if the provider is defined and has the right type.
// Check whether the provider is defined and has the right type.
if (isset($types[$type])) {
$provider_data = array();
foreach ($provider_fields as $field) {
......@@ -223,10 +385,10 @@ function language_negotiation_set($type, $language_providers) {
}
/**
* Return all the defined language providers.
* Returns all the defined language negotiation providers.
*
* @return
* An array of language providers.
* An array of language negotiation providers.
*/
function language_negotiation_info() {
$language_providers = &drupal_static(__FUNCTION__);
......@@ -235,7 +397,7 @@ function language_negotiation_info() {
// Collect all the module-defined language negotiation providers.
$language_providers = module_invoke_all('language_negotiation_info');
// Add the default language provider.
// Add the default language negotiation provider.
$language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array(
'callbacks' => array('language' => 'language_from_default'),
'weight' => 10,
......@@ -243,7 +405,7 @@ function language_negotiation_info() {
'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)),
);
// Let other modules alter the list of language providers.
// Let other modules alter the list of language negotiation providers.
drupal_alter('language_negotiation_info', $language_providers);
}
......@@ -251,16 +413,17 @@ function language_negotiation_info() {
}
/**
* Helper function used to cache the language providers results.
* Helper function used to cache the language negotiation providers results.
*
* @param $provider_id
* The language provider ID.
* The language negotiation provider's identifier.
* @param $provider
* The language provider to be invoked. If not passed it will be explicitly
* loaded through language_negotiation_info().
* (optional) An associative array of information about the provider to be
* invoked (see hook_language_negotiation_info() for details). If not passed
* in, it will be loaded through language_negotiation_info().
*
* @return
* The language provider's return value.
* A language object representing the language chosen by the provider.
*/
function language_provider_invoke($provider_id, $provider = NULL) {
$results = &drupal_static(__FUNCTION__);
......@@ -281,22 +444,26 @@ function language_provider_invoke($provider_id, $provider = NULL) {
require_once DRUPAL_ROOT . '/' . $provider['file'];
}
// If the language provider has no cache preference or this is satisfied
// we can execute the callback.
// If the language negotiation provider has no cache preference or this is
// satisfied we can execute the callback.
$cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0);
$callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE;
$langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE;
$results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
}
return $results[$provider_id];
// Since objects are resources, we need to return a clone to prevent the
// language negotiation provider cache from being unintentionally altered. The
// same providers might be used with different language types based on
// configuration.
return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id];
}
/**
* Return the passed language provider weight or a default value.
* Returns the passed language negotiation provider weight or a default value.
*
* @param $provider
* A language provider data structure.
* A language negotiation provider data structure.
*
* @return
* A numeric weight.
......@@ -307,32 +474,35 @@ function language_provider_weight($provider) {
}
/**
* Choose a language for the given type based on language negotiation settings.
* Chooses a language based on language negotiation provider settings.
*
* @param $type
* The language type.
* The language type key to find the language for.
*
* @return
* The negotiated language object.
*/
function language_initialize($type) {
// Execute the language providers in the order they were set up and return the
// Execute the language negotiation providers in the order they were set up and return the
// first valid language found.
$negotiation = variable_get("language_negotiation_$type", array());
foreach ($negotiation as $id => $provider) {
$language = language_provider_invoke($id, $provider);
foreach ($negotiation as $provider_id => $provider) {
$language = language_provider_invoke($provider_id, $provider);
if ($language) {
$language->provider = $provider_id;
return $language;
}
}
// If no other language was found use the default one.
return language_default();
$language = language_default();
$language->provider = LANGUAGE_NEGOTIATION_DEFAULT;
return $language;
}
/**
* Default language provider.
* Returns the default language negotiation provider.
*
* @return
* The default language code.
......@@ -342,10 +512,10 @@ function language_from_default() {
}
/**
* Split the given path into prefix and actual path.
* Splits the given path into prefix and actual path.
*
* Parse the given path and return the language object identified by the
* prefix and the actual path.
* Parse the given path and return the language object identified by the prefix
* and the actual path.
*
* @param $path
* The path to split.
......@@ -374,10 +544,10 @@ function language_url_split_prefix($path, $languages) {
}
/**
* Return the possible fallback languages ordered by language weight.
* Returns the possible fallback languages ordered by language weight.
*
* @param
* The language type.
* (optional) The language type. Defaults to LANGUAGE_TYPE_CONTENT.
*
* @return
* An array of language codes.
......@@ -405,3 +575,7 @@ function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) {
return $fallback_candidates;
}
/**
* @} End of "language_negotiation"
*/
<?php
// $Id: locale.inc,v 1.260 2010/10/06 13:38:39 dries Exp $
/**
* @file
......@@ -22,6 +21,12 @@ define('LOCALE_LANGUAGE_NEGOTIATION_BROWSER', 'locale-browser');
*/
define('LOCALE_LANGUAGE_NEGOTIATION_INTERFACE', 'locale-interface');
/**
* If no URL language is available language is determined using an already
* detected one.
*/
define('LOCALE_LANGUAGE_NEGOTIATION_URL_FALLBACK', 'locale-url-fallback');
/**
* The language is set based on the user language settings.
*/
......@@ -37,6 +42,36 @@ define('LOCALE_LANGUAGE_NEGOTIATION_SESSION', 'locale-session');
*/
define('LOCALE_JS_STRING', '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+');
/**
* Regular expression pattern used to match simple JS object literal.
*
* This pattern matches a basic JS object, but will fail on an object with
* nested objects. Used in JS file parsing for string arg processing.
*/
define('LOCALE_JS_OBJECT', '\{.*?\}');
/**
* Regular expression to match an object containing a key 'context'.
*
* Pattern to match a JS object containing a 'context key' with a string value,
* which is captured. Will fail if there are nested objects.
*/
define('LOCALE_JS_OBJECT_CONTEXT', '
\{ # match object literal start
.*? # match anything, non-greedy
(?: # match a form of "context"
\'context\'
|
"context"
|
context
)
\s*:\s* # match key-value separator ":"
(' . LOCALE_JS_STRING . ') # match context string
.*? # match anything, non-greedy
\} # match end of object literal
');
/**
* Translation import mode overwriting all existing translations
* if new translated version available.
......@@ -64,6 +99,10 @@ define('LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN', 1);
/**
* @defgroup locale-languages-negotiation Language negotiation options
* @{
* Functions for language negotiation.
*
* There are functions that provide the ability to identify the
* language. This behavior can be controlled by various options.
*/
/**
......@@ -84,39 +123,82 @@ function locale_language_from_interface() {
* otherwise we would cache a user-specific preference.
*
* @param $languages
* An array of valid language objects.
* An array of language objects for enabled languages ordered by weight.
*
* @return
* A valid language code on success, FALSE otherwise.
*/
function locale_language_from_browser($languages) {
// Specified by the user via the browser's Accept Language setting
if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
return FALSE;
}
// The Accept-Language header contains information about the language
// preferences configured in the user's browser / operating system.
// RFC 2616 (section 14.4) defines the Accept-Language header as follows:
// Accept-Language = "Accept-Language" ":"
// 1#( language-range [ ";" "q" "=" qvalue ] )
// language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
// Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
$browser_langs = array();
$browser_langcodes = array();
if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
// We can safely use strtolower() here, tags are ASCII.
// RFC2616 mandates that the decimal part is no more than three digits,
// so we multiply the qvalue by 1000 to avoid floating point comparisons.
$langcode = strtolower($match[1]);
$qvalue = isset($match[2]) ? (float) $match[2] : 1;
$browser_langcodes[$langcode] = (int) ($qvalue * 1000);
}
}
// We should take pristine values from the HTTP headers, but Internet Explorer
// from version 7 sends only specific language tags (eg. fr-CA) without the
// corresponding generic tag (fr) unless explicitly configured. In that case,
// we assume that the lowest value of the specific tags is the value of the
// generic language to be as close to the HTTP 1.1 spec as possible.
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and
// http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx
asort($browser_langcodes);
foreach ($browser_langcodes as $langcode => $qvalue) {
$generic_tag = strtok($langcode, '-');
if (!isset($browser_langcodes[$generic_tag])) {
$browser_langcodes[$generic_tag] = $qvalue;
}
}
// Find the enabled language with the greatest qvalue, following the rules
// of RFC 2616 (section 14.4). If several languages have the same qvalue,
// prefer the one with the greatest weight.
$best_match_langcode = FALSE;
$max_qvalue = 0;
foreach ($languages as $langcode => $language) {
// Language tags are case insensitive (RFC2616, sec 3.10).
$langcode = strtolower($langcode);
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
foreach ($browser_accept as $langpart) {
// The language part is either a code or a code with a quality.
// We cannot do anything with a * code, so it is skipped.
// If the quality is missing, it is assumed to be 1 according to the RFC.
if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($langpart), $found)) {
$browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0);
}
// If nothing matches below, the default qvalue is the one of the wildcard
// language, if set, or is 0 (which will never match).
$qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0;
// Find the longest possible prefix of the browser-supplied language
// ('the language-range') that matches this site language ('the language tag').
$prefix = $langcode;
do {
if (isset($browser_langcodes[$prefix])) {
$qvalue = $browser_langcodes[$prefix];
break;
}
}
while ($prefix = substr($prefix, 0, strrpos($prefix, '-')));
// Order the codes by quality
arsort($browser_langs);
// Try to find the first preferred language we have
foreach ($browser_langs as $langcode => $q) {
if (isset($languages[$langcode])) {
return $langcode;
// Find the best match.
if ($qvalue > $max_qvalue) {
$best_match_langcode = $language->language;
$max_qvalue = $qvalue;
}
}
return FALSE;
return $best_match_langcode;
}
/**
......@@ -197,19 +279,78 @@ function locale_language_from_url($languages) {
break;
case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
// Get only the host, not the port.
$http_host= $_SERVER['HTTP_HOST'];
if (strpos($http_host, ':') !== FALSE) {
$http_host_tmp = explode(':', $http_host);
$http_host = current($http_host_tmp);
}
foreach ($languages as $language) {
$host = parse_url($language->domain, PHP_URL_HOST);
if ($host && ($_SERVER['HTTP_HOST'] == $host)) {
// Skip check if the language doesn't have a domain.
if ($language->domain) {
// Only compare the domains not the protocols or ports.
// Remove protocol and add http:// so parse_url works
$host = 'http://' . str_replace(array('http://', 'https://'), '', $language->domain);
$host = parse_url($host, PHP_URL_HOST);
if ($http_host == $host) {
$language_url = $language->language;
break;
}
}
}
break;
}
return $language_url;
}
/**
* Determines the language to be assigned to URLs when none is detected.
*
* The language negotiation process has a fallback chain that ends with the
* default language provider. Each built-in language type has a separate
* initialization:
* - Interface language, which is the only configurable one, always gets a valid
* value. If no request-specific language is detected, the default language
* will be used.
* - Content language merely inherits the interface language by default.
* - URL language is detected from the requested URL and will be used to rewrite
* URLs appearing in the page being rendered. If no language can be detected,
* there are two possibilities:
* - If the default language has no configured path prefix or domain, then the
* default language is used. This guarantees that (missing) URL prefixes are
* preserved when navigating through the site.
* - If the default language has a configured path prefix or domain, a
* requested URL having an empty prefix or domain is an anomaly that must be
* fixed. This is done by introducing a prefix or domain in the rendered
* page matching the detected interface language.
*
* @param $languages
* (optional) An array of valid language objects. This is passed by
* language_provider_invoke() to every language provider callback, but it is
* not actually needed here. Defaults to NULL.
* @param $language_type
* (optional) The language type to fall back to. Defaults to the interface
* language.
*
* @return
* A valid language code.
*/
function locale_language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) {
$default = language_default();
$prefix = (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) == LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX);
// If the default language is not configured to convey language information,
// a missing URL language information indicates that URL language should be
// the default one, otherwise we fall back to an already detected language.
if (($prefix && empty($default->prefix)) || (!$prefix && empty($default->domain))) {
return $default->language;
}
else {
return $GLOBALS[$language_type]->language;
}
}
/**
* Return the URL language switcher block. Translation links may be provided by
* other modules.
......@@ -268,19 +409,54 @@ function locale_language_switcher_session($type, $path) {
* Rewrite URLs for the URL language provider.
*/
function locale_language_url_rewrite_url(&$path, &$options) {
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['languages'] = &drupal_static(__FUNCTION__);
}
$languages = &$drupal_static_fast['languages'];
if (!isset($languages)) {
$languages = language_list('enabled');
$languages = array_flip(array_keys($languages[1]));
}
// Language can be passed as an option, or we go for current URL language.
if (!isset($options['language'])) {
global $language_url;
$options['language'] = $language_url;
}
// We allow only enabled languages here.
elseif (!isset($languages[$options['language']->language])) {
unset($options['language']);
return;
}
if (isset($options['language'])) {
switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) {
case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
if ($options['language']->domain) {
// Ask for an absolute URL with our modified base_url.
global $is_https;
$url_scheme = ($is_https) ? 'https://' : 'http://';
$options['absolute'] = TRUE;
$options['base_url'] = $options['language']->domain;
// Take the domain without ports or protocols so we can apply the
// protocol needed. The setting might include a protocol.
// This is changed in Drupal 8 but we need to keep backwards
// compatibility for Drupal 7.
$host = 'http://' . str_replace(array('http://', 'https://'), '', $options['language']->domain);
$host = parse_url($host, PHP_URL_HOST);
// Apply the appropriate protocol to the URL.
$options['base_url'] = $url_scheme . $host;
if (isset($options['https']) && variable_get('https', FALSE)) {
if ($options['https'] === TRUE) {
$options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
}
elseif ($options['https'] === FALSE) {
$options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
}
}
}
break;
......@@ -351,8 +527,11 @@ function locale_string_is_safe($string) {
}
/**
* @defgroup locale-api-add Language addition API.
* @defgroup locale-api-add Language addition API
* @{
* Add a language.
*
* The language addition API is used to create languages and store them.
*/
/**
......@@ -429,8 +608,12 @@ function locale_add_language($langcode, $name = NULL, $native = NULL, $direction
*/
/**
* @defgroup locale-api-import Translation import API.
* @defgroup locale-api-import-export Translation import/export API.
* @{
* Functions to import and export translations.
*
* These functions provide the ability to import translations from
* external files and to export translations and translation templates.
*/
/**
......@@ -506,159 +689,250 @@ function _locale_import_po($file, $langcode, $mode, $group = NULL) {
*/
function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = 'default') {
$fd = fopen($file->uri, "rb"); // File will get closed by PHP on return
// The file will get closed by PHP on returning from this function.
$fd = fopen($file->uri, 'rb');
if (!$fd) {
_locale_import_message('The translation import failed, because the file %filename could not be read.', $file);
return FALSE;
}
$context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
$current = array(); // Current entry being read
$plural = 0; // Current plural form
$lineno = 0; // Current line
/*
* The parser context. Can be:
* - 'COMMENT' (#)
* - 'MSGID' (msgid)
* - 'MSGID_PLURAL' (msgid_plural)
* - 'MSGCTXT' (msgctxt)
* - 'MSGSTR' (msgstr or msgstr[])
* - 'MSGSTR_ARR' (msgstr_arg)
*/
$context = 'COMMENT';
// Current entry being read.
$current = array();
// Current plurality for 'msgstr[]'.
$plural = 0;
// Current line.
$lineno = 0;
while (!feof($fd)) {
$line = fgets($fd, 10*1024); // A line should not be this long
// A line should not be longer than 10 * 1024.
$line = fgets($fd, 10 * 1024);
if ($lineno == 0) {
// The first line might come with a UTF-8 BOM, which should be removed.
$line = str_replace("\xEF\xBB\xBF", '', $line);
}
$lineno++;
// Trim away the linefeed.
$line = trim(strtr($line, array("\\\n" => "")));
if (!strncmp("#", $line, 1)) { // A comment
if ($context == "COMMENT") { // Already in comment context: add
$current["#"][] = substr($line, 1);
if (!strncmp('#', $line, 1)) {
// Lines starting with '#' are comments.
if ($context == 'COMMENT') {
// Already in comment token, insert the comment.
$current['#'][] = substr($line, 1);
}
elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { // End current entry, start a new one
elseif (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
// We are currently in string token, close it out.
_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
// Start a new entry for the comment.
$current = array();
$current["#"][] = substr($line, 1);
$context = "COMMENT";
$current['#'][] = substr($line, 1);
$context = 'COMMENT';
}
else { // Parse error
else {
// A comment following any other token is a syntax error.
_locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno);
return FALSE;
}
}
elseif (!strncmp("msgid_plural", $line, 12)) {
if ($context != "MSGID") { // Must be plural form for current entry
elseif (!strncmp('msgid_plural', $line, 12)) {
// A plural form for the current message.
if ($context != 'MSGID') {
// A plural form cannot be added to anything else but the id directly.
_locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno);
return FALSE;
}
// Remove 'msgid_plural' and trim away whitespace.
$line = trim(substr($line, 12));
// At this point, $line should now contain only the plural form.
$quoted = _locale_import_parse_quoted($line);
if ($quoted === FALSE) {
// The plural form must be wrapped in quotes.
_locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
return FALSE;
}
$current["msgid"] = $current["msgid"] . "\0" . $quoted;
$context = "MSGID_PLURAL";
// Append the plural form to the current entry.
$current['msgid'] .= "\0" . $quoted;
$context = 'MSGID_PLURAL';
}
elseif (!strncmp("msgid", $line, 5)) {
if ($context == "MSGSTR") { // End current entry, start a new one
elseif (!strncmp('msgid', $line, 5)) {
// Starting a new message.
if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
// We are currently in a message string, close it out.
_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
// Start a new context for the id.
$current = array();
}
elseif ($context == "MSGID") { // Already in this context? Parse error
elseif ($context == 'MSGID') {
// We are currently already in the context, meaning we passed an id with no data.
_locale_import_message('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $file, $lineno);
return FALSE;
}
// Remove 'msgid' and trim away whitespace.
$line = trim(substr($line, 5));
// At this point, $line should now contain only the message id.
$quoted = _locale_import_parse_quoted($line);
if ($quoted === FALSE) {
// The message id must be wrapped in quotes.
_locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
return FALSE;
}
$current["msgid"] = $quoted;
$context = "MSGID";
$current['msgid'] = $quoted;
$context = 'MSGID';
}
elseif (!strncmp("msgctxt", $line, 7)) {
if ($context == "MSGSTR") { // End current entry, start a new one
elseif (!strncmp('msgctxt', $line, 7)) {
// Starting a new context.
if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
// We are currently in a message, start a new one.
_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
$current = array();
}
elseif (!empty($current["msgctxt"])) { // Already in this context? Parse error
elseif (!empty($current['msgctxt'])) {
// A context cannot apply to another context.
_locale_import_message('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $file, $lineno);
return FALSE;
}
// Remove 'msgctxt' and trim away whitespaces.
$line = trim(substr($line, 7));
// At this point, $line should now contain the context.
$quoted = _locale_import_parse_quoted($line);
if ($quoted === FALSE) {
// The context string must be quoted.
_locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
return FALSE;
}
$current["msgctxt"] = $quoted;
$context = "MSGCTXT";
$current['msgctxt'] = $quoted;
$context = 'MSGCTXT';
}
elseif (!strncmp("msgstr[", $line, 7)) {
if (($context != "MSGID") && ($context != "MSGCTXT") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgxtxt, msgid_plural, or msgstr[]
elseif (!strncmp('msgstr[', $line, 7)) {
// A message string for a specific plurality.
if (($context != 'MSGID') && ($context != 'MSGCTXT') && ($context != 'MSGID_PLURAL') && ($context != 'MSGSTR_ARR')) {
// Message strings must come after msgid, msgxtxt, msgid_plural, or other msgstr[] entries.
_locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno);
return FALSE;
}
if (strpos($line, "]") === FALSE) {
// Ensure the plurality is terminated.
if (strpos($line, ']') === FALSE) {
_locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
return FALSE;
}
$frombracket = strstr($line, "[");
$plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
// Extract the plurality.
$frombracket = strstr($line, '[');
$plural = substr($frombracket, 1, strpos($frombracket, ']') - 1);
// Skip to the next whitespace and trim away any further whitespace, bringing $line to the message data.
$line = trim(strstr($line, " "));
$quoted = _locale_import_parse_quoted($line);
if ($quoted === FALSE) {
// The string must be quoted.
_locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
return FALSE;
}
$current["msgstr"][$plural] = $quoted;
$context = "MSGSTR_ARR";
$current['msgstr'][$plural] = $quoted;
$context = 'MSGSTR_ARR';
}
elseif (!strncmp("msgstr", $line, 6)) {
if (($context != "MSGID") && ($context != "MSGCTXT")) { // Should come just after a msgid or msgctxt block
// A string for the an id or context.
if (($context != 'MSGID') && ($context != 'MSGCTXT')) {
// Strings are only valid within an id or context scope.
_locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno);
return FALSE;
}
// Remove 'msgstr' and trim away away whitespaces.
$line = trim(substr($line, 6));
// At this point, $line should now contain the message.
$quoted = _locale_import_parse_quoted($line);
if ($quoted === FALSE) {
// The string must be quoted.
_locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
return FALSE;
}
$current["msgstr"] = $quoted;
$context = "MSGSTR";
$current['msgstr'] = $quoted;
$context = 'MSGSTR';
}
elseif ($line != "") {
elseif ($line != '') {
// Anything that is not a token may be a continuation of a previous token.
$quoted = _locale_import_parse_quoted($line);
if ($quoted === FALSE) {
// The string must be quoted.
_locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
return FALSE;
}
if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
$current["msgid"] .= $quoted;
// Append the string to the current context.
if (($context == 'MSGID') || ($context == 'MSGID_PLURAL')) {
$current['msgid'] .= $quoted;
}
elseif ($context == "MSGCTXT") {
$current["msgctxt"] .= $quoted;
elseif ($context == 'MSGCTXT') {
$current['msgctxt'] .= $quoted;
}
elseif ($context == "MSGSTR") {
$current["msgstr"] .= $quoted;
elseif ($context == 'MSGSTR') {
$current['msgstr'] .= $quoted;
}
elseif ($context == "MSGSTR_ARR") {
$current["msgstr"][$plural] .= $quoted;
elseif ($context == 'MSGSTR_ARR') {
$current['msgstr'][$plural] .= $quoted;
}
else {
// No valid context to append to.
_locale_import_message('The translation file %filename contains an error: there is an unexpected string on line %line.', $file, $lineno);
return FALSE;
}
}
}
// End of PO file, flush last entry
if (!empty($current) && !empty($current['msgstr'])) {
// End of PO file, closed out the last entry.
if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
}
elseif ($context != "COMMENT") {
elseif ($context != 'COMMENT') {
_locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
return FALSE;
}
}
/**
......@@ -729,7 +1003,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
// data untouched or if we don't have an existing plural formula.
$header = _locale_import_parse_header($value['msgstr']);
// Get the plural formula and update in database.
// Get and store the plural formula if available.
if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->uri)) {
list($nplurals, $plural) = $p;
db_update('languages')
......@@ -740,15 +1014,6 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
->condition('language', $lang)
->execute();
}
else {
db_update('languages')
->fields(array(
'plurals' => 0,
'formula' => '',
))
->condition('language', $lang)
->execute();
}
}
$header_done = TRUE;
}
......@@ -1205,7 +1470,7 @@ function _locale_import_parse_quoted($string) {
}
}
/**
* @} End of "locale-api-import"
* @} End of "locale-api-import-export"
*/
/**
......@@ -1224,26 +1489,78 @@ function _locale_parse_js_file($filepath) {
// Match all calls to Drupal.t() in an array.
// Note: \s also matches newlines with the 's' modifier.
preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . LOCALE_JS_STRING . ')\s*[,\)]~s', $file, $t_matches);
preg_match_all('~
[^\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);
// Match all Drupal.formatPlural() calls in another array.
preg_match_all('~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*(' . LOCALE_JS_STRING . ')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s', $file, $plural_matches);
preg_match_all('~
[^\w]Drupal\s*\.\s*formatPlural\s* # match "Drupal.formatPlural" with whitespace
\( # match "(" argument list start
\s*.+?\s*,\s* # match count argument
(' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument
( # capture plural string argument
(?: # non-capturing group to repeat string pieces
(?:
\' # match start of single-quoted string
(?:\\\\\'|[^\'])* # match any character except unescaped single-quote
@count # match "@count"
(?:\\\\\'|[^\'])* # match any character except unescaped single-quote
\' # match end of single-quoted string
|
" # match start of double-quoted string
(?:\\\\"|[^"])* # match any character except unescaped double-quote
@count # match "@count"
(?:\\\\"|[^"])* # match any character except unescaped double-quote
" # match end of double-quoted string
)
(?:\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();
// Add strings from Drupal.t().
foreach ($t_matches[1] as $key => $string) {
$matches[] = array(
'string' => $string,
'context' => $t_matches[2][$key],
);
}
// 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 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];
$matches[] = array(
'string' => $plural_matches[2][$key],
'context' => $plural_matches[3][$key],
);
}
}
foreach ($strings as $key => $string) {
foreach ($matches as $key => $match) {
// Remove the quotes and string concatenations from the string.
$string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($string, 1, -1)));
$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 textgroup = 'default'", array(':source' => $string))->fetchObject();
$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.
......@@ -1268,17 +1585,16 @@ function _locale_parse_js_file($filepath) {
->fields(array(
'location' => $filepath,
'source' => $string,
'context' => '',
'context' => $context,
'textgroup' => 'default',
))
->execute();
}
}
}
}
/**
* @defgroup locale-api-export Translation (template) export API.
* @addtogroup locale-api-import-export
* @{
*/
......@@ -1515,12 +1831,16 @@ function _locale_export_remove_plural($entry) {
return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
}
/**
* @} End of "locale-api-export"
* @} End of "locale-api-import-export"
*/
/**
* @defgroup locale-api-seek String search functions.
* @defgroup locale-api-seek Translation search API
* @{
* Functions to search in translation files.
*
* These functions provide the functionality to search for specific
* translations.
*/
/**
......@@ -1540,7 +1860,16 @@ function _locale_translate_seek() {
}
$sql_query = db_select('locales_source', 's');
$limit_language = NULL;
if ($query['language'] != 'en' && $query['language'] != 'all') {
$sql_query->leftJoin('locales_target', 't', "t.lid = s.lid AND t.language = :langcode", array(':langcode' => $query['language']));
$limit_language = $query['language'];
}
else {
$sql_query->leftJoin('locales_target', 't', 't.lid = s.lid');
}
$sql_query->fields('s', array('source', 'location', 'context', 'lid', 'textgroup'));
$sql_query->fields('t', array('translation', 'language'));
......@@ -1569,12 +1898,6 @@ function _locale_translate_seek() {
break;
}
$limit_language = NULL;
if ($query['language'] != 'en' && $query['language'] != 'all') {
$sql_query->condition('language', $query['language']);
$limit_language = $query['language'];
}
// Add a condition on the text group.
if (!empty($query['group']) && $query['group'] != 'all') {
$sql_query->condition('s.textgroup', $query['group']);
......@@ -1689,11 +2012,11 @@ function _locale_rebuild_js($langcode = NULL) {
// Construct the array for JavaScript translations.
// Only add strings with a translation to the translations array.
$result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup AND t.translation IS NOT NULL", 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.
......@@ -1825,6 +2148,7 @@ function _locale_translate_language_list($translation, $limit_language) {
/**
* @defgroup locale-api-predefined List of predefined languages
* @{
* API to provide a list of predefined languages.
*/
/**
......@@ -1859,6 +2183,10 @@ function _locale_prepare_predefined_list() {
/**
* @defgroup locale-autoimport Automatic interface translation import
* @{
* Functions to create batches for importing translations.
*
* These functions can be used to import translations for installed
* modules.
*/
/**
......@@ -1984,7 +2312,7 @@ function _locale_batch_import($filepath, &$context) {
// The filename is either {langcode}.po or {prefix}.{langcode}.po, so
// we can extract the language code to use for the import from the end.
if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
$file = (object) array('filename' => basename($filepath), 'uri' => $filepath);
$file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath);
_locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
$context['results'][] = $filepath;
}
......
<?php
// $Id: lock.inc,v 1.5 2010/07/16 11:19:38 dries Exp $
/**
* @file
......@@ -7,8 +6,10 @@
*/
/**
* @defgroup lock Functions to coordinate long-running operations across requests.
* @defgroup lock Locking mechanisms
* @{
* Functions to coordinate long-running operations across requests.
*
* In most environments, multiple Drupal page requests (a.k.a. threads or
* processes) will execute in parallel. This leads to potential conflicts or
* race conditions when two requests execute the same code at the same time. A
......@@ -73,7 +74,10 @@ function lock_initialize() {
* Helper function to get this request's unique id.
*/
function _lock_id() {
$lock_id = &drupal_static(__FUNCTION__);
// Do not use drupal_static(). This identifier refers to the current
// client request, and must not be changed under any circumstances
// else the shutdown handler may fail to release our locks.
static $lock_id;
if (!isset($lock_id)) {
// Assign a unique id.
......@@ -186,7 +190,7 @@ function lock_may_be_available($name) {
* lock. This will block further execution until the lock is available or the
* specified delay in seconds is reached. This should not be used with locks
* that are acquired very frequently, since the lock is likely to be acquired
* again by a different request during the sleep().
* again by a different request while waiting.
*
* @param $name
* The name of the lock.
......@@ -197,12 +201,32 @@ function lock_may_be_available($name) {
* TRUE if the lock holds, FALSE if it is available.
*/
function lock_wait($name, $delay = 30) {
$delay = (int) $delay;
while ($delay--) {
// Pause the process for short periods between calling
// lock_may_be_available(). This prevents hitting the database with constant
// database queries while waiting, which could lead to performance issues.
// However, if the wait period is too long, there is the potential for a
// large number of processes to be blocked waiting for a lock, especially
// if the item being rebuilt is commonly requested. To address both of these
// concerns, begin waiting for 25ms, then add 25ms to the wait period each
// time until it reaches 500ms. After this point polling will continue every
// 500ms until $delay is reached.
// $delay is passed in seconds, but we will be using usleep(), which takes
// microseconds as a parameter. Multiply it by 1 million so that all
// further numbers are equivalent.
$delay = (int) $delay * 1000000;
// Begin sleeping at 25ms.
$sleep = 25000;
while ($delay > 0) {
// This function should only be called by a request that failed to get a
// lock, so we sleep first to give the parallel request a chance to finish
// and release the lock.
sleep(1);
usleep($sleep);
// After each sleep, increase the value of $sleep until it reaches
// 500ms, to reduce the potential for a lock stampede.
$delay = $delay - $sleep;
$sleep = min(500000, $sleep + 25000, $delay);
if (lock_may_be_available($name)) {
// No longer need to wait.
return FALSE;
......
<?php
// $Id: mail.inc,v 1.34 2010/07/28 02:19:56 dries Exp $
/**
* @file
......@@ -14,7 +13,7 @@
define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE ? "\r\n" : "\n");
/**
* Compose and optionally send an e-mail message.
* Composes and optionally sends an e-mail message.
*
* Sending an e-mail works with defining an e-mail template (subject, text
* and possibly e-mail headers) and the replacement values to use in the
......@@ -58,6 +57,12 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
* user_mail_tokens($variables, $data, $options);
* switch($key) {
* case 'notice':
* // If the recipient can receive such notices by instant-message, do
* // not send by email.
* if (example_im_send($key, $message, $params)) {
* $message['send'] = FALSE;
* break;
* }
* $langcode = $message['language']->language;
* $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode));
* $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode));
......@@ -66,6 +71,19 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
* }
* @endcode
*
* Another example, which uses drupal_mail() to format a message for sending
* later:
*
* @code
* $params = array('current_conditions' => $data);
* $to = 'user@example.com';
* $message = drupal_mail('example', 'notice', $to, $language, $params, FALSE);
* // Only add to the spool if sending was not canceled.
* if ($message['send']) {
* example_spool_message($message);
* }
* @endcode
*
* @param $module
* A module name to invoke hook_mail() on. The {$module}_mail() hook will be
* called to complete the $message structure which will already contain common
......@@ -75,7 +93,9 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
* will be {$module}_{$key}.
* @param $to
* The e-mail address or addresses where the message will be sent to. The
* formatting of this string must comply with RFC 2822. Some examples are:
* formatting of this string will be validated with the
* @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
* Some examples are:
* - user@example.com
* - user@example.com, anotheruser@example.com
* - User <user@example.com>
......@@ -87,8 +107,10 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
* @param $from
* Sets From to this value, if given.
* @param $send
* Send the message directly, without calling drupal_mail_system()->mail()
* manually.
* If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver
* the message, and store the result in $message['result']. Modules
* implementing hook_mail_alter() may cancel sending by setting
* $message['send'] to FALSE.
*
* @return
* The $message array structure containing all details of the
......@@ -109,6 +131,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
'from' => isset($from) ? $from : $default_from,
'language' => $language,
'params' => $params,
'send' => TRUE,
'subject' => '',
'body' => array()
);
......@@ -123,8 +146,8 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
if ($default_from) {
// To prevent e-mail from looking like spam, the addresses in the Sender and
// Return-Path headers should have a domain authorized to use the originating
// SMTP server. Errors-To is redundant, but shouldn't hurt.
$headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $default_from;
// SMTP server.
$headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $default_from;
}
if ($from) {
$headers['From'] = $from;
......@@ -149,20 +172,28 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
// Optionally send e-mail.
if ($send) {
// 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
// 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');
}
}
}
return $message;
}
/**
* Returns an object that implements the MailSystemInterface.
* Returns an object that implements the MailSystemInterface interface.
*
* Allows for one or more custom mail backends to format and send mail messages
* composed using drupal_mail().
......@@ -183,9 +214,9 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
* 'mail_system', which is a keyed array. The default implementation
* is the class whose name is the value of 'default-system' key. A more specific
* match first to key and then to module will be used in preference to the
* default. To specificy a different class for all mail sent by one module, set
* default. To specify a different class for all mail sent by one module, set
* the class name as the value for the key corresponding to the module name. To
* specificy a class for a particular message sent by one module, set the class
* specify a class for a particular message sent by one module, set the class
* name as the value for the array key that is the message id, which is
* "${module}_${key}".
*
......@@ -278,7 +309,9 @@ interface MailSystemInterface {
* - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy',
* 'user_password_reset'.
* - to: The mail address or addresses where the message will be sent to.
* The formatting of this string must comply with RFC 2822. Some examples:
* The formatting of this string will be validated with the
* @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink
* Some examples are:
* - user@example.com
* - user@example.com, anotheruser@example.com
* - User <user@example.com>
......@@ -289,8 +322,8 @@ interface MailSystemInterface {
* E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
* smart plain text wrapping.
* - headers: Associative array containing all additional mail headers not
* defined by one of the other parameters. PHP's mail() looks for Cc
* and Bcc headers and sends the mail to addresses in these headers too.
* defined by one of the other parameters. PHP's mail() looks for Cc and
* Bcc headers and sends the mail to addresses in these headers too.
*
* @return
* TRUE if the mail was successfully accepted for delivery, otherwise FALSE.
......@@ -299,7 +332,7 @@ interface MailSystemInterface {
}
/**
* Perform format=flowed soft wrapping for mail (RFC 3676).
* Performs format=flowed soft wrapping for mail (RFC 3676).
*
* We use delsp=yes wrapping, but only break non-spaced languages when
* absolutely necessary to avoid compatibility issues.
......@@ -311,6 +344,9 @@ interface MailSystemInterface {
* @param $indent (optional)
* A string to indent the text with. Only '>' characters are repeated on
* subsequent wrapped lines. Others are replaced by spaces.
*
* @return
* The content of the email as a string with formatting applied.
*/
function drupal_wrap_mail($text, $indent = '') {
// Convert CRLF into LF.
......@@ -342,8 +378,7 @@ function drupal_wrap_mail($text, $indent = '') {
}
/**
* Transform an HTML string into plain text, preserving the structure of the
* markup. Useful for preparing the body of a node to be sent by e-mail.
* Transforms an HTML string into plain text, preserving its structure.
*
* The output will be suitable for use as 'format=flowed; delsp=yes' text
* (RFC 3676) and can be passed directly to drupal_mail() for sending.
......@@ -431,7 +466,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
$indent[] = count($lists) ? ' "' : '>';
break;
case 'li':
$indent[] = is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * ';
$indent[] = isset($lists[0]) && is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * ';
break;
case 'dd':
$indent[] = ' ';
......@@ -510,7 +545,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
$chunk = $casing($chunk);
}
// Format it and apply the current indentation.
$output .= drupal_wrap_mail($chunk, implode('', $indent));
$output .= drupal_wrap_mail($chunk, implode('', $indent)) . MAIL_LINE_ENDINGS;
// Remove non-quotation markers from indentation.
$indent = array_map('_drupal_html_to_text_clean', $indent);
}
......@@ -522,9 +557,9 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
}
/**
* Helper function for array_walk in drupal_wrap_mail().
*
* Wraps words on a single line.
*
* Callback for array_walk() winthin drupal_wrap_mail().
*/
function _drupal_wrap_mail_line(&$line, $key, $values) {
// Use soft-breaks only for purely quoted or unindented text.
......@@ -534,9 +569,9 @@ function _drupal_wrap_mail_line(&$line, $key, $values) {
}
/**
* Helper function for drupal_html_to_text().
*
* Keeps track of URLs and replaces them with placeholder tokens.
*
* Callback for preg_replace_callback() within drupal_html_to_text().
*/
function _drupal_html_to_mail_urls($match = NULL, $reset = FALSE) {
global $base_url, $base_path;
......@@ -561,18 +596,18 @@ function _drupal_html_to_mail_urls($match = NULL, $reset = FALSE) {
}
/**
* Helper function for drupal_wrap_mail() and drupal_html_to_text().
* Replaces non-quotation markers from a given piece of indentation with spaces.
*
* Replace all non-quotation markers from a given piece of indentation with spaces.
* Callback for array_map() within drupal_html_to_text().
*/
function _drupal_html_to_text_clean($indent) {
return preg_replace('/[^>]/', ' ', $indent);
}
/**
* Helper function for drupal_html_to_text().
* Pads the last line with the given character.
*
* Pad the last line with the given character.
* @see drupal_html_to_text()
*/
function _drupal_html_to_text_pad($text, $pad, $prefix = '') {
// Remove last line break.
......
<?php
// $Id: menu.inc,v 1.416 2010/10/21 11:56:17 dries Exp $
/**
* @file
......@@ -14,7 +13,10 @@
* The Drupal menu system drives both the navigation system from a user
* perspective and the callback system that Drupal uses to respond to URLs
* passed from the browser. For this reason, a good understanding of the
* menu system is fundamental to the creation of complex modules.
* menu system is fundamental to the creation of complex modules. As a note,
* this is related to, but separate from menu.module, which allows menus
* (which in this context are hierarchical lists of links) to be customized from
* the Drupal administrative interface.
*
* Drupal's menu system follows a simple hierarchy defined by paths.
* Implementations of hook_menu() define menu items and assign them to
......@@ -49,8 +51,9 @@
* Access to the callback functions is also protected by the menu system.
* The "access callback" with an optional "access arguments" of each menu
* item is called before the page callback proceeds. If this returns TRUE,
* then access is granted; if FALSE, then access is denied. Menu items may
* omit this attribute to use the value provided by an ancestor item.
* then access is granted; if FALSE, then access is denied. Default local task
* menu items (see next paragraph) may omit this attribute to use the value
* provided by the parent item.
*
* In the default Drupal interface, you will notice many links rendered as
* tabs. These are known in the menu system as "local tasks", and they are
......@@ -124,8 +127,10 @@ define('MENU_IS_LOCAL_ACTION', 0x0100);
/**
* @defgroup menu_item_types Menu item types
* @{
* Definitions for various menu item types.
*
* Menu item definitions provide one of these constants, which are shortcuts for
* combinations of the above flags.
* combinations of @link menu_flags Menu flags @endlink.
*/
/**
......@@ -250,7 +255,7 @@ define('MENU_SITE_ONLINE', 5);
/**
* @defgroup menu_tree_parameters Menu tree parameters
* @{
* Menu tree
* Parameters for a menu tree.
*/
/**
......@@ -269,6 +274,20 @@ define('MENU_MAX_DEPTH', 9);
* @} End of "Menu tree parameters".
*/
/**
* 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.
*
......@@ -302,7 +321,14 @@ function menu_get_ancestors($parts) {
$ancestors = array();
$length = $number_parts - 1;
$end = (1 << $number_parts) - 1;
$masks = variable_get('menu_masks', array());
$masks = variable_get('menu_masks');
// If the optimized menu_masks array is not available use brute force to get
// the correct $ancestors and $placeholders returned. Do not use this as the
// default value of the menu_masks variable to avoid building such a big
// array.
if (!$masks) {
$masks = range(511, 1);
}
// Only examine patterns that actually exist as router items (the masks).
foreach ($masks as $i) {
if ($i > $end) {
......@@ -335,25 +361,39 @@ function menu_get_ancestors($parts) {
}
/**
* The menu system uses serialized arrays stored in the database for
* arguments. However, often these need to change according to the
* current path. This function unserializes such an array and does the
* necessary change.
* Unserializes menu data, using a map to replace path elements.
*
* The menu system stores various path-related information (such as the 'page
* arguments' and 'access arguments' components of a menu item) in the database
* using serialized arrays, where integer values in the arrays represent
* arguments to be replaced by values from the path. This function first
* unserializes such menu information arrays, and then does the path
* replacement.
*
* Integer values are mapped according to the $map parameter. For
* example, if unserialize($data) is array('view', 1) and $map is
* array('node', '12345') then 'view' will not be changed because
* it is not an integer, but 1 will as it is an integer. As $map[1]
* is '12345', 1 will be replaced with '12345'. So the result will
* be array('node_load', '12345').
* The path replacement acts on each integer-valued element of the unserialized
* menu data array ($data) using a map array ($map, which is typically an array
* of path arguments) as a list of replacements. For instance, if there is an
* element of $data whose value is the number 2, then it is replaced in $data
* with $map[2]; non-integer values in $data are left alone.
*
* @param @data
* A serialized array.
* @param @map
* An array of potential replacements.
* As an example, an unserialized $data array with elements ('node_load', 1)
* represents instructions for calling the node_load() function. Specifically,
* this instruction says to use the path component at index 1 as the input
* parameter to node_load(). If the path is 'node/123', then $map will be the
* array ('node', 123), and the returned array from this function will have
* elements ('node_load', 123), since $map[1] is 123. This return value will
* indicate specifically that node_load(123) is to be called to load the node
* whose ID is 123 for this menu item.
*
* @param $data
* A serialized array of menu data, as read from the database.
* @param $map
* A path argument array, used to replace integer values in $data; an integer
* value N in $data will be replaced by value $map[N]. Typically, the $map
* array is generated from a call to the arg() function.
*
* @return
* The $data array unserialized and mapped.
* The unserialized $data array, with path arguments replaced.
*/
function menu_unserialize($data, $map) {
if ($data = unserialize($data)) {
......@@ -377,9 +417,9 @@ function menu_unserialize($data, $map) {
* @param $path
* The path.
* @param $router_item
* The router item. Usually you take a router entry from menu_get_item and
* set it back either modified or to a different path. This lets you modify the
* navigation block, the page title, the breadcrumb and the page help in one
* The router item. Usually a router entry from menu_get_item() is either
* modified or set to a different path. This allows the navigation block,
* the page title, the breadcrumb, and the page help to be modified in one
* call.
*/
function menu_set_item($path, $router_item) {
......@@ -387,7 +427,7 @@ function menu_set_item($path, $router_item) {
}
/**
* Get a router item.
* Gets a router item.
*
* @param $path
* The path, for example node/5. The function will find the corresponding
......@@ -396,12 +436,13 @@ function menu_set_item($path, $router_item) {
* Internal use only.
*
* @return
* The router item, an associate array corresponding to one row in the
* menu_router table. The value of key map holds the loaded objects. The
* value of key access is TRUE if the current user can access this page.
* The values for key title, page_arguments, access_arguments, and
* theme_arguments will be filled in based on the database values and the
* objects loaded.
* The router item or, if an error occurs in _menu_translate(), FALSE. A
* router item is an associative array corresponding to one row in the
* menu_router table. The value corresponding to the key 'map' holds the
* loaded objects. The value corresponding to the key 'access' is TRUE if the
* current user can access this page. The values corresponding to the keys
* 'title', 'page_arguments', 'access_arguments', and 'theme_arguments' will
* be filled in based on the database values and the objects loaded.
*/
function menu_get_item($path = NULL, $router_item = NULL) {
$router_items = &drupal_static(__FUNCTION__);
......@@ -412,21 +453,22 @@ function menu_get_item($path = NULL, $router_item = NULL) {
$router_items[$path] = $router_item;
}
if (!isset($router_items[$path])) {
// Rebuild if we know it's needed, or if the menu masks are missing which
// occurs rarely, likely due to a race condition of multiple rebuilds.
if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
menu_rebuild();
}
$original_map = arg(NULL, $path);
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);
$ancestors = menu_get_ancestors($parts);
// Since there is no limit to the length of $path, use a hash to keep it
// short yet unique.
$cid = 'menu_item:' . hash('sha256', $path);
if ($cached = cache_get($cid, 'cache_menu')) {
$router_item = $cached->data;
}
else {
$router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
cache_set($cid, $router_item, 'cache_menu');
}
if ($router_item) {
// Allow modules to alter the router item before it is translated and
// checked for access.
drupal_alter('menu_get_item', $router_item, $path, $original_map);
$map = _menu_translate($router_item, $original_map);
$router_item['original_map'] = $original_map;
if ($map === FALSE) {
......@@ -467,11 +509,6 @@ function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
// Only continue if the site status is not set.
if ($page_callback_result == MENU_SITE_ONLINE) {
// Rebuild if we know it's needed, or if the menu masks are missing which
// occurs rarely, likely due to a race condition of multiple rebuilds.
if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
menu_rebuild();
}
if ($router_item = menu_get_item($path)) {
if ($router_item['access']) {
if ($router_item['include_file']) {
......@@ -517,8 +554,8 @@ function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
function _menu_load_objects(&$item, &$map) {
if ($load_functions = $item['load_functions']) {
// If someone calls this function twice, then unserialize will fail.
if ($load_functions_unserialized = unserialize($load_functions)) {
$load_functions = $load_functions_unserialized;
if (!is_array($load_functions)) {
$load_functions = unserialize($load_functions);
}
$path_map = $map;
foreach ($load_functions as $index => $function) {
......@@ -570,7 +607,7 @@ function _menu_load_objects(&$item, &$map) {
}
/**
* Check access to a menu item using the access callback
* Checks access to a menu item using the access callback.
*
* @param $item
* A menu router or menu link item
......@@ -581,6 +618,7 @@ function _menu_load_objects(&$item, &$map) {
* $item['access'] becomes TRUE if the item is accessible, FALSE otherwise.
*/
function _menu_check_access(&$item, $map) {
$item['access'] = FALSE;
// Determine access callback, which will decide whether or not the current
// user has access to this path.
$callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']);
......@@ -602,7 +640,7 @@ function _menu_check_access(&$item, $map) {
}
/**
* Localize the router item title using t() or another callback.
* Localizes the router item title using t() or another callback.
*
* Translate the title and description to allow storage of English title
* strings in the database, yet display of them in the language required
......@@ -710,7 +748,7 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
* $item['load_functions']. $item['access'] becomes TRUE if the item is
* accessible, FALSE otherwise. $item['href'] is set according to the map.
* If an error occurs during calling the load_functions (like trying to load
* a non existing node) then this function return FALSE.
* a non-existent node) then this function returns FALSE.
*/
function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
if ($to_arg && !empty($router_item['to_arg_functions'])) {
......@@ -760,14 +798,14 @@ function _menu_translate(&$router_item, $map, $to_arg = FALSE) {
}
/**
* This function translates the path elements in the map using any to_arg
* helper function. These functions take an argument and return an object.
* See http://drupal.org/node/109153 for more information.
* Translates the path elements in the map using any to_arg helper function.
*
* @param $map
* An array of path arguments (ex: array('node', '5'))
* @param $to_arg_functions
* An array of helper function (ex: array(2 => 'menu_tail_to_arg'))
*
* @see hook_menu()
*/
function _menu_link_map_translate(&$map, $to_arg_functions) {
$to_arg_functions = unserialize($to_arg_functions);
......@@ -783,13 +821,35 @@ function _menu_link_map_translate(&$map, $to_arg_functions) {
}
}
/**
* Returns a string containing the path relative to the current index.
*/
function menu_tail_to_arg($arg, $map, $index) {
return implode('/', array_slice($map, $index));
}
/**
* This function is similar to _menu_translate() but does link-specific
* preparation such as always calling to_arg functions
* Loads the path as one string relative to the current index.
*
* To use this load function, you must specify the load arguments
* in the router item as:
* @code
* $item['load arguments'] = array('%map', '%index');
* @endcode
*
* @see search_menu().
*/
function menu_tail_load($arg, &$map, $index) {
$arg = implode('/', array_slice($map, $index));
$map = array_slice($map, 0, $index);
return $arg;
}
/**
* Provides menu link access control, translation, and argument handling.
*
* This function is similar to _menu_translate(), but it also does
* link-specific preparation (such as always calling to_arg() functions).
*
* @param $item
* A menu link.
......@@ -883,7 +943,7 @@ function _menu_link_translate(&$item, $translate = FALSE) {
}
/**
* Get a loaded object from a router item.
* Gets a loaded object from a router item.
*
* menu_get_object() provides access to objects loaded by the current router
* item. For example, on the page node/%node, the router loads the %node object,
......@@ -1001,7 +1061,7 @@ function menu_tree_output($tree) {
}
// Allow menu-specific theme overrides.
$element['#theme'] = 'menu_link__' . $data['link']['menu_name'];
$element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
$element['#attributes']['class'] = $class;
$element['#title'] = $data['link']['title'];
$element['#href'] = $data['link']['href'];
......@@ -1023,7 +1083,7 @@ function menu_tree_output($tree) {
}
/**
* Get the data structure representing a named menu tree.
* Gets the data structure representing a named menu tree.
*
* Since this can be the full tree including hidden items, the data returned
* may be used for generating an an admin interface or a select.
......@@ -1091,7 +1151,46 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
}
/**
* Get the data structure representing a named menu tree, based on the current page.
* Sets the path for determining the active trail of the specified menu tree.
*
* This path will also affect the breadcrumbs under some circumstances.
* Breadcrumbs are built using the preferred link returned by
* menu_link_get_preferred(). If the preferred link is inside one of the menus
* specified in calls to menu_tree_set_path(), the preferred link will be
* overridden by the corresponding path returned by menu_tree_get_path().
*
* Setting this path does not affect the main content; for that use
* menu_set_active_item() instead.
*
* @param $menu_name
* The name of the affected menu tree.
* @param $path
* The path to use when finding the active trail.
*/
function menu_tree_set_path($menu_name, $path = NULL) {
$paths = &drupal_static(__FUNCTION__);
if (isset($path)) {
$paths[$menu_name] = $path;
}
return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL;
}
/**
* Gets the path for determining the active trail of the specified menu tree.
*
* @param $menu_name
* The menu name of the requested tree.
*
* @return
* A string containing the path. If no path has been specified with
* menu_tree_set_path(), NULL is returned.
*/
function menu_tree_get_path($menu_name) {
return menu_tree_set_path($menu_name);
}
/**
* Gets the data structure for a named menu tree, based on the current page.
*
* The tree order is maintained by storing each parent in an individual
* field, see http://drupal.org/node/141866 for more.
......@@ -1115,8 +1214,10 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) {
$tree = &drupal_static(__FUNCTION__, array());
// Check if the active trail has been overridden for this menu tree.
$active_path = menu_tree_get_path($menu_name);
// Load the menu item corresponding to the current page.
if ($item = menu_get_item()) {
if ($item = menu_get_item($active_path)) {
if (isset($max_depth)) {
$max_depth = min($max_depth, MENU_MAX_DEPTH);
}
......@@ -1155,8 +1256,9 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
// If the item for the current page is accessible, build the tree
// parameters accordingly.
if ($item['access']) {
// Find a menu link corresponding to the current path.
if ($active_link = menu_link_get_preferred()) {
// 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, $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
......@@ -1222,7 +1324,7 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
}
/**
* Build a menu tree, translate links, and check access.
* Builds a menu tree, translates links, and checks access.
*
* @param $menu_name
* The name of the menu.
......@@ -1237,9 +1339,11 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
* trail. This option is ignored, if 'expanded' is non-empty. Internally
* used for breadcrumbs.
* - min_depth: The minimum depth of menu links in the resulting tree.
* Defaults to 1, which is the default to build a whole tree for a menu, i.e.
* excluding menu container itself.
* Defaults to 1, which is the default to build a whole tree for a menu
* (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.
......@@ -1253,7 +1357,7 @@ function menu_build_tree($menu_name, array $parameters = array()) {
}
/**
* Build a menu tree.
* Builds a menu tree.
*
* This function may be used build the data for a menu tree only, for example
* to further massage the data manually before further processing happens.
......@@ -1323,6 +1427,12 @@ function _menu_build_tree($menu_name, array $parameters = array()) {
if (isset($parameters['max_depth'])) {
$query->condition('ml.depth', $parameters['max_depth'], '<=');
}
// Add custom query conditions, if any were passed.
if (isset($parameters['conditions'])) {
foreach ($parameters['conditions'] as $column => $value) {
$query->condition($column, $value);
}
}
// Build an ordered array of links using the query result object.
$links = array();
......@@ -1343,7 +1453,7 @@ function _menu_build_tree($menu_name, array $parameters = array()) {
}
/**
* Recursive helper function - collect node links.
* Collects node links from a given menu tree recursively.
*
* @param $tree
* The menu tree you wish to collect node links from.
......@@ -1366,7 +1476,7 @@ function menu_tree_collect_node_links(&$tree, &$node_links) {
}
/**
* Check access and perform other dynamic operations for each link in the tree.
* Checks access and performs dynamic operations for each link in the tree.
*
* @param $tree
* The menu tree you wish to operate on.
......@@ -1393,7 +1503,7 @@ function menu_tree_check_access(&$tree, $node_links = array()) {
}
/**
* Recursive helper function for menu_tree_check_access()
* Sorts the menu tree and recursively checks access for each item.
*/
function _menu_tree_check_access(&$tree) {
$new_tree = array();
......@@ -1416,18 +1526,30 @@ function _menu_tree_check_access(&$tree) {
}
/**
* Build the data representing a menu tree.
* Sorts and returns the built data representing a menu tree.
*
* @param $links
* An array of links (associative arrays) ordered by p1..p9.
* A flat array of menu links that are part of the menu. Each array element
* is an associative array of information about the menu link, containing the
* fields from the {menu_links} table, and optionally additional information
* from the {menu_router} table, if the menu item appears in both tables.
* This array must be ordered depth-first. See _menu_build_tree() for a sample
* query.
* @param $parents
* An array of the plid values that represent the path from the current page
* to the root of the menu tree.
* An array of the menu link ID values that are in the path from the current
* page to the root of the menu tree.
* @param $depth
* The minimum depth of any link in the $links array.
* The minimum depth to include in the returned menu tree.
*
* @return
* See menu_tree_page_data for a description of the data structure.
* An array of menu links in the form of a tree. Each item in the tree is an
* associative array containing:
* - link: The menu link item from $links, with additional element
* 'in_active_trail' (TRUE if the link ID was in $parents).
* - below: An array containing the sub-tree of this item, where each element
* is a tree item array with 'link' and 'below' elements. This array will be
* empty if the menu item has no items in its sub-tree having a depth
* greater than or equal to $depth.
*/
function menu_tree_data(array $links, array $parents = array(), $depth = 1) {
// Reverse the array so we can use the more efficient array_pop() function.
......@@ -1436,7 +1558,7 @@ function menu_tree_data(array $links, array $parents = array(), $depth = 1) {
}
/**
* Recursive helper function to build the data representing a menu tree.
* Builds the data representing a menu tree.
*
* The function is a bit complex because the rendering of a link depends on
* the next menu link.
......@@ -1471,7 +1593,7 @@ function _menu_tree_data(&$links, $parents, $depth) {
}
/**
* Preprocesses the rendered tree for theme_menu_tree().
* Implements template_preprocess_HOOK() for theme_menu_tree().
*/
function template_preprocess_menu_tree(&$variables) {
$variables['tree'] = $variables['tree']['#children'];
......@@ -1627,9 +1749,9 @@ function menu_get_custom_theme($initialize = FALSE) {
if (!empty($custom_themes)) {
$custom_theme = array_pop($custom_themes);
}
// Otherwise, execute the theme callback function for the current page, if
// there is one, in order to determine the custom theme to set.
if (!isset($custom_theme)) {
// If there is a theme callback function for the current page, execute it.
// If this returns a valid theme, it will override any theme that was set
// by a hook_custom_theme() implementation above.
$router_item = menu_get_item();
if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) {
$theme_name = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']);
......@@ -1638,7 +1760,6 @@ function menu_get_custom_theme($initialize = FALSE) {
}
}
}
}
return $custom_theme;
}
......@@ -1666,7 +1787,7 @@ function menu_get_names() {
}
/**
* Return an array containing the names of system-defined (default) menus.
* Returns an array containing the names of system-defined (default) menus.
*/
function menu_list_system_menus() {
return array(
......@@ -1678,14 +1799,14 @@ function menu_list_system_menus() {
}
/**
* Return an array of links to be rendered as the Main menu.
* Returns an array of links to be rendered as the Main menu.
*/
function menu_main_menu() {
return menu_navigation_links(variable_get('menu_main_links_source', 'main-menu'));
}
/**
* Return an array of links to be rendered as the Secondary links.
* Returns an array of links to be rendered as the Secondary links.
*/
function menu_secondary_menu() {
......@@ -1700,7 +1821,7 @@ function menu_secondary_menu() {
}
/**
* Return an array of links for a navigation menu.
* Returns an array of links for a navigation menu.
*
* @param $menu_name
* The name of the menu.
......@@ -1992,14 +2113,12 @@ function menu_local_tasks($level = 0) {
}
/**
* Retrieve contextual links for a system object based on registered local tasks.
* Retrieves contextual links for a path based on registered local tasks.
*
* This leverages the menu system to retrieve the first layer of registered
* local tasks for a given system path. All local tasks of the tab type
* MENU_CONTEXT_INLINE are taken into account.
*
* @see hook_menu()
*
* For example, when considering the following registered local tasks:
* - node/%node/view (default local task) with no 'context' defined
* - node/%node/edit with context: MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE
......@@ -2024,9 +2143,10 @@ function menu_local_tasks($level = 0) {
* node or array('system', 'navigation') for a certain block.
*
* @return
* A list of menu router items that are local tasks for the passed in path.
* A list of menu router items that are local tasks for the passed-in path.
*
* @see contextual_links_preprocess()
* @see hook_menu()
*/
function menu_contextual_links($module, $parent_path, $args) {
static $path_empty = array();
......@@ -2034,7 +2154,7 @@ function menu_contextual_links($module, $parent_path, $args) {
$links = array();
// Performance: In case a previous invocation for the same parent path did not
// return any links, we immediately return here.
if (isset($path_empty[$parent_path])) {
if (isset($path_empty[$parent_path]) && strpos($parent_path, '%') !== FALSE) {
return $links;
}
// Construct the item-specific parent path.
......@@ -2120,7 +2240,7 @@ function menu_local_actions() {
}
/**
* Returns the router path, or the path of the parent tab of a default local task.
* Returns the router path, or the path for a default local task's parent.
*/
function menu_tab_root_path() {
$links = menu_local_tasks();
......@@ -2128,29 +2248,57 @@ function menu_tab_root_path() {
}
/**
* Returns renderable local tasks.
* Returns a renderable element for the primary and secondary tabs.
*/
function menu_local_tabs() {
return array(
'#theme' => 'menu_local_tasks',
'#primary' => menu_primary_local_tasks(),
'#secondary' => menu_secondary_local_tasks(),
);
}
/**
* Returns HTML for primary and secondary local tasks.
*
* @param $variables
* An associative array containing:
* - primary: (optional) An array of local tasks (tabs).
* - secondary: (optional) An array of local tasks (tabs).
*
* @ingroup themeable
* @see menu_local_tasks()
*/
function theme_menu_local_tasks() {
$output = array();
function theme_menu_local_tasks(&$variables) {
$output = '';
if ($primary = menu_primary_local_tasks()) {
$primary['#prefix'] = '<h2 class="element-invisible">' . t('Primary tabs') . '</h2><ul class="tabs primary">';
$primary['#suffix'] = '</ul>';
$output[] = $primary;
if (!empty($variables['primary'])) {
$variables['primary']['#prefix'] = '<h2 class="element-invisible">' . t('Primary tabs') . '</h2>';
$variables['primary']['#prefix'] .= '<ul class="tabs primary">';
$variables['primary']['#suffix'] = '</ul>';
$output .= drupal_render($variables['primary']);
}
if ($secondary = menu_secondary_local_tasks()) {
$secondary['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2><ul class="tabs secondary">';
$secondary['#suffix'] = '</ul>';
$output[] = $secondary;
if (!empty($variables['secondary'])) {
$variables['secondary']['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2>';
$variables['secondary']['#prefix'] .= '<ul class="tabs secondary">';
$variables['secondary']['#suffix'] = '</ul>';
$output .= drupal_render($variables['secondary']);
}
return $output;
}
/**
* Set (or get) the active menu for the current page - determines the active trail.
* Sets (or gets) the active menu for the current page.
*
* The active menu for the 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__);
......@@ -2165,47 +2313,48 @@ function menu_set_active_menu_names($menu_names = NULL) {
}
/**
* Get the active menu for the current page - determines the active trail.
* Gets the active menu for the current page.
*/
function menu_get_active_menu_names() {
return menu_set_active_menu_names();
}
/**
* Set the active path, which determines which page is loaded.
*
* @param $path
* A Drupal path - not a path alias.
* Sets the active path, which determines which page is loaded.
*
* Note that this may not have the desired effect unless invoked very early
* in the page load, such as during hook_boot, or unless you call
* in the page load, such as during hook_boot(), or unless you call
* menu_execute_active_handler() to generate your page output.
*
* @param $path
* A Drupal path - not a path alias.
*/
function menu_set_active_item($path) {
$_GET['q'] = $path;
// Since the active item has changed, the active menu trail may also be out
// of date.
drupal_static_reset('menu_set_active_trail');
}
/**
* Sets or gets the active trail (path to menu tree root) of the current page.
* Sets the active trail (path to the menu tree root) of the current page.
*
* Any trail set by this function will only be used for functionality that calls
* menu_get_active_trail(). Drupal core only uses trails set here for
* breadcrumbs and the page title and not for menu trees or page content.
* Additionally, breadcrumbs set by drupal_set_breadcrumb() will override any
* trail set here.
*
* To affect the trail used by menu trees, use menu_tree_set_path(). To affect
* the page content, use menu_set_active_item() instead.
*
* @param $new_trail
* Menu trail to set, or NULL to use previously-set or calculated trail. If
* supplying a trail, use the same format as the return value (see below).
* Menu trail to set; the value is saved in a static variable and can be
* retrieved by menu_get_active_trail(). The format of this array should be
* the same as the return value of menu_get_active_trail().
*
* @return
* Path to menu root of the current page, as an array of menu link items,
* starting with the site's home page. Each link item is an associative array
* with the following components:
* - 'title': Title of the item.
* - 'href': Drupal path of the item.
* - 'localized_options': Options for passing into the l() function.
* - 'type': A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to
* indicate it's not really in the menu (used for the home page item).
* If $new_trail is supplied, the value is saved in a static variable and
* returned. If $new_trail is not supplied, and there is a saved value from
* a previous call, the saved value is returned. If $new_trail is not supplied
* and there is no saved value, the path to the current page is calculated,
* saved as the static value, and returned.
* The active trail. See menu_get_active_trail() for details.
*/
function menu_set_active_trail($new_trail = NULL) {
$trail = &drupal_static(__FUNCTION__);
......@@ -2266,11 +2415,11 @@ function menu_set_active_trail($new_trail = NULL) {
}
list($key, $curr) = each($tree);
}
// Make sure the current page is in the trail (needed for the page title),
// if the link's type allows it to be shown in the breadcrumb. Also exclude
// it if we are on the front page.
// Make sure the current page is in the trail to build the page title, by
// appending either the preferred link or the menu router item for the
// current page. Exclude it if we are on the front page.
$last = end($trail);
if ($last['href'] != $preferred_link['href'] && ($preferred_link['type'] & MENU_VISIBLE_IN_BREADCRUMB) == MENU_VISIBLE_IN_BREADCRUMB && !drupal_is_front_page()) {
if ($preferred_link && $last['href'] != $preferred_link['href'] && !drupal_is_front_page()) {
$trail[] = $preferred_link;
}
}
......@@ -2278,28 +2427,35 @@ function menu_set_active_trail($new_trail = NULL) {
}
/**
* Lookup the preferred menu link for a given system path.
* Looks up the preferred menu link for a given system path.
*
* @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 not 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
......@@ -2321,55 +2477,77 @@ function menu_link_get_preferred($path = NULL) {
// Retrieve a list of menu names, ordered by preference.
$menu_names = menu_get_active_menu_names();
// Put the selected menu at the front of the list.
array_unshift($menu_names, $selected_menu);
$query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
$query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
$query->fields('ml');
// Weight must be taken from {menu_links}, not {menu_router}.
$query->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')));
$query->condition('ml.menu_name', $menu_names, 'IN');
$query->addField('ml', 'weight', 'link_weight');
$query->fields('m');
$query->condition('ml.link_path', $path_candidates, 'IN');
// Sort candidates by link path and menu name.
$candidates = array();
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;
}
if (isset($candidates[$link_path])) {
foreach ($menu_names as $menu_name) {
if (!isset($candidates[$link_path][$menu_name])) {
continue;
}
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] = $candidate_item;
$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;
}
/**
* Gets the active trail (path to root menu root) of the current page.
*
* See menu_set_active_trail() for details of return value.
* If a trail is supplied to menu_set_active_trail(), that value is returned. If
* a trail is not supplied to menu_set_active_trail(), the path to the current
* page is calculated and returned. The calculated trail is also saved as a
* static value for use by subsequent calls to menu_get_active_trail().
*
* @return
* Path to menu root of the current page, as an array of menu link items,
* starting with the site's home page. Each link item is an associative array
* with the following components:
* - title: Title of the item.
* - href: Drupal path of the item.
* - localized_options: Options for passing into the l() function.
* - type: A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to
* indicate it's not really in the menu (used for the home page item).
*/
function menu_get_active_trail() {
return menu_set_active_trail();
}
/**
* Get the breadcrumb for the current page, as determined by the active trail.
* Gets the breadcrumb for the current page, as determined by the active trail.
*
* @see menu_set_active_trail()
*/
......@@ -2420,7 +2598,7 @@ function menu_get_active_breadcrumb() {
}
/**
* Get the title of the current page, as determined by the active trail.
* Gets the title of the current page, as determined by the active trail.
*/
function menu_get_active_title() {
$active_trail = menu_get_active_trail();
......@@ -2433,7 +2611,7 @@ function menu_get_active_title() {
}
/**
* Get a menu link by its mlid, access checked and link translated for rendering.
* Gets a translated, access-checked menu link that is ready for rendering.
*
* This function should never be called from within node_load() or any other
* function used as a menu object load function since an infinite recursion may
......@@ -2452,9 +2630,11 @@ function menu_link_load($mlid) {
$query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
$query->fields('ml');
// Weight should be taken from {menu_links}, not {menu_router}.
$query->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')));
$query->addField('ml', 'weight', 'link_weight');
$query->fields('m');
$query->condition('ml.mlid', $mlid);
if ($item = $query->execute()->fetchAssoc()) {
$item['weight'] = $item['link_weight'];
_menu_link_translate($item);
return $item;
}
......@@ -2482,7 +2662,9 @@ function menu_cache_clear($menu_name = 'navigation') {
}
/**
* Clears all cached menu data. This should be called any time broad changes
* Clears all cached menu data.
*
* This should be called any time broad changes
* might have been made to the router items or menu links.
*/
function menu_cache_clear_all() {
......@@ -2503,10 +2685,10 @@ function menu_reset_static_cache() {
}
/**
* (Re)populate the database tables used by various menu functions.
* Populates the database tables used by various menu functions.
*
* This function will clear and populate the {menu_router} table, add entries
* to {menu_links} for new router items, then remove stale items from
* to {menu_links} for new router items, and then remove stale items from
* {menu_links}. If called from update.php or install.php, it will also
* schedule a call to itself on the first real page load from
* menu_execute_active_handler(), because the maintenance page environment
......@@ -2552,7 +2734,7 @@ function menu_rebuild() {
}
/**
* Collect and alter the menu definitions.
* Collects and alters the menu definitions.
*/
function menu_router_build() {
// We need to manually call each module so that we can know which module
......@@ -2576,7 +2758,7 @@ function menu_router_build() {
}
/**
* Helper function to store the menu router if we have it in memory.
* Stores the menu router if we have it in memory.
*/
function _menu_router_cache($new_menu = NULL) {
$menu = &drupal_static(__FUNCTION__);
......@@ -2588,7 +2770,7 @@ function _menu_router_cache($new_menu = NULL) {
}
/**
* Get the menu router.
* Gets the menu router.
*/
function menu_get_router() {
// Check first if we have it in memory already.
......@@ -2625,7 +2807,7 @@ function _menu_link_build($item) {
}
/**
* Helper function to build menu links for the items in the menu router.
* Builds menu links for the items in the menu router.
*/
function _menu_navigation_links_rebuild($menu) {
// Add normal and suggested items as links.
......@@ -2637,19 +2819,15 @@ function _menu_navigation_links_rebuild($menu) {
}
}
if ($menu_links) {
// Keep an array of processed menu links, to allow menu_link_save() to
// check this for parents instead of querying the database.
$parent_candidates = array();
// Make sure no child comes before its parent.
array_multisort($sort, SORT_NUMERIC, $menu_links);
foreach ($menu_links as $item) {
foreach ($menu_links as $key => $item) {
$existing_item = db_select('menu_links')
->fields('menu_links', array(
'mlid',
'menu_name',
'plid',
'customized',
'has_children',
'updated',
))
->fields('menu_links')
->condition('link_path', $item['path'])
->condition('module', 'system')
->execute()->fetchAssoc();
......@@ -2668,9 +2846,14 @@ function _menu_navigation_links_rebuild($menu) {
$item['has_children'] = $existing_item['has_children'];
$item['updated'] = $existing_item['updated'];
}
if (!$existing_item || !$existing_item['customized']) {
if ($existing_item && $existing_item['customized']) {
$parent_candidates[$existing_item['mlid']] = $existing_item;
}
else {
$item = _menu_link_build($item);
menu_link_save($item);
menu_link_save($item, $existing_item, $parent_candidates);
$parent_candidates[$item['mlid']] = $item;
unset($menu_links[$key]);
}
}
}
......@@ -2724,7 +2907,7 @@ function _menu_navigation_links_rebuild($menu) {
}
/**
* Clone an array of menu links.
* Clones an array of menu links.
*
* @param $links
* An array of menu links to clone.
......@@ -2815,12 +2998,14 @@ function menu_link_delete($mlid, $path = NULL) {
}
/**
* Helper function for menu_link_delete; deletes a single menu link.
* Deletes a single menu link.
*
* @param $item
* Item to be deleted.
* @param $force
* Forces deletion. Internal use only, setting to TRUE is discouraged.
*
* @see menu_link_delete()
*/
function _menu_delete_item($item, $force = FALSE) {
$item = is_object($item) ? get_object_vars($item) : $item;
......@@ -2834,11 +3019,12 @@ function _menu_delete_item($item, $force = FALSE) {
menu_link_save($child);
}
}
db_delete('menu_links')->condition('mlid', $item['mlid'])->execute();
// Notify modules we have deleted the item.
// Notify modules we are deleting the item.
module_invoke_all('menu_link_delete', $item);
db_delete('menu_links')->condition('mlid', $item['mlid'])->execute();
// Update the has_children status of the parent.
_menu_update_parental_status($item);
menu_cache_clear($item['menu_name']);
......@@ -2847,24 +3033,37 @@ function _menu_delete_item($item, $force = FALSE) {
}
/**
* Save a menu link.
* Saves a menu link.
*
* After calling this function, rebuild the menu cache using
* menu_cache_clear_all().
*
* @param $item
* An array representing a menu link item. The only mandatory keys are
* link_path and link_title. Possible keys are:
* - menu_name: Default is navigation
* - weight: Default is 0
* - expanded: Whether the item is expanded.
* - options: An array of options, see l() for more.
* - mlid: Set to an existing value, or 0 or NULL to insert a new link.
* - plid: The mlid of the parent.
* - router_path: The path of the relevant router item.
* An associative array representing a menu link item, with elements:
* - link_path: (required) The path of the menu item, which should be
* normalized first by calling drupal_get_normal_path() on it.
* - link_title: (required) Title to appear in menu for the link.
* - menu_name: (optional) The machine name of the menu for the link.
* Defaults to 'navigation'.
* - weight: (optional) Integer to determine position in menu. Default is 0.
* - expanded: (optional) Boolean that determines if the item is expanded.
* - options: (optional) An array of options, see l() for more.
* - mlid: (optional) Menu link identifier, the primary integer key for each
* menu link. Can be set to an existing value, or to 0 or NULL
* to insert a new link.
* - plid: (optional) The mlid of the parent.
* - router_path: (optional) The path of the relevant router item.
* @param $existing_item
* Optional, the current record from the {menu_links} table as an array.
* @param $parent_candidates
* Optional array of menu links keyed by mlid. Used by
* _menu_navigation_links_rebuild() only.
*
* @return
* The mlid of the saved menu link, or FALSE if the menu link could not be
* saved.
*/
function menu_link_save(&$item) {
function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) {
drupal_alter('menu_link', $item);
// This is the easiest way to handle the unique internal path '<front>',
......@@ -2883,49 +3082,20 @@ function menu_link_save(&$item) {
'customized' => 0,
'updated' => 0,
);
$existing_item = FALSE;
if (isset($item['mlid'])) {
if ($existing_item = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['mlid']))->fetchAssoc()) {
$existing_item['options'] = unserialize($existing_item['options']);
}
}
// If we have a parent link ID, we use it to inherit 'menu_name' and 'depth'.
if (isset($item['plid'])) {
if ($item['plid']) {
$parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['plid']))->fetchAssoc();
if (!$existing_item) {
$existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc();
}
// If the parent link ID is zero, then this link lives at the top-level.
else {
$parent = FALSE;
if ($existing_item) {
$existing_item['options'] = unserialize($existing_item['options']);
}
}
// Otherwise, try to find a valid parent link for this link.
else {
$query = db_select('menu_links');
// Only links derived from router items should have module == 'system', and
// we want to find the parent even if it's in a different menu.
if ($item['module'] == 'system') {
$query->condition('module', 'system');
$existing_item = FALSE;
}
// We always respect the link's 'menu_name'; inheritance for router items is
// ensured in _menu_router_build().
$query->condition('menu_name', $item['menu_name']);
// Find the parent - it must be unique.
$parent_path = $item['link_path'];
do {
$parent = FALSE;
$parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
$new_query = clone $query;
$new_query->condition('link_path', $parent_path);
// Only valid if we get a unique result.
if ($new_query->countQuery()->execute()->fetchField() == 1) {
$parent = $new_query->fields('menu_links')->execute()->fetchAssoc();
}
} while ($parent === FALSE && $parent_path);
}
// If a parent link was found, assign it and derive its menu.
// Try to find a parent link. If found, assign it and derive its menu.
$parent = _menu_link_find_parent($item, $parent_candidates);
if (!empty($parent['mlid'])) {
$item['plid'] = $parent['mlid'];
$item['menu_name'] = $parent['menu_name'];
......@@ -2996,10 +3166,10 @@ function menu_link_save(&$item) {
}
// If every value in $existing_item is the same in the $item, there is no
// reason to run the update queries or clear the caches. We use
// array_intersect_assoc() with the $item as the first parameter because
// array_intersect_key() with the $item as the first parameter because
// $item may have additional keys left over from building a router entry.
// The intersect removes the extra keys, allowing a meaningful comparison.
if (!$existing_item || (array_intersect_assoc($item, $existing_item)) != $existing_item) {
if (!$existing_item || (array_intersect_key($item, $existing_item) != $existing_item)) {
db_update('menu_links')
->fields(array(
'menu_name' => $item['menu_name'],
......@@ -3047,7 +3217,87 @@ function menu_link_save(&$item) {
}
/**
* Helper function to clear the page and block caches at most twice per page load.
* Finds a possible parent for a given menu link.
*
* Because the parent of a given link might not exist anymore in the database,
* we apply a set of heuristics to determine a proper parent:
*
* - use the passed parent link if specified and existing.
* - else, use the first existing link down the previous link hierarchy
* - else, for system menu links (derived from hook_menu()), reparent
* based on the path hierarchy.
*
* @param $menu_link
* A menu link.
* @param $parent_candidates
* An array of menu links keyed by mlid.
*
* @return
* A menu link structure of the possible parent or FALSE if no valid parent
* has been found.
*/
function _menu_link_find_parent($menu_link, $parent_candidates = array()) {
$parent = FALSE;
// This item is explicitely top-level, skip the rest of the parenting.
if (isset($menu_link['plid']) && empty($menu_link['plid'])) {
return $parent;
}
// If we have a parent link ID, try to use that.
$candidates = array();
if (isset($menu_link['plid'])) {
$candidates[] = $menu_link['plid'];
}
// Else, if we have a link hierarchy try to find a valid parent in there.
if (!empty($menu_link['depth']) && $menu_link['depth'] > 1) {
for ($depth = $menu_link['depth'] - 1; $depth >= 1; $depth--) {
$candidates[] = $menu_link['p' . $depth];
}
}
foreach ($candidates as $mlid) {
if (isset($parent_candidates[$mlid])) {
$parent = $parent_candidates[$mlid];
}
else {
$parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
}
if ($parent) {
return $parent;
}
}
// If everything else failed, try to derive the parent from the path
// hierarchy. This only makes sense for links derived from menu router
// items (ie. from hook_menu()).
if ($menu_link['module'] == 'system') {
$query = db_select('menu_links');
$query->condition('module', 'system');
// We always respect the link's 'menu_name'; inheritance for router items is
// ensured in _menu_router_build().
$query->condition('menu_name', $menu_link['menu_name']);
// Find the parent - it must be unique.
$parent_path = $menu_link['link_path'];
do {
$parent = FALSE;
$parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
$new_query = clone $query;
$new_query->condition('link_path', $parent_path);
// Only valid if we get a unique result.
if ($new_query->countQuery()->execute()->fetchField() == 1) {
$parent = $new_query->fields('menu_links')->execute()->fetchAssoc();
}
} while ($parent === FALSE && $parent_path);
}
return $parent;
}
/**
* Clears the page and block caches at most twice per page load.
*/
function _menu_clear_page_cache() {
$cache_cleared = &drupal_static(__FUNCTION__, 0);
......@@ -3069,7 +3319,7 @@ function _menu_clear_page_cache() {
}
/**
* Helper function to update a list of menus with expanded items
* Updates a list of menus with expanded items.
*/
function _menu_set_expanded_menus() {
$names = db_query("SELECT menu_name FROM {menu_links} WHERE expanded <> 0 GROUP BY menu_name")->fetchCol();
......@@ -3077,7 +3327,7 @@ function _menu_set_expanded_menus() {
}
/**
* Find the router path which will serve this path.
* Finds the router path which will serve this path.
*
* @param $link_path
* The path for we are looking up its router path.
......@@ -3119,7 +3369,7 @@ function _menu_find_router_path($link_path) {
}
/**
* Insert, update or delete an uncustomized menu link related to a module.
* Inserts, updates, or deletes an uncustomized menu link related to a module.
*
* @param $module
* The name of the module.
......@@ -3159,7 +3409,7 @@ function menu_link_maintain($module, $op, $link_path, $link_title) {
}
/**
* Find the depth of an item's children relative to its depth.
* Finds the depth of an item's children relative to its depth.
*
* For example, if the item has a depth of 2, and the maximum of any child in
* the menu link tree is 5, the relative depth is 3.
......@@ -3191,7 +3441,7 @@ function menu_link_children_relative_depth($item) {
}
/**
* Update the children of a menu link that's being moved.
* Updates the children of a menu link that is being moved.
*
* The menu name, parents (p1 - p6), and depth are updated for all children of
* the link, and the has_children status of the previous parent is updated.
......@@ -3240,7 +3490,7 @@ function _menu_link_move_children($item, $existing_item) {
}
/**
* Check and update the has_children status for the parent of a link.
* Checks and updates the 'has_children' status for the parent of a link.
*/
function _menu_update_parental_status($item, $exclude = FALSE) {
// If plid == 0, there is nothing to update.
......@@ -3264,7 +3514,7 @@ function _menu_update_parental_status($item, $exclude = FALSE) {
}
/**
* Helper function that sets the p1..p9 values for a menu link being saved.
* Sets the p1 through p9 values for a menu link being saved.
*/
function _menu_link_parents_set(&$item, $parent) {
$i = 1;
......@@ -3282,7 +3532,7 @@ function _menu_link_parents_set(&$item, $parent) {
}
/**
* Helper function to build the router table based on the data from hook_menu.
* Builds the router table based on the data from hook_menu().
*/
function _menu_router_build($callbacks) {
// First pass: separate callbacks from paths, making paths ready for
......@@ -3305,8 +3555,7 @@ function _menu_router_build($callbacks) {
$match = FALSE;
// Look for wildcards in the form allowed to be used in PHP functions,
// because we are using these to construct the load function names.
// See http://php.net/manual/en/language.functions.php for reference.
if (preg_match('/^%(|[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/', $part, $matches)) {
if (preg_match('/^%(|' . DRUPAL_PHP_FUNCTION_PATTERN . ')$/', $part, $matches)) {
if (empty($matches[1])) {
$match = TRUE;
$load_functions[$k] = NULL;
......@@ -3342,7 +3591,7 @@ function _menu_router_build($callbacks) {
$fit = (1 << $number_parts) - 1;
}
$masks[$fit] = 1;
$item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
$item['_load_functions'] = $load_functions;
$item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
$item += array(
'title' => '',
......@@ -3445,6 +3694,21 @@ function _menu_router_build($callbacks) {
$item['theme arguments'] = $parent['theme arguments'];
}
}
// Same for load arguments: if a loader doesn't have any explict
// arguments, try to find arguments in the parent.
if (!isset($item['load arguments'])) {
foreach ($item['_load_functions'] as $k => $function) {
// This loader doesn't have any explict arguments...
if (!is_array($function)) {
// ... check the parent for a loader at the same position
// using the same function name and defining arguments...
if (isset($parent['_load_functions'][$k]) && is_array($parent['_load_functions'][$k]) && key($parent['_load_functions'][$k]) === $function) {
// ... and inherit the arguments on the child.
$item['_load_functions'][$k] = $parent['_load_functions'][$k];
}
}
}
}
}
}
if (!isset($item['access callback']) && isset($item['access arguments'])) {
......@@ -3458,6 +3722,7 @@ function _menu_router_build($callbacks) {
$item['access callback'] = intval($item['access callback']);
}
$item['load_functions'] = empty($item['_load_functions']) ? '' : serialize($item['_load_functions']);
$item += array(
'access arguments' => array(),
'access callback' => '',
......@@ -3494,7 +3759,7 @@ function _menu_router_build($callbacks) {
}
/**
* Helper function to save data from menu_router_build() to the router table.
* Saves data from menu_router_build() to the router table.
*/
function _menu_router_save($menu, $masks) {
// Delete the existing router since we have some data to replace it.
......
<?php
// $Id: module.inc,v 1.202 2010/10/03 02:04:55 dries Exp $
/**
* @file
......@@ -7,7 +6,7 @@
*/
/**
* Load all the modules that have been enabled in the system table.
* Loads all the modules that have been enabled in the system table.
*
* @param $bootstrap
* Whether to load only the reduced set of modules loaded in "bootstrap mode"
......@@ -32,27 +31,39 @@ function module_load_all($bootstrap = FALSE) {
/**
* Collect a list of all loaded modules. During the bootstrap, return only
* vital modules. See bootstrap.inc
* Returns a list of currently active modules.
*
* Usually, this returns a list of all enabled modules. When called early on in
* the bootstrap, it will return a list of vital modules only (those needed to
* generate cached pages).
*
* All parameters to this function are optional and should generally not be
* changed from their defaults.
*
* @param $refresh
* Whether to force the module list to be regenerated (such as after the
* administrator has changed the system settings).
* @param $bootstrap
* Whether to return the reduced set of modules loaded in "bootstrap mode"
* for cached pages. See bootstrap.inc.
* (optional) Whether to force the module list to be regenerated (such as
* after the administrator has changed the system settings). Defaults to
* FALSE.
* @param $bootstrap_refresh
* (optional) When $refresh is TRUE, setting $bootstrap_refresh to TRUE forces
* the module list to be regenerated using the reduced set of modules loaded
* in "bootstrap mode" for cached pages. Otherwise, setting $refresh to TRUE
* generates the complete list of enabled modules.
* @param $sort
* By default, modules are ordered by weight and module name. Set this option
* to TRUE to return a module list ordered only by module name.
* (optional) By default, modules are ordered by weight and module name. Set
* this option to TRUE to return a module list ordered only by module name.
* @param $fixed_list
* (Optional) Override the module list with the given modules. Stays until the
* next call with $refresh = TRUE.
* (optional) If an array of module names is provided, this will override the
* module list with the given set of modules. This will persist until the next
* call with $refresh set to TRUE or with a new $fixed_list passed in. This
* parameter is primarily intended for internal use (e.g., in install.php and
* update.php).
*
* @return
* An associative array whose keys and values are the names of all loaded
* modules.
* An associative array whose keys and values are the names of the modules in
* the list.
*/
function module_list($refresh = FALSE, $bootstrap = FALSE, $sort = FALSE, $fixed_list = NULL) {
function module_list($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE, $fixed_list = NULL) {
static $list = array(), $sorted_list;
if (empty($list) || $refresh || $fixed_list) {
......@@ -70,7 +81,7 @@ function module_list($refresh = FALSE, $bootstrap = FALSE, $sort = FALSE, $fixed
// data.
drupal_static_reset('system_list');
}
if ($bootstrap) {
if ($bootstrap_refresh) {
$list = system_list('bootstrap');
}
else {
......@@ -91,7 +102,7 @@ function module_list($refresh = FALSE, $bootstrap = FALSE, $sort = FALSE, $fixed
}
/**
* Build a list of bootstrap modules and enabled modules and themes.
* Builds a list of bootstrap modules and enabled modules and themes.
*
* @param $type
* The type of list to return:
......@@ -115,6 +126,9 @@ function system_list($type) {
// if not fetch only the required information to fire bootstrap hooks
// in case we are going to serve the page from cache.
if ($type == 'bootstrap') {
if (isset($lists['bootstrap'])) {
return $lists['bootstrap'];
}
if ($cached = cache_get('bootstrap_modules', 'cache_bootstrap')) {
$bootstrap_list = $cached->data;
}
......@@ -164,6 +178,34 @@ function system_list($type) {
$lists['filepaths'][] = array('type' => $record->type, 'name' => $record->name, 'filepath' => $record->filename);
}
}
foreach ($lists['theme'] as $key => $theme) {
if (!empty($theme->info['base theme'])) {
// Make a list of the theme's base themes.
require_once DRUPAL_ROOT . '/includes/theme.inc';
$lists['theme'][$key]->base_themes = drupal_find_base_themes($lists['theme'], $key);
// Don't proceed if there was a problem with the root base theme.
if (!current($lists['theme'][$key]->base_themes)) {
continue;
}
// Determine the root base theme.
$base_key = key($lists['theme'][$key]->base_themes);
// Add to the list of sub-themes for each of the theme's base themes.
foreach (array_keys($lists['theme'][$key]->base_themes) as $base_theme) {
$lists['theme'][$base_theme]->sub_themes[$key] = $lists['theme'][$key]->info['name'];
}
// Add the base theme's theme engine info.
$lists['theme'][$key]->info['engine'] = isset($lists['theme'][$base_key]->info['engine']) ? $lists['theme'][$base_key]->info['engine'] : 'theme';
}
else {
// A plain theme is its own engine.
$base_key = $key;
if (!isset($lists['theme'][$key]->info['engine'])) {
$lists['theme'][$key]->info['engine'] = 'theme';
}
}
// Set the theme engine prefix.
$lists['theme'][$key]->prefix = ($lists['theme'][$key]->info['engine'] == 'theme') ? $base_key : $lists['theme'][$key]->info['engine'];
}
cache_set('system_list', $lists, 'cache_bootstrap');
}
// To avoid a separate database lookup for the filepath, prime the
......@@ -177,17 +219,18 @@ function system_list($type) {
}
/**
* Reset all system_list() caches.
* Resets all system_list() caches.
*/
function system_list_reset() {
drupal_static_reset('system_list');
drupal_static_reset('system_rebuild_module_data');
drupal_static_reset('list_themes');
cache_clear_all('bootstrap_modules', 'cache_bootstrap');
cache_clear_all('system_list', 'cache_bootstrap');
}
/**
* Find dependencies any level deep and fill in required by information too.
* Determines which modules require and are required by each module.
*
* @param $files
* The array of filesystem objects used to rebuild the cache.
......@@ -220,7 +263,7 @@ function _module_build_dependencies($files) {
}
/**
* Determine whether a given module exists.
* Determines whether a given module exists.
*
* @param $module
* The name of the module (without the .module extension).
......@@ -234,17 +277,23 @@ function module_exists($module) {
}
/**
* Load a module's installation hooks.
* Loads a module's installation hooks.
*
* @param $module
* The name of the module (without the .module extension).
*
* @return
* The name of the module's install file, if successful; FALSE otherwise.
*/
function module_load_install($module) {
// Make sure the installation API is available
include_once DRUPAL_ROOT . '/includes/install.inc';
module_load_include('install', $module);
return module_load_include('install', $module);
}
/**
* Load a module include file.
* Loads a module include file.
*
* Examples:
* @code
......@@ -264,11 +313,14 @@ function module_load_install($module) {
* @param $module
* The module to which the include file belongs.
* @param $name
* Optionally, specify the base file name (without the $type extension).
* If not set, $module is used.
* (optional) The base file name (without the $type extension). If omitted,
* $module is used; i.e., resulting in "$module.$type" by default.
*
* @return
* The name of the included file, if successful; FALSE otherwise.
*/
function module_load_include($type, $module, $name = NULL) {
if (empty($name)) {
if (!isset($name)) {
$name = $module;
}
......@@ -283,8 +335,7 @@ function module_load_include($type, $module, $name = NULL) {
}
/**
* Load an include file for each of the modules that have been enabled in
* the system table.
* Loads an include file for each module enabled in the {system} table.
*/
function module_load_all_includes($type, $name = NULL) {
$modules = module_list();
......@@ -350,7 +401,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
// Add dependencies to the list, with a placeholder weight.
// The new modules will be processed as the while loop continues.
foreach ($module_data[$module]->info['dependencies'] as $dependency) {
foreach (array_keys($module_data[$module]->requires) as $dependency) {
if (!isset($module_list[$dependency])) {
$module_list[$dependency] = 0;
}
......@@ -401,6 +452,8 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
registry_update();
// Refresh the schema to include it.
drupal_get_schema(NULL, TRUE);
// Update the theme registry to include it.
drupal_theme_rebuild();
// Clear entity cache.
entity_info_cache_clear();
......@@ -450,7 +503,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
}
/**
* Disable a given set of modules.
* Disables a given set of modules.
*
* @param $module_list
* An array of module names.
......@@ -466,6 +519,7 @@ function module_disable($module_list, $disable_dependents = TRUE) {
// Create an associative array with weights as values.
$module_list = array_flip(array_values($module_list));
$profile = drupal_get_profile();
while (list($module) = each($module_list)) {
if (!isset($module_data[$module]) || !$module_data[$module]->status) {
// This module doesn't exist or is already disabled, skip it.
......@@ -477,7 +531,7 @@ function module_disable($module_list, $disable_dependents = TRUE) {
// Add dependent modules to the list, with a placeholder weight.
// The new modules will be processed as the while loop continues.
foreach ($module_data[$module]->required_by as $dependent => $dependent_data) {
if (!isset($module_list[$dependent]) && !strstr($module_data[$dependent]->filename, '.profile')) {
if (!isset($module_list[$dependent]) && $dependent != $profile) {
$module_list[$dependent] = 0;
}
}
......@@ -514,12 +568,15 @@ function module_disable($module_list, $disable_dependents = TRUE) {
system_list_reset();
module_list(TRUE);
module_implements('', FALSE, TRUE);
entity_info_cache_clear();
// Invoke hook_modules_disabled before disabling modules,
// so we can still call module hooks to get information.
module_invoke_all('modules_disabled', $invoke_modules);
// Update the registry to remove the newly-disabled module.
registry_update();
_system_update_bootstrap_status();
// Update the theme registry to remove the newly-disabled module.
drupal_theme_rebuild();
}
// If there remains no more node_access module, rebuilding will be
......@@ -557,7 +614,7 @@ function module_disable($module_list, $disable_dependents = TRUE) {
*/
/**
* Determine whether a module implements a hook.
* Determines whether a module implements a hook.
*
* @param $module
* The name of the module (without the .module extension).
......@@ -569,11 +626,24 @@ function module_disable($module_list, $disable_dependents = TRUE) {
* implemented in that module.
*/
function module_hook($module, $hook) {
return function_exists($module . '_' . $hook);
$function = $module . '_' . $hook;
if (function_exists($function)) {
return TRUE;
}
// If the hook implementation does not exist, check whether it may live in an
// optional include file registered via hook_hook_info().
$hook_info = module_hook_info();
if (isset($hook_info[$hook]['group'])) {
module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
if (function_exists($function)) {
return TRUE;
}
}
return FALSE;
}
/**
* Determine which modules are implementing a hook.
* Determines which modules are implementing a hook.
*
* @param $hook
* The name of the hook (e.g. "help" or "menu").
......@@ -637,7 +707,9 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
$list = module_list(FALSE, FALSE, $sort);
foreach ($list as $module) {
$include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']);
if (module_hook($module, $hook)) {
// Since module_hook() may needlessly try to load the include file again,
// function_exists() is used directly here.
if (function_exists($module . '_' . $hook)) {
$implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
}
}
......@@ -655,9 +727,11 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
module_load_include('inc', $module, "$module.$group");
}
// It is possible that a module removed a hook implementation without the
// implementations cache being rebuilt yet, so we check module_hook() on
// each request to avoid undefined function errors.
if (!module_hook($module, $hook)) {
// implementations cache being rebuilt yet, so we check whether the
// function exists on each request to avoid undefined function errors.
// Since module_hook() may needlessly try to load the include file again,
// function_exists() is used directly here.
if (!function_exists($module . '_' . $hook)) {
// Clear out the stale implementation from the cache and force a cache
// refresh to forget about no longer existing hook implementations.
unset($implementations[$hook][$module]);
......@@ -670,12 +744,27 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
}
/**
* Retrieve a list of what hooks are explicitly declared.
* Retrieves a list of hooks that are declared through hook_hook_info().
*
* @return
* An associative array whose keys are hook names and whose values are an
* associative array containing a group name. The structure of the array
* is the same as the return value of hook_hook_info().
*
* @see hook_hook_info()
*/
function module_hook_info() {
$hook_info = &drupal_static(__FUNCTION__, array());
// This function is indirectly invoked from bootstrap_invoke_all(), in which
// case common.inc, subsystems, and modules are not loaded yet, so it does not
// make sense to support hook groups resp. lazy-loaded include files prior to
// full bootstrap.
if (drupal_bootstrap(NULL, FALSE) != DRUPAL_BOOTSTRAP_FULL) {
return array();
}
$hook_info = &drupal_static(__FUNCTION__);
if (empty($hook_info)) {
if (!isset($hook_info)) {
$hook_info = array();
$cache = cache_get('hook_info', 'cache_bootstrap');
if ($cache === FALSE) {
// Rebuild the cache and save it.
......@@ -724,7 +813,7 @@ function module_implements_write_cache() {
}
/**
* Invoke a hook in a particular module.
* Invokes a hook in a particular module.
*
* @param $module
* The name of the module (without the .module extension).
......@@ -736,17 +825,17 @@ function module_implements_write_cache() {
* @return
* The return value of the hook implementation.
*/
function module_invoke() {
function module_invoke($module, $hook) {
$args = func_get_args();
$module = $args[0];
$hook = $args[1];
// Remove $module and $hook from the arguments.
unset($args[0], $args[1]);
if (module_hook($module, $hook)) {
return call_user_func_array($module . '_' . $hook, $args);
}
}
/**
* Invoke a hook in all enabled modules that implement it.
* Invokes a hook in all enabled modules that implement it.
*
* @param $hook
* The name of the hook to invoke.
......@@ -757,9 +846,9 @@ function module_invoke() {
* An array of return values of the hook implementations. If modules return
* arrays from their implementations, those are merged into one array.
*/
function module_invoke_all() {
function module_invoke_all($hook) {
$args = func_get_args();
$hook = $args[0];
// Remove $hook from the arguments.
unset($args[0]);
$return = array();
foreach (module_implements($hook) as $module) {
......@@ -783,13 +872,13 @@ function module_invoke_all() {
*/
/**
* Array of modules required by core.
* Returns an array of modules required by core.
*/
function drupal_required_modules() {
$files = drupal_system_listing('/\.info$/', 'modules', 'name', 0);
$files = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'modules', 'name', 0);
$required = array();
// An install profile is required and one must always be loaded.
// An installation profile is required and one must always be loaded.
$required[] = drupal_get_profile();
foreach ($files as $name => $file) {
......@@ -803,15 +892,16 @@ function drupal_required_modules() {
}
/**
* Hands off alterable variables to type-specific *_alter implementations.
* Passes alterable variables to specific hook_TYPE_alter() implementations.
*
* This dispatch function hands off the passed in variables to type-specific
* This dispatch function hands off the passed-in variables to type-specific
* hook_TYPE_alter() implementations in modules. It ensures a consistent
* interface for all altering operations.
*
* A maximum of 2 alterable arguments is supported. In case more arguments need
* to be passed and alterable, modules provide additional variables assigned by
* reference in the last $context argument:
* A maximum of 2 alterable arguments is supported (a third is supported for
* legacy reasons, but should not be used in new code). In case more arguments
* need to be passed and alterable, modules provide additional variables
* assigned by reference in the last $context argument:
* @code
* $context = array(
* 'alterable' => &$alterable,
......@@ -839,19 +929,25 @@ function drupal_required_modules() {
* values in $type. For example, when Form API is using drupal_alter() to
* execute both hook_form_alter() and hook_form_FORM_ID_alter()
* implementations, it passes array('form', 'form_' . $form_id) for $type.
* @param &$data
* @param $data
* The variable that will be passed to hook_TYPE_alter() implementations to be
* altered. The type of this variable depends on the value of the $type
* argument. For example, when altering a 'form', $data will be a structured
* array. When altering a 'profile', $data will be an object.
* @param &$context1
* @param $context1
* (optional) An additional variable that is passed by reference.
* @param &$context2
* @param $context2
* (optional) An additional variable that is passed by reference. If more
* context needs to be provided to implementations, then this should be an
* associative array as described above.
* @param $context3
* (optional) An additional variable that is passed by reference. This
* parameter is deprecated and will not exist in Drupal 8; consequently, it
* should not be used for new Drupal 7 code either. It is here only for
* backwards compatibility with older code that passed additional arguments
* to drupal_alter().
*/
function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL) {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
......@@ -902,10 +998,24 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
}
// If any modules implement one of the extra hooks that do not implement
// the primary hook, we need to add them to the $modules array in their
// appropriate order.
// appropriate order. module_implements() can only return ordered
// implementations of a single hook. To get the ordered implementations
// of multiple hooks, we mimic the module_implements() logic of first
// ordering by module_list(), and then calling
// drupal_alter('module_implements').
if (array_diff($extra_modules, $modules)) {
// Order the modules by the order returned by module_list().
// Merge the arrays and order by module_list().
$modules = array_intersect(module_list(), array_merge($modules, $extra_modules));
// Since module_implements() already took care of loading the necessary
// include files, we can safely pass FALSE for the array values.
$implementations = array_fill_keys($modules, FALSE);
// Let modules adjust the order solely based on the primary hook. This
// ensures the same module order regardless of whether this if block
// runs. Calling drupal_alter() recursively in this way does not result
// in an infinite loop, because this call is for a single $type, so we
// won't end up in this code block again.
drupal_alter('module_implements', $implementations, $hook);
$modules = array_keys($implementations);
}
foreach ($modules as $module) {
// Since $modules is a merged array, for any given module, we do not
......@@ -950,7 +1060,6 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
}
foreach ($functions[$cid] as $function) {
$function($data, $context1, $context2);
$function($data, $context1, $context2, $context3);
}
}
<?php
// $Id: pager.inc,v 1.85 2010/10/08 05:07:53 webchick Exp $
/**
* @file
......@@ -20,7 +19,7 @@ class PagerDefault extends SelectQueryExtender {
*
* @var int
*/
static protected $maxElement = 0;
static $maxElement = 0;
/**
* The number of elements per page to allow.
......@@ -201,7 +200,7 @@ function pager_find_page($element = 0) {
* to theme('pager') will render a pager that correctly corresponds to the
* items being displayed.
*
* If the items being displayed result from a database query peformed using
* If the items being displayed result from a database query performed using
* Drupal's database API, and if you have control over the construction of the
* database query, you do not need to call this function directly; instead, you
* can simply extend the query object with the 'PagerDefault' extender before
......@@ -431,8 +430,9 @@ function theme_pager($variables) {
/**
* @defgroup pagerpieces Pager pieces
* @{
* Use these pieces to construct your own custom pagers in your theme. Note that
* you should NOT modify this file to customize your pager.
* Theme functions for customizing pager elements.
*
* Note that you should NOT modify this file to customize your pager.
*/
/**
......@@ -574,13 +574,20 @@ function theme_pager_last($variables) {
*
* @param $variables
* An associative array containing:
* - text: The link text. Also used to figure out the title attribute of the
* link, if it is not provided in $variables['attributes']['title']; in
* this case, $variables['text'] must be one of the standard pager link
* text strings that would be generated by the pager theme functions, such
* as a number or t('« first').
* - page_new: The first result to display on the linked page.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager link.
* - attributes: An associative array of HTML attributes to apply to a pager
* anchor tag.
* - attributes: An associative array of HTML attributes to apply to the
* pager link.
*
* @see theme_pager()
*
* @ingroup themeable
*/
......@@ -623,7 +630,13 @@ function theme_pager_link($variables) {
}
}
return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => $query));
// @todo l() cannot be used here, since it adds an 'active' class based on the
// path only (which is always the current path for pager links). Apparently,
// none of the pager links is active at any time - but it should still be
// possible to use l() here.
// @see http://drupal.org/node/1410574
$attributes['href'] = url($_GET['q'], array('query' => $query));
return '<a' . drupal_attributes($attributes) . '>' . check_plain($text) . '</a>';
}
/**
......
<?php
// $Id: password.inc,v 1.8 2010/05/04 15:47:03 dries Exp $
/**
* @file
......@@ -19,7 +18,7 @@
* increase by 1 every Drupal version in order to counteract increases in the
* speed and power of computers available to crack the hashes.
*/
define('DRUPAL_HASH_COUNT', 14);
define('DRUPAL_HASH_COUNT', 15);
/**
* The minimum allowed log2 number of iterations for password stretching.
......@@ -44,7 +43,7 @@ function _password_itoa64() {
}
/**
* Encode bytes into printable base 64 using the *nix standard from crypt().
* Encodes bytes into printable base 64 using the *nix standard from crypt().
*
* @param $input
* The string containing bytes to encode.
......@@ -99,17 +98,37 @@ function _password_base64_encode($input, $count) {
*/
function _password_generate_salt($count_log2) {
$output = '$S$';
// Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT.
$count_log2 = max($count_log2, DRUPAL_MIN_HASH_COUNT);
// Maximum log2 iterations is DRUPAL_MAX_HASH_COUNT.
// Ensure that $count_log2 is within set bounds.
$count_log2 = _password_enforce_log2_boundaries($count_log2);
// We encode the final log2 iteration count in base 64.
$itoa64 = _password_itoa64();
$output .= $itoa64[min($count_log2, DRUPAL_MAX_HASH_COUNT)];
$output .= $itoa64[$count_log2];
// 6 bytes is the standard salt for a portable phpass hash.
$output .= _password_base64_encode(drupal_random_bytes(6), 6);
return $output;
}
/**
* Ensures that $count_log2 is within set bounds.
*
* @param $count_log2
* Integer that determines the number of iterations used in the hashing
* process. A larger value is more secure, but takes more time to complete.
*
* @return
* Integer within set bounds that is closest to $count_log2.
*/
function _password_enforce_log2_boundaries($count_log2) {
if ($count_log2 < DRUPAL_MIN_HASH_COUNT) {
return DRUPAL_MIN_HASH_COUNT;
}
elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) {
return DRUPAL_MAX_HASH_COUNT;
}
return (int) $count_log2;
}
/**
* Hash a password using a secure stretched hash.
*
......@@ -261,7 +280,8 @@ function user_needs_new_hash($account) {
if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) {
return TRUE;
}
// Ensure that $count_log2 is within set bounds.
$count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT));
// Check whether the iteration count used differs from the standard number.
return (_password_get_count_log2($account->pass) != variable_get('password_count_log2', DRUPAL_HASH_COUNT));
return (_password_get_count_log2($account->pass) !== $count_log2);
}
<?php
// $Id: path.inc,v 1.71 2010/08/09 00:13:06 dries Exp $
/**
* @file
......@@ -14,12 +13,12 @@
* Initialize the $_GET['q'] variable to the proper normal path.
*/
function drupal_path_initialize() {
if (!empty($_GET['q'])) {
$_GET['q'] = drupal_get_normal_path($_GET['q']);
}
else {
$_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node'));
// Ensure $_GET['q'] is set before calling drupal_normal_path(), to support
// path caching with hook_url_inbound_alter().
if (empty($_GET['q'])) {
$_GET['q'] = variable_get('site_frontpage', 'node');
}
$_GET['q'] = drupal_get_normal_path($_GET['q']);
}
/**
......@@ -94,13 +93,30 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) {
if ($cached = cache_get($cid, 'cache_path')) {
$cache['system_paths'] = $cached->data;
// Now fetch the aliases corresponding to these system paths.
// We order by ASC and overwrite array keys to ensure the correct
// alias is used when there are multiple aliases per path.
$cache['map'][$path_language] = db_query("SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC", array(
$args = array(
':system' => $cache['system_paths'],
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
))->fetchAllKeyed();
);
// Always get the language-specific alias before the language-neutral
// one. For example 'de' is less than 'und' so the order needs to be
// ASC, while 'xx-lolspeak' is more than 'und' so the order needs to
// be DESC. We also order by pid ASC so that fetchAllKeyed() returns
// the most recently created alias for each source. Subsequent queries
// using fetchField() must use pid DESC to have the same effect.
// For performance reasons, the query builder is not used here.
if ($path_language == LANGUAGE_NONE) {
// Prevent PDO from complaining about a token the query doesn't use.
unset($args[':language']);
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language = :language_none ORDER BY pid ASC', $args);
}
elseif ($path_language < LANGUAGE_NONE) {
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC', $args);
}
else {
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language DESC, pid ASC', $args);
}
$cache['map'][$path_language] = $result->fetchAllKeyed();
// Keep a record of paths with no alias to avoid querying twice.
$cache['no_aliases'][$path_language] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$path_language])));
}
......@@ -117,12 +133,22 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) {
}
// For system paths which were not cached, query aliases individually.
elseif (!isset($cache['no_aliases'][$path_language][$path])) {
// Get the most fitting result falling back with alias without language
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", array(
$args = array(
':source' => $path,
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
))->fetchField();
);
// See the queries above.
if ($path_language == LANGUAGE_NONE) {
unset($args[':language']);
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language = :language_none ORDER BY pid DESC", $args)->fetchField();
}
elseif ($path_language > LANGUAGE_NONE) {
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args)->fetchField();
}
else {
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args)->fetchField();
}
$cache['map'][$path_language][$path] = $alias;
return $alias;
}
......@@ -133,12 +159,23 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) {
// Look for the value $path within the cached $map
$source = FALSE;
if (!isset($cache['map'][$path_language]) || !($source = array_search($path, $cache['map'][$path_language]))) {
// Get the most fitting result falling back with alias without language
if ($source = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", array(
$args = array(
':alias' => $path,
':language' => $path_language,
':language_none' => LANGUAGE_NONE))
->fetchField()) {
':language_none' => LANGUAGE_NONE,
);
// See the queries above.
if ($path_language == LANGUAGE_NONE) {
unset($args[':language']);
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language = :language_none ORDER BY pid DESC", $args);
}
elseif ($path_language > LANGUAGE_NONE) {
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args);
}
else {
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args);
}
if ($source = $result->fetchField()) {
$cache['map'][$path_language][$source] = $path;
}
else {
......@@ -349,7 +386,7 @@ function drupal_path_alias_whitelist_rebuild($source = NULL) {
}
/**
* Fetch a specific URL alias from the database.
* Fetches a specific URL alias from the database.
*
* @param $conditions
* A string representing the source, a number representing the pid, or an
......@@ -394,22 +431,28 @@ function path_load($conditions) {
* - language: (optional) The language of the alias.
*/
function path_save(&$path) {
$path += array('pid' => NULL, 'language' => LANGUAGE_NONE);
$path += array('language' => LANGUAGE_NONE);
// Insert or update the alias.
$status = drupal_write_record('url_alias', $path, (!empty($path['pid']) ? 'pid' : array()));
// Load the stored alias, if any.
if (!empty($path['pid']) && !isset($path['original'])) {
$path['original'] = path_load($path['pid']);
}
// Verify that a record was written.
if ($status) {
if ($status === SAVED_NEW) {
if (empty($path['pid'])) {
drupal_write_record('url_alias', $path);
module_invoke_all('path_insert', $path);
}
else {
drupal_write_record('url_alias', $path, array('pid'));
module_invoke_all('path_update', $path);
}
// Clear internal properties.
unset($path['original']);
// Clear the static alias cache.
drupal_clear_path_cache($path['source']);
}
}
/**
* Delete a URL alias.
......@@ -432,11 +475,11 @@ function path_delete($criteria) {
}
/**
* Determine whether a path is in the administrative section of the site.
* Determines whether a path is in the administrative section of the site.
*
* By default, paths are considered to be non-administrative. If a path does not
* match any of the patterns in path_get_admin_paths(), or if it matches both
* administrative and non-administrative patterns, it is considered
* By default, paths are considered to be non-administrative. If a path does
* not match any of the patterns in path_get_admin_paths(), or if it matches
* both administrative and non-administrative patterns, it is considered
* non-administrative.
*
* @param $path
......@@ -460,7 +503,7 @@ function path_is_admin($path) {
}
/**
* Get a list of administrative and non-administrative paths.
* Gets a list of administrative and non-administrative paths.
*
* @return array
* An associative array containing the following keys:
......
<?php
// $Id: registry.inc,v 1.34 2010/09/17 14:45:46 dries Exp $
/**
* @file
......@@ -98,7 +97,8 @@ function _registry_update() {
_registry_check_code(REGISTRY_RESET_LOOKUP_CACHE);
}
catch (Exception $e) {
$transaction->rollback('registry', $e->getMessage(), array(), WATCHDOG_ERROR);
$transaction->rollback();
watchdog_exception('registry', $e);
throw $e;
}
......@@ -131,10 +131,6 @@ function _registry_parse_files($files) {
if (file_exists($filename)) {
$hash = hash_file('sha256', $filename);
if (empty($file['hash']) || $file['hash'] != $hash) {
// Delete registry entries for this file, so we can insert the new resources.
db_delete('registry')
->condition('filename', $filename)
->execute();
$file['hash'] = $hash;
$parsed_files[$filename] = $file;
}
......@@ -166,21 +162,28 @@ function _registry_parse_files($files) {
*/
function _registry_parse_file($filename, $contents, $module = '', $weight = 0) {
if (preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) {
$query = db_insert('registry')->fields(array('name', 'type', 'filename', 'module', 'weight'));
foreach ($matches[2] as $key => $name) {
$query->values(array(
db_merge('registry')
->key(array(
'name' => $name,
'type' => $matches[1][$key],
))
->fields(array(
'filename' => $filename,
'module' => $module,
'weight' => $weight,
));
))
->execute();
}
$query->execute();
// Delete any resources for this file where the name is not in the list
// we just merged in.
db_delete('registry')
->condition('filename', $filename)
->condition('name', $matches[2], 'NOT IN')
->execute();
}
}
/**
* @} End of "defgroup registry".
*/
<?php
// $Id: session.inc,v 1.90 2010/10/15 04:15:41 webchick Exp $
/**
* @file
......@@ -50,21 +49,23 @@ function _drupal_session_close() {
}
/**
* Session handler assigned by session_set_save_handler().
* Reads an entire session from the database (internal use only).
*
* This function will be called by PHP to retrieve the current user's
* session data, which is stored in the database. It also loads the
* current user's appropriate roles into the user object.
* Also initializes the $user object for the user associated with the session.
* This function is registered with session_set_save_handler() to support
* database-backed sessions. It is called on every page load when PHP sets
* up the $_SESSION superglobal.
*
* This function should not be called directly. Session data should
* instead be accessed via the $_SESSION superglobal.
* This function is an internal function and must not be called directly.
* Doing so may result in logging out the current user, corrupting session data
* or other unexpected behavior. Session data must always be accessed via the
* $_SESSION superglobal.
*
* @param $sid
* Session ID.
* The session ID of the session to retrieve.
*
* @return
* Either an array of the session data, or an empty string, if no data
* was found or the user is anonymous.
* The user's session, or an empty string if no session exists.
*/
function _drupal_session_read($sid) {
global $user, $is_https;
......@@ -100,6 +101,10 @@ function _drupal_session_read($sid) {
else {
$user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject();
}
$session_data = @gzinflate($user->session);
if ($session_data !== FALSE) {
$user->session = $session_data;
}
// We found the client's session record and they are an authenticated,
// active user.
......@@ -137,21 +142,22 @@ function _drupal_session_read($sid) {
}
/**
* Session handler assigned by session_set_save_handler().
* Writes an entire session to the database (internal use only).
*
* This function will be called by PHP to store the current user's
* session, which Drupal saves to the database.
* This function is registered with session_set_save_handler() to support
* database-backed sessions.
*
* This function should not be called directly. Session data should
* instead be accessed via the $_SESSION superglobal.
* This function is an internal function and must not be called directly.
* Doing so may result in corrupted session data or other unexpected behavior.
* Session data must always be accessed via the $_SESSION superglobal.
*
* @param $sid
* Session ID.
* The session ID of the session to write to.
* @param $value
* Serialized array of the session data.
* Session data to write as a serialized string.
*
* @return
* This function will always return TRUE.
* Always returns TRUE.
*/
function _drupal_session_write($sid, $value) {
global $user, $is_https;
......@@ -170,30 +176,35 @@ function _drupal_session_write($sid, $value) {
// For performance reasons, do not update the sessions table, unless
// $_SESSION has changed or more than 180 has passed since the last update.
if ($is_changed || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) {
if ($is_changed || !isset($user->timestamp) || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) {
// Either ssid or sid or both will be added from $key below.
$fields = array(
'uid' => $user->uid,
'cache' => isset($user->cache) ? $user->cache : 0,
'hostname' => ip_address(),
'session' => $value,
'session' => gzdeflate($value, 9),
'timestamp' => REQUEST_TIME,
);
// The "secure pages" setting allows a site to simultaneously use both
// secure and insecure session cookies. If enabled and both cookies are
// presented then use both keys. If not enabled but on HTTPS then use the
// PHP session id as 'ssid'. If on HTTP then use the PHP session id as
// 'sid'.
// Use the session ID as 'sid' and an empty string as 'ssid' by default.
// _drupal_session_read() does not allow empty strings so that's a safe
// default.
$key = array('sid' => $sid, 'ssid' => '');
// On HTTPS connections, use the session ID as both 'sid' and 'ssid'.
if ($is_https) {
$key['ssid'] = $sid;
// The "secure pages" setting allows a site to simultaneously use both
// secure and insecure session cookies. If enabled and both cookies are
// presented then use both keys.
if (variable_get('https', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) {
if (isset($_COOKIE[$insecure_session_name])) {
$key['sid'] = $_COOKIE[$insecure_session_name];
}
}
else {
$key['sid'] = $sid;
}
elseif (variable_get('https', FALSE)) {
unset($key['ssid']);
}
db_merge('sessions')
......@@ -234,7 +245,9 @@ function drupal_session_initialize() {
session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');
if (isset($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && isset($_COOKIE[substr(session_name(), 1)]))) {
// We use !empty() in the following check to ensure that blank session IDs
// are not valid.
if (!empty($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && !empty($_COOKIE[substr(session_name(), 1)]))) {
// If a session cookie exists, initialize the session. Otherwise the
// session is only started on demand in drupal_session_commit(), making
// anonymous users not use a session cookie unless something is stored in
......@@ -249,17 +262,23 @@ function drupal_session_initialize() {
// we lazily start sessions at the end of this request, and some
// processes (like drupal_get_token()) needs to know the future
// session ID in advance.
$GLOBALS['lazy_session'] = TRUE;
$user = drupal_anonymous_user();
// Less random sessions (which are much faster to generate) are used for
// anonymous users than are generated in drupal_session_regenerate() when
// a user becomes authenticated.
session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE)));
if ($is_https && variable_get('https', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
$session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE));
$_COOKIE[$insecure_session_name] = $session_id;
}
}
date_default_timezone_set(drupal_get_user_timezone());
}
/**
* Forcefully starts a session, preserving already set session data.
* Starts a session forcefully, preserving already set session data.
*
* @ingroup php_wrappers
*/
......@@ -285,7 +304,7 @@ function drupal_session_start() {
* If an anonymous user already have an empty session, destroy it.
*/
function drupal_session_commit() {
global $user;
global $user, $is_https;
if (!drupal_save_session()) {
// We don't have anything to do if we are not allowed to save the session.
......@@ -304,6 +323,12 @@ function drupal_session_commit() {
// started.
if (!drupal_session_started()) {
drupal_session_start();
if ($is_https && variable_get('https', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
$params = session_get_cookie_params();
$expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0;
setcookie($insecure_session_name, $_COOKIE[$insecure_session_name], $expire, $params['path'], $params['domain'], FALSE, $params['httponly']);
}
}
// Write the session data.
session_write_close();
......@@ -328,9 +353,14 @@ function drupal_session_started($set = NULL) {
*/
function drupal_session_regenerate() {
global $user, $is_https;
// Nothing to do if we are not allowed to change the session.
if (!drupal_save_session()) {
return;
}
if ($is_https && variable_get('https', FALSE)) {
$insecure_session_name = substr(session_name(), 1);
if (isset($_COOKIE[$insecure_session_name])) {
if (!isset($GLOBALS['lazy_session']) && isset($_COOKIE[$insecure_session_name])) {
$old_insecure_session_id = $_COOKIE[$insecure_session_name];
}
$params = session_get_cookie_params();
......@@ -397,6 +427,11 @@ function drupal_session_regenerate() {
function _drupal_session_destroy($sid) {
global $user, $is_https;
// Nothing to do if we are not allowed to change the session.
if (!drupal_save_session()) {
return;
}
// Delete session data.
db_delete('sessions')
->condition($is_https ? 'ssid' : 'sid', $sid)
......@@ -410,7 +445,10 @@ function _drupal_session_destroy($sid) {
// Unset the session cookies.
_drupal_session_delete_cookie(session_name());
if ($is_https) {
_drupal_session_delete_cookie(substr(session_name(), 1), TRUE);
_drupal_session_delete_cookie(substr(session_name(), 1), FALSE);
}
elseif (variable_get('https', FALSE)) {
_drupal_session_delete_cookie('S' . session_name(), TRUE);
}
}
......@@ -419,13 +457,17 @@ function _drupal_session_destroy($sid) {
*
* @param $name
* Name of session cookie to delete.
* @param $force_insecure
* Force cookie to be insecure.
* @param boolean $secure
* Force the secure value of the cookie.
*/
function _drupal_session_delete_cookie($name, $force_insecure = FALSE) {
if (isset($_COOKIE[$name])) {
function _drupal_session_delete_cookie($name, $secure = NULL) {
global $is_https;
if (isset($_COOKIE[$name]) || (!$is_https && $secure === TRUE)) {
$params = session_get_cookie_params();
setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], !$force_insecure && $params['secure'], $params['httponly']);
if ($secure !== NULL) {
$params['secure'] = $secure;
}
setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
unset($_COOKIE[$name]);
}
}
......@@ -437,6 +479,11 @@ function _drupal_session_delete_cookie($name, $force_insecure = FALSE) {
* User ID.
*/
function drupal_session_destroy_uid($uid) {
// Nothing to do if we are not allowed to change the session.
if (!drupal_save_session()) {
return;
}
db_delete('sessions')
->condition('uid', $uid)
->execute();
......@@ -479,7 +526,10 @@ function _drupal_session_garbage_collection($lifetime) {
* FALSE if writing session data has been disabled. Otherwise, TRUE.
*/
function drupal_save_session($status = NULL) {
$save_session = &drupal_static(__FUNCTION__, TRUE);
// PHP session ID, session, and cookie handling happens in the global scope.
// This value has to persist across calls to drupal_static_reset(), since a
// potentially wrong or disallowed session would be written otherwise.
static $save_session = TRUE;
if (isset($status)) {
$save_session = $status;
}
......
<?php
// $Id: stream_wrappers.inc,v 1.21 2010/10/21 12:09:41 dries Exp $
/**
* @file
......@@ -318,7 +317,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
}
$extension = '';
$file_parts = explode('.', basename($uri));
$file_parts = explode('.', drupal_basename($uri));
// Remove the first part: a full filename should not match an extension.
array_shift($file_parts);
......@@ -342,7 +341,11 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
* Base implementation of chmod().
*/
function chmod($mode) {
return @chmod($this->getLocalPath(), $mode);
$output = @chmod($this->getLocalPath(), $mode);
// We are modifying the underlying file here, so we have to clear the stat
// cache so that PHP understands that URI has changed too.
clearstatcache();
return $output;
}
/**
......@@ -353,10 +356,18 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
}
/**
* Return the local filesystem path.
* Returns the canonical absolute path of the URI, if possible.
*
* @param $uri
* Optional URI, supplied when doing a move or rename.
* @param string $uri
* (optional) The stream wrapper URI to be converted to a canonical
* absolute path. This may point to a directory or another type of file.
*
* @return string|false
* If $uri is not set, returns the canonical absolute path of the URI
* previously set by the DrupalStreamWrapperInterface::setUri() function.
* If $uri is set and valid for this class, returns its canonical absolute
* path, as determined by the realpath() function. If $uri is set but not
* valid, returns FALSE.
*/
protected function getLocalPath($uri = NULL) {
if (!isset($uri)) {
......@@ -366,7 +377,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
$realpath = realpath($path);
if (!$realpath) {
// This file does not yet exist.
$realpath = realpath(dirname($path)) . '/' . basename($path);
$realpath = realpath(dirname($path)) . '/' . drupal_basename($path);
}
$directory = realpath($this->getDirectoryPath());
if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
......@@ -384,7 +395,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
* The file mode ("r", "wb" etc.).
* @param $options
* A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
* @param &$opened_path
* @param $opened_path
* A string containing the path actually opened.
*
* @return
......@@ -398,7 +409,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
$this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
if ((bool) $this->handle && $options & STREAM_USE_PATH) {
$opened_url = $path;
$opened_path = $path;
}
return (bool) $this->handle;
......@@ -542,7 +553,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
* Support for unlink().
*
* @param $uri
* A string containing the uri to the resource to delete.
* A string containing the URI to the resource to delete.
*
* @return
* TRUE if resource was successfully deleted.
......@@ -558,9 +569,9 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
* Support for rename().
*
* @param $from_uri,
* The uri to the file to rename.
* The URI to the file to rename.
* @param $to_uri
* The new uri for file.
* The new URI for file.
*
* @return
* TRUE if file was successfully renamed.
......@@ -671,11 +682,14 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
*/
public function url_stat($uri, $flags) {
$this->uri = $uri;
if ($flags & STREAM_URL_STAT_QUIET) {
return @stat($this->getLocalPath());
$path = $this->getLocalPath();
// Suppress warnings if requested or if the file or directory does not
// exist. This is consistent with PHP's plain filesystem stream wrapper.
if ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) {
return @stat($path);
}
else {
return stat($this->getLocalPath());
return stat($path);
}
}
......
<?php
// $Id: tablesort.inc,v 1.59 2010/10/15 04:34:15 webchick Exp $
/**
* @file
......@@ -56,7 +55,7 @@ class TableSort extends SelectQueryExtender {
}
/**
* Initialize the table sort context.
* Initializes the table sort context.
*/
protected function init() {
$ts = $this->order();
......@@ -74,18 +73,7 @@ class TableSort extends SelectQueryExtender {
* The current sort direction ("asc" or "desc").
*/
protected function getSort() {
if (isset($_GET['sort'])) {
return ($_GET['sort'] == 'desc') ? 'desc' : 'asc';
}
// User has not specified a sort. Use default if specified; otherwise use "asc".
else {
foreach ($this->header as $header) {
if (is_array($header) && array_key_exists('sort', $header)) {
return $header['sort'];
}
}
}
return 'asc';
return tablesort_get_sort($this->header);
}
/**
......@@ -112,32 +100,7 @@ class TableSort extends SelectQueryExtender {
* - "sql": The name of the database field to sort on.
*/
protected function order() {
$order = isset($_GET['order']) ? $_GET['order'] : '';
foreach ($this->header as $header) {
if (isset($header['data']) && $order == $header['data']) {
return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
}
if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
$default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
}
}
if (isset($default)) {
return $default;
}
else {
// The first column specified is initial 'order by' field unless otherwise specified
$headers = array_values($this->header);
$header = $headers[0];
if (is_array($header)) {
$header += array('data' => NULL, 'field' => NULL);
return array('name' => $header['data'], 'sql' => $header['field']);
}
else {
return array('name' => $header);
}
}
return tablesort_get_order($this->header);
}
}
......@@ -152,7 +115,7 @@ function tablesort_init($header) {
}
/**
* Format a column header.
* Formats a column header.
*
* If the cell in question is the column header for the current sort criterion,
* it gets special formatting. All possible sort criteria become links.
......@@ -163,6 +126,7 @@ function tablesort_init($header) {
* An array of column headers in the format described in theme_table().
* @param $ts
* The current table sort context as returned from tablesort_init().
*
* @return
* A properly formatted cell, ready for _theme_table_cell().
*/
......@@ -188,7 +152,7 @@ function tablesort_header($cell, $header, $ts) {
}
/**
* Format a table cell.
* Formats a table cell.
*
* Adds a class attribute to all cells in the currently active column.
*
......@@ -200,6 +164,7 @@ function tablesort_header($cell, $header, $ts) {
* The current table sort context as returned from tablesort_init().
* @param $i
* The index of the cell's table column.
*
* @return
* A properly formatted cell, ready for _theme_table_cell().
*/
......@@ -216,7 +181,7 @@ function tablesort_cell($cell, $header, $ts, $i) {
}
/**
* Compose a URL query parameter array for table sorting links.
* Composes a URL query parameter array for table sorting links.
*
* @return
* A URL query parameter array that consists of all components of the current
......@@ -227,10 +192,11 @@ function tablesort_get_query_parameters() {
}
/**
* Determine the current sort criterion.
* Determines the current sort criterion.
*
* @param $headers
* An array of column headers in the format described in theme_table().
*
* @return
* An associative array describing the criterion, containing the keys:
* - "name": The localized title of the table column.
......@@ -239,47 +205,49 @@ function tablesort_get_query_parameters() {
function tablesort_get_order($headers) {
$order = isset($_GET['order']) ? $_GET['order'] : '';
foreach ($headers as $header) {
if (is_array($header)) {
if (isset($header['data']) && $order == $header['data']) {
return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
$default = $header;
break;
}
if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
$default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
if (empty($default) && isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
$default = $header;
}
}
if (isset($default)) {
return $default;
}
else {
// The first column specified is the initial 'order by' field unless otherwise specified.
$first = current($headers);
if (is_array($first)) {
$first += array('data' => NULL, 'field' => NULL);
return array('name' => $first['data'], 'sql' => $first['field']);
}
else {
return array('name' => $first, 'sql' => '');
if (!isset($default)) {
$default = reset($headers);
if (!is_array($default)) {
$default = array('data' => $default);
}
}
$default += array('data' => NULL, 'field' => NULL);
return array('name' => $default['data'], 'sql' => $default['field']);
}
/**
* Determine the current sort direction.
* Determines the current sort direction.
*
* @param $headers
* An array of column headers in the format described in theme_table().
*
* @return
* The current sort direction ("asc" or "desc").
*/
function tablesort_get_sort($headers) {
if (isset($_GET['sort'])) {
return ($_GET['sort'] == 'desc') ? 'desc' : 'asc';
return (strtolower($_GET['sort']) == 'desc') ? 'desc' : 'asc';
}
// User has not specified a sort. Use default if specified; otherwise use "asc".
// The user has not specified a sort. Use the default for the currently sorted
// header if specified; otherwise use "asc".
else {
// Find out which header is currently being sorted.
$ts = tablesort_get_order($headers);
foreach ($headers as $header) {
if (is_array($header) && array_key_exists('sort', $header)) {
if (is_array($header) && isset($header['data']) && $header['data'] == $ts['name'] && isset($header['sort'])) {
return $header['sort'];
}
}
......
<?php
// $Id: theme.inc,v 1.620 2010/10/21 19:31:39 dries Exp $
/**
* @file
......@@ -66,7 +65,7 @@ function _drupal_theme_access($theme) {
}
/**
* Initialize the theme system by loading the theme.
* Initializes the theme system by loading the theme.
*/
function drupal_theme_initialize() {
global $theme, $user, $theme_key;
......@@ -104,7 +103,7 @@ function drupal_theme_initialize() {
drupal_static_reset('drupal_alter');
// Provide the page with information about the theme that's used, so that a
// later AJAX request can be rendered using the same theme.
// later Ajax request can be rendered using the same theme.
// @see ajax_base_page_theme()
$setting['ajaxPageState'] = array(
'theme' => $theme_key,
......@@ -114,8 +113,9 @@ function drupal_theme_initialize() {
}
/**
* Initialize the theme system given already loaded information. This
* function is useful to initialize a theme when no database is present.
* Initializes the theme system given already loaded information.
*
* This function is useful to initialize a theme when no database is present.
*
* @param $theme
* An object with the following information:
......@@ -236,24 +236,52 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
}
/**
* Get the theme registry.
* Gets 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) {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['registry'] = &drupal_static('theme_get_registry');
}
$theme_registry = &$drupal_static_fast['registry'];
// Initialize the theme, if this is called early in the bootstrap, or after
// static variables have been reset.
if (!is_array($theme_registry)) {
drupal_theme_initialize();
$theme_registry = array();
}
if (!isset($theme_registry)) {
$key = (int) $complete;
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];
}
/**
* Set the callback that will be used by theme_get_registry() to fetch the registry.
* Sets the callback that will be used by theme_get_registry().
*
* @param $callback
* The name of the callback function.
......@@ -269,21 +297,28 @@ function _theme_registry_callback($callback = NULL, array $arguments = array())
}
/**
* Get the theme_registry cache from the database; if it doesn't exist, build it.
* Gets the theme_registry cache; if it doesn't exist, builds it.
*
* @param $theme
* The loaded $theme object as returned by list_themes().
* @param $base_theme
* An array of loaded $theme objects representing the ancestor themes in
* oldest first order.
* @param theme_engine
* @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) {
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.
$cache = cache_get("theme_registry:$theme->name", 'cache');
if (isset($cache->data)) {
$registry = $cache->data;
$cached = cache_get("theme_registry:$theme->name");
if (isset($cached->data)) {
$registry = $cached->data;
}
else {
// If not, build one and cache it.
......@@ -296,23 +331,139 @@ function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL)
}
return $registry;
}
else {
return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache');
}
}
/**
* Write the theme_registry cache into the database.
* Writes the theme_registry cache into the database.
*/
function _theme_save_registry($theme, $registry) {
cache_set("theme_registry:$theme->name", $registry);
}
/**
* Force the system to rebuild the theme registry; this should be called
* when modules are added to the system, or when a dynamic system needs
* to add more theme hooks.
* Forces the system to rebuild the theme registry.
*
* This function should be called when modules are added to the system, or when
* a dynamic system needs to add more theme hooks.
*/
function drupal_theme_rebuild() {
drupal_static_reset('theme_get_registry');
cache_clear_all('theme_registry', 'cache', TRUE);
}
/**
* 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().
*
......@@ -320,8 +471,8 @@ function drupal_theme_rebuild() {
* The theme registry that will eventually be cached; It is an associative
* array keyed by theme hooks, whose values are associative arrays describing
* the hook:
* - 'type': The passed in $type.
* - 'theme path': The passed in $path.
* - 'type': The passed-in $type.
* - 'theme path': The passed-in $path.
* - 'function': The name of the function generating output for this theme
* hook. Either defined explicitly in hook_theme() or, if neither 'function'
* nor 'template' is defined, then the default theme function name is used.
......@@ -363,7 +514,6 @@ function drupal_theme_rebuild() {
*/
function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
$result = array();
$function = $name . '_theme';
// Processor functions work in two distinct phases with the process
// functions always being executed after the preprocess functions.
......@@ -372,24 +522,43 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
'process functions' => 'process',
);
$hook_defaults = array(
'variables' => TRUE,
'render element' => TRUE,
'pattern' => TRUE,
'base hook' => TRUE,
);
// Invoke the hook_theme() implementation, process what is returned, and
// merge it into $cache.
$function = $name . '_theme';
if (function_exists($function)) {
$result = $function($cache, $type, $theme, $path);
foreach ($result as $hook => $info) {
// When a theme or engine overrides a module's theme function
// $result[$hook] will only contain key/value pairs for information being
// overridden. Pull the rest of the information from what was defined by
// an earlier hook.
// Fill in the type and path of the module, theme, or engine that
// implements this theme function.
$result[$hook]['type'] = $type;
$result[$hook]['theme path'] = $path;
// if function and file are left out, default to standard naming
// If function and file are omitted, default to standard naming
// conventions.
if (!isset($info['template']) && !isset($info['function'])) {
$result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook;
}
// If a path is set in the info, use what was set. Otherwise use the
// default path. This is mostly so system.module can declare theme
// functions on behalf of core .include files.
// All files are included to be safe. Conditionally included
// files can prevent them from getting registered.
if (isset($cache[$hook]['includes'])) {
$result[$hook]['includes'] = $cache[$hook]['includes'];
}
// If the theme implementation defines a file, then also use the path
// that it defined. Otherwise use the default path. This allows
// system.module to declare theme functions on behalf of core .include
// files.
if (isset($info['file'])) {
$include_file = isset($info['path']) ? $info['path'] : $path;
$include_file .= '/' . $info['file'];
......@@ -397,14 +566,10 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
$result[$hook]['includes'][] = $include_file;
}
// If these keys are left unspecified within overridden entries returned
// by hook_theme(), carry them forward from the prior entry. This is so
// that themes don't need to specify this information, since the module
// that registered the theme hook already has.
foreach (array('variables', 'render element', 'pattern', 'base hook') as $key) {
if (!array_key_exists($key, $info) && isset($cache[$hook][$key])) {
$result[$hook][$key] = $cache[$hook][$key];
}
// If the default keys are not set, use the default values registered
// by the module.
if (isset($cache[$hook])) {
$result[$hook] += array_intersect_key($cache[$hook], $hook_defaults);
}
// The following apply only to theming hooks implemented as templates.
......@@ -469,10 +634,11 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
}
// Merge the newly created theme hooks into the existing cache.
$cache = array_merge($cache, $result);
$cache = $result + $cache;
}
// Let themes have variable processors even if they didn't register a template.
// Let themes have variable processors even if they didn't register a
// template.
if ($type == 'theme' || $type == 'base_theme') {
foreach ($cache as $hook => $info) {
// Check only if not registered by the theme or engine.
......@@ -499,23 +665,34 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
}
/**
* Rebuild the theme registry cache.
* Builds the theme registry cache.
*
* @param $theme
* The loaded $theme object as returned by list_themes().
* @param $base_theme
* An array of loaded $theme objects representing the ancestor themes in
* oldest first order.
* @param theme_engine
* @param $theme_engine
* The name of the theme engine.
*/
function _theme_build_registry($theme, $base_theme, $theme_engine) {
$cache = array();
// First, process the theme hooks advertised by modules. This will
// serve as the basic registry.
// serve as the basic registry. Since the list of enabled modules is the same
// regardless of the theme used, this is cached in its own entry to save
// building it for every theme.
if ($cached = cache_get('theme_registry:build:modules')) {
$cache = $cached->data;
}
else {
foreach (module_implements('theme') as $module) {
_theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
}
// Only cache this registry if all modules are loaded.
if (module_load_all(NULL)) {
cache_set('theme_registry:build:modules', $cache);
}
}
// Process each base theme.
foreach ($base_theme as $base) {
......@@ -550,7 +727,7 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) {
}
/**
* Return a list of all currently available themes.
* Returns a list of all currently available themes.
*
* Retrieved from the database, if available and the site is not in maintenance
* mode; otherwise compiled freshly from the filesystem.
......@@ -560,20 +737,39 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) {
*
* @return
* An associative array of the currently available themes. The keys are the
* names of the themes and the values are objects having the following
* themes' machine names and the values are objects having the following
* properties:
* - 'filename': The name of the .info file.
* - 'name': The name of the theme.
* - 'status': 1 for enabled, 0 for disabled themes.
* - 'info': The contents of the .info file.
* - 'stylesheets': A two dimensional array, using the first key for the
* 'media' attribute (e.g. 'all'), the second for the name of the file
* (e.g. style.css). The value is a complete filepath
* (e.g. themes/bartik/style.css).
* - 'scripts': An associative array of JavaScripts, using the filename as key
* and the complete filepath as value.
* - 'engine': The name of the theme engine.
* - 'base theme': The name of the base theme.
* - filename: The filepath and name of the .info file.
* - name: The machine name of the theme.
* - status: 1 for enabled, 0 for disabled themes.
* - info: The contents of the .info file.
* - stylesheets: A two dimensional array, using the first key for the
* media attribute (e.g. 'all'), the second for the name of the file
* (e.g. style.css). The value is a complete filepath (e.g.
* themes/bartik/style.css). Not set if no stylesheets are defined in the
* .info file.
* - scripts: An associative array of JavaScripts, using the filename as key
* and the complete filepath as value. Not set if no scripts are defined in
* the .info file.
* - prefix: The base theme engine prefix.
* - engine: The machine name of the theme engine.
* - base_theme: If this is a sub-theme, the machine name of the base theme
* defined in the .info file. Otherwise, the element is not set.
* - base_themes: If this is a sub-theme, an associative array of the
* base-theme ancestors of this theme, starting with this theme's base
* theme, then the base theme's own base theme, etc. Each entry has an
* array key equal to the theme's machine name, and a value equal to the
* human-readable theme name; if a theme with matching machine name does
* not exist in the system, the value will instead be NULL (and since the
* system would not know whether that theme itself has a base theme, that
* will end the array of base themes). This is not set if the theme is not
* a sub-theme.
* - sub_themes: An associative array of themes on the system that are
* either direct sub-themes (that is, they declare this theme to be
* their base theme), direct sub-themes of sub-themes, etc. The keys are
* the themes' machine names, and the values are the themes' human-readable
* names. This element is not set if there are no themes on the system that
* declare this theme as their base theme.
*/
function list_themes($refresh = FALSE) {
$list = &drupal_static(__FUNCTION__, array());
......@@ -629,18 +825,77 @@ function list_themes($refresh = FALSE) {
return $list;
}
/**
* Find all the base themes for the specified theme.
*
* Themes can inherit templates and function implementations from earlier themes.
*
* @param $themes
* An array of available themes.
* @param $key
* The name of the theme whose base we are looking for.
* @param $used_keys
* A recursion parameter preventing endless loops.
* @return
* Returns an array of all of the theme's ancestors; the first element's value
* will be NULL if an error occurred.
*/
function drupal_find_base_themes($themes, $key, $used_keys = array()) {
$base_key = $themes[$key]->info['base theme'];
// Does the base theme exist?
if (!isset($themes[$base_key])) {
return array($base_key => NULL);
}
$current_base_theme = array($base_key => $themes[$base_key]->info['name']);
// Is the base theme itself a child of another theme?
if (isset($themes[$base_key]->info['base theme'])) {
// Do we already know the base themes of this theme?
if (isset($themes[$base_key]->base_themes)) {
return $themes[$base_key]->base_themes + $current_base_theme;
}
// Prevent loops.
if (!empty($used_keys[$base_key])) {
return array($base_key => NULL);
}
$used_keys[$base_key] = TRUE;
return drupal_find_base_themes($themes, $base_key, $used_keys) + $current_base_theme;
}
// If we get here, then this is our parent theme.
return $current_base_theme;
}
/**
* 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 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
* theme hook could be implemented by a function called 'theme_table' or a
* template file called 'table.tpl.php', but hook_theme() can override the
* default function or template name.
* the request and routes it to the appropriate
* @link themeable theme function or template @endlink, by checking the theme
* registry.
*
* Most commonly, the first argument to this function is the name of the theme
* hook. For instance, to theme a taxonomy term, the theme hook name is
* 'taxonomy_term'. Modules register theme hooks within a hook_theme()
* implementation and provide a default implementation via a function named
* theme_HOOK() (e.g., theme_taxonomy_term()) or via a template file named
* according to the value of the 'template' key registered with the theme hook
* (see hook_theme() for details). Default templates are implemented with the
* PHPTemplate rendering engine and are named the same as the theme hook, with
* underscores changed to hyphens, so for the 'taxonomy_term' theme hook, the
* default template is 'taxonomy-term.tpl.php'.
*
* Themes may also register new theme hooks within a hook_theme()
* implementation, but it is more common for themes to override default
* implementations provided by modules than to register entirely new theme
* hooks. Themes can override a default implementation by implementing a
* function named THEME_HOOK() (for example, the 'bartik' theme overrides the
* default implementation of the 'menu_tree' theme hook by implementing a
* bartik_menu_tree() function), or by adding a template file within its folder
* structure that follows the template naming structure used by the theme's
* rendering engine (for example, since the Bartik theme uses the PHPTemplate
* rendering engine, it overrides the default implementation of the 'page' theme
* hook by containing a 'page.tpl.php' file within its folder structure).
*
* If the implementation is a template file, several functions are called
* before the template file is invoked, to modify the $variables array. These
......@@ -648,43 +903,45 @@ function list_themes($refresh = FALSE) {
* executed (if they exist), in the following order (note that in the following
* list, HOOK indicates the theme hook name, MODULE indicates a module name,
* THEME indicates a theme name, and ENGINE indicates a theme engine name):
* - template_preprocess(&$variables, $hook): Creates a default set of variables
* for all theme hooks.
* - template_preprocess_HOOK(&$variables): Should be implemented by
* the module that registers the theme hook, to set up default variables.
* - template_preprocess(&$variables, $hook): Creates a default set of
* variables for all theme hooks with template implementations.
* - template_preprocess_HOOK(&$variables): Should be implemented by the module
* that registers the theme hook, to set up default variables.
* - MODULE_preprocess(&$variables, $hook): hook_preprocess() is invoked on all
* implementing modules.
* - MODULE_preprocess_HOOK(&$variables): hook_preprocess_HOOK() is invoked on
* all implementing modules, so that modules that didn't define the theme hook
* can alter the variables.
* all implementing modules, so that modules that didn't define the theme
* hook can alter the variables.
* - ENGINE_engine_preprocess(&$variables, $hook): Allows the theme engine to
* set necessary variables for all theme hooks.
* set necessary variables for all theme hooks with template implementations.
* - ENGINE_engine_preprocess_HOOK(&$variables): Allows the theme engine to set
* necessary variables for the particular theme hook.
* - THEME_preprocess(&$variables, $hook): Allows the theme to set necessary
* variables for all theme hooks.
* variables for all theme hooks with template implementations.
* - THEME_preprocess_HOOK(&$variables): Allows the theme to set necessary
* variables specific to the particular theme hook.
* - template_process(&$variables, $hook): Creates a default set of variables
* for all theme hooks.
* - template_process_HOOK(&$variables): This is the first processor specific
* to the theme hook; it should be implemented by the module that registers
* it.
* - template_process(&$variables, $hook): Creates an additional set of default
* variables for all theme hooks with template implementations. The variables
* created in this function are derived from ones created by
* template_preprocess(), but potentially altered by the other preprocess
* functions listed above. For example, any preprocess function can add to or
* modify the $variables['attributes_array'] variable, and after all of them
* have finished executing, template_process() flattens it into a
* $variables['attributes'] string for convenient use by templates.
* - template_process_HOOK(&$variables): Should be implemented by the module
* that registers the theme hook, if it needs to perform additional variable
* processing after all preprocess functions have finished.
* - MODULE_process(&$variables, $hook): hook_process() is invoked on all
* implementing modules.
* - MODULE_process_HOOK(&$variables): hook_process_HOOK() is invoked on
* on all implementing modules, so that modules that didn't define the theme
* hook can alter the variables.
* - ENGINE_engine_process(&$variables, $hook): Allows the theme engine to set
* necessary variables for all theme hooks.
* - ENGINE_engine_process_HOOK(&$variables): Allows the theme engine to set
* necessary variables for the particular theme hook.
* - ENGINE_process(&$variables, $hook): Allows the theme engine to process the
* variables.
* - ENGINE_process_HOOK(&$variables): Allows the theme engine to process the
* variables specific to the theme hook.
* - ENGINE_engine_process(&$variables, $hook): Allows the theme engine to
* process variables for all theme hooks with template implementations.
* - ENGINE_engine_process_HOOK(&$variables): Allows the theme engine to process
* the variables specific to the theme hook.
* - THEME_process(&$variables, $hook): Allows the theme to process the
* variables.
* variables for all theme hooks with template implementations.
* - THEME_process_HOOK(&$variables): Allows the theme to process the
* variables specific to the theme hook.
*
......@@ -709,10 +966,10 @@ function list_themes($refresh = FALSE) {
* @param $hook
* The name of the theme hook to call. If the name contains a
* double-underscore ('__') and there isn't an implementation for the full
* name, the part before the '__' is checked. This allows a fallback to a more
* generic implementation. For example, if theme('links__node', ...) is
* called, but there is no implementation of that theme hook, then the 'links'
* implementation is used. This process is iterative, so if
* name, the part before the '__' is checked. This allows a fallback to a
* more generic implementation. For example, if theme('links__node', ...) is
* called, but there is no implementation of that theme hook, then the
* 'links' implementation is used. This process is iterative, so if
* theme('links__contextual__node', ...) is called, theme() checks for the
* following implementations, and uses the first one that exists:
* - links__contextual__node
......@@ -734,10 +991,13 @@ function list_themes($refresh = FALSE) {
*
* @return
* An HTML string representing the themed output.
*
* @see themeable
* @see hook_theme()
* @see template_preprocess()
* @see template_process()
*/
function theme($hook, $variables = array()) {
static $hooks = NULL;
// If called before all modules are loaded, we do not necessarily have a full
// theme registry to work with, and therefore cannot process the theme
// request properly. See also _theme_load_registry().
......@@ -745,10 +1005,7 @@ function theme($hook, $variables = array()) {
throw new Exception(t('theme() may not be called until all modules are loaded.'));
}
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
// implementation.
......@@ -776,7 +1033,7 @@ function theme($hook, $variables = array()) {
// Only log a message when not trying theme suggestions ($hook being an
// array).
if (!isset($candidate)) {
watchdog('theme', 'Theme key "@key" not found.', array('@key' => $hook), WATCHDOG_WARNING);
watchdog('theme', 'Theme hook %hook not found.', array('%hook' => $hook), WATCHDOG_WARNING);
}
return '';
}
......@@ -788,7 +1045,8 @@ function theme($hook, $variables = array()) {
// point path_to_theme() to the currently used theme path:
$theme_path = $info['theme path'];
// Include a file if the theme function or variable processor is held elsewhere.
// Include a file if the theme function or variable processor is held
// elsewhere.
if (!empty($info['includes'])) {
foreach ($info['includes'] as $include_file) {
include_once DRUPAL_ROOT . '/' . $include_file;
......@@ -828,6 +1086,13 @@ function theme($hook, $variables = array()) {
if (isset($info['base hook'])) {
$base_hook = $info['base hook'];
$base_hook_info = $hooks[$base_hook];
// Include files required by the base hook, since its variable processors
// might reside there.
if (!empty($base_hook_info['includes'])) {
foreach ($base_hook_info['includes'] as $include_file) {
include_once DRUPAL_ROOT . '/' . $include_file;
}
}
if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) {
$variables['theme_hook_suggestion'] = $hook;
$hook = $base_hook;
......@@ -930,14 +1195,14 @@ function theme($hook, $variables = array()) {
}
/**
* Return the path to the current themed element.
*
* It can point to the active theme or the module handling a themed implementation.
* For example, when invoked within the scope of a theming call it will depend
* on where the theming function is handled. If implemented from a module, it
* will point to the module. If implemented from the active theme, it will point
* to the active theme. When called outside the scope of a theming call, it will
* always point to the active theme.
* Returns the path to the current themed element.
*
* It can point to the active theme or the module handling a themed
* implementation. For example, when invoked within the scope of a theming call
* it will depend on where the theming function is handled. If implemented from
* a module, it will point to the module. If implemented from the active theme,
* it will point to the active theme. When called outside the scope of a
* theming call, it will always point to the active theme.
*/
function path_to_theme() {
global $theme_path;
......@@ -950,7 +1215,7 @@ function path_to_theme() {
}
/**
* Allow themes and/or theme engines to easily discover overridden theme functions.
* Allows themes and/or theme engines to discover overridden theme functions.
*
* @param $cache
* The existing cache of theme hooks to test against.
......@@ -972,7 +1237,7 @@ function drupal_find_theme_functions($cache, $prefixes) {
// start with. The default is the name of the hook followed by '__'. An
// 'base hook' key is added to each entry made for a found suggestion,
// so that common functionality can be implemented for all suggestions of
// the same base hook. To keep things simple, deep heirarchy of
// the same base hook. To keep things simple, deep hierarchy of
// suggestions is not supported: each suggestion's 'base hook' key
// refers to a base hook, not to another suggestion, and all suggestions
// are found using the base hook's pattern, not a pattern from an
......@@ -982,7 +1247,7 @@ function drupal_find_theme_functions($cache, $prefixes) {
$matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']);
if ($matches) {
foreach ($matches as $match) {
$new_hook = str_replace($prefix . '_', '', $match);
$new_hook = substr($match, strlen($prefix) + 1);
$arg_name = isset($info['variables']) ? 'variables' : 'render element';
$implementations[$new_hook] = array(
'function' => $match,
......@@ -1007,7 +1272,7 @@ function drupal_find_theme_functions($cache, $prefixes) {
}
/**
* Allow themes and/or theme engines to easily discover overridden templates.
* Allows themes and/or theme engines to easily discover overridden templates.
*
* @param $cache
* The existing cache of theme hooks to test against.
......@@ -1084,7 +1349,8 @@ function drupal_find_theme_templates($cache, $extension, $path) {
if ($matches) {
foreach ($matches as $match) {
$file = substr($match, 0, strpos($match, '.'));
// Put the underscores back in for the hook name and register this pattern.
// Put the underscores back in for the hook name and register this
// pattern.
$arg_name = isset($info['variables']) ? 'variables' : 'render element';
$implementations[strtr($file, '-', '_')] = array(
'template' => $file,
......@@ -1100,7 +1366,7 @@ function drupal_find_theme_templates($cache, $extension, $path) {
}
/**
* Retrieve a setting for the current theme or for a given theme.
* Retrieves a setting for the current theme or for a given theme.
*
* The final setting is obtained from the last value found in the following
* sources:
......@@ -1218,7 +1484,7 @@ function theme_get_setting($setting_name, $theme = NULL) {
}
/**
* Render a system default template, which is essentially a PHP template.
* Renders a system default template, which is essentially a PHP template.
*
* @param $template_file
* The filename of the template to render.
......@@ -1229,14 +1495,21 @@ function theme_get_setting($setting_name, $theme = NULL) {
* The output generated by the template.
*/
function theme_render_template($template_file, $variables) {
extract($variables, EXTR_SKIP); // Extract the variables to a local namespace
ob_start(); // Start output buffering
include DRUPAL_ROOT . '/' . $template_file; // Include the template file
return ob_get_clean(); // End buffering and return its contents
// Extract the variables to a local namespace
extract($variables, EXTR_SKIP);
// Start output buffering
ob_start();
// Include the template file
include DRUPAL_ROOT . '/' . $template_file;
// End buffering and return its contents
return ob_get_clean();
}
/**
* Enable a given list of themes.
* Enables a given list of themes.
*
* @param $theme_list
* An array of theme names.
......@@ -1256,19 +1529,12 @@ function theme_enable($theme_list) {
menu_rebuild();
drupal_theme_rebuild();
// Notify locale module about new themes being enabled, so translations can
// be imported. This might start a batch, and only return to the redirect
// path after that.
module_invoke('locale', 'system_update', $theme_list);
// Invoke hook_themes_enabled after the themes have been enabled.
// Invoke hook_themes_enabled() after the themes have been enabled.
module_invoke_all('themes_enabled', $theme_list);
return;
}
/**
* Disable a given list of themes.
* Disables a given list of themes.
*
* @param $theme_list
* An array of theme names.
......@@ -1296,14 +1562,12 @@ function theme_disable($theme_list) {
menu_rebuild();
drupal_theme_rebuild();
// Invoke hook_themes_enabled after the themes have been enabled.
// Invoke hook_themes_disabled after the themes have been disabled.
module_invoke_all('themes_disabled', $theme_list);
return;
}
/**
* @ingroup themeable
* @addtogroup themeable
* @{
*/
......@@ -1356,13 +1620,13 @@ function theme_status_messages($variables) {
* theme('link') for rendering the anchor tag.
*
* To optimize performance for sites that don't need custom theming of links,
* the l() function includes an inline copy of this function, and uses that copy
* if none of the enabled modules or the active theme implement any preprocess
* or process functions or override this theme implementation.
* the l() function includes an inline copy of this function, and uses that
* copy if none of the enabled modules or the active theme implement any
* preprocess or process functions or override this theme implementation.
*
* @param $variables
* An associative array containing the keys 'text', 'path', and 'options'. See
* the l() function for information about these variables.
* An associative array containing the keys 'text', 'path', and 'options'.
* See the l() function for information about these variables.
*
* @see l()
*/
......@@ -1375,32 +1639,36 @@ function theme_link($variables) {
*
* @param $variables
* An associative array containing:
* - links: A keyed 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 following
* keys:
* - title: the link text
* - href: the link URL. If omitted, the 'title' is shown as a plain text
* - 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
* following elements:
* - title: The link text.
* - href: The link URL. If omitted, the 'title' is shown as a plain text
* item in the links list.
* - html: (optional) set this to TRUE if 'title' is HTML so it will be
* escaped.
* Array items are passed on to the l() function's $options parameter when
* creating the link.
* - attributes: A keyed array of attributes.
* - heading: An optional keyed array or a string for a heading to precede the
* links. When using an array the following keys can be used:
* - text: the heading text
* - level: the heading level (e.g. 'h2', 'h3')
* - class: (optional) an array of the CSS classes for the heading
* - html: (optional) Whether or not 'title' is HTML. If set, the title
* will not be passed through check_plain().
* - attributes: (optional) Attributes for the anchor, or for the <span>
* tag used in its place if no 'href' is supplied. If element 'class' is
* included, it must be an array of one or more class names.
* If the 'href' element is supplied, the entire link array is passed to
* l() as its $options parameter.
* - attributes: A keyed array of attributes for the UL containing the
* list of links.
* - heading: (optional) A heading to precede the links. May be an
* associative array or a string. If it's an array, it can have the
* following elements:
* - text: The heading text.
* - level: The heading level (e.g. 'h2', 'h3').
* - class: (optional) An array of the CSS classes for the heading.
* When using a string it will be used as the text of the heading and the
* level will default to 'h2'.
*
* Headings should be used on navigation menus and any list of links that
* consistently appears on multiple pages. To make the heading invisible
* use the 'element-invisible' CSS class. Do not use 'display:none', which
* removes it from screen-readers and assistive technology. Headings allow
* screen-reader and keyboard only users to navigate to or skip the links.
* See http://juicystudio.com/article/screen-readers-display-none.php
* and http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
* level will default to 'h2'. Headings should be used on navigation menus
* and any list of links that consistently appears on multiple pages. To
* make the heading invisible use the 'element-invisible' CSS class. Do not
* use 'display:none', which removes it from screen-readers and assistive
* technology. Headings allow screen-reader and keyboard only users to
* navigate to or skip the links. See
* http://juicystudio.com/article/screen-readers-display-none.php and
* http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
*/
function theme_links($variables) {
$links = $variables['links'];
......@@ -1492,8 +1760,8 @@ function theme_links($variables) {
* attribute to be omitted in some cases. Therefore, this variable defaults
* to an empty string, but can be set to NULL for the attribute to be
* omitted. Usually, neither omission nor an empty string satisfies
* accessibility requirements, so it is strongly encouraged for code calling
* theme('image') to pass a meaningful value for this variable.
* accessibility requirements, so it is strongly encouraged for code
* calling theme('image') to pass a meaningful value for this variable.
* - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8
* - http://www.w3.org/TR/xhtml1/dtds.html
* - http://dev.w3.org/html5/spec/Overview.html#alt
......@@ -1750,14 +2018,15 @@ function theme_table($variables) {
*
* @param $variables
* An associative array containing:
* - style: Set to either 'asc' or 'desc', this determines which icon to show.
* - style: Set to either 'asc' or 'desc', this determines which icon to
* show.
*/
function theme_tablesort_indicator($variables) {
if ($variables['style'] == "asc") {
return theme('image', array('path' => 'misc/arrow-asc.png', 'alt' => t('sort ascending'), 'title' => t('sort ascending')));
return theme('image', array('path' => 'misc/arrow-asc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort ascending'), 'title' => t('sort ascending')));
}
else {
return theme('image', array('path' => 'misc/arrow-desc.png', 'alt' => t('sort descending'), 'title' => t('sort descending')));
return theme('image', array('path' => 'misc/arrow-desc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort descending'), 'title' => t('sort descending')));
}
}
......@@ -1803,17 +2072,23 @@ function theme_item_list($variables) {
$type = $variables['type'];
$attributes = $variables['attributes'];
// Only output the list container and title, if there are any list items.
// Check to see whether the block title exists before adding a header.
// Empty headers are not semantic and present accessibility challenges.
$output = '<div class="item-list">';
if (isset($title)) {
if (isset($title) && $title !== '') {
$output .= '<h3>' . $title . '</h3>';
}
if (!empty($items)) {
$output .= "<$type" . drupal_attributes($attributes) . '>';
$num_items = count($items);
foreach ($items as $i => $item) {
$i = 0;
foreach ($items as $item) {
$attributes = array();
$children = array();
$data = '';
$i++;
if (is_array($item)) {
foreach ($item as $key => $value) {
if ($key == 'data') {
......@@ -1834,10 +2109,10 @@ function theme_item_list($variables) {
// Render nested list.
$data .= theme_item_list(array('items' => $children, 'title' => NULL, 'type' => $type, 'attributes' => $attributes));
}
if ($i == 0) {
if ($i == 1) {
$attributes['class'][] = 'first';
}
if ($i == $num_items - 1) {
if ($i == $num_items) {
$attributes['class'][] = 'last';
}
$output .= '<li' . drupal_attributes($attributes) . '>' . $data . "</li>\n";
......@@ -1853,7 +2128,7 @@ function theme_item_list($variables) {
*
* @param $variables
* An associative array containing:
* - url: The url for the link.
* - url: The URL for the link.
*/
function theme_more_help_link($variables) {
return '<div class="more-help-link">' . l(t('More help'), $variables['url']) . '</div>';
......@@ -1864,12 +2139,13 @@ function theme_more_help_link($variables) {
*
* @param $variables
* An associative array containing:
* - url: The url of the feed.
* - url: An internal system path or a fully qualified external URL of the
* feed.
* - title: A descriptive title of the feed.
*/
function theme_feed_icon($variables) {
$text = t('Subscribe to @feed-title', array('@feed-title' => $variables['title']));
if ($image = theme('image', array('path' => 'misc/feed.png', 'alt' => $text))) {
$text = t('Subscribe to !feed-title', array('!feed-title' => $variables['title']));
if ($image = theme('image', array('path' => 'misc/feed.png', 'width' => 16, 'height' => 16, 'alt' => $text))) {
return l($image, $variables['url'], array('html' => TRUE, 'attributes' => array('class' => array('feed-icon'), 'title' => $text)));
}
}
......@@ -1886,7 +2162,8 @@ function theme_feed_icon($variables) {
* - script: To load JavaScript.
* - #attributes: (optional) An array of HTML attributes to apply to the
* tag.
* - #value: (optional) A string containing tag content, such as inline CSS.
* - #value: (optional) A string containing tag content, such as inline
* CSS.
* - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA
* wrapper prefix.
* - #value_suffix: (optional) A string to append to #value, e.g. a CDATA
......@@ -1894,11 +2171,12 @@ function theme_feed_icon($variables) {
*/
function theme_html_tag($variables) {
$element = $variables['element'];
$attributes = isset($element['#attributes']) ? drupal_attributes($element['#attributes']) : '';
if (!isset($element['#value'])) {
return '<' . $element['#tag'] . drupal_attributes($element['#attributes']) . " />\n";
return '<' . $element['#tag'] . $attributes . " />\n";
}
else {
$output = '<' . $element['#tag'] . drupal_attributes($element['#attributes']) . '>';
$output = '<' . $element['#tag'] . $attributes . '>';
if (isset($element['#value_prefix'])) {
$output .= $element['#value_prefix'];
}
......@@ -1916,7 +2194,7 @@ function theme_html_tag($variables) {
*
* @param $variables
* An associative array containing:
* - url: The url of the main page.
* - url: The URL of the main page.
* - title: A descriptive verb for the link, like 'Read more'.
*/
function theme_more_link($variables) {
......@@ -1960,6 +2238,8 @@ function theme_username($variables) {
/**
* Returns HTML for a progress bar.
*
* Note that the core Batch API uses this only for non-JavaScript batch jobs.
*
* @param $variables
* An associative array containing:
* - percent: The percentage of the progress.
......@@ -1991,7 +2271,7 @@ function theme_indentation($variables) {
}
/**
* @} End of "ingroup themeable".
* @} End of "addtogroup themeable".
*/
/**
......@@ -2037,15 +2317,23 @@ function _theme_table_cell($cell, $header = FALSE) {
/**
* Adds a default set of helper variables for variable processors and templates.
* This comes in before any other preprocess function which makes it possible to
* be used in default theme implementations (non-overridden theme functions).
*
* This function is called for theme hooks implemented as templates only, not
* for theme hooks implemented as functions. This preprocess function is the
* first in the sequence of preprocessing and processing functions that is
* called when preparing variables for a template. See theme() for more details
* about the full sequence.
*
* @see theme()
* @see template_process()
*/
function template_preprocess(&$variables, $hook) {
global $user;
static $count = array();
// Track run count for each hook to provide zebra striping.
// See "template_preprocess_block()" which provides the same feature specific to blocks.
// Track run count for each hook to provide zebra striping. See
// "template_preprocess_block()" which provides the same feature specific to
// blocks.
$count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1;
$variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even';
$variables['id'] = $count[$hook]++;
......@@ -2074,7 +2362,7 @@ function template_preprocess(&$variables, $hook) {
}
/**
* Returns hook-independant variables to template_preprocess().
* Returns hook-independent variables to template_preprocess().
*/
function _template_preprocess_default_variables() {
global $user;
......@@ -2115,7 +2403,19 @@ function _template_preprocess_default_variables() {
}
/**
* A default process function used to alter variables as late as possible.
* Adds helper variables derived from variables defined during preprocessing.
*
* When preparing variables for a theme hook implementation, all 'preprocess'
* functions run first, then all 'process' functions (see theme() for details
* about the full sequence).
*
* This function serializes array variables manipulated during the preprocessing
* phase into strings for convenient use by templates. As with
* template_preprocess(), this function does not get called for theme hooks
* implemented as functions.
*
* @see theme()
* @see template_preprocess()
*/
function template_process(&$variables, $hook) {
// Flatten out classes.
......@@ -2192,14 +2492,18 @@ function template_preprocess_html(&$variables) {
// Construct page title.
if (drupal_get_title()) {
$head_title = array(strip_tags(drupal_get_title()), check_plain(variable_get('site_name', 'Drupal')));
$head_title = array(
'title' => strip_tags(drupal_get_title()),
'name' => check_plain(variable_get('site_name', 'Drupal')),
);
}
else {
$head_title = array(check_plain(variable_get('site_name', 'Drupal')));
$head_title = array('name' => check_plain(variable_get('site_name', 'Drupal')));
if (variable_get('site_slogan', '')) {
$head_title[] = filter_xss_admin(variable_get('site_slogan', ''));
$head_title['slogan'] = filter_xss_admin(variable_get('site_slogan', ''));
}
}
$variables['head_title_array'] = $head_title;
$variables['head_title'] = implode(' | ', $head_title);
// Populate the page template suggestions.
......@@ -2250,13 +2554,12 @@ function template_preprocess_page(&$variables) {
$variables['language'] = $GLOBALS['language'];
$variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
$variables['logo'] = theme_get_setting('logo');
$variables['messages'] = $variables['show_messages'] ? theme('status_messages') : '';
$variables['main_menu'] = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array();
$variables['secondary_menu'] = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array();
$variables['action_links'] = menu_local_actions();
$variables['site_name'] = (theme_get_setting('toggle_name') ? filter_xss_admin(variable_get('site_name', 'Drupal')) : '');
$variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? filter_xss_admin(variable_get('site_slogan', '')) : '');
$variables['tabs'] = theme('menu_local_tasks');
$variables['tabs'] = menu_local_tabs();
if ($node = menu_get_object()) {
$variables['node'] = $node;
......@@ -2288,6 +2591,12 @@ function template_process_page(&$variables) {
if (!isset($variables['title'])) {
$variables['title'] = drupal_get_title();
}
// Generate messages last in order to capture as many as possible for the
// current page.
if (!isset($variables['messages'])) {
$variables['messages'] = $variables['show_messages'] ? theme('status_messages') : '';
}
}
/**
......@@ -2352,8 +2661,19 @@ function theme_get_suggestions($args, $base, $delimiter = '__') {
$suggestions = array();
$prefix = $base;
foreach ($args as $arg) {
// Remove slashes or null per SA-CORE-2009-003.
$arg = str_replace(array("/", "\\", "\0"), '', $arg);
// Remove slashes or null per SA-CORE-2009-003 and change - (hyphen) to _
// (underscore).
//
// When we discover templates in @see drupal_find_theme_templates,
// hyphens (-) are converted to underscores (_) before the theme hook
// is registered. We do this because the hyphens used for delimiters
// in hook suggestions cannot be used in the function names of the
// associated preprocess functions. Any page templates designed to be used
// on paths that contain a hyphen are also registered with these hyphens
// converted to underscores so here we must convert any hyphens in path
// arguments to underscores here before fetching theme hook suggestions
// to ensure the templates are appropriately recognized.
$arg = str_replace(array("/", "\\", "\0", '-'), array('', '', '', '_'), $arg);
// The percent acts as a wildcard for numeric arguments since
// asterisks are not valid filename characters on many filesystems.
if (is_numeric($arg)) {
......@@ -2373,13 +2693,13 @@ function theme_get_suggestions($args, $base, $delimiter = '__') {
}
/**
* The variables array generated here is a mirror of template_preprocess_page().
* This preprocessor will run its course when theme_maintenance_page() is
* invoked.
* Process variables for maintenance-page.tpl.php.
*
* An alternate template file of "maintenance-page--offline.tpl.php" can be
* used when the database is offline to hide errors and completely replace the
* content.
* The variables array generated here is a mirror of
* template_preprocess_page(). This preprocessor will run its course when
* theme_maintenance_page() is invoked. An alternate template file of
* maintenance-page--offline.tpl.php can be used when the database is offline to
* hide errors and completely replace the content.
*
* The $variables array contains the following arguments:
* - $content
......@@ -2417,18 +2737,22 @@ function template_preprocess_maintenance_page(&$variables) {
// Construct page title
if (drupal_get_title()) {
$head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal'));
$head_title = array(
'title' => strip_tags(drupal_get_title()),
'name' => variable_get('site_name', 'Drupal'),
);
}
else {
$head_title = array(variable_get('site_name', 'Drupal'));
$head_title = array('name' => variable_get('site_name', 'Drupal'));
if (variable_get('site_slogan', '')) {
$head_title[] = variable_get('site_slogan', '');
$head_title['slogan'] = variable_get('site_slogan', '');
}
}
// set the default language if necessary
$language = isset($GLOBALS['language']) ? $GLOBALS['language'] : language_default();
$variables['head_title_array'] = $head_title;
$variables['head_title'] = implode(' | ', $head_title);
$variables['base_path'] = base_path();
$variables['front_page'] = url();
......@@ -2445,7 +2769,6 @@ function template_preprocess_maintenance_page(&$variables) {
$variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : '');
$variables['tabs'] = '';
$variables['title'] = drupal_get_title();
$variables['closure'] = '';
// Compile a list of classes that are going to be applied to the body element.
$variables['classes_array'][] = 'in-maintenance';
......@@ -2470,10 +2793,13 @@ function template_preprocess_maintenance_page(&$variables) {
}
/**
* Theme process function for theme_maintenance_field().
*
* The variables array generated here is a mirror of template_process_html().
* This processor will run its course when theme_maintenance_page() is invoked.
*
* @see maintenance-page.tpl.php
* @see template_process_html()
*/
function template_process_maintenance_page(&$variables) {
$variables['head'] = drupal_get_html_head();
......@@ -2485,7 +2811,7 @@ function template_process_maintenance_page(&$variables) {
/**
* Preprocess variables for region.tpl.php
*
* Prepare the values passed to the theme_region function to be passed into a
* Prepares the values passed to the theme_region function to be passed into a
* pluggable template engine. Uses the region name to generate a template file
* suggestions. If none are found, the default region.tpl.php is used.
*
......
<?php
// $Id: theme.maintenance.inc,v 1.67 2010/09/19 18:10:41 dries Exp $
/**
* @file
......@@ -11,9 +10,9 @@
*
* Used for site installs, updates and when the site is in maintenance mode.
* It also applies when the database is unavailable or bootstrap was not
* complete. Seven is always used for the initial install and update operations.
* In other cases, Bartik is used, but this can be overridden by setting a
* "maintenance_theme" key in the $conf variable in settings.php.
* complete. Seven is always used for the initial install and update
* operations. In other cases, Bartik is used, but this can be overridden by
* setting a "maintenance_theme" key in the $conf variable in settings.php.
*/
function _drupal_maintenance_theme() {
global $theme, $theme_key, $conf;
......@@ -41,7 +40,6 @@ function _drupal_maintenance_theme() {
// to work. See _drupal_log_error().
if (!class_exists('Database', FALSE)) {
require_once DRUPAL_ROOT . '/includes/database/database.inc';
spl_autoload_register('db_autoload');
}
// We use the default theme as the maintenance theme. If a default theme
......@@ -87,7 +85,7 @@ function _drupal_maintenance_theme() {
}
/**
* This builds the registry when the site needs to bypass any database calls.
* Builds the registry when the site needs to bypass any database calls.
*/
function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
return _theme_build_registry($theme, $base_theme, $theme_engine);
......@@ -162,7 +160,7 @@ function theme_update_page($variables) {
}
/**
* Returns HTML for a report of the results from an operation run via authorize.php.
* Returns HTML for a results report of an operation run by authorize.php.
*
* @param $variables
* An associative array containing:
......@@ -176,13 +174,14 @@ function theme_authorize_report($variables) {
if (!empty($messages)) {
$output .= '<div id="authorize-results">';
foreach ($messages as $heading => $logs) {
$output .= '<h3>' . check_plain($heading) . '</h3>';
$items = array();
foreach ($logs as $number => $log_message) {
if ($number === '#abort') {
continue;
}
$output .= theme('authorize_message', array('message' => $log_message['message'], 'success' => $log_message['success']));
$items[] = theme('authorize_message', array('message' => $log_message['message'], 'success' => $log_message['success']));
}
$output .= theme('item_list', array('items' => $items, 'title' => $heading));
}
$output .= '</div>';
}
......@@ -200,14 +199,13 @@ function theme_authorize_report($variables) {
* @ingroup themeable
*/
function theme_authorize_message($variables) {
$output = '';
$message = $variables['message'];
$success = $variables['success'];
if ($success) {
$output .= '<li class="success">' . $message . '</li>';
$item = array('data' => $message, 'class' => array('success'));
}
else {
$output .= '<li class="failure"><strong>' . t('Failed') . ':</strong> ' . $message . '</li>';
$item = array('data' => '<strong>' . $message . '</strong>', 'class' => array('failure'));
}
return $output;
return $item;
}
<?php
// $Id: token.inc,v 1.10 2010/10/18 01:13:07 dries Exp $
/**
* @file
* Drupal placeholder/token replacement system.
*
* Provides a set of extensible API functions for replacing placeholders in text
* with meaningful values.
* API functions for replacing placeholders in text with meaningful values.
*
* For example: When configuring automated emails, an administrator enters standard
* text for the email. Variables like the title of a node and the date the email
* was sent can be entered as placeholders like [node:title] and [date:short].
* When a Drupal module prepares to send the email, it can call the token_replace()
* function, passing in the text. The token system will scan the text for placeholder
* tokens, give other modules an opportunity to replace them with meaningful text,
* then return the final product to the original module.
* For example: When configuring automated emails, an administrator enters
* standard text for the email. Variables like the title of a node and the date
* the email was sent can be entered as placeholders like [node:title] and
* [date:short]. When a Drupal module prepares to send the email, it can call
* the token_replace() function, passing in the text. The token system will
* scan the text for placeholder tokens, give other modules an opportunity to
* replace them with meaningful text, then return the final product to the
* original module.
*
* Tokens follow the form: [$type:$name], where $type is a general class of
* tokens like 'node', 'user', or 'comment' and $name is the name of a given
* placeholder. For example, [node:title].
* placeholder. For example, [node:title] or [node:created:since].
*
* In addition to raw text containing placeholders, modules may pass in an array
* of objects to be used when performing the replacement. The objects should be
......@@ -38,8 +37,8 @@
* Some tokens may be chained in the form of [$type:$pointer:$name], where $type
* is a normal token type, $pointer is a reference to another token type, and
* $name is the name of a given placeholder. For example, [node:author:mail]. In
* that example, 'author' is a pointer to the 'user' account that created the node,
* and 'mail' is a placeholder available for any 'user'.
* that example, 'author' is a pointer to the 'user' account that created the
* node, and 'mail' is a placeholder available for any 'user'.
*
* @see token_replace()
* @see hook_tokens()
......@@ -47,7 +46,7 @@
*/
/**
* Replace all tokens in a given string with appropriate values.
* Replaces all tokens in a given string with appropriate values.
*
* @param $text
* A string potentially containing replaceable tokens.
......@@ -55,28 +54,36 @@
* (optional) An array of keyed objects. For simple replacement scenarios
* 'node', 'user', and others are common keys, with an accompanying node or
* user object being the value. Some token types, like 'site', do not require
* any explicit information from $data and can be replaced even if it is empty.
* any explicit information from $data and can be replaced even if it is
* empty.
* @param $options
* (optional) A keyed array of settings and flags to control the token
* replacement process. Supported options are:
* - language: A language object to be used when generating locale-sensitive
* tokens.
* - callback: A callback function that will be used to post-process the array
* of token replacements after they are generated. For example, a module using
* tokens in a text-only email might provide a callback to strip HTML
* of token replacements after they are generated. For example, a module
* using tokens in a text-only email might provide a callback to strip HTML
* entities from token values before they are inserted into the final text.
* - clear: A boolean flag indicating that tokens should be removed from the
* final text if no replacement value can be generated.
* - sanitize: A boolean flag indicating that tokens should be sanitized for
* display to a web browser. Defaults to TRUE. Developers who set this option
* to FALSE assume responsibility for running filter_xss(), check_plain() or
* other appropriate scrubbing functions before displaying data to users.
* display to a web browser. Defaults to TRUE. Developers who set this
* option to FALSE assume responsibility for running filter_xss(),
* check_plain() or other appropriate scrubbing functions before displaying
* data to users.
*
* @return
* 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, '');
......@@ -96,17 +103,25 @@ function token_replace($text, array $data = array(), array $options = array()) {
}
/**
* Build a list of all token-like patterns that appear in the text.
* Builds a list of all token-like patterns that appear in the text.
*
* @param $text
* The text to be scanned for possible tokens.
*
* @return
* An associative array of discovered tokens, grouped by type.
*/
function token_scan($text) {
// Matches tokens with the following pattern: [$type:$token]
// $type and $token may not contain white spaces.
preg_match_all('/\[([^\s\]:]*):([^\s\]]*)\]/', $text, $matches);
// Matches tokens with the following pattern: [$type:$name]
// $type and $name may not contain [ ] characters.
// $type may not contain : or whitespace characters, but $name may.
preg_match_all('/
\[ # [ - pattern start
([^\s\[\]:]*) # match $type not containing whitespace : [ or ]
: # : - separator
([^\[\]]*) # match $name not containing [ or ]
\] # ] - pattern end
/x', $text, $matches);
$types = $matches[1];
$tokens = $matches[2];
......@@ -123,7 +138,7 @@ function token_scan($text) {
}
/**
* Generate replacement values for a list of tokens.
* Generates replacement values for a list of tokens.
*
* @param $type
* The type of token being replaced. 'node', 'user', and 'date' are common.
......@@ -134,20 +149,22 @@ function token_scan($text) {
* (optional) An array of keyed objects. For simple replacement scenarios
* 'node', 'user', and others are common keys, with an accompanying node or
* user object being the value. Some token types, like 'site', do not require
* any explicit information from $data and can be replaced even if it is empty.
* any explicit information from $data and can be replaced even if it is
* empty.
* @param $options
* (optional) A keyed array of settings and flags to control the token
* replacement process. Supported options are:
* - 'language' A language object to be used when generating locale-sensitive
* - language: A language object to be used when generating locale-sensitive
* tokens.
* - 'callback' A callback function that will be used to post-process the array
* of token replacements after they are generated. Can be used when modules
* require special formatting of token text, for example URL encoding or
* truncation to a specific length.
* - 'sanitize' A boolean flag indicating that tokens should be sanitized for
* - callback: A callback function that will be used to post-process the
* array of token replacements after they are generated. Can be used when
* modules require special formatting of token text, for example URL
* encoding or truncation to a specific length.
* - sanitize: A boolean flag indicating that tokens should be sanitized for
* display to a web browser. Developers who set this option to FALSE assume
* responsibility for running filter_xss(), check_plain() or other
* appropriate scrubbing functions before displaying data to users.
*
* @return
* An associative array of replacement values, keyed by the original 'raw'
* tokens that were found in the source text. For example:
......@@ -173,15 +190,15 @@ function token_generate($type, array $tokens, array $data = array(), array $opti
}
/**
* Given a list of tokens, return those that begin with a specific prefix.
* Returns a list of tokens that begin with a specific prefix.
*
* Used to extract a group of 'chained' tokens (such as [node:author:name]) from
* the full list of tokens found in text. For example:
* Used to extract a group of 'chained' tokens (such as [node:author:name])
* from the full list of tokens found in text. For example:
* @code
* $data = array(
* 'author:name' => '[node:author:name]',
* 'title' => '[node:title]',
* 'created' => '[node:author:name]',
* 'created' => '[node:created]',
* );
* $results = token_find_with_prefix($data, 'author');
* $results == array('name' => '[node:author:name]');
......@@ -194,6 +211,7 @@ function token_generate($type, array $tokens, array $data = array(), array $opti
* @param $delimiter
* An optional string containing the character that separates the prefix from
* the rest of the token. Defaults to ':'.
*
* @return
* An associative array of discovered tokens, with the prefix and delimiter
* stripped from the key.
......@@ -212,8 +230,10 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') {
/**
* Returns metadata describing supported tokens.
*
* The metadata array contains token type, name, and description data as well as
* an optional pointer indicating that the token chains to another set of tokens.
* The metadata array contains token type, name, and description data as well
* as an optional pointer indicating that the token chains to another set of
* tokens.
*
* For example:
* @code
* $data['types']['node'] = array(
......@@ -230,6 +250,7 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') {
* 'type' => 'user',
* );
* @endcode
*
* @return
* An associative array of token information, grouped by token type.
*/
......
<?php
// $Id: unicode.entities.inc,v 1.2 2009/04/26 15:14:55 dries Exp $
/**
* @file
......