summaryrefslogtreecommitdiff
path: root/includes
diff options
context:
space:
mode:
Diffstat (limited to 'includes')
-rw-r--r--includes/batch.inc18
-rw-r--r--includes/bootstrap.inc13
-rw-r--r--includes/common.inc15
-rw-r--r--includes/database/database.inc51
-rw-r--r--includes/database/mysql/database.inc52
-rw-r--r--includes/database/select.inc2
-rw-r--r--includes/database/sqlite/database.inc14
-rw-r--r--includes/entity.inc15
-rw-r--r--includes/errors.inc6
-rw-r--r--includes/file.inc22
-rw-r--r--includes/form.inc34
-rw-r--r--includes/install.inc2
-rw-r--r--includes/locale.inc2
13 files changed, 180 insertions, 66 deletions
diff --git a/includes/batch.inc b/includes/batch.inc
index 7011abfbd..727c62560 100644
--- a/includes/batch.inc
+++ b/includes/batch.inc
@@ -339,6 +339,8 @@ function _batch_process() {
$progress_message = $old_set['progress_message'];
}
+ // Total progress is the number of operations that have fully run plus the
+ // completion level of the current operation.
$current = $total - $remaining + $finished;
$percentage = _batch_api_percentage($total, $current);
$elapsed = isset($current_set['elapsed']) ? $current_set['elapsed'] : 0;
@@ -373,7 +375,10 @@ function _batch_process() {
* @param $total
* The total number of operations.
* @param $current
- * The number of the current operation.
+ * The number of the current operation. This may be a floating point number
+ * rather than an integer in the case of a multi-step operation that is not
+ * yet complete; in that case, the fractional part of $current represents the
+ * fraction of the operation that has been completed.
* @return
* The properly formatted percentage, as a string. We output percentages
* using the correct number of decimal places so that we never print "100%"
@@ -390,7 +395,16 @@ function _batch_api_percentage($total, $current) {
// We add a new digit at 200, 2000, etc. (since, for example, 199/200
// would round up to 100% if we didn't).
$decimal_places = max(0, floor(log10($total / 2.0)) - 1);
- $percentage = sprintf('%01.' . $decimal_places . 'f', round($current / $total * 100, $decimal_places));
+ do {
+ // Calculate the percentage to the specified number of decimal places.
+ $percentage = sprintf('%01.' . $decimal_places . 'f', round($current / $total * 100, $decimal_places));
+ // When $current is an integer, the above calculation will always be
+ // correct. However, if $current is a floating point number (in the case
+ // of a multi-step batch operation that is not yet complete), $percentage
+ // may be erroneously rounded up to 100%. To prevent that, we add one
+ // more decimal place and try again.
+ $decimal_places++;
+ } while ($percentage == '100');
}
return $percentage;
}
diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc
index 830894f52..c53286406 100644
--- a/includes/bootstrap.inc
+++ b/includes/bootstrap.inc
@@ -1122,13 +1122,12 @@ function drupal_serve_page_from_cache(stdClass $cache) {
}
}
- // If a cache is served from a HTTP proxy without hitting the web server,
- // the boot and exit hooks cannot be fired, so only allow caching in
- // proxies if boot hooks are disabled. If the client send a session cookie,
- // do not bother caching the page in a public proxy, because the cached copy
- // will only be served to that particular user due to Vary: Cookie, unless
- // the Vary header has been replaced or unset in hook_boot() (see below).
- $max_age = !variable_get('page_cache_invoke_hooks', TRUE) && (!isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary'])) ? variable_get('page_cache_maximum_age', 0) : 0;
+ // If the client sent a session cookie, a cached copy will only be served
+ // to that one particular client due to Vary: Cookie. Thus, do not set
+ // max-age > 0, allowing the page to be cached by external proxies, when a
+ // session cookie is present unless the Vary header has been replaced or
+ // unset in hook_boot().
+ $max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? variable_get('page_cache_maximum_age', 0) : 0;
$default_headers['Cache-Control'] = 'public, max-age=' . $max_age;
// Entity tag should change if the output changes.
diff --git a/includes/common.inc b/includes/common.inc
index 5dadb4d16..9b582c446 100644
--- a/includes/common.inc
+++ b/includes/common.inc
@@ -752,7 +752,8 @@ function drupal_access_denied() {
* received.
* - redirect_code: If redirected, an integer containing the initial response
* status code.
- * - redirect_url: If redirected, a string containing the redirection location.
+ * - redirect_url: If redirected, a string containing the URL of the redirect
+ * target.
* - error: If an error occurred, the error message. Otherwise not set.
* - headers: An array containing the response headers as name/value pairs.
* HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
@@ -1008,7 +1009,9 @@ function drupal_http_request($url, array $options = array()) {
$result = drupal_http_request($location, $options);
$result->redirect_code = $code;
}
- $result->redirect_url = $location;
+ if (!isset($result->redirect_url)) {
+ $result->redirect_url = $location;
+ }
break;
default:
$result->error = $status_message;
@@ -2822,6 +2825,8 @@ function drupal_add_html_head_link($attributes, $header = FALSE) {
*
* @return
* An array of queued cascading stylesheets.
+ *
+ * @see drupal_get_css()
*/
function drupal_add_css($data = NULL, $options = NULL) {
$css = &drupal_static(__FUNCTION__, array());
@@ -2902,8 +2907,11 @@ function drupal_add_css($data = NULL, $options = NULL) {
* (optional) If set to TRUE, this function skips calling drupal_alter() on
* $css, useful when the calling function passes a $css array that has already
* been altered.
+ *
* @return
* A string of XHTML CSS tags.
+ *
+ * @see drupal_add_css()
*/
function drupal_get_css($css = NULL, $skip_alter = FALSE) {
if (!isset($css)) {
@@ -7431,7 +7439,8 @@ function entity_create_stub_entity($entity_type, $ids) {
* Whether to reset the internal cache for the requested entity type.
*
* @return
- * An array of entity objects indexed by their ids.
+ * An array of entity objects indexed by their ids. When no results are
+ * found, an empty array is returned.
*
* @todo Remove $conditions in Drupal 8.
*/
diff --git a/includes/database/database.inc b/includes/database/database.inc
index 4cc1a33d7..e08f9074f 100644
--- a/includes/database/database.inc
+++ b/includes/database/database.inc
@@ -273,20 +273,25 @@ abstract class DatabaseConnection extends PDO {
protected $schema = NULL;
/**
- * The default prefix used by this database connection.
+ * The prefixes used by this database connection.
*
- * Separated from the other prefixes for performance reasons.
+ * @var array
+ */
+ protected $prefixes = array();
+
+ /**
+ * List of search values for use in prefixTables().
*
- * @var string
+ * @var array
*/
- protected $defaultPrefix = '';
+ protected $prefixSearch = array();
/**
- * The non-default prefixes used by this database connection.
+ * List of replacement values for use in prefixTables().
*
* @var array
*/
- protected $prefixes = array();
+ protected $prefixReplace = array();
function __construct($dsn, $username, $password, $driver_options = array()) {
// Initialize and prepare the connection prefix.
@@ -375,7 +380,7 @@ abstract class DatabaseConnection extends PDO {
}
/**
- * Preprocess the prefixes used by this database connection.
+ * Set the list of prefixes used by this database connection.
*
* @param $prefix
* The prefixes, in any of the multiple forms documented in
@@ -383,14 +388,27 @@ abstract class DatabaseConnection extends PDO {
*/
protected function setPrefix($prefix) {
if (is_array($prefix)) {
- $this->defaultPrefix = isset($prefix['default']) ? $prefix['default'] : '';
- unset($prefix['default']);
- $this->prefixes = $prefix;
+ $this->prefixes = $prefix + array('default' => '');
}
else {
- $this->defaultPrefix = $prefix;
- $this->prefixes = array();
+ $this->prefixes = array('default' => $prefix);
}
+
+ // Set up variables for use in prefixTables(). Replace table-specific
+ // prefixes first.
+ $this->prefixSearch = array();
+ $this->prefixReplace = array();
+ foreach ($this->prefixes as $key => $val) {
+ if ($key != 'default') {
+ $this->prefixSearch[] = '{' . $key . '}';
+ $this->prefixReplace[] = $val . $key;
+ }
+ }
+ // Then replace remaining tables with the default prefix.
+ $this->prefixSearch[] = '{';
+ $this->prefixReplace[] = $this->prefixes['default'];
+ $this->prefixSearch[] = '}';
+ $this->prefixReplace[] = '';
}
/**
@@ -408,12 +426,7 @@ abstract class DatabaseConnection extends PDO {
* The properly-prefixed string.
*/
public function prefixTables($sql) {
- // Replace specific table prefixes first.
- foreach ($this->prefixes as $key => $val) {
- $sql = strtr($sql, array('{' . $key . '}' => $val . $key));
- }
- // Then replace remaining tables with the default prefix.
- return strtr($sql, array('{' => $this->defaultPrefix , '}' => ''));
+ return str_replace($this->prefixSearch, $this->prefixReplace, $sql);
}
/**
@@ -427,7 +440,7 @@ abstract class DatabaseConnection extends PDO {
return $this->prefixes[$table];
}
else {
- return $this->defaultPrefix;
+ return $this->prefixes['default'];
}
}
diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc
index 262cc6051..bc31feaaf 100644
--- a/includes/database/mysql/database.inc
+++ b/includes/database/mysql/database.inc
@@ -133,6 +133,58 @@ class DatabaseConnection_mysql extends DatabaseConnection {
catch (PDOException $e) {
}
}
+
+ /**
+ * Overridden to work around issues to MySQL not supporting transactional DDL.
+ */
+ public function popTransaction($name) {
+ if (!$this->supportsTransactions()) {
+ return;
+ }
+ if (!$this->inTransaction()) {
+ throw new DatabaseTransactionNoActiveException();
+ }
+
+ // Commit everything since SAVEPOINT $name.
+ while ($savepoint = array_pop($this->transactionLayers)) {
+ if ($savepoint != $name) {
+ continue;
+ }
+
+ // If there are no more layers left then we should commit.
+ if (empty($this->transactionLayers)) {
+ if (!PDO::commit()) {
+ throw new DatabaseTransactionCommitFailedException();
+ }
+ }
+ else {
+ // Attempt to release this savepoint in the standard way.
+ try {
+ $this->query('RELEASE SAVEPOINT ' . $name);
+ }
+ catch (PDOException $e) {
+ // However, in MySQL (InnoDB), savepoints are automatically committed
+ // when tables are altered or created (DDL transactions are not
+ // supported). This can cause exceptions due to trying to release
+ // savepoints which no longer exist.
+ //
+ // To avoid exceptions when no actual error has occurred, we silently
+ // succeed for PDOExceptions with SQLSTATE 42000 ("Syntax error or
+ // access rule violation") and MySQL error code 1305 ("SAVEPOINT does
+ // not exist").
+ if ($e->getCode() == '42000' && $e->errorInfo[1] == '1305') {
+ // If one SAVEPOINT was released automatically, then all were.
+ // Therefore, we keep just the topmost transaction.
+ $this->transactionLayers = array('drupal_transaction');
+ }
+ else {
+ throw $e;
+ }
+ }
+ break;
+ }
+ }
+ }
}
diff --git a/includes/database/select.inc b/includes/database/select.inc
index 716c2fc3d..53be20adc 100644
--- a/includes/database/select.inc
+++ b/includes/database/select.inc
@@ -414,7 +414,7 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn
* @param $start
* The first record from the result set to return. If NULL, removes any
* range directives that are set.
- * @param $limit
+ * @param $length
* The number of records to return from the result set.
* @return SelectQueryInterface
* The called object.
diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc
index cf3b9551f..0fc0b5528 100644
--- a/includes/database/sqlite/database.inc
+++ b/includes/database/sqlite/database.inc
@@ -71,12 +71,8 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
));
// Attach one database for each registered prefix.
- $prefixes = &$this->prefixes;
- if (!empty($this->defaultPrefix)) {
- // Add in the default prefix, which is also attached.
- $prefixes[] = &$this->defaultPrefix;
- }
- foreach ($this->prefixes as $table => &$prefix) {
+ $prefixes = $this->prefixes;
+ foreach ($prefixes as $table => &$prefix) {
// Empty prefix means query the main database -- no need to attach anything.
if (!empty($prefix)) {
// Only attach the database once.
@@ -90,6 +86,8 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
$prefix .= '.';
}
}
+ // Regenerate the prefixes replacement table.
+ $this->setPrefix($prefixes);
// Detect support for SAVEPOINT.
$version = $this->query('SELECT sqlite_version()')->fetchField();
@@ -240,7 +238,9 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
// Generate a new temporary table name and protect it from prefixing.
// SQLite requires that temporary tables to be non-qualified.
$tablename = $this->generateTemporaryTableName();
- $this->prefixes[$tablename] = '';
+ $prefixes = $this->prefixes;
+ $prefixes[$tablename] = '';
+ $this->setPrefix($prefixes);
$this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE ' . $tablename . ' AS SELECT', $query), $args, $options);
return $tablename;
diff --git a/includes/entity.inc b/includes/entity.inc
index a3cdf7417..9ee7889cf 100644
--- a/includes/entity.inc
+++ b/includes/entity.inc
@@ -39,7 +39,8 @@ interface DrupalEntityControllerInterface {
* An array of conditions in the form 'field' => $value.
*
* @return
- * An array of entity objects indexed by their ids.
+ * An array of entity objects indexed by their ids. When no results are
+ * found, an empty array is returned.
*/
public function load($ids = array(), $conditions = array());
}
@@ -650,7 +651,11 @@ class EntityFieldQuery {
*/
public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) {
if (is_scalar($field)) {
- $field = field_info_field($field);
+ $field_definition = field_info_field($field);
+ if (empty($field_definition)) {
+ throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field)));
+ }
+ $field = $field_definition;
}
// Ensure the same index is used for fieldConditions as for fields.
$index = count($this->fields);
@@ -752,7 +757,11 @@ class EntityFieldQuery {
*/
public function fieldOrderBy($field, $column, $direction = 'ASC') {
if (is_scalar($field)) {
- $field = field_info_field($field);
+ $field_definition = field_info_field($field);
+ if (empty($field_definition)) {
+ throw new EntityFieldQueryException(t('Unknown field: @field_name', array('@field_name' => $field)));
+ }
+ $field = $field_definition;
}
// Save the index used for the new field, for later use in field storage.
$index = count($this->fields);
diff --git a/includes/errors.inc b/includes/errors.inc
index 3a97b6daa..be7242856 100644
--- a/includes/errors.inc
+++ b/includes/errors.inc
@@ -172,9 +172,9 @@ function error_displayable($error = NULL) {
* Log a PHP error or exception, display an error page in fatal cases.
*
* @param $error
- * An array with the following keys: %type, !message, %function, %file, %line.
- * All the parameters are plain-text, exception message, which needs to be
- * a safe HTML string.
+ * 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.
* @param $fatal
* TRUE if the error is fatal.
*/
diff --git a/includes/file.inc b/includes/file.inc
index 6dc7f88b3..73e75cd4f 100644
--- a/includes/file.inc
+++ b/includes/file.inc
@@ -737,8 +737,7 @@ function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $c
* A file object.
* @param $destination
* A string containing the destination that $source should be copied to.
- * This must be a stream wrapper URI. If this value is omitted, Drupal's
- * default files scheme will be used, usually "public://".
+ * This must be a stream wrapper URI.
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
@@ -967,8 +966,7 @@ function file_destination($destination, $replace) {
* A file object.
* @param $destination
* A string containing the destination that $source should be moved to.
- * This must be a stream wrapper URI. If this value is omitted, Drupal's
- * default files scheme will be used, usually "public://".
+ * This must be a stream wrapper URI.
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
@@ -1755,9 +1753,9 @@ function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0,
* @param $data
* A string containing the contents of the file.
* @param $destination
- * A string containing the destination URI.
- * This must be a stream wrapper URI. If this value is omitted, Drupal's
- * default files scheme will be used, usually "public://".
+ * A string containing the destination URI. 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://".
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with
@@ -1823,10 +1821,9 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM
* @param $data
* A string containing the contents of the file.
* @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 is saved using Drupal's
- * default files scheme, usually "public://".
+ * 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://".
* @param $replace
* Replace behavior when the destination file already exists:
* - FILE_EXISTS_REPLACE - Replace the existing file.
@@ -2153,8 +2150,7 @@ function drupal_unlink($uri, $context = NULL) {
* @see http://drupal.org/node/515192
*
* @param $uri
- * A string containing the URI to verify. If this value is omitted,
- * Drupal's public files directory will be used [public://].
+ * A string containing the URI to verify.
*
* @return
* The absolute pathname, or FALSE on failure.
diff --git a/includes/form.inc b/includes/form.inc
index a337b03d1..38ef41cf0 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -104,8 +104,10 @@
* function via $form_state. This is commonly used for wizard-style
* multi-step forms, add-more buttons, and the like. For further information
* see drupal_build_form().
- * - 'redirect': a URL that will be used to redirect the form on submission.
- * See drupal_redirect_form() for complete information.
+ * - 'redirect': $form_state['redirect'] is used to redirect the form on
+ * submission. It may either be a string containing the destination URL, or
+ * an array of arguments compatible with drupal_goto(). See
+ * drupal_redirect_form() for complete information.
* - 'storage': $form_state['storage'] is not a special key, and no specific
* support is provided for it in the Form API, but by tradition it was
* the location where application-specific data was stored for communication
@@ -1128,10 +1130,28 @@ function drupal_validate_form($form_id, &$form, &$form_state) {
* Redirects the user to a URL after a form has been processed.
*
* After a form was executed, the data in $form_state controls whether the form
- * is redirected. By default, we redirect to a new destination page. The path of
- * the destination page can be set in $form_state['redirect']. If that is not
- * set, the user is redirected to the current page to display a fresh,
- * unpopulated copy of the form.
+ * is redirected. By default, we redirect to a new destination page. The path
+ * of the destination page can be set in $form_state['redirect'], as either a
+ * string containing the destination or an array of arguments compatible with
+ * drupal_goto(). If that is not set, the user is redirected to the current
+ * page to display a fresh, unpopulated copy of the form.
+ *
+ * For example, to redirect to 'node':
+ * @code
+ * $form_state['redirect'] = 'node';
+ * @endcode
+ * Or to redirect to 'node/123?foo=bar#baz':
+ * @code
+ * $form_state['redirect'] = array(
+ * 'node/123',
+ * array(
+ * 'query' => array(
+ * 'foo' => 'bar',
+ * ),
+ * 'fragment' => 'baz',
+ * ),
+ * );
+ * @endcode
*
* There are several triggers that may prevent a redirection though:
* - If $form_state['redirect'] is FALSE, a form builder function or form
@@ -4074,6 +4094,8 @@ function _form_set_class(&$element, $class = array()) {
* Sample 'finished' callback:
* @code
* function batch_test_finished($success, $results, $operations) {
+ * // The 'success' parameter means no fatal PHP errors were detected. All
+ * // other error management should be handled using 'results'.
* if ($success) {
* $message = format_plural(count($results), 'One post processed.', '@count posts processed.');
* }
diff --git a/includes/install.inc b/includes/install.inc
index d22f4f9cb..089cdee8f 100644
--- a/includes/install.inc
+++ b/includes/install.inc
@@ -570,7 +570,7 @@ abstract class DatabaseTasks {
}
/**
- * @class Exception class used to throw error if the DatabaseInstaller fails.
+ * Exception thrown if the database installer fails.
*/
class DatabaseTaskException extends Exception {
}
diff --git a/includes/locale.inc b/includes/locale.inc
index 578b1b3c6..6154cf3c3 100644
--- a/includes/locale.inc
+++ b/includes/locale.inc
@@ -1863,7 +1863,7 @@ function _locale_rebuild_js($langcode = NULL) {
// Construct the array for JavaScript translations.
// Only add strings with a translation to the translations array.
- $result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup AND t.translation IS NOT NULL", array(':language' => $language->language, ':textgroup' => 'default'));
+ $result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup", array(':language' => $language->language, ':textgroup' => 'default'));
$translations = array();
foreach ($result as $data) {