diff options
Diffstat (limited to 'includes')
30 files changed, 618 insertions, 342 deletions
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 763aad6d7..50e8253bb 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -68,32 +68,32 @@ define('WATCHDOG_EMERGENCY', 0); define('WATCHDOG_ALERT', 1); /** - * Log message severity -- Critical: critical conditions. + * Log message severity -- Critical conditions. */ define('WATCHDOG_CRITICAL', 2); /** - * Log message severity -- Error: error conditions. + * Log message severity -- Error conditions. */ define('WATCHDOG_ERROR', 3); /** - * Log message severity -- Warning: warning conditions. + * Log message severity -- Warning conditions. */ define('WATCHDOG_WARNING', 4); /** - * Log message severity -- Notice: normal but significant condition. + * Log message severity -- Normal but significant conditions. */ define('WATCHDOG_NOTICE', 5); /** - * Log message severity -- Informational: informational messages. + * Log message severity -- Informational messages. */ define('WATCHDOG_INFO', 6); /** - * Log message severity -- Debug: debug-level messages. + * Log message severity -- Debug-level messages. */ define('WATCHDOG_DEBUG', 7); @@ -279,7 +279,7 @@ define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*' * means that assigning an offset via arrayAccess will only apply while the * object is in scope and will not be written back to the persistent cache. * This follows a similar pattern to static vs. persistent caching in - * procedural code. Extending classes may wish to alter this behaviour, for + * procedural code. Extending classes may wish to alter this behavior, for * example by overriding offsetSet() and adding an automatic call to persist(). * * @see SchemaCache @@ -966,7 +966,7 @@ function variable_initialize($conf = array()) { * The default value to use if this variable has never been set. * * @return - * The value of the variable. + * The value of the variable. Unserialization is taken care of as necessary. * * @see variable_del() * @see variable_set() @@ -1691,8 +1691,16 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia * NULL if message is already translated or not possible to * translate. * @param $severity - * The severity of the message, as per RFC 3164. Possible values are - * WATCHDOG_ERROR, WATCHDOG_WARNING, etc. + * The severity of the message; one of the following values as defined in + * @link http://www.faqs.org/rfcs/rfc3164.html RFC 3164: @endlink + * - WATCHDOG_EMERGENCY: Emergency, system is unusable. + * - WATCHDOG_ALERT: Alert, action must be taken immediately. + * - WATCHDOG_CRITICAL: Critical conditions. + * - WATCHDOG_ERROR: Error conditions. + * - WATCHDOG_WARNING: Warning conditions. + * - WATCHDOG_NOTICE: (default) Normal but significant conditions. + * - WATCHDOG_INFO: Informational messages. + * - WATCHDOG_DEBUG: Debug-level messages. * @param $link * A link to associate with the message. * @@ -1709,6 +1717,9 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO if (!$in_error_state && function_exists('module_implements')) { $in_error_state = TRUE; + // The user object may not exist in all conditions, so 0 is substituted if needed. + $user_uid = isset($user->uid) ? $user->uid : 0; + // Prepare the fields to be logged $log_entry = array( 'type' => $type, @@ -1717,6 +1728,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO 'severity' => $severity, 'link' => $link, 'user' => $user, + 'uid' => $user_uid, 'request_uri' => $base_root . request_uri(), 'referer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '', 'ip' => ip_address(), @@ -1913,7 +1925,7 @@ function drupal_block_denied($ip) { */ function drupal_random_bytes($count) { // $random_state does not use drupal_static as it stores random bytes. - static $random_state, $bytes; + static $random_state, $bytes, $php_compatible; // Initialize on the first call. The contents of $_SERVER includes a mix of // user-specific and system information that varies a little with each page. if (!isset($random_state)) { @@ -1925,6 +1937,11 @@ function drupal_random_bytes($count) { $bytes = ''; } if (strlen($bytes) < $count) { + // PHP versions prior 5.3.4 experienced openssl_random_pseudo_bytes() + // locking on Windows and rendered it unusable. + if (!isset($php_compatible)) { + $php_compatible = version_compare(PHP_VERSION, '5.3.4', '>='); + } // /dev/urandom is available on many *nix systems and is considered the // best commonly available pseudo-random source. if ($fh = @fopen('/dev/urandom', 'rb')) { @@ -1934,6 +1951,11 @@ function drupal_random_bytes($count) { $bytes .= fread($fh, max(4096, $count)); fclose($fh); } + // openssl_random_pseudo_bytes() will find entropy in a system-dependent + // way. + elseif ($php_compatible && function_exists('openssl_random_pseudo_bytes')) { + $bytes .= openssl_random_pseudo_bytes($count - strlen($bytes)); + } // If /dev/urandom is not available or returns no bytes, this loop will // generate a good set of pseudo-random bytes on any system. // Note that it may be important that our $random_state is passed @@ -2428,7 +2450,8 @@ function drupal_valid_test_ua() { } } - return FALSE; + $test_prefix = FALSE; + return $test_prefix; } /** @@ -3069,11 +3092,30 @@ function registry_rebuild() { * to be called, because it is already known that the list of files in the * {system} table matches those in the file system. * + * @return + * TRUE if the registry was rebuilt, FALSE if another thread was rebuilding + * in parallel and the current thread just waited for completion. + * * @see registry_rebuild() */ function registry_update() { + // install_system_module() calls module_enable() which calls into this + // function during initial system installation, so the lock system is neither + // loaded nor does its storage exist yet. + $in_installer = drupal_installation_attempted(); + if (!$in_installer && !lock_acquire(__FUNCTION__)) { + // Another request got the lock, wait for it to finish. + lock_wait(__FUNCTION__); + return FALSE; + } + require_once DRUPAL_ROOT . '/includes/registry.inc'; _registry_update(); + + if (!$in_installer) { + lock_release(__FUNCTION__); + } + return TRUE; } /** diff --git a/includes/cache.inc b/includes/cache.inc index eb7f09040..a19d3c38c 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -379,11 +379,31 @@ class DrupalDatabaseCache implements DrupalCacheInterface { * The bin being requested. */ protected function garbageCollection() { - global $user; + $cache_lifetime = variable_get('cache_lifetime', 0); - // Garbage collection necessary when enforcing a minimum cache lifetime. + // Clean-up the per-user cache expiration session data, so that the session + // handler can properly clean-up the session data for anonymous users. + if (isset($_SESSION['cache_expiration'])) { + $expire = REQUEST_TIME - $cache_lifetime; + foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) { + if ($timestamp < $expire) { + unset($_SESSION['cache_expiration'][$bin]); + } + } + if (!$_SESSION['cache_expiration']) { + unset($_SESSION['cache_expiration']); + } + } + + // Garbage collection of temporary items is only necessary when enforcing + // a minimum cache lifetime. + if (!$cache_lifetime) { + return; + } + // When cache lifetime is in force, avoid running garbage collection too + // often since this will remove temporary cache items indiscriminately. $cache_flush = variable_get('cache_flush_' . $this->bin, 0); - if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) { + if ($cache_flush && ($cache_flush + $cache_lifetime <= REQUEST_TIME)) { // Reset the variable immediately to prevent a meltdown in heavy load situations. variable_set('cache_flush_' . $this->bin, 0); // Time to flush old cache data @@ -413,17 +433,16 @@ class DrupalDatabaseCache implements DrupalCacheInterface { if (!isset($cache->data)) { return FALSE; } - // If enforcing a minimum cache lifetime, validate that the data is - // currently valid for this user before we return it by making sure the cache - // entry was created before the timestamp in the current session's cache - // timer. The cache variable is loaded into the $user object by _drupal_session_read() - // in session.inc. If the data is permanent or we're not enforcing a minimum - // cache lifetime always return the cached data. - if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && $user->cache > $cache->created) { - // This cache data is too old and thus not valid for us, ignore it. + // If the cached data is temporary and subject to a per-user minimum + // lifetime, compare the cache entry timestamp with the user session + // cache_expiration timestamp. If the cache entry is too old, ignore it. + if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && isset($_SESSION['cache_expiration'][$this->bin]) && $_SESSION['cache_expiration'][$this->bin] > $cache->created) { + // Ignore cache data that is too old and thus not valid for this user. return FALSE; } + // If the data is permanent or not subject to a minimum cache lifetime, + // unserialize and return the cached data. if ($cache->serialized) { $cache->data = unserialize($cache->data); } @@ -468,11 +487,10 @@ class DrupalDatabaseCache implements DrupalCacheInterface { if (empty($cid)) { if (variable_get('cache_lifetime', 0)) { - // We store the time in the current user's $user->cache variable which - // will be saved into the sessions bin by _drupal_session_write(). We then - // simulate that the cache was flushed for this user by not returning - // cached data that was cached before the timestamp. - $user->cache = REQUEST_TIME; + // We store the time in the current user's session. We then simulate + // that the cache was flushed for this user by not returning cached + // data that was cached before the timestamp. + $_SESSION['cache_expiration'][$this->bin] = REQUEST_TIME; $cache_flush = variable_get('cache_flush_' . $this->bin, 0); if ($cache_flush == 0) { diff --git a/includes/common.inc b/includes/common.inc index 43e211813..f3c936e95 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -532,8 +532,8 @@ function drupal_get_destination() { * Parses a system URL string into an associative array suitable for url(). * * This function should only be used for URLs that have been generated by the - * system, resp. url(). It should not be used for URLs that come from external - * sources, or URLs that link to external resources. + * system, such as via url(). It should not be used for URLs that come from + * external sources, or URLs that link to external resources. * * The returned array contains a 'path' that may be passed separately to url(). * For example: @@ -1734,7 +1734,8 @@ function format_plural($count, $singular, $plural, array $args = array(), array // Get the plural index through the gettext formula. $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1; - // Backwards compatibility. + // If the index cannot be computed, use the plural as a fallback (which + // allows for most flexiblity with the replaceable @count value). if ($index < 0) { return t($plural, $args, $options); } @@ -2821,7 +2822,7 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { * - 'group': A number identifying the group in which to add the stylesheet. * Available constants are: * - CSS_SYSTEM: Any system-layer CSS. - * - CSS_DEFAULT: Any module-layer CSS. + * - CSS_DEFAULT: (default) Any module-layer CSS. * - CSS_THEME: Any theme-layer CSS. * The group number serves as a weight: the markup for loading a stylesheet * within a lower weight group is output to the page before the markup for @@ -2969,6 +2970,18 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) { // Sort CSS items, so that they appear in the correct order. uasort($css, 'drupal_sort_css_js'); + // Provide the page with information about the individual CSS files used, + // information not otherwise available when CSS aggregation is enabled. The + // setting is attached later in this function, but is set here, so that CSS + // files removed below are still considered "used" and prevented from being + // added in a later AJAX request. + // Skip if no files were added to the page or jQuery.extend() will overwrite + // the Drupal.settings.ajaxPageState.css object with an empty array. + if (!empty($css)) { + // Cast the array to an object to be on the safe side even if not empty. + $setting['ajaxPageState']['css'] = (object) array_fill_keys(array_keys($css), 1); + } + // Remove the overridden CSS files. Later CSS files override former ones. $previous_item = array(); foreach ($css as $key => $item) { @@ -2989,10 +3002,9 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) { '#items' => $css, ); - // Provide the page with information about the individual CSS files used, - // information not otherwise available when CSS aggregation is enabled. - $setting['ajaxPageState']['css'] = array_fill_keys(array_keys($css), 1); - $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting); + if (!empty($setting)) { + $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting); + } return drupal_render($styles); } @@ -3443,7 +3455,13 @@ function drupal_build_css_cache($css) { $data = ''; $uri = ''; $map = variable_get('drupal_css_cache_files', array()); - $key = hash('sha256', serialize($css)); + // Create a new array so that only the file names are used to create the hash. + // This prevents new aggregates from being created unnecessarily. + $css_data = array(); + foreach ($css as $css_file) { + $css_data[] = $css_file['data']; + } + $key = hash('sha256', serialize($css_data)); if (isset($map[$key])) { $uri = $map[$key]; } @@ -4804,7 +4822,13 @@ function drupal_build_js_cache($files) { $contents = ''; $uri = ''; $map = variable_get('drupal_js_cache_files', array()); - $key = hash('sha256', serialize($files)); + // Create a new array so that only the file names are used to create the hash. + // This prevents new aggregates from being created unnecessarily. + $js_data = array(); + foreach ($files as $file) { + $js_data[] = $file['data']; + } + $key = hash('sha256', serialize($js_data)); if (isset($map[$key])) { $uri = $map[$key]; } @@ -5219,8 +5243,6 @@ function drupal_cron_cleanup() { function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { $config = conf_path(); - $profile = drupal_get_profile(); - $searchdir = array($directory); $files = array(); @@ -5228,8 +5250,24 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) // themes as organized by a distribution. It is pristine in the same way // that /modules is pristine for core; users should avoid changing anything // there in favor of sites/all or sites/<domain> directories. - if (file_exists("profiles/$profile/$directory")) { - $searchdir[] = "profiles/$profile/$directory"; + $profiles = array(); + $profile = drupal_get_profile(); + // For SimpleTest to be able to test modules packaged together with a + // distribution we need to include the profile of the parent site (in which + // test runs are triggered). + if (drupal_valid_test_ua()) { + $testing_profile = variable_get('simpletest_parent_profile', FALSE); + if ($testing_profile && $testing_profile != $profile) { + $profiles[] = $testing_profile; + } + } + // In case both profile directories contain the same extension, the actual + // profile always has precedence. + $profiles[] = $profile; + foreach ($profiles as $profile) { + if (file_exists("profiles/$profile/$directory")) { + $searchdir[] = "profiles/$profile/$directory"; + } } // Always search sites/all/* as well as the global directories. diff --git a/includes/database/database.inc b/includes/database/database.inc index 6e40b2765..6efe298d2 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1430,9 +1430,6 @@ abstract class Database { /** * Gets the connection object for the specified database key and target. * - * Note: do not use the setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE) on the - * returned object because of http://bugs.php.net/bug.php?id=43139. - * * @param $target * The database target name. * @param $key diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index e024a7f39..7278a2bc8 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -46,8 +46,6 @@ class DatabaseConnection_mysql extends DatabaseConnection { PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, // Because MySQL's prepared statements skip the query cache, because it's dumb. PDO::ATTR_EMULATE_PREPARES => TRUE, - // Force column names to lower case. - PDO::ATTR_CASE => PDO::CASE_LOWER, ); parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); diff --git a/includes/database/mysql/schema.inc b/includes/database/mysql/schema.inc index 4e88fa169..d6aea4d94 100644 --- a/includes/database/mysql/schema.inc +++ b/includes/database/mysql/schema.inc @@ -131,8 +131,13 @@ class DatabaseSchema_mysql extends DatabaseSchema { protected function createFieldSql($name, $spec) { $sql = "`" . $name . "` " . $spec['mysql_type']; - if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT')) && isset($spec['length'])) { - $sql .= '(' . $spec['length'] . ')'; + if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) { + if (isset($spec['length'])) { + $sql .= '(' . $spec['length'] . ')'; + } + if (!empty($spec['binary'])) { + $sql .= ' BINARY'; + } } elseif (isset($spec['precision']) && isset($spec['scale'])) { $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')'; @@ -381,7 +386,7 @@ class DatabaseSchema_mysql extends DatabaseSchema { // Returns one row for each column in the index. Result is string or FALSE. // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html $row = $this->connection->query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'")->fetchAssoc(); - return isset($row['key_name']); + return isset($row['Key_name']); } public function addPrimaryKey($table, $fields) { diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index d42a1cc3c..d80b47551 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -62,8 +62,6 @@ class DatabaseConnection_pgsql extends DatabaseConnection { PDO::ATTR_EMULATE_PREPARES => TRUE, // Convert numeric values to strings when fetching. PDO::ATTR_STRINGIFY_FETCHES => TRUE, - // Force column names to lower case. - PDO::ATTR_CASE => PDO::CASE_LOWER, ); parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']); diff --git a/includes/database/pgsql/schema.inc b/includes/database/pgsql/schema.inc index 9ed8a2620..49adbf907 100644 --- a/includes/database/pgsql/schema.inc +++ b/includes/database/pgsql/schema.inc @@ -328,9 +328,9 @@ class DatabaseSchema_pgsql extends DatabaseSchema { // rename them when renaming the table. $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name)); foreach ($indexes as $index) { - if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)_idx$/', $index->indexname, $matches)) { + if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) { $index_name = $matches[1]; - $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name . '_idx'); + $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name); } } diff --git a/includes/database/query.inc b/includes/database/query.inc index 6020b0ea5..750aea7a0 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -1898,7 +1898,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { function __clone() { $this->changed = TRUE; foreach ($this->conditions as $key => $condition) { - if ($condition['field'] instanceOf QueryConditionInterface) { + if ($key !== '#conjunction' && $condition['field'] instanceOf QueryConditionInterface) { $this->conditions[$key]['field'] = clone($condition['field']); } } diff --git a/includes/database/schema.inc b/includes/database/schema.inc index e2a1c4caa..d3943b29b 100644 --- a/includes/database/schema.inc +++ b/includes/database/schema.inc @@ -76,8 +76,13 @@ require_once dirname(__FILE__) . '/query.inc'; * the precision (total number of significant digits) and scale * (decimal digits right of the decimal point). Both values are * mandatory. Ignored for other field types. + * - 'binary': A boolean indicating that MySQL should force 'char', + * 'varchar' or 'text' fields to use case-sensitive binary collation. + * This has no effect on other database types for which case sensitivity + * is already the default behavior. * All parameters apart from 'type' are optional except that type - * 'numeric' columns must specify 'precision' and 'scale'. + * 'numeric' columns must specify 'precision' and 'scale', and type + * 'varchar' must specify the 'length' parameter. * - 'primary key': An array of one or more key column specifiers (see below) * that form the primary key. * - 'unique keys': An associative array of unique keys ('keyname' => diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index b4e41b527..ea91e9143 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -37,7 +37,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection { /** * All databases attached to the current database. This is used to allow * prefixes to be safely handled without locking the table - * + * * @var array */ protected $attachedDatabases = array(); @@ -46,10 +46,10 @@ class DatabaseConnection_sqlite extends DatabaseConnection { * Whether or not a table has been dropped this request: the destructor will * only try to get rid of unnecessary databases if there is potential of them * being empty. - * + * * This variable is set to public because DatabaseSchema_sqlite needs to * access it. However, it should not be manually set. - * + * * @var boolean */ var $tableDropped = FALSE; @@ -68,8 +68,6 @@ class DatabaseConnection_sqlite extends DatabaseConnection { 'pdo' => array(), ); $connection_options['pdo'] += array( - // Force column names to lower case. - PDO::ATTR_CASE => PDO::CASE_LOWER, // Convert numeric values to strings when fetching. PDO::ATTR_STRINGIFY_FETCHES => TRUE, ); diff --git a/includes/date.inc b/includes/date.inc index 27634f9e3..01ab131b3 100644 --- a/includes/date.inc +++ b/includes/date.inc @@ -2,7 +2,7 @@ /** * @file - * Initialize the list of date formats and their locales. + * Initializes the list of date formats and their locales. */ /** diff --git a/includes/entity.inc b/includes/entity.inc index 07ee06162..ae7807794 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -468,7 +468,7 @@ class EntityFieldQuery { * @var array * * @see EntityFieldQuery::fieldLanguageCondition() - * @see EntityFieldQuery::fielDeltaCondition() + * @see EntityFieldQuery::fieldDeltaCondition() */ public $fieldMetaConditions = array(); @@ -1106,12 +1106,13 @@ class EntityFieldQuery { * contains everything necessary for processing. * * @return - * Either a number if count() was called or an array of associative - * arrays of stub entities. The outer array keys are entity types, and the - * inner array keys are the relevant ID. (In most this cases this will be - * the entity ID. The only exception is when age=FIELD_LOAD_REVISION is used - * and field conditions or sorts are present -- in this case, the key will - * be the revision ID.) The inner array values are always stub entities, as + * Either a number if count() was called or an array of associative arrays + * of stub entities. The outer array keys are entity types, and the inner + * array keys are the relevant ID. (In most cases this will be the entity + * ID. The only exception is when age=FIELD_LOAD_REVISION is used and field + * conditions or sorts are present -- in this case, the key will be the + * revision ID.) The entity type will only exist in the outer array if + * results were found. The inner array values are always stub entities, as * returned by entity_create_stub_entity(). To traverse the returned array: * @code * foreach ($query->execute() as $entity_type => $entities) { @@ -1121,7 +1122,9 @@ class EntityFieldQuery { * the entities found: * @code * $result = $query->execute(); - * $entities = entity_load($my_type, array_keys($result[$my_type])); + * if (!empty($result[$my_type])) { + * $entities = entity_load($my_type, array_keys($result[$my_type])); + * } * @endcode */ public function execute() { @@ -1260,12 +1263,11 @@ class EntityFieldQuery { /** * Get the total number of results and initialize a pager for the query. * - * This query can be detected by checking for ($this->pager && $this->count), - * which allows a driver to return 0 from the count query and disable - * the pager. + * The pager can be disabled by either setting the pager limit to 0, or by + * setting this query to be a count query. */ function initializePager() { - if ($this->pager && !$this->count) { + if ($this->pager && !empty($this->pager['limit']) && !$this->count) { $page = pager_find_page($this->pager['element']); $count_query = clone $this; $this->pager['total'] = $count_query->count()->execute(); diff --git a/includes/errors.inc b/includes/errors.inc index cb708d860..f62bf06a5 100644 --- a/includes/errors.inc +++ b/includes/errors.inc @@ -2,7 +2,7 @@ /** * @file - * Functions for error handling + * Functions for error handling. */ /** @@ -21,7 +21,8 @@ define('ERROR_REPORTING_DISPLAY_SOME', 1); define('ERROR_REPORTING_DISPLAY_ALL', 2); /** - * Map PHP error constants to watchdog severity levels. + * Maps PHP error constants to watchdog severity levels. + * * The error constants are documented at * http://php.net/manual/en/errorfunc.constants.php * @@ -52,7 +53,7 @@ function drupal_error_levels() { } /** - * Custom PHP error handler. + * Provides custom PHP error handling. * * @param $error_level * The level of the error raised. @@ -63,7 +64,8 @@ function drupal_error_levels() { * @param $line * The line number the error was raised at. * @param $context - * An array that points to the active symbol table at the point the error occurred. + * An array that points to the active symbol table at the point the error + * occurred. */ function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) { if ($error_level & error_reporting()) { @@ -90,10 +92,11 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c } /** - * Decode an exception, especially to retrive the correct caller. + * Decodes an exception and retrieves the correct caller. * * @param $exception * The exception object that was thrown. + * * @return * An error in the format expected by _drupal_log_error(). */ @@ -136,7 +139,7 @@ function _drupal_decode_exception($exception) { } /** - * Render an error message for an exception without any possibility of a further exception occurring. + * Renders an exception error message without further exceptions. * * @param $exception * The exception object that was thrown. @@ -171,12 +174,12 @@ function error_displayable($error = NULL) { } /** - * Log a PHP error or exception, display an error page in fatal cases. + * Logs a PHP error or exception and displays an error page in fatal cases. * * @param $error * An array with the following keys: %type, !message, %function, %file, %line - * and severity_level. All the parameters are plain-text, with the exception of - * !message, which needs to be a safe HTML string. + * and severity_level. All the parameters are plain-text, with the exception + * of !message, which needs to be a safe HTML string. * @param $fatal * TRUE if the error is fatal. */ @@ -263,6 +266,7 @@ function _drupal_log_error($error, $fatal = FALSE) { * * @param $backtrace * A standard PHP backtrace. + * * @return * An associative array with keys 'file', 'line' and 'function'. */ diff --git a/includes/file.inc b/includes/file.inc index 676b32f15..c5e5cf07d 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -70,11 +70,7 @@ define('FILE_EXISTS_ERROR', 2); define('FILE_STATUS_PERMANENT', 1); /** - * Methods to manage a registry of stream wrappers. - */ - -/** - * Drupal stream wrapper registry. + * Provides Drupal stream wrapper registry. * * A stream wrapper is an abstraction of a file system that allows Drupal to * use the same set of methods to access both local files and remote resources. @@ -206,7 +202,7 @@ function file_uri_scheme($uri) { } /** - * Check that the scheme of a stream URI is valid. + * Checks that the scheme of a stream URI is valid. * * Confirms that there is a registered stream handler for the provided scheme * and that it is callable. This is useful if you want to confirm a valid @@ -232,7 +228,7 @@ function file_stream_wrapper_valid_scheme($scheme) { /** - * Returns the part of an URI after the schema. + * Returns the part of a URI after the schema. * * @param $uri * A stream, referenced as "scheme://target". @@ -252,7 +248,7 @@ function file_uri_target($uri) { } /** - * Get the default file stream implementation. + * Gets the default file stream implementation. * * @return * 'public', 'private' or any other file scheme defined as the default. @@ -322,7 +318,7 @@ function file_stream_wrapper_get_instance_by_uri($uri) { } /** - * Returns a reference to the stream wrapper class responsible for a given scheme. + * Returns a reference to the stream wrapper class responsible for a scheme. * * This helper method returns a stream instance using a scheme. That is, the * passed string does not contain a "://". For example, "public" is a scheme @@ -357,7 +353,6 @@ function file_stream_wrapper_get_instance_by_scheme($scheme) { * Creates a web-accessible URL for a stream to an external or local file. * * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 * * There are two kinds of local files: * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper. @@ -375,6 +370,8 @@ function file_stream_wrapper_get_instance_by_scheme($scheme) { * If the provided string already contains a preceding 'http', 'https', or * '/', nothing is done and the same string is returned. If a stream wrapper * could not be found to generate an external URL, then FALSE is returned. + * + * @see http://drupal.org/node/515192 */ function file_create_url($uri) { // Allow the URI to be altered, e.g. to serve a file from a CDN or static @@ -417,7 +414,7 @@ function file_create_url($uri) { } /** - * Check that the directory exists and is writable. + * Checks that the directory exists and is writable. * * Directories need to have execute permissions to be considered a directory by * FTP servers, etc. @@ -459,7 +456,7 @@ function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) } /** - * If missing, create a .htaccess file in each Drupal files directory. + * Creates a .htaccess file in each Drupal files directory if it is missing. */ function file_ensure_htaccess() { file_create_htaccess('public://', FALSE); @@ -470,7 +467,7 @@ function file_ensure_htaccess() { } /** - * Creates an .htaccess file in the given directory. + * Creates a .htaccess file in the given directory. * * @param $directory * The directory. @@ -526,25 +523,25 @@ function file_create_htaccess($directory, $private = TRUE) { * @return * An array of file objects, indexed by fid. * + * @todo Remove $conditions in Drupal 8. + * * @see hook_file_load() * @see file_load() * @see entity_load() * @see EntityFieldQuery - * - * @todo Remove $conditions in Drupal 8. */ function file_load_multiple($fids = array(), $conditions = array()) { return entity_load('file', $fids, $conditions); } /** - * Load a file object from the database. + * Loads a single file object from the database. * * @param $fid * A file ID. * * @return - * A file object. + * An object representing the file, or FALSE if the file was not found. * * @see hook_file_load() * @see file_load_multiple() @@ -555,7 +552,7 @@ function file_load($fid) { } /** - * Save a file object to the database. + * Saves a file object to the database. * * If the $file->fid is not set a new record will be added. * @@ -797,7 +794,7 @@ function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS } /** - * Determine whether the URI has a valid scheme for file API operations. + * Determines whether the URI has a valid scheme for file API operations. * * There must be a scheme and it must be a Drupal-provided scheme like * 'public', 'private', 'temporary', or an extension provided with @@ -926,7 +923,7 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST } /** - * Given a relative path, construct a URI into Drupal's default files location. + * Constructs a URI to Drupal's default files location given a relative path. */ function file_build_uri($path) { $uri = file_default_scheme() . '://' . $path; @@ -934,8 +931,7 @@ function file_build_uri($path) { } /** - * Determines the destination path for a file depending on how replacement of - * existing files should be handled. + * Determines the destination path for a file. * * @param $destination * A string specifying the desired final URI or filepath. @@ -972,7 +968,7 @@ function file_destination($destination, $replace) { } /** - * Move a file to a new location and update the file's database entry. + * Moves a file to a new location and update the file's database entry. * * Moving a file is performed by copying the file to the new location and then * deleting the original. @@ -1052,8 +1048,7 @@ function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS } /** - * Move a file to a new location without calling any hooks or making any - * changes to the database. + * Moves a file to a new location without database changes or hook invocation. * * @param $source * A string specifying the filepath or URI of the original file. @@ -1082,7 +1077,7 @@ function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXIST } /** - * Modify a filename as needed for security purposes. + * Modifies a filename as needed for security purposes. * * Munging a file name prevents unknown file extensions from masking exploit * files. When web servers such as Apache decide how to process a URL request, @@ -1146,7 +1141,7 @@ function file_munge_filename($filename, $extensions, $alerts = TRUE) { } /** - * Undo the effect of file_munge_filename(). + * Undoes the effect of file_munge_filename(). * * @param $filename * String with the filename to be unmunged. @@ -1159,7 +1154,7 @@ function file_unmunge_filename($filename) { } /** - * Create a full file path from a directory and filename. + * Creates a full file path from a directory and filename. * * If a file with the specified name already exists, an alternative will be * used. @@ -1214,7 +1209,7 @@ function file_create_filename($basename, $directory) { } /** - * Delete a file and its database record. + * Deletes a file and its database record. * * If the $force parameter is not TRUE, file_usage_list() will be called to * determine if the file is being used by any modules. If the file is being @@ -1269,8 +1264,7 @@ function file_delete(stdClass $file, $force = FALSE) { } /** - * Delete a file without calling any hooks or making any changes to the - * database. + * Deletes a file without database changes or hook invocations. * * This function should be used when the file to be deleted does not have an * entry recorded in the files table. @@ -1306,7 +1300,7 @@ function file_unmanaged_delete($path) { } /** - * Recursively delete all files and directories in the specified filepath. + * Deletes all files and directories in the specified filepath recursively. * * If the specified path is a directory then the function will call itself * recursively to process the contents. Once the contents have been removed the @@ -1344,7 +1338,7 @@ function file_unmanaged_delete_recursive($path) { } /** - * Determine total disk space used by a single user or the whole filesystem. + * Determines total disk space used by a single user or the whole filesystem. * * @param $uid * Optional. A user id, specifying NULL returns the total space used by all @@ -1581,7 +1575,6 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, * or open_basedir are enabled, so this function fills that gap. * * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 * * @param $filename * The filename of the uploaded file. @@ -1592,6 +1585,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, * TRUE on success, or FALSE on failure. * * @see move_uploaded_file() + * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_move_uploaded_file($filename, $uri) { @@ -1612,7 +1606,7 @@ function drupal_move_uploaded_file($filename, $uri) { } /** - * Check that a file meets the criteria specified by the validators. + * Checks that a file meets the criteria specified by the validators. * * After executing the validator callbacks specified hook_file_validate() will * also be called to allow other modules to report errors about the file. @@ -1647,10 +1641,11 @@ function file_validate(stdClass &$file, $validators = array()) { } /** - * Check for files with names longer than we can store in the database. + * Checks for files with names longer than we can store in the database. * * @param $file * A Drupal file object. + * * @return * An array. If the file name is too long, it will contain an error message. */ @@ -1667,7 +1662,7 @@ function file_validate_name_length(stdClass $file) { } /** - * Check that the filename ends with an allowed extension. + * Checks that the filename ends with an allowed extension. * * @param $file * A Drupal file object. @@ -1691,7 +1686,7 @@ function file_validate_extensions(stdClass $file, $extensions) { } /** - * Check that the file's size is below certain limits. + * Checks that the file's size is below certain limits. * * This check is not enforced for the user #1. * @@ -1730,7 +1725,7 @@ function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) { } /** - * Check that the file is recognized by image_get_info() as an image. + * Checks that the file is recognized by image_get_info() as an image. * * @param $file * A Drupal file object. @@ -1752,7 +1747,7 @@ function file_validate_is_image(stdClass $file) { } /** - * Verify that image dimensions are within the specified maximum and minimum. + * Verifies that image dimensions are within the specified maximum and minimum. * * Non-image files will be ignored. If a image toolkit is available the image * will be scaled to fit within the desired maximum dimensions. @@ -1810,7 +1805,7 @@ function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0, } /** - * Save a string to the specified destination and create a database file entry. + * Saves a file to the specified destination and creates a database entry. * * @param $data * A string containing the contents of the file. @@ -1874,7 +1869,7 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM } /** - * Save a string to the specified destination without invoking file API. + * Saves a string to the specified destination without invoking file API. * * This function is identical to file_save_data() except the file will not be * saved to the {file_managed} table and none of the file_* hooks will be @@ -1885,7 +1880,8 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM * @param $destination * A string containing the destination location. This must be a stream wrapper * URI. If no value is provided, a randomized name will be generated and the - * file will be saved using Drupal's default files scheme, usually "public://". + * file will be saved using Drupal's default files scheme, usually + * "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. @@ -1911,7 +1907,7 @@ function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EX } /** - * Transfer file using HTTP to client. + * Transfers a file to the client using HTTP. * * Pipes a file through Drupal to the client. * @@ -1953,7 +1949,7 @@ function file_transfer($uri, $headers) { * exists but no modules responded drupal_access_denied() will be returned. * If the file does not exist drupal_not_found() will be returned. * - * @see hook_file_download() + * @see system_menu() */ function file_download() { // Merge remainder of arguments from GET['q'], into relative file path. @@ -2068,7 +2064,7 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { } /** - * Determine the maximum file upload size by querying the PHP settings. + * Determines the maximum file upload size by querying the PHP settings. * * @return * A file size limit in bytes based on the PHP upload_max_filesize and @@ -2092,7 +2088,7 @@ function file_upload_max_size() { } /** - * Determine an Internet Media Type, or MIME type from a filename. + * Determines an Internet Media Type or MIME type from a filename. * * @param $uri * A string containing the URI, path, or filename. @@ -2121,7 +2117,7 @@ function file_get_mimetype($uri, $mapping = NULL) { } /** - * Set the permissions on a file or directory. + * Sets the permissions on a file or directory. * * This function will use the 'file_chmod_directory' and 'file_chmod_file' * variables for the default modes for directories and uploaded/generated @@ -2227,10 +2223,11 @@ function drupal_unlink($uri, $context = NULL) { * The absolute local filesystem path (with no symbolic links), or FALSE on * failure. * + * @todo This function is deprecated, and should be removed wherever possible. + * * @see DrupalStreamWrapperInterface::realpath() * @see http://php.net/manual/function.realpath.php * @ingroup php_wrappers - * @todo: This function is deprecated, and should be removed wherever possible. */ function drupal_realpath($uri) { // If this URI is a stream, pass it off to the appropriate stream wrapper. @@ -2257,7 +2254,6 @@ function drupal_realpath($uri) { * PHP's dirname() as a fallback. * * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 * * @param $uri * A URI or path. @@ -2266,6 +2262,7 @@ function drupal_realpath($uri) { * A string containing the directory name. * * @see dirname() + * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_dirname($uri) { @@ -2315,7 +2312,6 @@ function drupal_basename($uri, $suffix = NULL) { * is not provided, this function will make sure that Drupal's is used. * * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 * * @param $uri * A URI or pathname. @@ -2330,6 +2326,7 @@ function drupal_basename($uri, $suffix = NULL) { * Boolean TRUE on success, or FALSE on failure. * * @see mkdir() + * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { @@ -2346,7 +2343,7 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { } /** - * Remove a directory. + * Removes a directory. * * PHP's rmdir() is broken on Windows, as it can fail to remove a directory * when it has a read-only flag set. @@ -2383,7 +2380,6 @@ function drupal_rmdir($uri, $context = NULL) { * given a filepath. * * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 * * @param $directory * The directory where the temporary filename will be created. @@ -2395,6 +2391,7 @@ function drupal_rmdir($uri, $context = NULL) { * The new temporary filename, or FALSE on failure. * * @see tempnam() + * @see http://drupal.org/node/515192 * @ingroup php_wrappers */ function drupal_tempnam($directory, $prefix) { @@ -2417,7 +2414,7 @@ function drupal_tempnam($directory, $prefix) { } /** - * Get the path of system-appropriate temporary directory. + * Gets the path of system-appropriate temporary directory. */ function file_directory_temp() { $temporary_directory = variable_get('file_temporary_path', NULL); @@ -2474,6 +2471,7 @@ function file_directory_temp() { * * @param $file * A file object. + * * @return * An associative array of headers, as expected by file_transfer(). */ diff --git a/includes/form.inc b/includes/form.inc index 3d5f6f22e..3ef32ab41 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1,4 +1,8 @@ <?php + /** + * @file + * Functions for form and batch generation and processing. + */ /** * @defgroup forms Form builder functions @@ -90,7 +94,11 @@ */ /** - * Wrapper for drupal_build_form() for use when $form_state is not needed. + * Returns a renderable form array for a given form ID. + * + * This function should be used instead of drupal_build_form() when $form_state + * is not needed (i.e., when initially rendering the form) and is often + * used as a menu callback. * * @param $form_id * The unique string identifying the desired form. If a function with that @@ -98,7 +106,7 @@ * generate the same form (or very similar forms) using different $form_ids * can implement hook_forms(), which maps different $form_id values to the * proper form constructor function. Examples may be found in node_forms(), - * search_forms(), and user_forms(). + * and search_forms(). * @param ... * Any additional arguments are passed on to the functions called by * drupal_get_form(), including the unique form constructor function. For @@ -124,7 +132,7 @@ function drupal_get_form($form_id) { } /** - * Build and process a form based on a form id. + * Builds and process a form based on a form id. * * The form may also be retrieved from the cache if the form was built in a * previous page-load. The form is then passed on for processing, validation @@ -136,7 +144,7 @@ function drupal_get_form($form_id) { * generate the same form (or very similar forms) using different $form_ids * can implement hook_forms(), which maps different $form_id values to the * proper form constructor function. Examples may be found in node_forms(), - * search_forms(), and user_forms(). + * and search_forms(). * @param $form_state * An array which stores information about the form. This is passed as a * reference so that the caller can use it to examine what in the form changed @@ -207,8 +215,8 @@ function drupal_get_form($form_id) { * validation functions and submit functions use this array for nearly all * their decision making. (Note that * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#tree #tree @endlink - * determines whether the values are a flat array or an array whose structure - * parallels the $form array.) + * determines whether the values are a flat array or an array whose + * structure parallels the $form array.) * - input: The array of values as they were submitted by the user. These are * raw and unvalidated, so should not be used without a thorough * understanding of security implications. In almost all cases, code should @@ -377,7 +385,7 @@ function drupal_build_form($form_id, &$form_state) { } /** - * Retrieve default values for the $form_state array. + * Retrieves default values for the $form_state array. */ function form_state_defaults() { return array( @@ -422,7 +430,7 @@ function form_state_defaults() { * Modules that need to generate the same form (or very similar forms) * using different $form_ids can implement hook_forms(), which maps * different $form_id values to the proper form constructor function. Examples - * may be found in node_forms(), search_forms(), and user_forms(). + * may be found in node_forms() and search_forms(). * @param $form_state * A keyed array containing the current state of the form. * @param $old_form @@ -483,7 +491,7 @@ function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) { } /** - * Fetch a form from cache. + * Fetches a form from cache. */ function form_get_cache($form_build_id, &$form_state) { if ($cached = cache_get('form_' . $form_build_id, 'cache_form')) { @@ -514,7 +522,7 @@ function form_get_cache($form_build_id, &$form_state) { } /** - * Store a form in the cache. + * Stores a form in the cache. */ function form_set_cache($form_build_id, $form, $form_state) { // 6 hours cache life time for forms should be plenty. @@ -564,7 +572,7 @@ function form_state_keys_no_cache() { } /** - * Loads an include file and makes sure it is loaded whenever the form is processed. + * Ensures an include file is loaded loaded whenever the form is processed. * * Example: * @code @@ -628,7 +636,7 @@ function form_load_include(&$form_state, $type, $module, $name = NULL) { * Modules that need to generate the same form (or very similar forms) * using different $form_ids can implement hook_forms(), which maps * different $form_id values to the proper form constructor function. Examples - * may be found in node_forms(), search_forms(), and user_forms(). + * may be found in node_forms() and search_forms(). * @param $form_state * A keyed array containing the current state of the form. Most important is * the $form_state['values'] collection, a tree of data used to simulate the @@ -930,9 +938,10 @@ function drupal_process_form($form_id, &$form, &$form_state) { } /** - * Prepares a structured form array by adding required elements, - * executing any hook_form_alter functions, and optionally inserting - * a validation token to prevent tampering. + * Prepares a structured form array. + * + * Adds required elements, executes any hook_form_alter functions, and + * optionally inserts a validation token to prevent tampering. * * @param $form_id * A unique string identifying the form for validation, submission, @@ -1060,8 +1069,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) { /** - * Validates user-submitted form data from the $form_state using - * the validate functions defined in a structured form array. + * Validates user-submitted form data in the $form_state array. * * @param $form_id * A unique string identifying the form for validation, submission, @@ -1235,9 +1243,11 @@ function drupal_redirect_form($form_state) { } /** - * Performs validation on form elements. First ensures required fields are - * completed, #maxlength is not exceeded, and selected options were in the - * list of options given to the user. Then calls user-defined validators. + * Performs validation on form elements. + * + * First ensures required fields are completed, #maxlength is not exceeded, and + * selected options were in the list of options given to the user. Then calls + * user-defined validators. * * @param $elements * An associative array containing the structure of the form. @@ -1389,9 +1399,10 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { } /** - * A helper function used to execute custom validation and submission - * handlers for a given form. Button-specific handlers are checked - * first. If none exist, the function falls back to form-level handlers. + * Executes custom validation and submission handlers for a given form. + * + * Button-specific handlers are checked first. If none exist, the function + * falls back to form-level handlers. * * @param $type * The type of handler to execute. 'validate' or 'submit' are the @@ -1513,8 +1524,6 @@ function form_execute_handlers($type, &$form, &$form_state) { * doing anything with that data that requires it to be valid, PHP errors * would be triggered if the input processing and validation steps were fully * skipped. - * @see http://drupal.org/node/370537 - * @see http://drupal.org/node/763376 * * @param $name * The name of the form element. If the #parents property of your form @@ -1530,6 +1539,9 @@ function form_execute_handlers($type, &$form, &$form_state) { * @return * Return value is for internal use only. To get a list of errors, use * form_get_errors() or form_get_error(). + * + * @see http://drupal.org/node/370537 + * @see http://drupal.org/node/763376 */ function form_set_error($name = NULL, $message = '', $limit_validation_errors = NULL) { $form = &drupal_static(__FUNCTION__, array()); @@ -1575,14 +1587,14 @@ function form_set_error($name = NULL, $message = '', $limit_validation_errors = } /** - * Clear all errors against all form elements made by form_set_error(). + * Clears all errors against all form elements made by form_set_error(). */ function form_clear_error() { drupal_static_reset('form_set_error'); } /** - * Return an associative array of all errors. + * Returns an associative array of all errors. */ function form_get_errors() { $form = form_set_error(); @@ -1610,16 +1622,18 @@ function form_get_error($element) { } /** - * Flag an element as having an error. + * Flags an element as having an error. */ function form_error(&$element, $message = '') { form_set_error(implode('][', $element['#parents']), $message); } /** - * Walk through the structured form array, adding any required properties to - * each element and mapping the incoming input data to the proper elements. - * Also, execute any #process handlers attached to a specific element. + * Builds and processes all elements in the structured form array. + * + * Adds any required properties to each element, maps the incoming input data + * to the proper elements, and executes any #process handlers attached to a + * specific element. * * This is one of the three primary functions that recursively iterates a form * array. This one does it for completing the form building process. The other @@ -1891,8 +1905,7 @@ function form_builder($form_id, &$element, &$form_state) { } /** - * Populate the #value and #name properties of input elements so they - * can be processed and rendered. + * Adds the #name and #value properties of an input element before rendering. */ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { if (!isset($element['#name'])) { @@ -2031,7 +2044,7 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { } /** - * Helper function to handle the convoluted logic of button click detection. + * Detects if an element triggered the form submission via Ajax. * * This detects button or non-button controls that trigger a form submission via * Ajax or some other scriptable environment. These environments can set the @@ -2051,7 +2064,7 @@ function _form_element_triggered_scripted_submission($element, &$form_state) { } /** - * Helper function to handle the convoluted logic of button click detection. + * Determines if a given button triggered the form submission. * * This detects button controls that trigger a form submission by being clicked * and having the click processed by the browser rather than being captured by @@ -2146,7 +2159,7 @@ function form_state_values_clean(&$form_state) { } /** - * Helper function to determine the value for an image button form element. + * Determines the value for an image button form element. * * @param $form * The form element whose value is being populated. @@ -2155,6 +2168,7 @@ function form_state_values_clean(&$form_state) { * the element's default value should be returned. * @param $form_state * A keyed array containing the current state of the form. + * * @return * The data that will appear in the $form_state['values'] collection * for this element. Return nothing to use the default. @@ -2193,13 +2207,14 @@ function form_type_image_button_value($form, $input, $form_state) { } /** - * Helper function to determine the value for a checkbox form element. + * Determines the value for a checkbox form element. * * @param $form * The form element whose value is being populated. -* @param $input + * @param $input * The incoming input to populate the form element. If this is FALSE, * the element's default value should be returned. + * * @return * The data that will appear in the $element_state['values'] collection * for this element. Return nothing to use the default. @@ -2233,13 +2248,14 @@ function form_type_checkbox_value($element, $input = FALSE) { } /** - * Helper function to determine the value for a checkboxes form element. + * Determines the value for a checkboxes form element. * * @param $element * The form element whose value is being populated. * @param $input * The incoming input to populate the form element. If this is FALSE, * the element's default value should be returned. + * * @return * The data that will appear in the $element_state['values'] collection * for this element. Return nothing to use the default. @@ -2273,13 +2289,14 @@ function form_type_checkboxes_value($element, $input = FALSE) { } /** - * Helper function to determine the value for a tableselect form element. + * Determines the value for a tableselect form element. * * @param $element * The form element whose value is being populated. * @param $input * The incoming input to populate the form element. If this is FALSE, * the element's default value should be returned. + * * @return * The data that will appear in the $element_state['values'] collection * for this element. Return nothing to use the default. @@ -2308,14 +2325,56 @@ function form_type_tableselect_value($element, $input = FALSE) { } /** - * Helper function to determine the value for a password_confirm form - * element. + * Form value callback: Determines the value for a #type radios form element. + * + * @param $element + * The form element whose value is being populated. + * @param $input + * (optional) The incoming input to populate the form element. If FALSE, the + * element's default value is returned. Defaults to FALSE. + * + * @return + * The data that will appear in the $element_state['values'] collection for + * this element. + */ +function form_type_radios_value(&$element, $input = FALSE) { + if ($input !== FALSE) { + // There may not be a submitted value for multiple radio buttons, if none of + // the options was checked by default. If there is no submitted input value + // for this element (NULL), _form_builder_handle_input_element() + // automatically attempts to use the #default_value (if set) or an empty + // string (''). However, an empty string would fail validation in + // _form_validate(), in case it is not contained in the list of allowed + // values in #options. + if (!isset($input)) { + // Signify a garbage value to disable the #default_value handling and take + // over NULL as #value. + $element['#has_garbage_value'] = TRUE; + // There was a user submission so validation is a must. If this element is + // #required, then an appropriate error message will be output. While an + // optional #type 'radios' does not necessarily make sense from a user + // interaction perspective, there may be use-cases for that and it is not + // the job of Form API to artificially limit possibilities. + $element['#needs_validation'] = TRUE; + } + // The value stays the same, but the flags above will ensure it is + // processed properly. + return $input; + } + elseif (isset($element['#default_value'])) { + return $element['#default_value']; + } +} + +/** + * Determines the value for a password_confirm form element. * * @param $element * The form element whose value is being populated. * @param $input * The incoming input to populate the form element. If this is FALSE, * the element's default value should be returned. + * * @return * The data that will appear in the $element_state['values'] collection * for this element. Return nothing to use the default. @@ -2328,13 +2387,14 @@ function form_type_password_confirm_value($element, $input = FALSE) { } /** - * Helper function to determine the value for a select form element. + * Determines the value for a select form element. * * @param $element * The form element whose value is being populated. * @param $input * The incoming input to populate the form element. If this is FALSE, * the element's default value should be returned. + * * @return * The data that will appear in the $element_state['values'] collection * for this element. Return nothing to use the default. @@ -2368,13 +2428,14 @@ function form_type_select_value($element, $input = FALSE) { } /** - * Helper function to determine the value for a textfield form element. + * Determines the value for a textfield form element. * * @param $element * The form element whose value is being populated. * @param $input * The incoming input to populate the form element. If this is FALSE, * the element's default value should be returned. + * * @return * The data that will appear in the $element_state['values'] collection * for this element. Return nothing to use the default. @@ -2388,13 +2449,14 @@ function form_type_textfield_value($element, $input = FALSE) { } /** - * Helper function to determine the value for form's token value. + * Determines the value for form's token value. * * @param $element * The form element whose value is being populated. * @param $input * The incoming input to populate the form element. If this is FALSE, * the element's default value should be returned. + * * @return * The data that will appear in the $element_state['values'] collection * for this element. Return nothing to use the default. @@ -2406,7 +2468,7 @@ function form_type_token_value($element, $input = FALSE) { } /** - * Changes submitted form values in $form_state. + * Changes submitted form values during form validation. * * Use this function to change the submitted value of a form element in a form * validation function, so that the changed value persists in $form_state @@ -2458,11 +2520,9 @@ function form_options_flatten($array) { } /** - * Helper function for form_options_flatten(). + * Iterates over an array and returns a flat array with duplicate keys removed. * - * Iterates over arrays which may share common values and produces a flat - * array that has removed duplicate keys. Also handles cases where objects - * are passed as array values. + * This function also handles cases where objects are passed as array values. */ function _form_options_flatten($array) { $return = &drupal_static(__FUNCTION__); @@ -2572,7 +2632,7 @@ function theme_select($variables) { } /** - * Converts a select form element's options array into an HTML. + * Converts a select form element's options array into HTML. * * @param $element * An associative array containing the properties of the element. @@ -2580,6 +2640,7 @@ function theme_select($variables) { * Mixed: Either an associative array of items to list as choices, or an * object with an 'option' member that is an associative array. This * parameter is only used internally and should not be passed. + * * @return * An HTML string of options for the select form element. */ @@ -2616,8 +2677,7 @@ function form_select_options($element, $choices = NULL) { } /** - * Traverses a select element's #option array looking for any values - * that hold the given key. Returns an array of indexes that match. + * Returns the indexes of a select element's options matching a given key. * * This function is useful if you need to modify the options that are * already in a form element; for example, to remove choices which are @@ -2643,6 +2703,7 @@ function form_select_options($element, $choices = NULL) { * The select element to search. * @param $key * The key to look for. + * * @return * An array of indexes that match the given $key. Array will be * empty if no elements were found. FALSE if optgroups were found. @@ -2747,6 +2808,9 @@ function theme_radios($variables) { if (!empty($element['#attributes']['class'])) { $attributes['class'] .= ' ' . implode(' ', $element['#attributes']['class']); } + if (isset($element['#attributes']['title'])) { + $attributes['title'] = $element['#attributes']['title']; + } return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; } @@ -2779,7 +2843,7 @@ function form_process_password_confirm($element) { } /** - * Validate password_confirm element. + * Validates a password_confirm element. */ function password_confirm_validate($element, &$element_state) { $pass1 = trim($element['pass1']['#value']); @@ -2830,7 +2894,7 @@ function theme_date($variables) { } /** - * Roll out a single date element. + * Expands a date element into year, month, and day select elements. */ function form_process_date($element) { // Default to current date @@ -2886,7 +2950,7 @@ function form_process_date($element) { } /** - * Validates the date type to stop dates like February 30, 2006. + * Validates the date type to prevent invalid dates (e.g., February 30, 2006). */ function date_validate($element) { if (!checkdate($element['#value']['month'], $element['#value']['day'], $element['#value']['year'])) { @@ -2916,7 +2980,7 @@ function map_month($month) { } /** - * If no default value is set for weight select boxes, use 0. + * Sets the value for a weight element, with zero as a default. */ function weight_value(&$form) { if (isset($form['#default_value'])) { @@ -2928,8 +2992,7 @@ function weight_value(&$form) { } /** - * Roll out a single radios element to a list of radios, - * using the options array as index. + * Expands a radios element into individual radio elements. */ function form_process_radios($element) { if (count($element['#options']) > 0) { @@ -2950,7 +3013,9 @@ function form_process_radios($element) { // The key is sanitized in drupal_attributes() during output from the // theme function. '#return_value' => $key, - '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL, + // Use default or FALSE. A value of FALSE means that the radio button is + // not 'checked'. + '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE, '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), @@ -2969,7 +3034,7 @@ function form_process_radios($element) { * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #title, #value, #return_value, #description, #required, - * #attributes. + * #attributes, #checked. * * @ingroup themeable */ @@ -3008,15 +3073,19 @@ function theme_checkboxes($variables) { if (!empty($element['#attributes']['class'])) { $attributes['class'] = array_merge($attributes['class'], $element['#attributes']['class']); } + if (isset($element['#attributes']['title'])) { + $attributes['title'] = $element['#attributes']['title']; + } return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; } /** - * Add form_element theming to an element if title or description is set. + * Adds form element theming to an element if its title or description is set. * * This is used as a pre render function for checkboxes and radios. */ function form_pre_render_conditional_form_element($element) { + $t = get_t(); // Set the element's title attribute to show #title as a tooltip, if needed. if (isset($element['#title']) && $element['#title_display'] == 'attribute') { $element['#attributes']['title'] = $element['#title']; @@ -3059,6 +3128,9 @@ function form_process_checkbox($element, $form_state) { return $element; } +/** + * Processes a checkboxes form element. + */ function form_process_checkboxes($element) { $value = is_array($element['#value']) ? $element['#value'] : array(); $element['#tree'] = TRUE; @@ -3120,6 +3192,7 @@ function form_process_actions($element, &$form_state) { * container. * @param $form_state * The $form_state array for the form this element belongs to. + * * @return * The processed element. */ @@ -3230,11 +3303,12 @@ function theme_tableselect($variables) { } /** - * Create the correct amount of checkbox or radio elements to populate the table. + * Creates checkbox or radio elements to populate a tableselect table. * * @param $element * An associative array containing the properties and children of the * tableselect element. + * * @return * The processed element. */ @@ -3244,7 +3318,7 @@ function form_process_tableselect($element) { $value = is_array($element['#value']) ? $element['#value'] : array(); } else { - // Advanced selection behaviour make no sense for radios. + // Advanced selection behavior makes no sense for radios. $element['#js_select'] = FALSE; } @@ -3328,6 +3402,9 @@ function form_process_tableselect($element) { * different character, 'replace_pattern' needs to be set accordingly. * - error: (optional) A custom form error message string to show, if the * machine name contains disallowed characters. + * - standalone: (optional) Whether the live preview should stay in its own + * form element rather than in the suffix of the source element. Defaults + * to FALSE. * - #maxlength: (optional) Should be set to the maximum allowed length of the * machine name. Defaults to 64. * - #disabled: (optional) Should be set to TRUE in case an existing machine @@ -3339,6 +3416,9 @@ function form_process_machine_name($element, &$form_state) { '#title' => t('Machine-readable name'), '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), '#machine_name' => array(), + '#field_prefix' => '', + '#field_suffix' => '', + '#suffix' => '', ); // A form element that only wants to set one #machine_name property (usually // 'source' only) would leave all other properties undefined, if the defaults @@ -3349,6 +3429,9 @@ function form_process_machine_name($element, &$form_state) { 'label' => t('Machine name'), 'replace_pattern' => '[^a-z0-9_]+', 'replace' => '_', + 'standalone' => FALSE, + 'field_prefix' => $element['#field_prefix'], + 'field_suffix' => $element['#field_suffix'], ); // By default, machine names are restricted to Latin alphanumeric characters. @@ -3364,7 +3447,7 @@ function form_process_machine_name($element, &$form_state) { } // Retrieve the form element containing the human-readable name from the - // complete form in $form_state. By reference, because we need to append + // complete form in $form_state. By reference, because we may need to append // a #field_suffix that will hold the live preview. $key_exists = NULL; $source = drupal_array_get_nested_value($form_state['complete form'], $element['#machine_name']['source'], $key_exists); @@ -3372,16 +3455,21 @@ function form_process_machine_name($element, &$form_state) { return $element; } - // Append a field suffix to the source form element, which will contain - // the live preview of the machine name. $suffix_id = $source['#id'] . '-machine-name-suffix'; - $source += array('#field_suffix' => ''); - $source['#field_suffix'] .= ' <small id="' . $suffix_id . '"> </small>'; + $element['#machine_name']['suffix'] = '#' . $suffix_id; - $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); - drupal_array_set_nested_value($form_state['complete form'], $parents, $source['#field_suffix']); + if ($element['#machine_name']['standalone']) { + $element['#suffix'] .= ' <small id="' . $suffix_id . '"> </small>'; + } + else { + // Append a field suffix to the source form element, which will contain + // the live preview of the machine name. + $source += array('#field_suffix' => ''); + $source['#field_suffix'] .= ' <small id="' . $suffix_id . '"> </small>'; - $element['#machine_name']['suffix'] = '#' . $suffix_id; + $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); + drupal_array_set_nested_value($form_state['complete form'], $parents, $source['#field_suffix']); + } $js_settings = array( 'type' => 'setting', @@ -3398,7 +3486,7 @@ function form_process_machine_name($element, &$form_state) { } /** - * Form element validation handler for #type 'machine_name'. + * Form element validation handler for machine_name elements. * * Note that #maxlength is validated by _form_validate() already. */ @@ -3436,8 +3524,7 @@ function form_validate_machine_name(&$element, &$form_state) { } /** - * Adds fieldsets to the specified group or adds group members to this - * fieldset. + * Arranges fieldsets into groups. * * @param $element * An associative array containing the properties and children of the @@ -3445,6 +3532,7 @@ function form_validate_machine_name(&$element, &$form_state) { * child elements are taken over into $form_state. * @param $form_state * The $form_state array for the form this fieldset belongs to. + * * @return * The processed element. */ @@ -3548,6 +3636,7 @@ function form_pre_render_fieldset($element) { * fieldset. * @param $form_state * The $form_state array for the form this vertical tab widget belongs to. + * * @return * The processed element. */ @@ -3582,8 +3671,8 @@ function form_process_vertical_tabs($element, &$form_state) { * * @param $variables * An associative array containing: - * - element: An associative array containing the properties and children of the - * fieldset. Properties used: #children. + * - element: An associative array containing the properties and children of + * the fieldset. Properties used: #children. * * @ingroup themeable */ @@ -3792,7 +3881,7 @@ function theme_password($variables) { } /** - * Expand weight elements into selects. + * Expands a weight element into a select element. */ function form_process_weight($element) { $element['#is_weight'] = TRUE; @@ -3976,7 +4065,8 @@ function theme_form_required_marker($variables) { * * Form element labels include the #title and a #required marker. The label is * associated with the element itself by the element #id. Labels may appear - * before or after elements, depending on theme_form_element() and #title_display. + * before or after elements, depending on theme_form_element() and + * #title_display. * * This function will not be called for elements with no labels, depending on * #title_display. For elements that have an empty #title and are not required, @@ -4055,7 +4145,7 @@ function _form_set_class(&$element, $class = array()) { } /** - * Helper form element validator: integer. + * Form element validation handler for integer elements. */ function element_validate_integer($element, &$form_state) { $value = $element['#value']; @@ -4065,7 +4155,7 @@ function element_validate_integer($element, &$form_state) { } /** - * Helper form element validator: integer > 0. + * Form element validation handler for integer elements that must be positive. */ function element_validate_integer_positive($element, &$form_state) { $value = $element['#value']; @@ -4075,7 +4165,7 @@ function element_validate_integer_positive($element, &$form_state) { } /** - * Helper form element validator: number. + * Form element validation handler for number elements. */ function element_validate_number($element, &$form_state) { $value = $element['#value']; @@ -4091,7 +4181,7 @@ function element_validate_number($element, &$form_state) { /** * @defgroup batch Batch operations * @{ - * Create and process batch operations. + * Creates and processes batch operations. * * Functions allowing forms processing to be spread out over several page * requests, thus ensuring that the processing does not get interrupted @@ -4200,13 +4290,24 @@ function element_validate_number($element, &$form_state) { */ /** - * Opens a new batch. - * - * @param $batch - * An array defining the batch. The following keys can be used -- only - * 'operations' is required, and batch_init() provides default values for - * the messages. - * - 'operations': Array of function calls to be performed. + * Adds a new batch. + * + * Batch operations are added as new batch sets. Batch sets are used to spread + * processing (primarily, but not exclusively, forms processing) over several + * page requests. This helps to ensure that the processing is not interrupted + * due to PHP timeouts, while users are still able to receive feedback on the + * progress of the ongoing operations. Combining related operations into + * distinct batch sets provides clean code independence for each batch set, + * ensuring that two or more batches, submitted independently, can be processed + * without mutual interference. Each batch set may specify its own set of + * operations and results, produce its own UI messages, and trigger its own + * 'finished' callback. Batch sets are processed sequentially, with the progress + * bar starting afresh for each new set. + * + * @param $batch_definition + * An associative array defining the batch, with the following elements (all + * are optional except as noted): + * - operations: (required) Array of function calls to be performed. * Example: * @code * array( @@ -4214,35 +4315,26 @@ function element_validate_number($element, &$form_state) { * array('my_function_2', array($arg2_1, $arg2_2)), * ) * @endcode - * - 'title': Title for the progress page. Only safe strings should be passed. - * Defaults to t('Processing'). - * - 'init_message': Message displayed while the processing is initialized. + * - title: A safe, translated string to use as the title for the progress + * page. Defaults to t('Processing'). + * - init_message: Message displayed while the processing is initialized. * Defaults to t('Initializing.'). - * - 'progress_message': Message displayed while processing the batch. - * Available placeholders are @current, @remaining, @total, @percentage, - * @estimate and @elapsed. Defaults to t('Completed @current of @total.'). - * - 'error_message': Message displayed if an error occurred while processing + * - progress_message: Message displayed while processing the batch. Available + * placeholders are @current, @remaining, @total, @percentage, @estimate and + * @elapsed. Defaults to t('Completed @current of @total.'). + * - error_message: Message displayed if an error occurred while processing * the batch. Defaults to t('An error has occurred.'). - * - 'finished': Name of a function to be executed after the batch has - * completed. This should be used to perform any result massaging that - * may be needed, and possibly save data in $_SESSION for display after - * final page redirection. - * - 'file': Path to the file containing the definitions of the - * 'operations' and 'finished' functions, for instance if they don't - * reside in the main .module file. The path should be relative to - * base_path(), and thus should be built using drupal_get_path(). - * - 'css': Array of paths to CSS files to be used on the progress page. - * - 'url_options': options passed to url() when constructing redirect - * URLs for the batch. - * - * Operations are added as new batch sets. Batch sets are used to ensure - * clean code independence, ensuring that several batches submitted by - * different parts of the code (core / contrib modules) can be processed - * correctly while not interfering or having to cope with each other. Each - * batch set gets to specify his own UI messages, operates on its own set - * of operations and results, and triggers its own 'finished' callback. - * Batch sets are processed sequentially, with the progress bar starting - * fresh for every new set. + * - finished: Name of a function to be executed after the batch has + * completed. This should be used to perform any result massaging that may + * be needed, and possibly save data in $_SESSION for display after final + * page redirection. + * - file: Path to the file containing the definitions of the 'operations' and + * 'finished' functions, for instance if they don't reside in the main + * .module file. The path should be relative to base_path(), and thus should + * be built using drupal_get_path(). + * - css: Array of paths to CSS files to be used on the progress page. + * - url_options: options passed to url() when constructing redirect URLs for + * the batch. */ function batch_set($batch_definition) { if ($batch_definition) { @@ -4422,6 +4514,7 @@ function &batch_get() { * The batch array. * @param $set_id * The id of the set to process. + * * @return * The name and class of the queue are added by reference to the batch set. */ @@ -4451,6 +4544,7 @@ function _batch_populate_queue(&$batch, $set_id) { * * @param $batch_set * The batch set. + * * @return * The queue object. */ diff --git a/includes/graph.inc b/includes/graph.inc index 7fcc57a45..9ef86a145 100644 --- a/includes/graph.inc +++ b/includes/graph.inc @@ -7,7 +7,7 @@ /** - * Perform a depth first sort on a directed acyclic graph. + * Performs a depth-first sort on a directed acyclic graph. * * @param $graph * A three dimensional associated array, with the first keys being the names @@ -72,7 +72,7 @@ function drupal_depth_first_search(&$graph) { } /** - * Helper function to perform a depth first sort. + * Performs a depth-first sort on a graph. * * @param $graph * A three dimensional associated graph array. diff --git a/includes/language.inc b/includes/language.inc index b7057f2a1..20909f5a6 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -349,7 +349,10 @@ function language_provider_invoke($provider_id, $provider = NULL) { $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 + // provider cache to be 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]; } /** @@ -380,15 +383,18 @@ function language_initialize($type) { // 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; } /** diff --git a/includes/locale.inc b/includes/locale.inc index 0db4d4a07..7fb8d6424 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -141,7 +141,7 @@ function locale_language_from_browser($languages) { // language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" ) // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" $browser_langcodes = array(); - if (preg_match_all('@([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) { + 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, @@ -430,8 +430,27 @@ function locale_language_url_rewrite_url(&$path, &$options) { 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; @@ -978,7 +997,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') @@ -989,15 +1008,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; } diff --git a/includes/menu.inc b/includes/menu.inc index 25a87af12..b25a374ac 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -321,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) { @@ -452,18 +459,10 @@ function menu_get_item($path = NULL, $router_item = NULL) { } $original_map = arg(NULL, $path); - // 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 { - $parts = array_slice($original_map, 0, MENU_MAX_PARTS); - $ancestors = menu_get_ancestors($parts); - $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'); - } + $parts = array_slice($original_map, 0, MENU_MAX_PARTS); + $ancestors = menu_get_ancestors($parts); + $router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc(); + if ($router_item) { // Allow modules to alter the router item before it is translated and // checked for access. @@ -2152,7 +2151,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. @@ -2321,6 +2320,9 @@ function menu_get_active_menu_names() { */ 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'); } /** @@ -2406,7 +2408,7 @@ function menu_set_active_trail($new_trail = NULL) { // 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'] && !drupal_is_front_page()) { + if ($preferred_link && $last['href'] != $preferred_link['href'] && !drupal_is_front_page()) { $trail[] = $preferred_link; } } @@ -3149,10 +3151,10 @@ function menu_link_save(&$item, $existing_item = array(), $parent_candidates = a } // 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'], diff --git a/includes/module.inc b/includes/module.inc index 77e35b7b0..500bc5ebc 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -425,6 +425,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(); @@ -546,6 +548,8 @@ function module_disable($module_list, $disable_dependents = TRUE) { // 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 diff --git a/includes/pager.inc b/includes/pager.inc index a5d3e6be0..c060d0e7c 100644 --- a/includes/pager.inc +++ b/includes/pager.inc @@ -630,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>'; } /** diff --git a/includes/path.inc b/includes/path.inc index ed5b639fb..411a7a71c 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -431,21 +431,27 @@ 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) { - module_invoke_all('path_insert', $path); - } - else { - module_invoke_all('path_update', $path); - } - drupal_clear_path_cache($path['source']); + 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']); } /** diff --git a/includes/registry.inc b/includes/registry.inc index c7b8702c4..f6c81eb1a 100644 --- a/includes/registry.inc +++ b/includes/registry.inc @@ -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; } @@ -156,9 +152,9 @@ function _registry_parse_files($files) { * Parse a file and save its function and class listings. * * @param $filename - * Name of the file we are going to parse. + * Name of the file we are going to parse. * @param $contents - * Contents of the file we are going to parse as a string. + * Contents of the file we are going to parse as a string. * @param $module * (optional) Name of the module this file belongs to. * @param $weight @@ -166,17 +162,25 @@ 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( - 'name' => $name, - 'type' => $matches[1][$key], - 'filename' => $filename, - 'module' => $module, - 'weight' => $weight, - )); + 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(); } } diff --git a/includes/session.inc b/includes/session.inc index fd04de875..8f1bcafc4 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -172,7 +172,7 @@ 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, @@ -199,6 +199,9 @@ function _drupal_session_write($sid, $value) { } } } + elseif (variable_get('https', FALSE)) { + unset($key['ssid']); + } db_merge('sessions') ->key($key) @@ -255,11 +258,17 @@ 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()); } @@ -291,7 +300,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. @@ -310,6 +319,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(); @@ -336,7 +351,7 @@ function drupal_session_regenerate() { global $user, $is_https; 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(); @@ -416,7 +431,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); } } @@ -425,13 +443,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]); } } diff --git a/includes/theme.inc b/includes/theme.inc index da4200e56..51e1075ca 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -252,7 +252,20 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb * class. */ function theme_get_registry($complete = TRUE) { - static $theme_registry = array(); + // 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(); + } + $key = (int) $complete; if (!isset($theme_registry[$key])) { @@ -335,6 +348,7 @@ function _theme_save_registry($theme, $registry) { * to add more theme hooks. */ function drupal_theme_rebuild() { + drupal_static_reset('theme_get_registry'); cache_clear_all('theme_registry', 'cache', TRUE); } @@ -899,8 +913,6 @@ function list_themes($refresh = FALSE) { * @see themeable */ 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(). @@ -908,10 +920,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(FALSE); - } + $hooks = theme_get_registry(FALSE); // If an array of hook candidates were passed, use the first one that has an // implementation. @@ -991,6 +1000,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; @@ -1960,8 +1976,11 @@ 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>'; } diff --git a/includes/unicode.inc b/includes/unicode.inc index 9dde2ca70..cd9cd9bf0 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -73,7 +73,7 @@ define('PREG_CLASS_UNICODE_WORD_BOUNDARY', '\x{A836}-\x{A839}\x{A874}-\x{A877}\x{A8CE}-\x{A8CF}\x{A8F8}-\x{A8FA}' . '\x{A92E}-\x{A92F}\x{A95F}\x{A9C1}-\x{A9CD}\x{A9DE}-\x{A9DF}' . '\x{AA5C}-\x{AA5F}\x{AA77}-\x{AA79}\x{AADE}-\x{AADF}\x{ABEB}' . - '\x{D800}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' . + '\x{E000}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' . '\x{FE10}-\x{FE19}\x{FE30}-\x{FE6B}\x{FEFF}-\x{FF0F}\x{FF1A}-\x{FF20}' . '\x{FF3B}-\x{FF40}\x{FF5B}-\x{FF65}\x{FFE0}-\x{FFFD}'); diff --git a/includes/update.inc b/includes/update.inc index 41f33f402..6588fac00 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -410,15 +410,18 @@ function update_fix_d7_block_deltas(&$sandbox, $renamed_deltas, $moved_deltas) { // Initialize batch update information. $sandbox['progress'] = 0; $sandbox['last_user_processed'] = -1; - $sandbox['max'] = db_query("SELECT COUNT(*) FROM {users} WHERE data IS NOT NULL")->fetchField(); + $sandbox['max'] = db_query("SELECT COUNT(*) FROM {users} WHERE data LIKE :block", array( + ':block' => '%' . db_like(serialize('block')) . '%', + )) + ->fetchField(); } // Now do the batch update of the user-specific block visibility settings. $limit = 100; $result = db_select('users', 'u') ->fields('u', array('uid', 'data')) ->condition('uid', $sandbox['last_user_processed'], '>') + ->condition('data', '%' . db_like(serialize('block')) . '%', 'LIKE') ->orderBy('uid', 'ASC') - ->where('data IS NOT NULL') ->range(0, $limit) ->execute(); foreach ($result as $row) { @@ -815,9 +818,6 @@ function update_fix_d7_install_profile() { 'files' => array(), ); - // The install profile is always required. - $file->info['required'] = TRUE; - $values = array( 'filename' => $filename, 'name' => $profile, diff --git a/includes/updater.inc b/includes/updater.inc index 9801629b1..d4f99e974 100644 --- a/includes/updater.inc +++ b/includes/updater.inc @@ -330,7 +330,7 @@ class Updater { } catch (FileTransferException $e) { $message = t($e->getMessage(), $e->arguments); - $throw_message = t('Unable to create %directory due to the following: %reason', array('%directory' => $install_location, '%reason' => $message)); + $throw_message = t('Unable to create %directory due to the following: %reason', array('%directory' => $directory, '%reason' => $message)); throw new UpdaterException($throw_message); } } diff --git a/includes/utility.inc b/includes/utility.inc index d195bff74..5019852c7 100644 --- a/includes/utility.inc +++ b/includes/utility.inc @@ -51,7 +51,7 @@ function drupal_var_export($var, $prefix = '') { // magic method __set_state() leaving the export broken. This // workaround avoids this by casting the object as an array for // export and casting it back to an object when evaluated. - $output .= '(object) ' . drupal_var_export((array) $var, $prefix); + $output = '(object) ' . drupal_var_export((array) $var, $prefix); } else { $output = var_export($var, TRUE); |