diff options
105 files changed, 4290 insertions, 1807 deletions
@@ -56,6 +56,13 @@ DirectoryIndex index.php index.html index.htm <IfModule mod_rewrite.c> RewriteEngine on + # Set "protossl" to "s" if we were accessed via https://. This is used later + # if you enable "www." stripping or enforcement, in order to ensure that + # you don't bounce between http and https. + RewriteRule ^ - [E=protossl] + RewriteCond %{HTTPS} on + RewriteRule ^ - [E=protossl:s] + # Block access to "hidden" directories whose names begin with a period. This # includes directories used by version control systems such as Subversion or # Git to store control files. Files whose names begin with a period, as well @@ -78,14 +85,15 @@ DirectoryIndex index.php index.html index.htm # To redirect all users to access the site WITH the 'www.' prefix, # (http://example.com/... will be redirected to http://www.example.com/...) # uncomment the following: + # RewriteCond %{HTTP_HOST} . # RewriteCond %{HTTP_HOST} !^www\. [NC] - # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + # RewriteRule ^ http%{ENV:protossl}://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301] # # To redirect all users to access the site WITHOUT the 'www.' prefix, # (http://www.example.com/... will be redirected to http://example.com/...) # uncomment the following: # RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] - # RewriteRule ^ http://%1%{REQUEST_URI} [L,R=301] + # RewriteRule ^ http%{ENV:protossl}://%1%{REQUEST_URI} [L,R=301] # Modify the RewriteBase if you are using Drupal in a subdirectory or in a # VirtualDocumentRoot and the rewrite rules are not working properly. diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 69d94de73..70ed4a4ea 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,37 @@ +Drupal 7.22, xxxx-xx-xx (development version) +----------------------- +- Fixed a bug which caused the denial-of-service protection added in Drupal + 7.20 to break certain valid image URLs that had an extra slash in them. +- Fixed a bug with update queries in the SQLite database driver that prevented + Drupal from being installed with SQLite on PHP 5.4. +- Fixed enforced dependencies errors updating to recent versions of Drupal 7 on + certain non-MySQL databases. +- Refactored the Field module's caching behavior to obtain large improvements + in memory usage for sites with many fields and instances (API addition: + http://drupal.org/node/1880666). +- Fixed entity argument not being passed to implementations of + hook_file_download_access_alter(). The fix adds an additional context + parameter that can be passed when calling drupal_alter() for any hook (API + change: http://drupal.org/node/1882722). +- Fixed broken support for translatable comment fields (API change: + http://drupal.org/node/1874724). +- Added an assertThemeOutput() method to Simpletest to allow tests to check + that themed output matches an expected HTML string (API addition). +- Added a link to "Install another module" after a module has been successfully + downloaded via the Update Manager (UI change). +- Added an optional "exclusive" flag to installation profile .info files which + allows Drupal distributions to force a profile to be selected during + installation (API addition). +- Fixed a bug which caused the database API to not properly close database + connections. +- Added link to the URL for running cron from outside the site to the Cron + settings page (UI change). +- Fixed a bug which prevented image styles from being reverted on PHP 5.4. +- Made the default .htaccess rules protocol sensitive to improve security for + sites which use HTTPS and redirect between "www" and non-"www" versions of + the page. + Drupal 7.21, 2013-03-06 ----------------------- - Allowed sites using the 'image_allow_insecure_derivatives' variable to still diff --git a/authorize.php b/authorize.php index d14fa6e59..3ea2b20ac 100644 --- a/authorize.php +++ b/authorize.php @@ -4,16 +4,16 @@ * @file * Administrative script for running authorized file operations. * - * Using this script, the site owner (the user actually owning the files on - * the webserver) can authorize certain file-related operations to proceed - * with elevated privileges, for example to deploy and upgrade modules or - * themes. Users should not visit this page directly, but instead use an - * administrative user interface which knows how to redirect the user to this - * script as part of a multistep process. This script actually performs the - * selected operations without loading all of Drupal, to be able to more - * gracefully recover from errors. Access to the script is controlled by a - * global killswitch in settings.php ('allow_authorize_operations') and via - * the 'administer software updates' permission. + * Using this script, the site owner (the user actually owning the files on the + * webserver) can authorize certain file-related operations to proceed with + * elevated privileges, for example to deploy and upgrade modules or themes. + * Users should not visit this page directly, but instead use an administrative + * user interface which knows how to redirect the user to this script as part of + * a multistep process. This script actually performs the selected operations + * without loading all of Drupal, to be able to more gracefully recover from + * errors. Access to the script is controlled by a global killswitch in + * settings.php ('allow_authorize_operations') and via the 'administer software + * updates' permission. * * There are helper functions for setting up an operation to run via this * system in modules/system/system.module. For more information, see: @@ -21,16 +21,17 @@ */ /** - * Root directory of Drupal installation. + * Defines the root directory of the Drupal installation. */ define('DRUPAL_ROOT', getcwd()); /** - * Global flag to identify update.php and authorize.php runs, and so - * avoid various unwanted operations, such as hook_init() and - * hook_exit() invokes, css/js preprocessing and translation, and - * solve some theming issues. This flag is checked on several places - * in Drupal code (not just authorize.php). + * Global flag to identify update.php and authorize.php runs. + * + * Identifies update.php and authorize.php runs, avoiding unwanted operations + * such as hook_init() and hook_exit() invokes, css/js preprocessing and + * translation, and solves some theming issues. The flag is checked in other + * places in Drupal code (not just authorize.php). */ define('MAINTENANCE_MODE', 'update'); @@ -51,7 +52,7 @@ function authorize_access_denied_page() { * have access to the 'administer software updates' permission. * * @return - * TRUE if the current user can run authorize.php, otherwise FALSE. + * TRUE if the current user can run authorize.php, and FALSE if not. */ function authorize_access_allowed() { return variable_get('allow_authorize_operations', TRUE) && user_access('administer software updates'); diff --git a/includes/ajax.inc b/includes/ajax.inc index 4107029fe..d4082d2d8 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -836,7 +836,8 @@ function ajax_command_insert($selector, $html, $settings = NULL) { * @return * An array suitable for use with the ajax_render() function. * - * See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink + * See + * @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink */ function ajax_command_replace($selector, $html, $settings = NULL) { return array( diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 93322de45..c6c98ecb6 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.21'); +define('VERSION', '7.22-dev'); /** * Core API compatibility. @@ -716,7 +716,6 @@ function drupal_settings_initialize() { if (isset($base_url)) { // Parse fixed base URL from settings.php. $parts = parse_url($base_url); - $http_protocol = $parts['scheme']; if (!isset($parts['path'])) { $parts['path'] = ''; } @@ -811,7 +810,7 @@ function drupal_settings_initialize() { * than by consulting the database. * * @return - * The filename of the requested item. + * The filename of the requested item or NULL if the item is not found. */ function drupal_get_filename($type, $name, $filename = NULL) { // The location of files will not change during the request, so do not use @@ -1186,10 +1185,11 @@ function _drupal_set_preferred_header_name($name = NULL) { * Headers are set in drupal_add_http_header(). Default headers are not set * if they have been replaced or unset using drupal_add_http_header(). * - * @param $default_headers - * An array of headers as name/value pairs. - * @param $single - * If TRUE and headers have already be sent, send only the specified header. + * @param array $default_headers + * (optional) An array of headers as name/value pairs. + * @param bool $only_default + * (optional) If TRUE and headers have already been sent, send only the + * specified headers. */ function drupal_send_headers($default_headers = array(), $only_default = FALSE) { $headers_sent = &drupal_static(__FUNCTION__, FALSE); @@ -1420,8 +1420,9 @@ function drupal_unpack($obj, $field = 'data') { * Basically, you can put variables like @name into your string, and t() will * substitute their sanitized values at translation time. (See the * Localization API pages referenced above and the documentation of - * format_string() for details.) Translators can then rearrange the string as - * necessary for the language (e.g., in Spanish, it might be "blog de @name"). + * format_string() for details about how to define variables in your string.) + * Translators can then rearrange the string as necessary for the language + * (e.g., in Spanish, it might be "blog de @name"). * * During the Drupal installation phase, some resources used by t() wil not be * available to code that needs localization. See st() and get_t() for @@ -1484,21 +1485,34 @@ function t($string, array $args = array(), array $options = array()) { } /** - * Replaces placeholders with sanitized values in a string. + * Formats a string for HTML display by replacing variable placeholders. + * + * This function replaces variable placeholders in a string with the requested + * values and escapes the values so they can be safely displayed as HTML. It + * should be used on any unknown text that is intended to be printed to an HTML + * page (especially text that may have come from untrusted users, since in that + * case it prevents cross-site scripting and other security problems). + * + * In most cases, you should use t() rather than calling this function + * directly, since it will translate the text (on non-English-only sites) in + * addition to formatting it. * * @param $string * A string containing placeholders. * @param $args * An associative array of replacements to make. Occurrences in $string of - * any key in $args are replaced with the corresponding value, after - * sanitization. The sanitization function depends on the first character of - * the key: - * - !variable: Inserted as is. Use this for text that has already been - * sanitized. - * - @variable: Escaped to HTML using check_plain(). Use this for anything - * displayed on a page on the site. - * - %variable: Escaped as a placeholder for user-submitted content using - * drupal_placeholder(), which shows up as <em>emphasized</em> text. + * any key in $args are replaced with the corresponding value, after optional + * sanitization and formatting. The type of sanitization and formatting + * depends on the first character of the key: + * - @variable: Escaped to HTML using check_plain(). Use this as the default + * choice for anything displayed on a page on the site. + * - %variable: Escaped to HTML and formatted using drupal_placeholder(), + * which makes it display as <em>emphasized</em> text. + * - !variable: Inserted as is, with no sanitization or formatting. Only use + * this for text that has already been prepared for HTML display (for + * example, user-supplied text that has already been run through + * check_plain() previously, or is expected to contain some limited HTML + * tags and has already been run through filter_xss() previously). * * @see t() * @ingroup sanitization diff --git a/includes/cache.inc b/includes/cache.inc index a19d3c38c..f76164b91 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -80,43 +80,15 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * same name. Other implementations might want to store several bins in data * structures that get flushed together. While it is not a problem for most * cache bins if the entries in them are flushed before their expire time, some - * might break functionality or are extremely expensive to recalculate. These - * will be marked with a (*). The other bins expired automatically by core. - * Contributed modules can add additional bins and get them expired - * automatically by implementing hook_flush_caches(). - * - * - cache: Generic cache storage bin (used for variables, theme registry, - * locale date, list of simpletest tests etc). - * - * - cache_block: Stores the content of various blocks. - * - * - cache field: Stores the field data belonging to a given object. - * - * - cache_filter: Stores filtered pieces of content. - * - * - cache_form(*): Stores multistep forms. Flushing this bin means that some - * forms displayed to users lose their state and the data already submitted - * to them. - * - * - cache_menu: Stores the structure of visible navigation menus per page. - * - * - cache_page: Stores generated pages for anonymous users. It is flushed - * very often, whenever a page changes, at least for every ode and comment - * submission. This is the only bin affected by the page cache setting on - * the administrator panel. - * - * - cache path: Stores the system paths that have an alias. - * - * - cache update(*): Stores available releases. The update server (for - * example, drupal.org) needs to produce the relevant XML for every project - * installed on the current site. As this is different for (almost) every - * site, it's very expensive to recalculate for the update server. + * might break functionality or are extremely expensive to recalculate. The + * other bins are expired automatically by core. Contributed modules can add + * additional bins and get them expired automatically by implementing + * hook_flush_caches(). * * The reasons for having several bins are as follows: - * - * - smaller bins mean smaller database tables and allow for faster selects and - * inserts - * - we try to put fast changing cache items and rather static ones into + * - Smaller bins mean smaller database tables and allow for faster selects and + * inserts. + * - We try to put fast changing cache items and rather static ones into * different bins. The effect is that only the fast changing bins will need a * lot of writes to disk. The more static bins will also be better cacheable * with MySQL's query cache. @@ -125,13 +97,27 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * The cache ID of the data to store. * @param $data * The data to store in the cache. Complex data types will be automatically - * serialized before insertion. - * Strings will be stored as plain text and not serialized. + * serialized before insertion. Strings will be stored as plain text and are + * not serialized. * @param $bin - * The cache bin to store the data in. Valid core values are 'cache_block', - * 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form', - * 'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default - * cache. + * The cache bin to store the data in. Valid core values are: + * - cache: (default) Generic cache storage bin (used for theme registry, + * locale date, list of simpletest tests, etc.). + * - cache_block: Stores the content of various blocks. + * - cache_bootstrap: Stores the class registry, the system list of modules, + * the list of which modules implement which hooks, and the Drupal variable + * list. + * - cache_field: Stores the field data belonging to a given object. + * - cache_filter: Stores filtered pieces of content. + * - cache_form: Stores multistep forms. Flushing this bin means that some + * forms displayed to users lose their state and the data already submitted + * to them. This bin should not be flushed before its expired time. + * - cache_menu: Stores the structure of visible navigation menus per page. + * - cache_page: Stores generated pages for anonymous users. It is flushed + * very often, whenever a page changes, at least for every node and comment + * submission. This is the only bin affected by the page cache setting on + * the administrator panel. + * - cache_path: Stores the system paths that have an alias. * @param $expire * One of the following values: * - CACHE_PERMANENT: Indicates that the item should never be removed unless @@ -141,6 +127,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { * - A Unix timestamp: Indicates that the item should be kept at least until * the given time, after which it behaves like CACHE_TEMPORARY. * + * @see _update_cache_set() * @see cache_get() */ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { diff --git a/includes/common.inc b/includes/common.inc index 8276576e0..f6171cfd9 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1167,7 +1167,8 @@ function fix_gpc_magic() { /** * Verifies the syntax of the given e-mail address. * - * See @link http://tools.ietf.org/html/rfc5321 RFC 5321 @endlink for details. + * This uses the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * * @param $mail * A string containing an e-mail address. @@ -2379,6 +2380,14 @@ function drupal_attributes(array $attributes = array()) { * internal links output by modules should be generated by this function if * possible. * + * However, for links enclosed in translatable text you should use t() and + * embed the HTML anchor tag directly in the translated string. For example: + * @code + * t('Visit the <a href="@url">settings</a> page', array('@url' => url('admin'))); + * @endcode + * This keeps the context of the link title ('settings' in the example) for + * translators. + * * @param string $text * The translated link text for the anchor tag. * @param string $path @@ -2779,7 +2788,7 @@ function drupal_set_time_limit($time_limit) { * The name of the item for which the path is requested. * * @return - * The path to the requested item. + * The path to the requested item or an empty string if the item is not found. */ function drupal_get_path($type, $name) { return dirname(drupal_get_filename($type, $name)); @@ -5038,6 +5047,11 @@ function drupal_get_private_key() { * * @param $value * An additional value to base the token on. + * + * @return string + * A 43-character URL-safe token for validation, based on the user session ID, + * the global $drupal_hash_salt variable from settings.php, and the + * 'drupal_private_key' configuration variable. */ function drupal_get_token($value = '') { return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt()); @@ -5568,7 +5582,7 @@ function drupal_pre_render_link($element) { * @code * $node->content['links'] = array( * '#theme' => 'links__node', - * '#pre_render' = array('drupal_pre_render_links'), + * '#pre_render' => array('drupal_pre_render_links'), * 'comment' => array( * '#theme' => 'links__node__comment', * '#links' => array( diff --git a/includes/database/database.inc b/includes/database/database.inc index cae50fb87..339c9b03e 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -167,7 +167,7 @@ * } * @endcode * - * @link http://drupal.org/developing/api/database @endlink + * @see http://drupal.org/developing/api/database */ @@ -194,7 +194,7 @@ abstract class DatabaseConnection extends PDO { /** * The key representing this connection. - * + * * The key is a unique string which identifies a database connection. A * connection can be a single server or a cluster of master and slaves (use * target to pick between master and slave). @@ -303,13 +303,29 @@ abstract class DatabaseConnection extends PDO { // Call PDO::__construct and PDO::setAttribute. parent::__construct($dsn, $username, $password, $driver_options); - // Set a specific PDOStatement class if the driver requires that. + // Set a Statement class, unless the driver opted out. if (!empty($this->statementClass)) { $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); } } /** + * Destroys this Connection object. + * + * PHP does not destruct an object if it is still referenced in other + * variables. In case of PDO database connection objects, PHP only closes the + * connection when the PDO object is destructed, so any references to this + * object may cause the number of maximum allowed connections to be exceeded. + */ + public function destroy() { + // Destroy all references to this connection by setting them to NULL. + // The Statement class attribute only accepts a new value that presents a + // proper callable, so we reset it to PDOStatement. + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array())); + $this->schema = NULL; + } + + /** * Returns the default query options for any given query. * * A given query can be customized with a number of option flags in an @@ -1627,8 +1643,8 @@ abstract class Database { */ final public static function removeConnection($key) { if (isset(self::$databaseInfo[$key])) { + self::closeConnection(NULL, $key); unset(self::$databaseInfo[$key]); - unset(self::$connections[$key]); return TRUE; } else { @@ -1694,11 +1710,24 @@ abstract class Database { if (!isset($key)) { $key = self::$activeKey; } - // To close the connection, we need to unset the static variable. + // To close a connection, it needs to be set to NULL and removed from the + // static variable. In all cases, closeConnection() might be called for a + // connection that was not opened yet, in which case the key is not defined + // yet and we just ensure that the connection key is undefined. if (isset($target)) { + if (isset(self::$connections[$key][$target])) { + self::$connections[$key][$target]->destroy(); + self::$connections[$key][$target] = NULL; + } unset(self::$connections[$key][$target]); } else { + if (isset(self::$connections[$key])) { + foreach (self::$connections[$key] as $target => $connection) { + self::$connections[$key][$target]->destroy(); + self::$connections[$key][$target] = NULL; + } + } unset(self::$connections[$key]); } } @@ -1852,8 +1881,8 @@ class DatabaseTransaction { */ protected $name; - public function __construct(DatabaseConnection &$connection, $name = NULL) { - $this->connection = &$connection; + public function __construct(DatabaseConnection $connection, $name = NULL) { + $this->connection = $connection; // If there is no transaction depth, then no transaction has started. Name // the transaction 'drupal_transaction'. if (!$depth = $connection->transactionDepth()) { diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index 7ad019e58..00d81f473 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -13,11 +13,11 @@ class DatabaseConnection_mysql extends DatabaseConnection { /** - * Flag to indicate if we have registered the nextID cleanup function. + * Flag to indicate if the cleanup function in __destruct() should run. * * @var boolean */ - protected $shutdownRegistered = FALSE; + protected $needsCleanup = FALSE; public function __construct(array $connection_options = array()) { // This driver defaults to transaction support, except if explicitly passed FALSE. @@ -78,6 +78,12 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->exec(implode('; ', $connection_options['init_commands'])); } + public function __destruct() { + if ($this->needsCleanup) { + $this->nextIdDelete(); + } + } + public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options); } @@ -115,12 +121,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id)); $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID)); } - if (!$this->shutdownRegistered) { - // Use register_shutdown_function() here to keep the database system - // independent of Drupal. - register_shutdown_function(array($this, 'nextIdDelete')); - $shutdownRegistered = TRUE; - } + $this->needsCleanup = TRUE; return $new_id; } diff --git a/includes/database/query.inc b/includes/database/query.inc index 612985e02..8beeef1e8 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -1898,8 +1898,13 @@ class DatabaseCondition implements QueryConditionInterface, Countable { function __clone() { $this->changed = TRUE; foreach ($this->conditions as $key => $condition) { - if ($key !== '#conjunction' && $condition['field'] instanceOf QueryConditionInterface) { - $this->conditions[$key]['field'] = clone($condition['field']); + if ($key !== '#conjunction') { + if ($condition['field'] instanceOf QueryConditionInterface) { + $this->conditions[$key]['field'] = clone($condition['field']); + } + if ($condition['value'] instanceOf SelectQueryInterface) { + $this->conditions[$key]['value'] = clone($condition['value']); + } } } } diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index 74ff9ba20..1bf609db1 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -57,39 +57,18 @@ class InsertQuery_sqlite extends InsertQuery { * we don't select those rows. * * A query like this one: - * UPDATE test SET name = 'newname' WHERE tid = 1 + * UPDATE test SET col1 = 'newcol1', col2 = 'newcol2' WHERE tid = 1 * will become: - * UPDATE test SET name = 'newname' WHERE tid = 1 AND name <> 'newname' + * UPDATE test SET col1 = 'newcol1', col2 = 'newcol2' WHERE tid = 1 AND (col1 <> 'newcol1' OR col2 <> 'newcol2') */ class UpdateQuery_sqlite extends UpdateQuery { - /** - * Helper function that removes the fields that are already in a condition. - * - * @param $fields - * The fields. - * @param QueryConditionInterface $condition - * A database condition. - */ - protected function removeFieldsInCondition(&$fields, QueryConditionInterface $condition) { - foreach ($condition->conditions() as $child_condition) { - if ($child_condition['field'] instanceof QueryConditionInterface) { - $this->removeFieldsInCondition($fields, $child_condition['field']); - } - else { - unset($fields[$child_condition['field']]); - } - } - } - public function execute() { if (!empty($this->queryOptions['sqlite_return_matched_rows'])) { return parent::execute(); } - // Get the fields used in the update query, and remove those that are already - // in the condition. + // Get the fields used in the update query. $fields = $this->expressionFields + $this->fields; - $this->removeFieldsInCondition($fields, $this->condition); // Add the inverse of the fields to the condition. $condition = new DatabaseCondition('OR'); diff --git a/includes/file.inc b/includes/file.inc index 278be3ddc..09a442435 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -89,7 +89,7 @@ define('FILE_STATUS_PERMANENT', 1); * wrappers that are appropriate for particular usage. For example, this returns * only stream wrappers that use local file storage: * @code - * $local_stream_wrappers = file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL); + * $local_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL); * @endcode * * The $filter parameter can only filter to types containing a particular flag. @@ -99,7 +99,7 @@ define('FILE_STATUS_PERMANENT', 1); * array_diff_key() function can be used to help with this. For example, this * returns only stream wrappers that do not use local file storage: * @code - * $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL)); + * $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL)); * @endcode * * @param $filter @@ -892,7 +892,7 @@ function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXIST $destination = file_destination($destination, $replace); if ($destination === FALSE) { drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error'); - watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%destination' => $destination)); + watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%directory' => $destination)); return FALSE; } diff --git a/includes/form.inc b/includes/form.inc index aa90eca69..babd18b49 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -3662,35 +3662,6 @@ function form_pre_render_fieldset($element) { /** * Creates a group formatted as vertical tabs. * - * Note that autocomplete callbacks should include special handling as the - * user's input may contain forward slashes. If the user-submitted string has a - * '/' in the text that is sent in the autocomplete request, the menu system - * will split the text and pass it to the callback as multiple arguments. - * - * Suppose your autocomplete path in the menu system is 'mymodule_autocomplete.' - * In your form you have: - * @code - * '#autocomplete_path' => 'mymodule_autocomplete/' . $some_key . '/' . $some_id, - * @endcode - * The user types in "keywords" so the full path called is: - * 'mymodule_autocomplete/$some_key/$some_id/keywords' - * - * You should include code similar to the following to handle slashes in the - * input: - * @code - * function mymodule_autocomplete_callback($arg1, $arg2, $keywords) { - * $args = func_get_args(); - * // We need to remove $arg1 and $arg2 from the beginning of the array so we - * // are left with the keywords. - * array_shift($args); - * array_shift($args); - * // We store the user's original input in $keywords, including any slashes. - * $keywords = implode('/', $args); - * - * // Your code here. - * } - * @endcode - * * @param $element * An associative array containing the properties and children of the * fieldset. diff --git a/includes/image.inc b/includes/image.inc index ee5a086de..e30a33855 100644 --- a/includes/image.inc +++ b/includes/image.inc @@ -233,11 +233,11 @@ function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NUL * @param $image * An image object returned by image_load(). * @param $width - * The target width, in pixels. This value is omitted then the scaling will - * based only on the height value. + * The target width, in pixels. If this value is NULL then the scaling will + * be based only on the height value. * @param $height - * The target height, in pixels. This value is omitted then the scaling will - * based only on the width value. + * The target height, in pixels. If this value is NULL then the scaling will + * be based only on the width value. * @param $upscale * Boolean indicating that files smaller than the dimensions will be scaled * up. This generally results in a low quality image. diff --git a/includes/install.core.inc b/includes/install.core.inc index 9805e1c88..7a694e9bb 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -1041,7 +1041,21 @@ function install_select_profile(&$install_state) { } /** - * Selects an installation profile from a list or from a $_POST submission. + * Selects an installation profile. + * + * A profile will be selected if: + * - Only one profile is available, + * - A profile was submitted through $_POST, + * - Exactly one of the profiles is marked as "exclusive". + * If multiple profiles are marked as "exclusive" then no profile will be + * selected. + * + * @param array $profiles + * An associative array of profiles with the machine-readable names as keys. + * + * @return + * The machine-readable name of the selected profile or NULL if no profile was + * selected. */ function _install_select_profile($profiles) { if (sizeof($profiles) == 0) { @@ -1061,6 +1075,23 @@ function _install_select_profile($profiles) { } } } + // Check for a profile marked as "exclusive" and ensure that only one + // profile is marked as such. + $exclusive_profile = NULL; + foreach ($profiles as $profile) { + $profile_info = install_profile_info($profile->name); + if (!empty($profile_info['exclusive'])) { + if (empty($exclusive_profile)) { + $exclusive_profile = $profile->name; + } + else { + // We found a second "exclusive" profile. There's no way to choose + // between them, so we ignore the property. + return; + } + } + } + return $exclusive_profile; } /** diff --git a/includes/install.inc b/includes/install.inc index 0372483b6..c4bcb88b2 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -1244,6 +1244,12 @@ function drupal_check_module($module) { * - distribution_name: The name of the Drupal distribution that is being * installed, to be shown throughout the installation process. Defaults to * 'Drupal'. + * - exclusive: If the install profile is intended to be the only eligible + * choice in a distribution, setting exclusive = TRUE will auto-select it + * during installation, and the install profile selection screen will be + * skipped. If more than one profile is found where exclusive = TRUE then + * this property will have no effect and the profile selection screen will + * be shown as normal with all available profiles shown. * * Note that this function does an expensive file system scan to get info file * information for dependencies. If you only need information from the info diff --git a/includes/language.inc b/includes/language.inc index d0ea83113..ea63948d9 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -2,7 +2,9 @@ /** * @file - * Multiple language handling functionality. + * Language Negotiation API. + * + * @see http://drupal.org/node/1497272 */ /** @@ -11,7 +13,96 @@ define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default'); /** - * Return all the defined language types. + * @defgroup language_negotiation Language Negotiation API functionality + * @{ + * Functions to customize the language types and the negotiation process. + * + * The language negotiation API is based on two major concepts: + * - Language types: types of translatable data (the types of data that a user + * can view or request). + * - Language negotiation providers: functions for determining which language to + * use to present a particular piece of data to the user. + * Both language types and language negotiation providers are customizable. + * + * Drupal defines three built-in language types: + * - Interface language: The page's main language, used to present translated + * user interface elements such as titles, labels, help text, and messages. + * - Content language: The language used to present content that is available + * in more than one language (see + * @link field_language Field Language API @endlink for details). + * - URL language: The language associated with URLs. When generating a URL, + * this value will be used by url() as a default if no explicit preference is + * provided. + * Modules can define additional language types through + * hook_language_types_info(), and alter existing language type definitions + * through hook_language_types_info_alter(). + * + * Language types may be configurable or fixed. The language negotiation + * providers associated with a configurable language type can be explicitly + * set through the user interface. A fixed language type has predetermined + * (module-defined) language negotiation settings and, thus, does not appear in + * the configuration page. Here is a code snippet that makes the content + * language (which by default inherits the interface language's values) + * configurable: + * @code + * function mymodule_language_types_info_alter(&$language_types) { + * unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']); + * } + * @endcode + * + * Every language type can have a different set of language negotiation + * providers assigned to it. Different language types often share the same + * language negotiation settings, but they can have independent settings if + * needed. If two language types are configured the same way, their language + * switcher configuration will be functionally identical and the same settings + * will act on both language types. + * + * Drupal defines the following built-in language negotiation providers: + * - URL: Determine the language from the URL (path prefix or domain). + * - Session: Determine the language from a request/session parameter. + * - User: Follow the user's language preference. + * - Browser: Determine the language from the browser's language settings. + * - Default language: Use the default site language. + * Language negotiation providers are simple callback functions that implement a + * particular logic to return a language code. For instance, the URL provider + * searches for a valid path prefix or domain name in the current request URL. + * If a language negotiation provider does not return a valid language code, the + * next provider associated to the language type (based on provider weight) is + * invoked. + * + * Modules can define additional language negotiation providers through + * hook_language_negotiation_info(), and alter existing providers through + * hook_language_negotiation_info_alter(). Here is an example snippet that lets + * path prefixes be ignored for administrative paths: + * @code + * function mymodule_language_negotiation_info_alter(&$negotiation_info) { + * // Replace the core function with our own function. + * module_load_include('language', 'inc', 'language.negotiation'); + * $negotiation_info[LANGUAGE_NEGOTIATION_URL]['callbacks']['negotiation'] = 'mymodule_from_url'; + * $negotiation_info[LANGUAGE_NEGOTIATION_URL]['file'] = drupal_get_path('module', 'mymodule') . '/mymodule.module'; + * } + * + * function mymodule_from_url($languages) { + * // Use the core URL language negotiation provider to get a valid language + * // code. + * module_load_include('language', 'inc', 'language.negotiation'); + * $langcode = language_from_url($languages); + * + * // If we are on an administrative path, override with the default language. + * if (isset($_GET['q']) && strtok($_GET['q'], '/') == 'admin') { + * return language_default()->langcode; + * } + * return $langcode; + * } + * ?> + * @endcode + * + * For more information, see + * @link http://drupal.org/node/1497272 Language Negotiation API @endlink + */ + +/** + * Returns all the defined language types. * * @return * An array of language type names. The name will be used as the global @@ -30,11 +121,11 @@ function language_types_info() { } /** - * Return only the configurable language types. + * Returns only the configurable language types. * * A language type maybe configurable or fixed. A fixed language type is a type - * whose negotiation values are unchangeable and defined while defining the - * language type itself. + * whose language negotiation providers are module-defined and not altered + * through the user interface. * * @param $stored * Optional. By default retrieves values from the 'language_types' variable to @@ -68,7 +159,7 @@ function language_types_configurable($stored = TRUE) { } /** - * Disable the given language types. + * Disables the given language types. * * @param $types * An array of language types. @@ -122,16 +213,17 @@ function language_types_set() { } /** - * Check if a language provider is enabled. + * Checks whether a language negotiation provider is enabled for a language type. * * This has two possible behaviors: * - If $provider_id is given return its ID if enabled, FALSE otherwise. - * - If no ID is passed the first enabled language provider is returned. + * - If no ID is passed the first enabled language negotiation provider is + * returned. * * @param $type - * The language negotiation type. + * The language negotiation provider type. * @param $provider_id - * The language provider ID. + * The language negotiation provider ID. * * @return * The provider ID if it is enabled, FALSE otherwise. @@ -155,14 +247,13 @@ function language_negotiation_get($type, $provider_id = NULL) { } /** - * Check if the given language provider is enabled for any configurable language - * type. + * Checks if the language negotiation provider is enabled for any language type. * * @param $provider_id - * The language provider ID. + * The language negotiation provider ID. * * @return - * TRUE if there is at least one language type for which the give language + * TRUE if there is at least one language type for which the given language * provider is enabled, FALSE otherwise. */ function language_negotiation_get_any($provider_id) { @@ -176,7 +267,7 @@ function language_negotiation_get_any($provider_id) { } /** - * Return the language switch links for the given language. + * Returns the language switch links for the given language. * * @param $type * The language negotiation type. @@ -223,7 +314,7 @@ function language_negotiation_get_switch_links($type, $path) { } /** - * Updates language configuration to remove any language provider that is no longer defined. + * Removes any unused language negotation providers from the configuration. */ function language_negotiation_purge() { // Ensure that we are getting the defined language negotiation information. An @@ -246,12 +337,12 @@ function language_negotiation_purge() { } /** - * Save a list of language providers. + * Saves a list of language negotiation providers. * * @param $type * The language negotiation type. * @param $language_providers - * An array of language provider weights keyed by id. + * An array of language negotiation provider weights keyed by provider ID. * @see language_provider_weight() */ function language_negotiation_set($type, $language_providers) { @@ -277,7 +368,7 @@ function language_negotiation_set($type, $language_providers) { // If the provider does not express any preference about types, make it // available for any configurable type. $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types); - // Check if the provider is defined and has the right type. + // Check whether the provider is defined and has the right type. if (isset($types[$type])) { $provider_data = array(); foreach ($provider_fields as $field) { @@ -294,10 +385,10 @@ function language_negotiation_set($type, $language_providers) { } /** - * Return all the defined language providers. + * Returns all the defined language negotiation providers. * * @return - * An array of language providers. + * An array of language negotiation providers. */ function language_negotiation_info() { $language_providers = &drupal_static(__FUNCTION__); @@ -306,7 +397,7 @@ function language_negotiation_info() { // Collect all the module-defined language negotiation providers. $language_providers = module_invoke_all('language_negotiation_info'); - // Add the default language provider. + // Add the default language negotiation provider. $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( 'callbacks' => array('language' => 'language_from_default'), 'weight' => 10, @@ -314,7 +405,7 @@ function language_negotiation_info() { 'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)), ); - // Let other modules alter the list of language providers. + // Let other modules alter the list of language negotiation providers. drupal_alter('language_negotiation_info', $language_providers); } @@ -322,16 +413,17 @@ function language_negotiation_info() { } /** - * Helper function used to cache the language providers results. + * Helper function used to cache the language negotiation providers results. * * @param $provider_id - * The language provider ID. + * The language negotiation provider's identifier. * @param $provider - * The language provider to be invoked. If not passed it will be explicitly - * loaded through language_negotiation_info(). + * (optional) An associative array of information about the provider to be + * invoked (see hook_language_negotiation_info() for details). If not passed + * in, it will be loaded through language_negotiation_info(). * * @return - * The language provider's return value. + * A language object representing the language chosen by the provider. */ function language_provider_invoke($provider_id, $provider = NULL) { $results = &drupal_static(__FUNCTION__); @@ -352,25 +444,26 @@ function language_provider_invoke($provider_id, $provider = NULL) { require_once DRUPAL_ROOT . '/' . $provider['file']; } - // If the language provider has no cache preference or this is satisfied - // we can execute the callback. + // If the language negotiation provider has no cache preference or this is + // satisfied we can execute the callback. $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0); $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE; $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE; $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; } - // 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. + // Since objects are resources, we need to return a clone to prevent the + // language negotiation provider cache from being unintentionally altered. The + // same providers might be used with different language types based on + // configuration. return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id]; } /** - * Return the passed language provider weight or a default value. + * Returns the passed language negotiation provider weight or a default value. * * @param $provider - * A language provider data structure. + * A language negotiation provider data structure. * * @return * A numeric weight. @@ -381,16 +474,16 @@ function language_provider_weight($provider) { } /** - * Choose a language for the given type based on language negotiation settings. + * Chooses a language based on language negotiation provider settings. * * @param $type - * The language type. + * The language type key to find the language for. * * @return * The negotiated language object. */ function language_initialize($type) { - // Execute the language providers in the order they were set up and return the + // Execute the language negotiation providers in the order they were set up and return the // first valid language found. $negotiation = variable_get("language_negotiation_$type", array()); @@ -409,7 +502,7 @@ function language_initialize($type) { } /** - * Default language provider. + * Returns the default language negotiation provider. * * @return * The default language code. @@ -421,8 +514,8 @@ function language_from_default() { /** * Splits the given path into prefix and actual path. * - * Parse the given path and return the language object identified by the - * prefix and the actual path. + * Parse the given path and return the language object identified by the prefix + * and the actual path. * * @param $path * The path to split. @@ -482,3 +575,7 @@ function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) { return $fallback_candidates; } + +/** + * @} End of "language_negotiation" + */ diff --git a/includes/mail.inc b/includes/mail.inc index 8479d8e9b..bbb55357d 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -93,7 +93,9 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER * will be {$module}_{$key}. * @param $to * The e-mail address or addresses where the message will be sent to. The - * formatting of this string must comply with RFC 2822. Some examples are: + * formatting of this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink + * Some examples are: * - user@example.com * - user@example.com, anotheruser@example.com * - User <user@example.com> @@ -212,9 +214,9 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N * 'mail_system', which is a keyed array. The default implementation * is the class whose name is the value of 'default-system' key. A more specific * match first to key and then to module will be used in preference to the - * default. To specificy a different class for all mail sent by one module, set + * default. To specify a different class for all mail sent by one module, set * the class name as the value for the key corresponding to the module name. To - * specificy a class for a particular message sent by one module, set the class + * specify a class for a particular message sent by one module, set the class * name as the value for the array key that is the message id, which is * "${module}_${key}". * @@ -307,19 +309,21 @@ interface MailSystemInterface { * - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy', * 'user_password_reset'. * - to: The mail address or addresses where the message will be sent to. - * The formatting of this string must comply with RFC 2822. Some examples: + * The formatting of this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink + * Some examples are: * - user@example.com * - user@example.com, anotheruser@example.com * - User <user@example.com> * - User <user@example.com>, Another User <anotheruser@example.com> - * - subject: Subject of the e-mail to be sent. This must not contain any - * newline characters, or the mail may not be sent properly. - * - body: Message to be sent. Accepts both CRLF and LF line-endings. - * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for - * smart plain text wrapping. - * - headers: Associative array containing all additional mail headers not - * defined by one of the other parameters. PHP's mail() looks for Cc - * and Bcc headers and sends the mail to addresses in these headers too. + * - subject: Subject of the e-mail to be sent. This must not contain any + * newline characters, or the mail may not be sent properly. + * - body: Message to be sent. Accepts both CRLF and LF line-endings. + * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for + * smart plain text wrapping. + * - headers: Associative array containing all additional mail headers not + * defined by one of the other parameters. PHP's mail() looks for Cc and + * Bcc headers and sends the mail to addresses in these headers too. * * @return * TRUE if the mail was successfully accepted for delivery, otherwise FALSE. diff --git a/includes/menu.inc b/includes/menu.inc index 0cb9d23b8..2be090327 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -618,6 +618,7 @@ function _menu_load_objects(&$item, &$map) { * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. */ function _menu_check_access(&$item, $map) { + $item['access'] = FALSE; // Determine access callback, which will decide whether or not the current // user has access to this path. $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']); diff --git a/includes/module.inc b/includes/module.inc index d932f07b9..341cd7911 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -898,9 +898,10 @@ function drupal_required_modules() { * hook_TYPE_alter() implementations in modules. It ensures a consistent * interface for all altering operations. * - * A maximum of 2 alterable arguments is supported. In case more arguments need - * to be passed and alterable, modules provide additional variables assigned by - * reference in the last $context argument: + * A maximum of 2 alterable arguments is supported (a third is supported for + * legacy reasons, but should not be used in new code). In case more arguments + * need to be passed and alterable, modules provide additional variables + * assigned by reference in the last $context argument: * @code * $context = array( * 'alterable' => &$alterable, @@ -939,8 +940,14 @@ function drupal_required_modules() { * (optional) An additional variable that is passed by reference. If more * context needs to be provided to implementations, then this should be an * associative array as described above. + * @param $context3 + * (optional) An additional variable that is passed by reference. This + * parameter is deprecated and will not exist in Drupal 8; consequently, it + * should not be used for new Drupal 7 code either. It is here only for + * backwards compatibility with older code that passed additional arguments + * to drupal_alter(). */ -function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { +function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL) { // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { @@ -1053,6 +1060,6 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { } foreach ($functions[$cid] as $function) { - $function($data, $context1, $context2); + $function($data, $context1, $context2, $context3); } } diff --git a/includes/password.inc b/includes/password.inc index d4f5f738a..3d5a400d2 100644 --- a/includes/password.inc +++ b/includes/password.inc @@ -43,7 +43,7 @@ function _password_itoa64() { } /** - * Encode bytes into printable base 64 using the *nix standard from crypt(). + * Encodes bytes into printable base 64 using the *nix standard from crypt(). * * @param $input * The string containing bytes to encode. diff --git a/includes/path.inc b/includes/path.inc index 411a7a71c..234430ea1 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -386,7 +386,7 @@ function drupal_path_alias_whitelist_rebuild($source = NULL) { } /** - * Fetch a specific URL alias from the database. + * Fetches a specific URL alias from the database. * * @param $conditions * A string representing the source, a number representing the pid, or an @@ -475,11 +475,11 @@ function path_delete($criteria) { } /** - * Determine whether a path is in the administrative section of the site. + * Determines whether a path is in the administrative section of the site. * - * By default, paths are considered to be non-administrative. If a path does not - * match any of the patterns in path_get_admin_paths(), or if it matches both - * administrative and non-administrative patterns, it is considered + * By default, paths are considered to be non-administrative. If a path does + * not match any of the patterns in path_get_admin_paths(), or if it matches + * both administrative and non-administrative patterns, it is considered * non-administrative. * * @param $path @@ -503,7 +503,7 @@ function path_is_admin($path) { } /** - * Get a list of administrative and non-administrative paths. + * Gets a list of administrative and non-administrative paths. * * @return array * An associative array containing the following keys: diff --git a/includes/session.inc b/includes/session.inc index b04c18eb3..16727df6d 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -274,7 +274,7 @@ function drupal_session_initialize() { } /** - * Forcefully starts a session, preserving already set session data. + * Starts a session forcefully, preserving already set session data. * * @ingroup php_wrappers */ diff --git a/includes/tablesort.inc b/includes/tablesort.inc index 121a1b909..e589526c6 100644 --- a/includes/tablesort.inc +++ b/includes/tablesort.inc @@ -55,7 +55,7 @@ class TableSort extends SelectQueryExtender { } /** - * Initialize the table sort context. + * Initializes the table sort context. */ protected function init() { $ts = $this->order(); @@ -115,7 +115,7 @@ function tablesort_init($header) { } /** - * Format a column header. + * Formats a column header. * * If the cell in question is the column header for the current sort criterion, * it gets special formatting. All possible sort criteria become links. @@ -126,6 +126,7 @@ function tablesort_init($header) { * An array of column headers in the format described in theme_table(). * @param $ts * The current table sort context as returned from tablesort_init(). + * * @return * A properly formatted cell, ready for _theme_table_cell(). */ @@ -151,7 +152,7 @@ function tablesort_header($cell, $header, $ts) { } /** - * Format a table cell. + * Formats a table cell. * * Adds a class attribute to all cells in the currently active column. * @@ -163,6 +164,7 @@ function tablesort_header($cell, $header, $ts) { * The current table sort context as returned from tablesort_init(). * @param $i * The index of the cell's table column. + * * @return * A properly formatted cell, ready for _theme_table_cell(). */ @@ -179,7 +181,7 @@ function tablesort_cell($cell, $header, $ts, $i) { } /** - * Compose a URL query parameter array for table sorting links. + * Composes a URL query parameter array for table sorting links. * * @return * A URL query parameter array that consists of all components of the current @@ -190,10 +192,11 @@ function tablesort_get_query_parameters() { } /** - * Determine the current sort criterion. + * Determines the current sort criterion. * * @param $headers * An array of column headers in the format described in theme_table(). + * * @return * An associative array describing the criterion, containing the keys: * - "name": The localized title of the table column. @@ -226,10 +229,11 @@ function tablesort_get_order($headers) { } /** - * Determine the current sort direction. + * Determines the current sort direction. * * @param $headers * An array of column headers in the format described in theme_table(). + * * @return * The current sort direction ("asc" or "desc"). */ diff --git a/includes/theme.inc b/includes/theme.inc index 777922f05..4c454ba40 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -65,7 +65,7 @@ function _drupal_theme_access($theme) { } /** - * Initialize the theme system by loading the theme. + * Initializes the theme system by loading the theme. */ function drupal_theme_initialize() { global $theme, $user, $theme_key; @@ -113,8 +113,9 @@ function drupal_theme_initialize() { } /** - * Initialize the theme system given already loaded information. This - * function is useful to initialize a theme when no database is present. + * Initializes the theme system given already loaded information. + * + * This function is useful to initialize a theme when no database is present. * * @param $theme * An object with the following information: @@ -235,7 +236,7 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb } /** - * Get the theme registry. + * Gets the theme registry. * * @param $complete * Optional boolean to indicate whether to return the complete theme registry @@ -280,7 +281,7 @@ function theme_get_registry($complete = TRUE) { } /** - * Set the callback that will be used by theme_get_registry() to fetch the registry. + * Sets the callback that will be used by theme_get_registry(). * * @param $callback * The name of the callback function. @@ -296,7 +297,7 @@ function _theme_registry_callback($callback = NULL, array $arguments = array()) } /** - * Get the theme_registry cache; if it doesn't exist, build it. + * Gets the theme_registry cache; if it doesn't exist, builds it. * * @param $theme * The loaded $theme object as returned by list_themes(). @@ -336,16 +337,17 @@ function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, } /** - * Write the theme_registry cache into the database. + * Writes the theme_registry cache into the database. */ function _theme_save_registry($theme, $registry) { cache_set("theme_registry:$theme->name", $registry); } /** - * Force the system to rebuild the theme registry; this should be called - * when modules are added to the system, or when a dynamic system needs - * to add more theme hooks. + * Forces the system to rebuild the theme registry. + * + * This function should be called when modules are added to the system, or when + * a dynamic system needs to add more theme hooks. */ function drupal_theme_rebuild() { drupal_static_reset('theme_get_registry'); @@ -635,7 +637,8 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { $cache = $result + $cache; } - // Let themes have variable processors even if they didn't register a template. + // Let themes have variable processors even if they didn't register a + // template. if ($type == 'theme' || $type == 'base_theme') { foreach ($cache as $hook => $info) { // Check only if not registered by the theme or engine. @@ -662,7 +665,7 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) { } /** - * Build the theme registry cache. + * Builds the theme registry cache. * * @param $theme * The loaded $theme object as returned by list_themes(). @@ -724,7 +727,7 @@ function _theme_build_registry($theme, $base_theme, $theme_engine) { } /** - * Return a list of all currently available themes. + * Returns a list of all currently available themes. * * Retrieved from the database, if available and the site is not in maintenance * mode; otherwise compiled freshly from the filesystem. @@ -900,15 +903,15 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) { * executed (if they exist), in the following order (note that in the following * list, HOOK indicates the theme hook name, MODULE indicates a module name, * THEME indicates a theme name, and ENGINE indicates a theme engine name): - * - template_preprocess(&$variables, $hook): Creates a default set of variables - * for all theme hooks with template implementations. + * - template_preprocess(&$variables, $hook): Creates a default set of + * variables for all theme hooks with template implementations. * - template_preprocess_HOOK(&$variables): Should be implemented by the module * that registers the theme hook, to set up default variables. * - MODULE_preprocess(&$variables, $hook): hook_preprocess() is invoked on all * implementing modules. * - MODULE_preprocess_HOOK(&$variables): hook_preprocess_HOOK() is invoked on - * all implementing modules, so that modules that didn't define the theme hook - * can alter the variables. + * all implementing modules, so that modules that didn't define the theme + * hook can alter the variables. * - ENGINE_engine_preprocess(&$variables, $hook): Allows the theme engine to * set necessary variables for all theme hooks with template implementations. * - ENGINE_engine_preprocess_HOOK(&$variables): Allows the theme engine to set @@ -963,10 +966,10 @@ function drupal_find_base_themes($themes, $key, $used_keys = array()) { * @param $hook * The name of the theme hook to call. If the name contains a * double-underscore ('__') and there isn't an implementation for the full - * name, the part before the '__' is checked. This allows a fallback to a more - * generic implementation. For example, if theme('links__node', ...) is - * called, but there is no implementation of that theme hook, then the 'links' - * implementation is used. This process is iterative, so if + * name, the part before the '__' is checked. This allows a fallback to a + * more generic implementation. For example, if theme('links__node', ...) is + * called, but there is no implementation of that theme hook, then the + * 'links' implementation is used. This process is iterative, so if * theme('links__contextual__node', ...) is called, theme() checks for the * following implementations, and uses the first one that exists: * - links__contextual__node @@ -1042,7 +1045,8 @@ function theme($hook, $variables = array()) { // point path_to_theme() to the currently used theme path: $theme_path = $info['theme path']; - // Include a file if the theme function or variable processor is held elsewhere. + // Include a file if the theme function or variable processor is held + // elsewhere. if (!empty($info['includes'])) { foreach ($info['includes'] as $include_file) { include_once DRUPAL_ROOT . '/' . $include_file; @@ -1191,14 +1195,14 @@ function theme($hook, $variables = array()) { } /** - * Return the path to the current themed element. - * - * It can point to the active theme or the module handling a themed implementation. - * For example, when invoked within the scope of a theming call it will depend - * on where the theming function is handled. If implemented from a module, it - * will point to the module. If implemented from the active theme, it will point - * to the active theme. When called outside the scope of a theming call, it will - * always point to the active theme. + * Returns the path to the current themed element. + * + * It can point to the active theme or the module handling a themed + * implementation. For example, when invoked within the scope of a theming call + * it will depend on where the theming function is handled. If implemented from + * a module, it will point to the module. If implemented from the active theme, + * it will point to the active theme. When called outside the scope of a + * theming call, it will always point to the active theme. */ function path_to_theme() { global $theme_path; @@ -1211,7 +1215,7 @@ function path_to_theme() { } /** - * Allow themes and/or theme engines to easily discover overridden theme functions. + * Allows themes and/or theme engines to discover overridden theme functions. * * @param $cache * The existing cache of theme hooks to test against. @@ -1268,7 +1272,7 @@ function drupal_find_theme_functions($cache, $prefixes) { } /** - * Allow themes and/or theme engines to easily discover overridden templates. + * Allows themes and/or theme engines to easily discover overridden templates. * * @param $cache * The existing cache of theme hooks to test against. @@ -1345,7 +1349,8 @@ function drupal_find_theme_templates($cache, $extension, $path) { if ($matches) { foreach ($matches as $match) { $file = substr($match, 0, strpos($match, '.')); - // Put the underscores back in for the hook name and register this pattern. + // Put the underscores back in for the hook name and register this + // pattern. $arg_name = isset($info['variables']) ? 'variables' : 'render element'; $implementations[strtr($file, '-', '_')] = array( 'template' => $file, @@ -1361,7 +1366,7 @@ function drupal_find_theme_templates($cache, $extension, $path) { } /** - * Retrieve a setting for the current theme or for a given theme. + * Retrieves a setting for the current theme or for a given theme. * * The final setting is obtained from the last value found in the following * sources: @@ -1479,7 +1484,7 @@ function theme_get_setting($setting_name, $theme = NULL) { } /** - * Render a system default template, which is essentially a PHP template. + * Renders a system default template, which is essentially a PHP template. * * @param $template_file * The filename of the template to render. @@ -1490,14 +1495,21 @@ function theme_get_setting($setting_name, $theme = NULL) { * The output generated by the template. */ function theme_render_template($template_file, $variables) { - extract($variables, EXTR_SKIP); // Extract the variables to a local namespace - ob_start(); // Start output buffering - include DRUPAL_ROOT . '/' . $template_file; // Include the template file - return ob_get_clean(); // End buffering and return its contents + // Extract the variables to a local namespace + extract($variables, EXTR_SKIP); + + // Start output buffering + ob_start(); + + // Include the template file + include DRUPAL_ROOT . '/' . $template_file; + + // End buffering and return its contents + return ob_get_clean(); } /** - * Enable a given list of themes. + * Enables a given list of themes. * * @param $theme_list * An array of theme names. @@ -1522,7 +1534,7 @@ function theme_enable($theme_list) { } /** - * Disable a given list of themes. + * Disables a given list of themes. * * @param $theme_list * An array of theme names. @@ -1608,13 +1620,13 @@ function theme_status_messages($variables) { * theme('link') for rendering the anchor tag. * * To optimize performance for sites that don't need custom theming of links, - * the l() function includes an inline copy of this function, and uses that copy - * if none of the enabled modules or the active theme implement any preprocess - * or process functions or override this theme implementation. + * the l() function includes an inline copy of this function, and uses that + * copy if none of the enabled modules or the active theme implement any + * preprocess or process functions or override this theme implementation. * * @param $variables - * An associative array containing the keys 'text', 'path', and 'options'. See - * the l() function for information about these variables. + * An associative array containing the keys 'text', 'path', and 'options'. + * See the l() function for information about these variables. * * @see l() */ @@ -1635,15 +1647,16 @@ function theme_link($variables) { * item in the links list. * - html: (optional) Whether or not 'title' is HTML. If set, the title * will not be passed through check_plain(). - * - attributes: (optional) Attributes for the anchor, or for the <span> tag - * used in its place if no 'href' is supplied. If element 'class' is + * - attributes: (optional) Attributes for the anchor, or for the <span> + * tag used in its place if no 'href' is supplied. If element 'class' is * included, it must be an array of one or more class names. - * If the 'href' element is supplied, the entire link array is passed to l() - * as its $options parameter. + * If the 'href' element is supplied, the entire link array is passed to + * l() as its $options parameter. * - attributes: A keyed array of attributes for the UL containing the * list of links. - * - heading: (optional) A heading to precede the links. May be an associative - * array or a string. If it's an array, it can have the following elements: + * - heading: (optional) A heading to precede the links. May be an + * associative array or a string. If it's an array, it can have the + * following elements: * - text: The heading text. * - level: The heading level (e.g. 'h2', 'h3'). * - class: (optional) An array of the CSS classes for the heading. @@ -1747,8 +1760,8 @@ function theme_links($variables) { * attribute to be omitted in some cases. Therefore, this variable defaults * to an empty string, but can be set to NULL for the attribute to be * omitted. Usually, neither omission nor an empty string satisfies - * accessibility requirements, so it is strongly encouraged for code calling - * theme('image') to pass a meaningful value for this variable. + * accessibility requirements, so it is strongly encouraged for code + * calling theme('image') to pass a meaningful value for this variable. * - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 * - http://www.w3.org/TR/xhtml1/dtds.html * - http://dev.w3.org/html5/spec/Overview.html#alt @@ -2005,7 +2018,8 @@ function theme_table($variables) { * * @param $variables * An associative array containing: - * - style: Set to either 'asc' or 'desc', this determines which icon to show. + * - style: Set to either 'asc' or 'desc', this determines which icon to + * show. */ function theme_tablesort_indicator($variables) { if ($variables['style'] == "asc") { @@ -2148,7 +2162,8 @@ function theme_feed_icon($variables) { * - script: To load JavaScript. * - #attributes: (optional) An array of HTML attributes to apply to the * tag. - * - #value: (optional) A string containing tag content, such as inline CSS. + * - #value: (optional) A string containing tag content, such as inline + * CSS. * - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA * wrapper prefix. * - #value_suffix: (optional) A string to append to #value, e.g. a CDATA @@ -2316,8 +2331,9 @@ function template_preprocess(&$variables, $hook) { global $user; static $count = array(); - // Track run count for each hook to provide zebra striping. - // See "template_preprocess_block()" which provides the same feature specific to blocks. + // Track run count for each hook to provide zebra striping. See + // "template_preprocess_block()" which provides the same feature specific to + // blocks. $count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1; $variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even'; $variables['id'] = $count[$hook]++; @@ -2677,13 +2693,13 @@ function theme_get_suggestions($args, $base, $delimiter = '__') { } /** - * The variables array generated here is a mirror of template_preprocess_page(). - * This preprocessor will run its course when theme_maintenance_page() is - * invoked. + * Process variables for maintenance-page.tpl.php. * - * An alternate template file of "maintenance-page--offline.tpl.php" can be - * used when the database is offline to hide errors and completely replace the - * content. + * The variables array generated here is a mirror of + * template_preprocess_page(). This preprocessor will run its course when + * theme_maintenance_page() is invoked. An alternate template file of + * maintenance-page--offline.tpl.php can be used when the database is offline to + * hide errors and completely replace the content. * * The $variables array contains the following arguments: * - $content @@ -2777,10 +2793,13 @@ function template_preprocess_maintenance_page(&$variables) { } /** + * Theme process function for theme_maintenance_field(). + * * The variables array generated here is a mirror of template_process_html(). * This processor will run its course when theme_maintenance_page() is invoked. * * @see maintenance-page.tpl.php + * @see template_process_html() */ function template_process_maintenance_page(&$variables) { $variables['head'] = drupal_get_html_head(); @@ -2792,7 +2811,7 @@ function template_process_maintenance_page(&$variables) { /** * Preprocess variables for region.tpl.php * - * Prepare the values passed to the theme_region function to be passed into a + * Prepares the values passed to the theme_region function to be passed into a * pluggable template engine. Uses the region name to generate a template file * suggestions. If none are found, the default region.tpl.php is used. * diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc index 218a8adaa..6baf219b0 100644 --- a/includes/theme.maintenance.inc +++ b/includes/theme.maintenance.inc @@ -10,9 +10,9 @@ * * Used for site installs, updates and when the site is in maintenance mode. * It also applies when the database is unavailable or bootstrap was not - * complete. Seven is always used for the initial install and update operations. - * In other cases, Bartik is used, but this can be overridden by setting a - * "maintenance_theme" key in the $conf variable in settings.php. + * complete. Seven is always used for the initial install and update + * operations. In other cases, Bartik is used, but this can be overridden by + * setting a "maintenance_theme" key in the $conf variable in settings.php. */ function _drupal_maintenance_theme() { global $theme, $theme_key, $conf; @@ -85,7 +85,7 @@ function _drupal_maintenance_theme() { } /** - * This builds the registry when the site needs to bypass any database calls. + * Builds the registry when the site needs to bypass any database calls. */ function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) { return _theme_build_registry($theme, $base_theme, $theme_engine); @@ -160,7 +160,7 @@ function theme_update_page($variables) { } /** - * Returns HTML for a report of the results from an operation run via authorize.php. + * Returns HTML for a results report of an operation run by authorize.php. * * @param $variables * An associative array containing: diff --git a/includes/token.inc b/includes/token.inc index 0b05c68f4..402aeae01 100644 --- a/includes/token.inc +++ b/includes/token.inc @@ -190,10 +190,10 @@ function token_generate($type, array $tokens, array $data = array(), array $opti } /** - * Given a list of tokens, returns those that begin with a specific prefix. + * Returns a list of tokens that begin with a specific prefix. * - * Used to extract a group of 'chained' tokens (such as [node:author:name]) from - * the full list of tokens found in text. For example: + * Used to extract a group of 'chained' tokens (such as [node:author:name]) + * from the full list of tokens found in text. For example: * @code * $data = array( * 'author:name' => '[node:author:name]', @@ -230,8 +230,10 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') { /** * Returns metadata describing supported tokens. * - * The metadata array contains token type, name, and description data as well as - * an optional pointer indicating that the token chains to another set of tokens. + * The metadata array contains token type, name, and description data as well + * as an optional pointer indicating that the token chains to another set of + * tokens. + * * For example: * @code * $data['types']['node'] = array( diff --git a/includes/unicode.inc b/includes/unicode.inc index 81a0a4dfe..fd497cca7 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -1,6 +1,11 @@ <?php /** +* @file +* Provides Unicode-related conversions and operations. +*/ + +/** * Indicates an error during check for PHP unicode support. */ define('UNICODE_ERROR', -1); @@ -19,8 +24,6 @@ define('UNICODE_MULTIBYTE', 1); /** * Matches Unicode characters that are word boundaries. * - * @see http://unicode.org/glossary - * * Characters with the following General_category (gc) property values are used * as word boundaries. While this does not fully conform to the Word Boundaries * algorithm described in http://unicode.org/reports/tr29, as PCRE does not @@ -39,6 +42,8 @@ define('UNICODE_MULTIBYTE', 1); * Note that the PCRE property matcher is not used because we wanted to be * compatible with Unicode 5.2.0 regardless of the PCRE version used (and any * bugs in PCRE property tables). + * + * @see http://unicode.org/glossary */ define('PREG_CLASS_UNICODE_WORD_BOUNDARY', '\x{0}-\x{2F}\x{3A}-\x{40}\x{5B}-\x{60}\x{7B}-\x{A9}\x{AB}-\x{B1}\x{B4}' . @@ -125,7 +130,7 @@ function _unicode_check() { } /** - * Return Unicode library status and errors. + * Returns Unicode library status and errors. */ function unicode_requirements() { // Ensure translations don't break during installation. @@ -157,14 +162,14 @@ function unicode_requirements() { } /** - * Prepare a new XML parser. + * Prepares a new XML parser. * - * This is a wrapper around xml_parser_create() which extracts the encoding from - * the XML data first and sets the output encoding to UTF-8. This function should - * be used instead of xml_parser_create(), because PHP 4's XML parser doesn't - * check the input encoding itself. "Starting from PHP 5, the input encoding is - * automatically detected, so that the encoding parameter specifies only the - * output encoding." + * This is a wrapper around xml_parser_create() which extracts the encoding + * from the XML data first and sets the output encoding to UTF-8. This function + * should be used instead of xml_parser_create(), because PHP 4's XML parser + * doesn't check the input encoding itself. "Starting from PHP 5, the input + * encoding is automatically detected, so that the encoding parameter specifies + * only the output encoding." * * This is also where unsupported encodings will be converted. Callers should * take this into account: $data might have been changed after the call. @@ -213,7 +218,7 @@ function drupal_xml_parser_create(&$data) { } /** - * Convert data to UTF-8 + * Converts data to UTF-8. * * Requires the iconv, GNU recode or mbstring PHP extension. * @@ -244,15 +249,15 @@ function drupal_convert_to_utf8($data, $encoding) { } /** - * Truncate a UTF-8-encoded string safely to a number of bytes. + * Truncates a UTF-8-encoded string safely to a number of bytes. * * If the end position is in the middle of a UTF-8 sequence, it scans backwards * until the beginning of the byte sequence. * * Use this function whenever you want to chop off a string at an unsure * location. On the other hand, if you're sure that you're splitting on a - * character boundary (e.g. after using strpos() or similar), you can safely use - * substr() instead. + * character boundary (e.g. after using strpos() or similar), you can safely + * use substr() instead. * * @param $string * The string to truncate. @@ -306,7 +311,7 @@ function drupal_truncate_bytes($string, $len) { * boundaries, giving you "See myverylongurl..." (assuming you had set * $add_ellipses to TRUE). * - * @return + * @return string * The truncated string. */ function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) { @@ -356,8 +361,7 @@ function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = } /** - * Encodes MIME/HTTP header values that contain non-ASCII, UTF-8 encoded - * characters. + * Encodes MIME/HTTP header values that contain incorrectly encoded characters. * * For example, mime_header_encode('tést.txt') returns "=?UTF-8?B?dMOpc3QudHh0?=". * @@ -369,6 +373,14 @@ function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = * each chunk starts and ends on a character boundary. * - Using \n as the chunk separator may cause problems on some systems and may * have to be changed to \r\n or \r. + * + * @param $string + * The header to encode. + * + * @return string + * The mime-encoded header. + * + * @see mime_header_decode() */ function mime_header_encode($string) { if (preg_match('/[^\x20-\x7E]/', $string)) { @@ -388,7 +400,15 @@ function mime_header_encode($string) { } /** - * Complement to mime_header_encode + * Decodes MIME/HTTP encoded header values. + * + * @param $header + * The header to decode. + * + * @return string + * The mime-decoded header. + * + * @see mime_header_encode() */ function mime_header_decode($header) { // First step: encoded chunks followed by other encoded chunks (need to collapse whitespace) @@ -398,7 +418,17 @@ function mime_header_decode($header) { } /** - * Helper function to mime_header_decode + * Decodes encoded header data passed from mime_header_decode(). + * + * Callback for preg_replace_callback() within mime_header_decode(). + * + * @param $matches + * The array of matches from preg_replace_callback(). + * + * @return string + * The mime-decoded string. + * + * @see mime_header_decode() */ function _mime_header_decode($matches) { // Regexp groups: @@ -415,9 +445,9 @@ function _mime_header_decode($matches) { /** * Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes. * - * Double-escaped entities will only be decoded once ("&lt;" becomes "<", - * not "<"). Be careful when using this function, as decode_entities can revert - * previous sanitization efforts (<script> will become <script>). + * Double-escaped entities will only be decoded once ("&lt;" becomes "<" + * , not "<"). Be careful when using this function, as decode_entities can + * revert previous sanitization efforts (<script> will become <script>). * * @param $text * The text to decode entities in. @@ -430,8 +460,15 @@ function decode_entities($text) { } /** - * Count the amount of characters in a UTF-8 string. This is less than or - * equal to the byte count. + * Counts the number of characters in a UTF-8 string. + * + * This is less than or equal to the byte count. + * + * @param $text + * The string to run the operation on. + * + * @return integer + * The length of the string. * * @ingroup php_wrappers */ @@ -449,6 +486,12 @@ function drupal_strlen($text) { /** * Uppercase a UTF-8 string. * + * @param $text + * The string to run the operation on. + * + * @return string + * The string in uppercase. + * * @ingroup php_wrappers */ function drupal_strtoupper($text) { @@ -468,6 +511,12 @@ function drupal_strtoupper($text) { /** * Lowercase a UTF-8 string. * + * @param $text + * The string to run the operation on. + * + * @return string + * The string in lowercase. + * * @ingroup php_wrappers */ function drupal_strtolower($text) { @@ -485,15 +534,28 @@ function drupal_strtolower($text) { } /** - * Helper function for case conversion of Latin-1. - * Used for flipping U+C0-U+DE to U+E0-U+FD and back. + * Flips U+C0-U+DE to U+E0-U+FD and back. + * + * @param $matches + * An array of matches. + * + * @return array + * The Latin-1 version of the array of matches. + * + * @see drupal_strtolower() */ function _unicode_caseflip($matches) { return $matches[0][0] . chr(ord($matches[0][1]) ^ 32); } /** - * Capitalize the first letter of a UTF-8 string. + * Capitalizes the first letter of a UTF-8 string. + * + * @param $text + * The string to convert. + * + * @return + * The string with the first letter as uppercase. * * @ingroup php_wrappers */ @@ -503,12 +565,21 @@ function drupal_ucfirst($text) { } /** - * Cut off a piece of a string based on character indices and counts. Follows - * the same behavior as PHP's own substr() function. + * Cuts off a piece of a string based on character indices and counts. * - * Note that for cutting off a string at a known character/substring - * location, the usage of PHP's normal strpos/substr is safe and - * much faster. + * Follows the same behavior as PHP's own substr() function. Note that for + * cutting off a string at a known character/substring location, the usage of + * PHP's normal strpos/substr is safe and much faster. + * + * @param $text + * The input string. + * @param $start + * The position at which to start reading. + * @param $length + * The number of characters to read. + * + * @return + * The shortened string. * * @ingroup php_wrappers */ diff --git a/includes/update.inc b/includes/update.inc index 5f1c2331c..a17161c9e 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -38,7 +38,7 @@ function update_fix_compatibility() { } /** - * Helper function to test compatibility of a module or theme. + * Tests the compatibility of a module or theme. */ function update_check_incompatibility($name, $type = 'module') { static $themes, $modules; @@ -908,7 +908,7 @@ function update_get_d6_session_name() { } /** - * Perform one update and store the results for display on finished page. + * Performs one update and stores the results for display on the results page. * * If an update function completes successfully, it should return a message * as a string indicating success, for example: @@ -1008,7 +1008,7 @@ function update_do_one($module, $number, $dependency_map, &$context) { class DrupalUpdateException extends Exception { } /** - * Start the database update batch process. + * Starts the database update batch process. * * @param $start * An array whose keys contain the names of modules to be updated during the @@ -1078,7 +1078,7 @@ function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $ } /** - * Finish the update process and store results for eventual display. + * Finishes the update process and stores the results for eventual display. * * After the updates run, all caches are flushed. The update results are * stored into the session (for example, to be displayed on the update results @@ -1115,7 +1115,7 @@ function update_finished($success, $results, $operations) { } /** - * Return a list of all the pending database updates. + * Returns a list of all the pending database updates. * * @return * An associative array keyed by module name which contains all information @@ -1409,7 +1409,7 @@ function update_already_performed($module, $number) { } /** - * Invoke hook_update_dependencies() in all installed modules. + * Invokes hook_update_dependencies() in all installed modules. * * This function is similar to module_invoke_all(), with the main difference * that it does not require that a module be enabled to invoke its hook, only diff --git a/includes/utility.inc b/includes/utility.inc index 5019852c7..f651fd631 100644 --- a/includes/utility.inc +++ b/includes/utility.inc @@ -12,6 +12,7 @@ * The variable to export. * @param $prefix * A prefix that will be added at the beginning of every lines of the output. + * * @return * The variable exported in a way compatible to Drupal's coding standards. */ diff --git a/install.php b/install.php index 881ca5a82..685d3b4ee 100644 --- a/install.php +++ b/install.php @@ -6,12 +6,12 @@ */ /** - * Root directory of Drupal installation. + * Defines the root directory of the Drupal installation. */ define('DRUPAL_ROOT', getcwd()); /** - * Global flag to indicate that site is in installation mode. + * Global flag to indicate the site is in installation mode. */ define('MAINTENANCE_MODE', 'install'); diff --git a/modules/aggregator/aggregator-rtl.css b/modules/aggregator/aggregator-rtl.css index ea59ca3a1..057d01565 100644 --- a/modules/aggregator/aggregator-rtl.css +++ b/modules/aggregator/aggregator-rtl.css @@ -1,3 +1,6 @@ +/** + * Right-to-Left styles for theme in the Aggregator module. + */ #aggregator .feed-source .feed-icon { float: left; diff --git a/modules/aggregator/aggregator.admin.inc b/modules/aggregator/aggregator.admin.inc index 8b817c0fa..443facb12 100644 --- a/modules/aggregator/aggregator.admin.inc +++ b/modules/aggregator/aggregator.admin.inc @@ -2,11 +2,11 @@ /** * @file - * Admin page callbacks for the aggregator module. + * Administration page callbacks for the Aggregator module. */ /** - * Menu callback; displays the aggregator administration page. + * Page callback: Displays the Aggregator module administration page. */ function aggregator_admin_overview() { return aggregator_view(); @@ -16,7 +16,7 @@ function aggregator_admin_overview() { * Displays the aggregator administration page. * * @return - * The page HTML. + * A HTML-formatted string with administration page content. */ function aggregator_view() { $result = db_query('SELECT f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block ORDER BY f.title'); @@ -56,8 +56,8 @@ function aggregator_view() { * Form constructor for adding and editing feed sources. * * @param $feed - * If editing a feed, the feed to edit as a PHP stdClass value; if adding a - * new feed, NULL. + * (optional) If editing a feed, the feed to edit as a PHP stdClass value; if + * adding a new feed, NULL. Defaults to NULL. * * @ingroup forms * @see aggregator_form_feed_validate() @@ -165,6 +165,7 @@ function aggregator_form_feed_validate($form, &$form_state) { * Form submission handler for aggregator_form_feed(). * * @see aggregator_form_feed_validate() + * * @todo Add delete confirmation dialog. */ function aggregator_form_feed_submit($form, &$form_state) { @@ -398,7 +399,7 @@ function _aggregator_parse_opml($opml) { } /** - * Menu callback; refreshes a feed, then redirects to the overview page. + * Page callback: Refreshes a feed, then redirects to the overview page. * * @param $feed * An object describing the feed to be refreshed. @@ -590,6 +591,7 @@ function aggregator_form_category_validate($form, &$form_state) { * Form submission handler for aggregator_form_category(). * * @see aggregator_form_category_validate() + * * @todo Add delete confirmation dialog. */ function aggregator_form_category_submit($form, &$form_state) { diff --git a/modules/aggregator/aggregator.css b/modules/aggregator/aggregator.css index 13c58ffe7..4285631e8 100644 --- a/modules/aggregator/aggregator.css +++ b/modules/aggregator/aggregator.css @@ -1,3 +1,6 @@ +/** + * Styles for theme in the Aggregator module. + */ #aggregator .feed-source .feed-title { margin-top: 0; diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 93457c68d..70f8c5cd8 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -266,13 +266,13 @@ function aggregator_menu() { } /** - * Title callback: Returns a title for aggregatory category pages. + * Title callback: Returns a title for aggregator category pages. * * @param $category * An aggregator category. * * @return - * An aggregator category title. + * A string with the aggregator category title. */ function _aggregator_category_title($category) { return $category['title']; @@ -723,7 +723,7 @@ function theme_aggregator_block_item($variables) { } /** - * Safely renders HTML content, as allowed. + * Renders the HTML content safely, as allowed. * * @param $value * The content to be filtered. @@ -739,7 +739,7 @@ function aggregator_filter_xss($value) { * Checks and sanitizes the aggregator configuration. * * Goes through all fetchers, parsers and processors and checks whether they - * are available. If one is missing resets to standard configuration. + * are available. If one is missing, resets to standard configuration. * * @return * TRUE if this function resets the configuration; FALSE if not. @@ -775,7 +775,7 @@ function aggregator_sanitize_configuration() { * Items count. * * @return - * Plural-formatted "@count items" + * A string that is plural-formatted as "@count items". */ function _aggregator_items($count) { return format_plural($count, '1 item', '@count items'); diff --git a/modules/aggregator/aggregator.pages.inc b/modules/aggregator/aggregator.pages.inc index cd1c4cb2c..bfba3fffb 100644 --- a/modules/aggregator/aggregator.pages.inc +++ b/modules/aggregator/aggregator.pages.inc @@ -2,14 +2,14 @@ /** * @file - * User page callbacks for the aggregator module. + * User page callbacks for the Aggregator module. */ /** - * Menu callback; displays the most recent items gathered from any feed. + * Page callback: Displays the most recent items gathered from any feed. * * @return - * The items HTML. + * The rendered list of items for the feed. */ function aggregator_page_last() { drupal_add_feed('aggregator/rss', variable_get('site_name', 'Drupal') . ' ' . t('aggregator')); @@ -20,13 +20,15 @@ function aggregator_page_last() { } /** - * Menu callback; displays all the items captured from a particular feed. + * Page callback: Displays all the items captured from the particular feed. * * @param $feed * The feed for which to display all items. * * @return * The rendered list of items for a feed. + * + * @see aggregator_menu() */ function aggregator_page_source($feed) { drupal_set_title($feed->title); @@ -40,13 +42,13 @@ function aggregator_page_source($feed) { } /** - * Menu callback; displays a form with all items captured from a feed. + * Page callback: Displays a form with all items captured from a feed. * * @param $feed - * The feed for which to list all the aggregated items. + * The feed for which to list all of the aggregated items. * * @return - * The rendered list of items for a feed. + * The rendered list of items for the feed. * * @see aggregator_page_source() */ @@ -55,13 +57,13 @@ function aggregator_page_source_form($form, $form_state, $feed) { } /** - * Menu callback; displays all the items aggregated in a particular category. + * Page callback: Displays all the items aggregated in a particular category. * * @param $category - * The category for which to list all the aggregated items. + * The category for which to list all of the aggregated items. * * @return -* The rendered list of items for a category. + * The rendered list of items for the feed. */ function aggregator_page_category($category) { drupal_add_feed('aggregator/rss/' . $category['cid'], variable_get('site_name', 'Drupal') . ' ' . t('aggregator - @title', array('@title' => $category['title']))); @@ -74,13 +76,13 @@ function aggregator_page_category($category) { } /** - * Menu callback; displays a form containing items aggregated in a category. + * Page callback: Displays a form containing items aggregated in a category. * * @param $category - * The category for which to list all the aggregated items. + * The category for which to list all of the aggregated items. * * @return -* The rendered list of items for a category. + * The rendered list of items for the feed. * * @see aggregator_page_category() */ @@ -166,7 +168,7 @@ function aggregator_feed_items_load($type, $data = NULL) { * The feed source URL. * * @return - * The rendered list of items for a feed. + * The rendered list of items for the feed. */ function _aggregator_page_list($items, $op, $feed_source = '') { if (user_access('administer news feeds') && ($op == 'categorize')) { @@ -191,10 +193,14 @@ function _aggregator_page_list($items, $op, $feed_source = '') { * @param $items * An array of the feed items. * @param $feed_source - * The feed source URL. + * (optional) The feed source URL. Defaults to an empty string. + * + * @return array + * An array of FAPI elements. * - * @ingroup forms * @see aggregator_categorize_items_submit() + * @see theme_aggregator_categorize_items() + * @ingroup forms */ function aggregator_categorize_items($items, $feed_source = '') { $form['#submit'][] = 'aggregator_categorize_items_submit'; @@ -334,7 +340,12 @@ function template_preprocess_aggregator_item(&$variables) { } /** - * Menu callback; displays all the feeds used by the aggregator. + * Page callback: Displays all the feeds used by the aggregator. + * + * @return + * An HTML-formatted string. + * + * @see aggregator_menu() */ function aggregator_page_sources() { $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title'); @@ -358,7 +369,12 @@ function aggregator_page_sources() { } /** - * Menu callback; displays all the categories used by the aggregator. + * Page callback: Displays all the categories used by the Aggregator module. + * + * @return string + * An HTML formatted string. + * + * @see aggregator_menu() */ function aggregator_page_categories() { $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description'); @@ -380,7 +396,10 @@ function aggregator_page_categories() { } /** - * Menu callback; generate an RSS 0.92 feed of aggregator items or categories. + * Page callback: Generates an RSS 0.92 feed of aggregator items or categories. + * + * @return string + * An HTML formatted string. */ function aggregator_page_rss() { $result = NULL; @@ -448,12 +467,14 @@ function theme_aggregator_page_rss($variables) { } /** - * Menu callback; generates an OPML representation of all feeds. + * Page callback: Generates an OPML representation of all feeds. * * @param $cid - * If set, feeds are exported only from a category with this ID. Otherwise, all feeds are exported. + * (optional) If set, feeds are exported only from a category with this ID. + * Otherwise, all feeds are exported. Defaults to NULL. + * * @return - * The output XML. + * An OPML formatted string. */ function aggregator_page_opml($cid = NULL) { if ($cid) { @@ -468,14 +489,12 @@ function aggregator_page_opml($cid = NULL) { } /** - * Prints the OPML page for a feed. + * Prints the OPML page for the feed. * * @param $variables * An associative array containing: * - feeds: An array of the feeds to theme. * - * @return void - * * @ingroup themeable */ function theme_aggregator_page_opml($variables) { diff --git a/modules/aggregator/aggregator.processor.inc b/modules/aggregator/aggregator.processor.inc index 3f1319c8c..44ed54996 100644 --- a/modules/aggregator/aggregator.processor.inc +++ b/modules/aggregator/aggregator.processor.inc @@ -131,6 +131,12 @@ function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) { * * Callback for drupal_map_assoc() within * aggregator_form_aggregator_admin_form_alter(). + * + * @param $length + * The desired length of teaser text, in bytes. + * + * @return + * A translated string explaining the teaser string length. */ function _aggregator_characters($length) { return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters'); diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test index eff31020f..8b95d6e37 100644 --- a/modules/aggregator/aggregator.test +++ b/modules/aggregator/aggregator.test @@ -5,6 +5,9 @@ * Tests for aggregator.module. */ +/** + * Defines a base class for testing the Aggregator module. + */ class AggregatorTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('aggregator', 'aggregator_test'); @@ -13,10 +16,15 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Create an aggregator feed (simulate form submission on admin/config/services/aggregator/add/feed). + * Creates an aggregator feed. + * + * This method simulates the form submission on path + * admin/config/services/aggregator/add/feed. * * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * (optional) If given, feed will be created with this URL, otherwise + * /rss.xml will be used. Defaults to NULL. + * * @return $feed * Full feed object if possible. * @@ -33,7 +41,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Delete an aggregator feed. + * Deletes an aggregator feed. * * @param $feed * Feed object representing the feed. @@ -44,10 +52,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Return a randomly generated feed edit array. + * Returns a randomly generated feed edit array. * * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * (optional) If given, feed will be created with this URL, otherwise + * /rss.xml will be used. Defaults to NULL. * @return * A feed array. */ @@ -68,7 +77,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Return the count of the randomly created feed array. + * Returns the count of the randomly created feed array. * * @return * Number of feed items on default feed created by createFeed(). @@ -80,10 +89,13 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Update feed items (simulate click to admin/config/services/aggregator/update/$fid). + * Updates the feed items. + * + * This method simulates a click to + * admin/config/services/aggregator/update/$fid. * * @param $feed - * Feed object representing the feed. + * Feed object representing the feed, passed by reference. * @param $expected_count * Expected number of feed items. */ @@ -112,7 +124,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Confirm item removal from a feed. + * Confirms an item removal from a feed. * * @param $feed * Feed object representing the feed. @@ -123,7 +135,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Add and remove feed items and ensure that the count is zero. + * Adds and removes feed items and ensure that the count is zero. * * @param $feed * Feed object representing the feed. @@ -140,7 +152,7 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Pull feed categories from aggregator_category_feed table. + * Pulls feed categories from {aggregator_category_feed} table. * * @param $feed * Feed object representing the feed. @@ -154,7 +166,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Pull categories from aggregator_category table. + * Pulls categories from {aggregator_category} table. + * + * @return + * An associative array keyed by category ID and values are set to the + * category names. */ function getCategories() { $categories = array(); @@ -165,14 +181,14 @@ class AggregatorTestCase extends DrupalWebTestCase { return $categories; } - /** - * Check if the feed name and URL is unique. + * Checks whether the feed name and URL are unique. * * @param $feed_name * String containing the feed name to check. * @param $feed_url * String containing the feed URL to check. + * * @return * TRUE if feed is unique. */ @@ -182,10 +198,11 @@ class AggregatorTestCase extends DrupalWebTestCase { } /** - * Create a valid OPML file from an array of feeds. + * Creates a valid OPML file from an array of feeds. * * @param $feeds * An array of feeds. + * * @return * Path to valid OPML file. */ @@ -223,7 +240,7 @@ EOF; } /** - * Create an invalid OPML file. + * Creates an invalid OPML file. * * @return * Path to invalid OPML file. @@ -240,7 +257,7 @@ EOF; } /** - * Create a valid but empty OPML file. + * Creates a valid but empty OPML file. * * @return * Path to empty OPML file. @@ -275,7 +292,7 @@ EOF; * Creates sample article nodes. * * @param $count - * (optional) The number of nodes to generate. + * (optional) The number of nodes to generate. Defaults to five. */ function createSampleNodes($count = 5) { $langcode = LANGUAGE_NONE; @@ -290,7 +307,7 @@ EOF; } /** - * Tests aggregator configuration settings. + * Tests functionality of the configuration settings in the Aggregator module. */ class AggregatorConfigurationTestCase extends AggregatorTestCase { public static function getInfo() { @@ -321,6 +338,9 @@ class AggregatorConfigurationTestCase extends AggregatorTestCase { } } +/** + * Tests adding aggregator feeds. + */ class AddFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -331,7 +351,7 @@ class AddFeedTestCase extends AggregatorTestCase { } /** - * Create a feed, ensure that it is unique, check the source, and delete the feed. + * Creates and ensures that a feed is unique, checks source, and deletes feed. */ function testAddFeed() { $feed = $this->createFeed(); @@ -381,6 +401,9 @@ class AddFeedTestCase extends AggregatorTestCase { } } +/** + * Tests the categorize feed functionality in the Aggregator module. + */ class CategorizeFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -391,7 +414,7 @@ class CategorizeFeedTestCase extends AggregatorTestCase { } /** - * Create a feed and make sure you can add more than one category to it. + * Creates a feed and makes sure you can add more than one category to it. */ function testCategorizeFeed() { @@ -424,6 +447,9 @@ class CategorizeFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of updating the feed in the Aggregator module. + */ class UpdateFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -434,7 +460,7 @@ class UpdateFeedTestCase extends AggregatorTestCase { } /** - * Create a feed and attempt to update it. + * Creates a feed and attempts to update it. */ function testUpdateFeed() { $remamining_fields = array('title', 'url', ''); @@ -466,6 +492,9 @@ class UpdateFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality for removing feeds in the Aggregator module. + */ class RemoveFeedTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -476,7 +505,7 @@ class RemoveFeedTestCase extends AggregatorTestCase { } /** - * Remove a feed and ensure that all it services are removed. + * Removes a feed and ensures that all of its services are removed. */ function testRemoveFeed() { $feed = $this->createFeed(); @@ -494,6 +523,9 @@ class RemoveFeedTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of updating a feed item in the Aggregator module. + */ class UpdateFeedItemTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -504,7 +536,7 @@ class UpdateFeedItemTestCase extends AggregatorTestCase { } /** - * Test running "update items" from the 'admin/config/services/aggregator' page. + * Tests running "update items" from 'admin/config/services/aggregator' page. */ function testUpdateFeedItem() { $this->createSampleNodes(); @@ -564,7 +596,7 @@ class RemoveFeedItemTestCase extends AggregatorTestCase { } /** - * Test running "remove items" from the 'admin/config/services/aggregator' page. + * Tests running "remove items" from 'admin/config/services/aggregator' page. */ function testRemoveFeedItem() { // Create a bunch of test feeds. @@ -592,6 +624,9 @@ class RemoveFeedItemTestCase extends AggregatorTestCase { } } +/** + * Tests categorization functionality in the Aggregator module. + */ class CategorizeFeedItemTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -602,6 +637,8 @@ class CategorizeFeedItemTestCase extends AggregatorTestCase { } /** + * Checks that children of a feed inherit a defined category. + * * If a feed has a category, make sure that the children inherit that * categorization. */ @@ -649,6 +686,9 @@ class CategorizeFeedItemTestCase extends AggregatorTestCase { } } +/** + * Tests importing feeds from OPML functionality for the Aggregator module. + */ class ImportOPMLTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -659,7 +699,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Open OPML import form. + * Opens OPML import form. */ function openImportForm() { db_delete('aggregator_category')->execute(); @@ -681,7 +721,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Submit form filled with invalid fields. + * Submits form filled with invalid fields. */ function validateImportFormFields() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -707,7 +747,7 @@ class ImportOPMLTestCase extends AggregatorTestCase { } /** - * Submit form with invalid, empty and valid OPML files. + * Submits form with invalid, empty, and valid OPML files. */ function submitImportForm() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -766,6 +806,9 @@ class ImportOPMLTestCase extends AggregatorTestCase { $this->assertTrue($category, 'Categories are correct.'); } + /** + * Tests the import of an OPML file. + */ function testOPMLImport() { $this->openImportForm(); $this->validateImportFormFields(); @@ -773,6 +816,9 @@ class ImportOPMLTestCase extends AggregatorTestCase { } } +/** + * Tests functionality of the cron process in the Aggregator module. + */ class AggregatorCronTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -783,7 +829,7 @@ class AggregatorCronTestCase extends AggregatorTestCase { } /** - * Add feeds update them on cron. + * Adds feeds and updates them via cron process. */ public function testCron() { // Create feed and test basic updating on cron. @@ -819,6 +865,9 @@ class AggregatorCronTestCase extends AggregatorTestCase { } } +/** + * Tests rendering functionality in the Aggregator module. + */ class AggregatorRenderingTestCase extends AggregatorTestCase { public static function getInfo() { return array( @@ -829,9 +878,9 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Add a feed block to the page and checks its links. + * Adds a feed block to the page and checks its links. * - * TODO: Test the category block as well. + * @todo Test the category block as well. */ public function testBlockLinks() { // Create feed. @@ -890,7 +939,7 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Create a feed and check that feed's page. + * Creates a feed and checks that feed's page. */ public function testFeedPage() { // Increase the number of items published in the rss.xml feed so we have @@ -913,7 +962,7 @@ class AggregatorRenderingTestCase extends AggregatorTestCase { } /** - * Tests for feed parsing. + * Tests feed parsing in the Aggregator module. */ class FeedParserTestCase extends AggregatorTestCase { public static function getInfo() { @@ -933,7 +982,7 @@ class FeedParserTestCase extends AggregatorTestCase { } /** - * Test a feed that uses the RSS 0.91 format. + * Tests a feed that uses the RSS 0.91 format. */ function testRSS091Sample() { $feed = $this->createFeed($this->getRSS091Sample()); @@ -955,7 +1004,7 @@ class FeedParserTestCase extends AggregatorTestCase { } /** - * Test a feed that uses the Atom format. + * Tests a feed that uses the Atom format. */ function testAtomSample() { $feed = $this->createFeed($this->getAtomSample()); diff --git a/modules/book/book-rtl.css b/modules/book/book-rtl.css index f3a84c20e..40dff0e53 100644 --- a/modules/book/book-rtl.css +++ b/modules/book/book-rtl.css @@ -1,3 +1,7 @@ +/** + * @file + * Right-to-Left styling for the Book module. + */ .book-navigation .menu { padding: 1em 3em 0 0; diff --git a/modules/book/book.admin.inc b/modules/book/book.admin.inc index 62c6e841a..cc3f08fc8 100644 --- a/modules/book/book.admin.inc +++ b/modules/book/book.admin.inc @@ -2,11 +2,16 @@ /** * @file - * Admin page callbacks for the book module. + * Administration page callbacks for the Book module. */ /** * Returns an administrative overview of all books. + * + * @return string + * A HTML-formatted string with the administrative page content. + * + * @see book_menu() */ function book_admin_overview() { $rows = array(); @@ -53,6 +58,8 @@ function book_admin_settings() { /** * Form validation handler for book_admin_settings(). + * + * @see book_admin_settings_submit() */ function book_admin_settings_validate($form, &$form_state) { $child_type = $form_state['values']['book_child_type']; @@ -149,7 +156,7 @@ function book_admin_edit_submit($form, &$form_state) { * @param $node * The node of the top-level page in the book. * @param $form - * The form that is being modified. + * The form that is being modified, passed by reference. * * @see book_admin_edit() */ @@ -184,10 +191,10 @@ function _book_admin_table($node, &$form) { * @param $tree * A subtree of the book menu hierarchy. * @param $form - * The form that is being modified. + * The form that is being modified, passed by reference. * * @return - * The form that is being modified. + * The modified form array. * * @see book_admin_edit() */ diff --git a/modules/book/book.css b/modules/book/book.css index a8d2136df..00e379ee0 100644 --- a/modules/book/book.css +++ b/modules/book/book.css @@ -1,3 +1,7 @@ + /** + * @file + * Styling for the Book module. + */ .book-navigation .menu { border-top: 1px solid #888; diff --git a/modules/book/book.js b/modules/book/book.js index 0853e8ecb..64f4aee68 100644 --- a/modules/book/book.js +++ b/modules/book/book.js @@ -3,7 +3,6 @@ * Javascript behaviors for the Book module. */ - (function ($) { Drupal.behaviors.bookFieldsetSummaries = { diff --git a/modules/book/book.module b/modules/book/book.module index 1fb0c0b11..71b89945e 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -221,6 +221,9 @@ function _book_outline_remove_access($node) { * * A node can be removed from a book if it is actually in a book and it either * is not a top-level page or is a top-level page with no children. + * + * @param $node + * The node to remove from the outline. */ function _book_node_is_removable($node) { return (!empty($node->book['bid']) && (($node->book['bid'] != $node->nid) || !$node->book['has_children'])); @@ -734,7 +737,7 @@ function book_get_flat_menu($book_link) { * @param $tree * A tree of menu links in an array. * @param $flat - * A flat array of the menu links from $tree. + * A flat array of the menu links from $tree, passed by reference. * * @see book_get_flat_menu(). */ @@ -1062,8 +1065,9 @@ function _book_link_defaults($nid) { * to the structured data but can also simply iterate over all elements and * render them (as in the default template). * - * The $variables array contains the following elements: - * - book_menus + * @param $variables + * An associative array containing the following key: + * - book_menus * * @see book-all-books-block.tpl.php */ @@ -1079,8 +1083,9 @@ function template_preprocess_book_all_books_block(&$variables) { /** * Processes variables for book-navigation.tpl.php. * - * The $variables array contains the following elements: - * - book_link + * @param $variables + * An associative array containing the following key: + * - book_link * * @see book-navigation.tpl.php */ @@ -1151,8 +1156,9 @@ function template_preprocess_book_navigation(&$variables) { * Reference to the table of contents array. This is modified in place, so the * function does not have a return value. * @param $exclude - * Optional array of menu link ID values. Any link whose menu link ID is in - * this array will be excluded (along with its children). + * (optional) An array of menu link ID values. Any link whose menu link ID is + * in this array will be excluded (along with its children). Defaults to an + * empty array. * @param $depth_limit * Any link deeper than this value will be excluded (along with its children). */ @@ -1198,10 +1204,11 @@ function book_toc($bid, $depth_limit, $exclude = array()) { /** * Processes variables for book-export-html.tpl.php. * - * The $variables array contains the following elements: - * - title - * - contents - * - depth + * @param $variables + * An associative array containing the following keys: + * - title + * - contents + * - depth * * @see book-export-html.tpl.php */ @@ -1261,7 +1268,8 @@ function book_export_traverse($tree, $visit_func) { * @param $node * The node that will be output. * @param $children - * All the rendered child nodes within the current node. + * (optional) All the rendered child nodes within the current node. Defaults + * to an empty string. * * @return * The HTML generated for the given node. @@ -1280,9 +1288,10 @@ function book_node_export($node, $children = '') { /** * Processes variables for book-node-export-html.tpl.php. * - * The $variables array contains the following elements: - * - node - * - children + * @param $variables + * An associative array containing the following keys: + * - node + * - children * * @see book-node-export-html.tpl.php */ @@ -1294,6 +1303,12 @@ function template_preprocess_book_node_export_html(&$variables) { /** * Determine if a given node type is in the list of types allowed for books. + * + * @param $type + * A node type. + * + * @return + * A Boolean TRUE if the node type can be included in books; otherwise, FALSE. */ function book_type_is_allowed($type) { return in_array($type, variable_get('book_allowed_types', array('book'))); @@ -1336,7 +1351,7 @@ function book_node_type_update($type) { * * @return * A menu link, with the link translated for rendering and data added from the - * {book} table. + * {book} table. FALSE if there is an error. */ function book_link_load($mlid) { if ($item = db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = :mlid", array( diff --git a/modules/book/book.pages.inc b/modules/book/book.pages.inc index 63a1d15a4..e5a04c5a2 100644 --- a/modules/book/book.pages.inc +++ b/modules/book/book.pages.inc @@ -7,6 +7,11 @@ /** * Menu callback: Prints a listing of all books. + * + * @return string + * A HTML-formatted string with the listing of all books content. + * + * @see book_menu() */ function book_render() { $book_list = array(); @@ -36,6 +41,8 @@ function book_render() { * @return * A string representing the node and its children in the book hierarchy in a * format determined by the $type parameter. + * + * @see book_menu() */ function book_export($type, $nid) { // Check that the node exists and that the current user has access to it. @@ -100,6 +107,11 @@ function book_export_html($nid) { * * @param $node * The book node for which to show the outline. + * + * @return string + * A HTML-formatted string with the outline form for a single node. + * + * @see book_menu() */ function book_outline($node) { drupal_set_title($node->title); diff --git a/modules/book/book.test b/modules/book/book.test index 2708e3674..81f4524ac 100644 --- a/modules/book/book.test +++ b/modules/book/book.test @@ -5,14 +5,37 @@ * Tests for book.module. */ +/** + * Tests the functionality of the Book module. + */ class BookTestCase extends DrupalWebTestCase { + + /** + * A book node. + * + * @var object + */ protected $book; - // $book_author is a user with permission to create and edit books. + + /** + * A user with permission to create and edit books. + * + * @var object + */ protected $book_author; - // $web_user is a user with permission to view a book - // and access the printer-friendly version. + + /** + * A user with permission to view a book and access printer-friendly version. + * + * @var object + */ protected $web_user; - // $admin_user is a user with permission to create and edit books and to administer blocks. + + /** + * A user with permission to create and edit books and to administer blocks. + * + * @var object + */ protected $admin_user; public static function getInfo() { @@ -36,7 +59,7 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Create a new book with a page hierarchy. + * Creates a new book with a page hierarchy. */ function createBook() { // Create new book. @@ -67,7 +90,7 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Test book functionality through node interfaces. + * Tests book functionality through node interfaces. */ function testBook() { // Create new book. @@ -106,18 +129,20 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Check the outline of sub-pages; previous, up, and next; and printer friendly version. + * Checks the outline of sub-pages; previous, up, and next. + * + * Also checks the printer friendly version of the outline. * * @param $node * Node to check. * @param $nodes * Nodes that should be in outline. * @param $previous - * Previous link node. + * (optional) Previous link node. Defaults to FALSE. * @param $up - * Up link node. + * (optional) Up link node. Defaults to FALSE. * @param $next - * Next link node. + * (optional) Next link node. Defaults to FALSE. * @param $breadcrumb * The nodes that should be displayed in the breadcrumb. */ @@ -129,23 +154,23 @@ class BookTestCase extends DrupalWebTestCase { // Check outline structure. if ($nodes !== NULL) { - $this->assertPattern($this->generateOutlinePattern($nodes), t('Node ' . $number . ' outline confirmed.')); + $this->assertPattern($this->generateOutlinePattern($nodes), format_string('Node %number outline confirmed.', array('%number' => $number))); } else { - $this->pass(t('Node ' . $number . ' doesn\'t have outline.')); + $this->pass(format_string('Node %number does not have outline.', array('%number' => $number))); } // Check previous, up, and next links. if ($previous) { - $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => array('page-previous'), 'title' => t('Go to previous page')))), t('Previous page link found.')); + $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => array('page-previous'), 'title' => t('Go to previous page')))), 'Previous page link found.'); } if ($up) { - $this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => array('page-up'), 'title' => t('Go to parent page')))), t('Up page link found.')); + $this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => array('page-up'), 'title' => t('Go to parent page')))), 'Up page link found.'); } if ($next) { - $this->assertRaw(l($next->title . ' ›', 'node/' . $next->nid, array('attributes' => array('class' => array('page-next'), 'title' => t('Go to next page')))), t('Next page link found.')); + $this->assertRaw(l($next->title . ' ›', 'node/' . $next->nid, array('attributes' => array('class' => array('page-next'), 'title' => t('Go to next page')))), 'Next page link found.'); } // Compute the expected breadcrumb. @@ -163,20 +188,24 @@ class BookTestCase extends DrupalWebTestCase { } // Compare expected and got breadcrumbs. - $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, t('The breadcrumb is correctly displayed on the page.')); + $this->assertIdentical($expected_breadcrumb, $got_breadcrumb, 'The breadcrumb is correctly displayed on the page.'); // Check printer friendly version. $this->drupalGet('book/export/html/' . $node->nid); - $this->assertText($node->title, t('Printer friendly title found.')); - $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Printer friendly body found.')); + $this->assertText($node->title, 'Printer friendly title found.'); + $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), 'Printer friendly body found.'); $number++; } /** - * Create a regular expression to check for the sub-nodes in the outline. + * Creates a regular expression to check for the sub-nodes in the outline. + * + * @param array $nodes + * An array of nodes to check in outline. * - * @param array $nodes Nodes to check in outline. + * @return + * A regular expression that locates sub-nodes of the outline. */ function generateOutlinePattern($nodes) { $outline = ''; @@ -188,10 +217,12 @@ class BookTestCase extends DrupalWebTestCase { } /** - * Create book node. + * Creates a book node. * - * @param integer $book_nid Book node id or set to 'new' to create new book. - * @param integer $parent Parent book reference id. + * @param $book_nid + * A book node ID or set to 'new' to create a new book. + * @param $parent + * (optional) Parent book reference ID. Defaults to NULL. */ function createBookNode($book_nid, $parent = NULL) { // $number does not use drupal_static as it should not be reset @@ -216,7 +247,7 @@ class BookTestCase extends DrupalWebTestCase { // Check to make sure the book node was created. $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertNotNull(($node === FALSE ? NULL : $node), t('Book node found in database.')); + $this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.'); $number++; return $node; @@ -236,28 +267,28 @@ class BookTestCase extends DrupalWebTestCase { // Make sure each part of the book is there. foreach ($nodes as $node) { - $this->assertText($node->title, t('Node title found in printer friendly version.')); - $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), t('Node body found in printer friendly version.')); + $this->assertText($node->title, 'Node title found in printer friendly version.'); + $this->assertRaw(check_markup($node->body[LANGUAGE_NONE][0]['value'], $node->body[LANGUAGE_NONE][0]['format']), 'Node body found in printer friendly version.'); } // Make sure we can't export an unsupported format. $this->drupalGet('book/export/foobar/' . $this->book->nid); - $this->assertResponse('404', t('Unsupported export format returned "not found".')); + $this->assertResponse('404', 'Unsupported export format returned "not found".'); // Make sure we get a 404 on a not existing book node. $this->drupalGet('book/export/html/123'); - $this->assertResponse('404', t('Not existing book node returned "not found".')); + $this->assertResponse('404', 'Not existing book node returned "not found".'); // Make sure an anonymous user cannot view printer-friendly version. $this->drupalLogout(); // Load the book and verify there is no printer-friendly version link. $this->drupalGet('node/' . $this->book->nid); - $this->assertNoLink(t('Printer-friendly version'), t('Anonymous user is not shown link to printer-friendly version.')); + $this->assertNoLink(t('Printer-friendly version'), 'Anonymous user is not shown link to printer-friendly version.'); // Try getting the URL directly, and verify it fails. $this->drupalGet('book/export/html/' . $this->book->nid); - $this->assertResponse('403', t('Anonymous user properly forbidden.')); + $this->assertResponse('403', 'Anonymous user properly forbidden.'); // Now grant anonymous users permission to view the printer-friendly // version and verify that node access restrictions still prevent them from @@ -276,30 +307,30 @@ class BookTestCase extends DrupalWebTestCase { // Set block title to confirm that the interface is available. $block_title = $this->randomName(16); $this->drupalPost('admin/structure/block/manage/book/navigation/configure', array('title' => $block_title), t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.'); // Set the block to a region to confirm block is available. $edit = array(); $edit['blocks[book_navigation][region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); + $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.'); // Give anonymous users the permission 'node test view'. $edit = array(); $edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE; $this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t("Permission 'node test view' successfully assigned to anonymous users.")); + $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users."); // Test correct display of the block. $nodes = $this->createBook(); $this->drupalGet('<front>'); - $this->assertText($block_title, t('Book navigation block is displayed.')); - $this->assertText($this->book->title, t('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->title))); - $this->assertNoText($nodes[0]->title, t('No links to individual book pages are displayed.')); + $this->assertText($block_title, 'Book navigation block is displayed.'); + $this->assertText($this->book->title, format_string('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->title))); + $this->assertNoText($nodes[0]->title, 'No links to individual book pages are displayed.'); } /** - * Test the book navigation block when an access module is enabled. + * Tests the book navigation block when an access module is enabled. */ function testNavigationBlockOnAccessModuleEnabled() { $this->drupalLogin($this->admin_user); @@ -312,19 +343,19 @@ class BookTestCase extends DrupalWebTestCase { // Set block display to 'Show block only on book pages'. $edit['book_block_mode'] = 'book pages'; $this->drupalPost('admin/structure/block/manage/book/navigation/configure', $edit, t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + $this->assertText(t('The block configuration has been saved.'), 'Block configuration set.'); // Set the block to a region to confirm block is available. $edit = array(); $edit['blocks[book_navigation][region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); + $this->assertText(t('The block settings have been updated.'), 'Block successfully move to footer region.'); // Give anonymous users the permission 'node test view'. $edit = array(); $edit[DRUPAL_ANONYMOUS_RID . '[node test view]'] = TRUE; $this->drupalPost('admin/people/permissions/' . DRUPAL_ANONYMOUS_RID, $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t('Permission \'node test view\' successfully assigned to anonymous users.')); + $this->assertText(t('The changes have been saved.'), "Permission 'node test view' successfully assigned to anonymous users."); // Create a book. $this->createBook(); @@ -332,12 +363,12 @@ class BookTestCase extends DrupalWebTestCase { // Test correct display of the block to registered users. $this->drupalLogin($this->web_user); $this->drupalGet('node/' . $this->book->nid); - $this->assertText($block_title, t('Book navigation block is displayed to registered users.')); + $this->assertText($block_title, 'Book navigation block is displayed to registered users.'); $this->drupalLogout(); // Test correct display of the block to anonymous users. $this->drupalGet('node/' . $this->book->nid); - $this->assertText($block_title, t('Book navigation block is displayed to anonymous users.')); + $this->assertText($block_title, 'Book navigation block is displayed to anonymous users.'); } /** @@ -350,10 +381,10 @@ class BookTestCase extends DrupalWebTestCase { // Test access to delete top-level and child book nodes. $this->drupalGet('node/' . $this->book->nid . '/outline/remove'); - $this->assertResponse('403', t('Deleting top-level book node properly forbidden.')); + $this->assertResponse('403', 'Deleting top-level book node properly forbidden.'); $this->drupalPost('node/' . $nodes[4]->nid . '/outline/remove', $edit, t('Remove')); $node4 = node_load($nodes[4]->nid, NULL, TRUE); - $this->assertTrue(empty($node4->book), t('Deleting child book node properly allowed.')); + $this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.'); // Delete all child book nodes and retest top-level node deletion. foreach ($nodes as $node) { @@ -362,6 +393,6 @@ class BookTestCase extends DrupalWebTestCase { node_delete_multiple($nids); $this->drupalPost('node/' . $this->book->nid . '/outline/remove', $edit, t('Remove')); $node = node_load($this->book->nid, NULL, TRUE); - $this->assertTrue(empty($node->book), t('Deleting childless top-level book node properly allowed.')); + $this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.'); } } diff --git a/modules/comment/comment.admin.inc b/modules/comment/comment.admin.inc index 4f3d35071..43b53e27a 100644 --- a/modules/comment/comment.admin.inc +++ b/modules/comment/comment.admin.inc @@ -98,13 +98,14 @@ function comment_admin_overview($form, &$form_state, $arg) { // Remove the first node title from the node_titles array and attach to // the comment. $comment->node_title = array_shift($node_titles); + $comment_body = field_get_items('comment', $comment, 'comment_body'); $options[$comment->cid] = array( 'subject' => array( 'data' => array( '#type' => 'link', '#title' => $comment->subject, '#href' => 'comment/' . $comment->cid, - '#options' => array('attributes' => array('title' => truncate_utf8($comment->comment_body[LANGUAGE_NONE][0]['value'], 128)), 'fragment' => 'comment-' . $comment->cid), + '#options' => array('attributes' => array('title' => truncate_utf8($comment_body[0]['value'], 128)), 'fragment' => 'comment-' . $comment->cid), ), ), 'author' => theme('username', array('account' => $comment)), diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 4241538a0..46115be04 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -2041,7 +2041,8 @@ function comment_form($form, &$form_state, $comment) { // Attach fields. $comment->node_type = 'comment_node_' . $node->type; - field_attach_form('comment', $comment, $form, $form_state); + $langcode = entity_language('comment', $comment); + field_attach_form('comment', $comment, $form, $form_state, $langcode); return $form; } @@ -2066,7 +2067,8 @@ function comment_preview($comment) { $node = node_load($comment->nid); if (!form_get_errors()) { - $comment->format = $comment->comment_body[LANGUAGE_NONE][0]['format']; + $comment_body = field_get_items('comment', $comment, 'comment_body'); + $comment->format = $comment_body[0]['format']; // Attach the user and time information. if (!empty($comment->name)) { $account = user_load_by_name($comment->name); @@ -2190,7 +2192,9 @@ function comment_submit($comment) { // 1) Filter it into HTML // 2) Strip out all HTML tags // 3) Convert entities back to plain-text. - $comment_body = $comment->comment_body[LANGUAGE_NONE][0]; + $field = field_info_field('comment_body'); + $langcode = field_is_translatable('comment', $field) ? entity_language('comment', $comment) : LANGUAGE_NONE; + $comment_body = $comment->comment_body[$langcode][0]; if (isset($comment_body['format'])) { $comment_text = check_markup($comment_body['value'], $comment_body['format']); } @@ -2284,8 +2288,16 @@ function template_preprocess_comment(&$variables) { $variables['comment'] = $comment; $variables['node'] = $node; $variables['author'] = theme('username', array('account' => $comment)); + $variables['created'] = format_date($comment->created); - $variables['changed'] = format_date($comment->changed); + + // Avoid calling format_date() twice on the same timestamp. + if ($comment->changed == $comment->created) { + $variables['changed'] = $variables['created']; + } + else { + $variables['changed'] = format_date($comment->changed); + } $variables['new'] = !empty($comment->new) ? t('new') : ''; $variables['picture'] = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', array('account' => $comment)) : ''; diff --git a/modules/dblog/dblog-rtl.css b/modules/dblog/dblog-rtl.css index 282fe971d..0fab8d065 100644 --- a/modules/dblog/dblog-rtl.css +++ b/modules/dblog/dblog-rtl.css @@ -1,3 +1,7 @@ +/** + * @file + * Right-to-Left styling for the Database Logging module. + */ .form-item-type, .form-item-severity { diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc index 0655e7564..7c1c0e20f 100644 --- a/modules/dblog/dblog.admin.inc +++ b/modules/dblog/dblog.admin.inc @@ -2,14 +2,19 @@ /** * @file - * Administrative page callbacks for the dblog module. + * Administrative page callbacks for the Database Logging module. */ /** - * Menu callback; displays a listing of log messages. + * Page callback: Displays a listing of database log messages. * - * Messages are truncated at 56 chars. Full-length message could be viewed at - * the message details page. + * Messages are truncated at 56 chars. Full-length messages can be viewed on the + * message details page. + * + * @see dblog_clear_log_form() + * @see dblog_event() + * @see dblog_filter_form() + * @see dblog_menu() * * @ingroup logging_severity_levels */ @@ -81,12 +86,18 @@ function dblog_overview() { } /** - * Menu callback; generic function to display a page of the most frequent events. + * Page callback: Shows the most frequent log messages of a given event type. + * + * Messages are not truncated on this page because events detailed herein do not + * have links to a detailed view. + * + * @param string $type + * Type of database log events to display (e.g., 'search'). * - * Messages are not truncated because events from this page have no detail view. + * @return array + * A build array in the format expected by drupal_render(). * - * @param $type - * type of dblog events to display. + * @see dblog_menu() */ function dblog_top($type) { @@ -127,7 +138,16 @@ function dblog_top($type) { } /** - * Menu callback; displays details about a log message. + * Page callback: Displays details about a specific database log message. + * + * @param int $id + * Unique ID of the database log message. + * + * @return array|string + * If the ID is located in the Database Logging table, a build array in the + * format expected by drupal_render(); otherwise, an empty string. + * + * @see dblog_menu() */ function dblog_event($id) { $severity = watchdog_severity_levels(); @@ -184,7 +204,10 @@ function dblog_event($id) { } /** - * Build query for dblog administration filters based on session. + * Builds a query for database log administration filters based on session. + * + * @return array + * An associative array with keys 'where' and 'args'. */ function dblog_build_filter_query() { if (empty($_SESSION['dblog_overview_filter'])) { @@ -213,9 +236,16 @@ function dblog_build_filter_query() { ); } - /** - * List dblog administration filters that can be applied. + * Creates a list of database log administration filters that can be applied. + * + * @return array + * Associative array of filters. The top-level keys are used as the form + * element names for the filters, and the values are arrays with the following + * elements: + * - title: Title of the filter. + * - where: The filter condition. + * - options: Array of options for the select list for the filter. */ function dblog_filters() { $filters = array(); @@ -244,7 +274,7 @@ function dblog_filters() { /** * Returns HTML for a log message. * - * @param $variables + * @param array $variables * An associative array containing: * - event: An object with at least the message and variables properties. * - link: (optional) Format message as link, event->wid is required. @@ -274,11 +304,13 @@ function theme_dblog_message($variables) { } /** - * Return form for dblog administration filters. + * Form constructor for the database logging filter form. * - * @ingroup forms - * @see dblog_filter_form_submit() * @see dblog_filter_form_validate() + * @see dblog_filter_form_submit() + * @see dblog_overview() + * + * @ingroup forms */ function dblog_filter_form($form) { $filters = dblog_filters(); @@ -316,12 +348,13 @@ function dblog_filter_form($form) { '#value' => t('Reset') ); } - return $form; } /** - * Validate result from dblog administration filter form. + * Form validation handler for dblog_filter_form(). + * + * @see dblog_filter_form_submit() */ function dblog_filter_form_validate($form, &$form_state) { if ($form_state['values']['op'] == t('Filter') && empty($form_state['values']['type']) && empty($form_state['values']['severity'])) { @@ -330,7 +363,9 @@ function dblog_filter_form_validate($form, &$form_state) { } /** - * Process result from dblog administration filter form. + * Form submission handler for dblog_filter_form(). + * + * @see dblog_filter_form_validate() */ function dblog_filter_form_submit($form, &$form_state) { $op = $form_state['values']['op']; @@ -351,10 +386,10 @@ function dblog_filter_form_submit($form, &$form_state) { } /** - * Return form for dblog clear button. + * Form constructor for the form that clears out the log. * - * @ingroup forms * @see dblog_clear_log_submit() + * @ingroup forms */ function dblog_clear_log_form($form) { $form['dblog_clear'] = array( @@ -374,7 +409,7 @@ function dblog_clear_log_form($form) { } /** - * Submit callback: clear database with log messages. + * Form submission handler for dblog_clear_log_form(). */ function dblog_clear_log_submit() { $_SESSION['dblog_overview_filter'] = array(); diff --git a/modules/dblog/dblog.css b/modules/dblog/dblog.css index 88f4ba01b..b1278862a 100644 --- a/modules/dblog/dblog.css +++ b/modules/dblog/dblog.css @@ -1,3 +1,8 @@ +/** + * @file + * Admin styles for the Database Logging module. + */ + .form-item-type, .form-item-severity { float: left; /* LTR */ diff --git a/modules/dblog/dblog.module b/modules/dblog/dblog.module index d831548c9..9183eed69 100644 --- a/modules/dblog/dblog.module +++ b/modules/dblog/dblog.module @@ -4,9 +4,9 @@ * @file * System monitoring and logging for administrators. * - * The dblog module monitors your site and keeps a list of - * recorded events containing usage and performance data, errors, - * warnings, and similar operational information. + * The Database Logging module monitors your site and keeps a list of recorded + * events containing usage and performance data, errors, warnings, and similar + * operational information. * * @see watchdog() */ @@ -96,7 +96,7 @@ function dblog_init() { /** * Implements hook_cron(). * - * Remove expired log messages. + * Controls the size of the log table, paring it to 'dblog_row_limit' messages. */ function dblog_cron() { // Cleanup the watchdog table. @@ -121,6 +121,12 @@ function dblog_cron() { } } +/** + * Gathers a list of uniquely defined database log message types. + * + * @return array + * List of uniquely defined database log message types. + */ function _dblog_get_message_types() { $types = array(); @@ -135,7 +141,7 @@ function _dblog_get_message_types() { /** * Implements hook_watchdog(). * - * Note some values may be truncated for database column size restrictions. + * Note: Some values may be truncated to meet database column size restrictions. */ function dblog_watchdog(array $log_entry) { Database::getConnection('default', 'default')->insert('watchdog') @@ -155,7 +161,7 @@ function dblog_watchdog(array $log_entry) { } /** - * Implements hook_form_FORM_ID_alter(). + * Implements hook_form_FORM_ID_alter() for system_logging_settings(). */ function dblog_form_system_logging_settings_alter(&$form, $form_state) { $form['dblog_row_limit'] = array( diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test index ad01e97f5..cd101930d 100644 --- a/modules/dblog/dblog.test +++ b/modules/dblog/dblog.test @@ -5,8 +5,23 @@ * Tests for dblog.module. */ +/** + * Tests logging messages to the database. + */ class DBLogTestCase extends DrupalWebTestCase { + + /** + * A user with some relevent administrative permissions. + * + * @var object + */ protected $big_user; + + /** + * A user without any permissions. + * + * @var object + */ protected $any_user; public static function getInfo() { @@ -28,7 +43,11 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Login users, create dblog events, and test dblog functionality through the admin and user interfaces. + * Tests Database Logging module functionality through interfaces. + * + * First logs in users, then creates database log events, and finally tests + * Database Logging module functionality through both the admin and user + * interfaces. */ function testDBLog() { // Login the admin user. @@ -46,12 +65,13 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify setting of the dblog row limit. + * Verifies setting of the database log row limit. * - * @param integer $count Log row limit. + * @param int $row_limit + * The row limit. */ private function verifyRowLimit($row_limit) { - // Change the dblog row limit. + // Change the database log row limit. $edit = array(); $edit['dblog_row_limit'] = $row_limit; $this->drupalPost('admin/config/development/logging', $edit, t('Save configuration')); @@ -66,33 +86,35 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify cron applies the dblog row limit. + * Verifies that cron correctly applies the database log row limit. * - * @param integer $count Log row limit. + * @param int $row_limit + * The row limit. */ private function verifyCron($row_limit) { // Generate additional log entries. $this->generateLogEntries($row_limit + 10); - // Verify dblog row count exceeds row limit. + // Verify that the database log row count exceeds the row limit. $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField(); $this->assertTrue($count > $row_limit, t('Dblog row count of @count exceeds row limit of @limit', array('@count' => $count, '@limit' => $row_limit))); - // Run cron job. + // Run a cron job. $this->cronRun(); - // Verify dblog row count equals row limit plus one because cron adds a record after it runs. + // Verify that the database log row count equals the row limit plus one + // because cron adds a record after it runs. $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField(); $this->assertTrue($count == $row_limit + 1, t('Dblog row count of @count equals row limit of @limit plus one', array('@count' => $count, '@limit' => $row_limit))); } /** - * Generate dblog entries. + * Generates a number of random database log events. * - * @param integer $count - * Number of log entries to generate. - * @param $type - * The type of watchdog entry. - * @param $severity - * The severity of the watchdog entry. + * @param int $count + * Number of watchdog entries to generate. + * @param string $type + * (optional) The type of watchdog entry. Defaults to 'custom'. + * @param int $severity + * (optional) The severity of the watchdog entry. Defaults to WATCHDOG_NOTICE. */ private function generateLogEntries($count, $type = 'custom', $severity = WATCHDOG_NOTICE) { global $base_root; @@ -119,42 +141,43 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify the logged in user has the desired access to the various dblog nodes. + * Confirms that database log reports are displayed at the correct paths. * - * @param integer $response HTTP response code. + * @param int $response + * (optional) HTTP response code. Defaults to 200. */ private function verifyReports($response = 200) { $quote = '''; - // View dblog help node. + // View the database log help page. $this->drupalGet('admin/help/dblog'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Database logging'), t('DBLog help was displayed')); } - // View dblog report node. + // View the database log report page. $this->drupalGet('admin/reports/dblog'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Recent log messages'), t('DBLog report was displayed')); } - // View dblog page-not-found report node. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Top ' . $quote . 'page not found' . $quote . ' errors'), t('DBLog page-not-found report was displayed')); } - // View dblog access-denied report node. + // View the database log access-denied report page. $this->drupalGet('admin/reports/access-denied'); $this->assertResponse($response); if ($response == 200) { $this->assertText(t('Top ' . $quote . 'access denied' . $quote . ' errors'), t('DBLog access-denied report was displayed')); } - // View dblog event node. + // View the database log event page. $this->drupalGet('admin/reports/event/1'); $this->assertResponse($response); if ($response == 200) { @@ -163,7 +186,7 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Verify events. + * Generates and then verifies various types of events. */ private function verifyEvents() { // Invoke events. @@ -179,14 +202,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Generate and verify user events. - * + * Generates and then verifies some user events. */ private function doUser() { // Set user variables. $name = $this->randomName(); $pass = user_password(); - // Add user using form to generate add user event (which is not triggered by drupalCreateUser). + // Add a user using the form to generate an add user event (which is not + // triggered by drupalCreateUser). $edit = array(); $edit['name'] = $name; $edit['mail'] = $name . '@example.com'; @@ -195,15 +218,16 @@ class DBLogTestCase extends DrupalWebTestCase { $edit['status'] = 1; $this->drupalPost('admin/people/create', $edit, t('Create new account')); $this->assertResponse(200); - // Retrieve user object. + // Retrieve the user object. $user = user_load_by_name($name); $this->assertTrue($user != NULL, t('User @name was loaded', array('@name' => $name))); - $user->pass_raw = $pass; // Needed by drupalLogin. + // pass_raw property is needed by drupalLogin. + $user->pass_raw = $pass; // Login user. $this->drupalLogin($user); // Logout user. $this->drupalLogout(); - // Fetch row ids in watchdog that relate to the user. + // Fetch the row IDs in watchdog that relate to the user. $result = db_query('SELECT wid FROM {watchdog} WHERE uid = :uid', array(':uid' => $user->uid)); foreach ($result as $row) { $ids[] = $row->wid; @@ -213,17 +237,18 @@ class DBLogTestCase extends DrupalWebTestCase { // Login the admin user. $this->drupalLogin($this->big_user); - // Delete user. + // Delete the user created at the start of this test. // We need to POST here to invoke batch_process() in the internal browser. $this->drupalPost('user/' . $user->uid . '/cancel', array('user_cancel_method' => 'user_cancel_reassign'), t('Cancel account')); - // View the dblog report. + // View the database log report. $this->drupalGet('admin/reports/dblog'); $this->assertResponse(200); - // Verify events were recorded. + // Verify that the expected events were recorded. // Add user. - // Default display includes name and email address; if too long then email is replaced by three periods. + // Default display includes name and email address; if too long, the email + // address is replaced by three periods. $this->assertLogMessage(t('New user: %name (%email).', array('%name' => $name, '%email' => $user->mail)), t('DBLog event was recorded: [add user]')); // Login user. $this->assertLogMessage(t('Session opened for %name.', array('%name' => $name)), t('DBLog event was recorded: [login user]')); @@ -232,7 +257,7 @@ class DBLogTestCase extends DrupalWebTestCase { // Delete user. $message = t('Deleted user: %name %email.', array('%name' => $name, '%email' => '<' . $user->mail . '>')); $message_text = truncate_utf8(filter_xss($message, array()), 56, TRUE, TRUE); - // Verify full message on details page. + // Verify that the full message displays on the details page. $link = FALSE; if ($links = $this->xpath('//a[text()="' . html_entity_decode($message_text) . '"]')) { // Found link with the message text. @@ -253,7 +278,7 @@ class DBLogTestCase extends DrupalWebTestCase { $not_found_url = $this->randomName(60); $this->drupalGet($not_found_url); $this->assertResponse(404); - // View dblog page-not-found report page. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse(200); // Check that full-length URL displayed. @@ -261,9 +286,10 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Generate and verify node events. + * Generates and then verifies some node events. * - * @param string $type Content type. + * @param string $type + * A node type (e.g., 'article', 'page' or 'poll'). */ private function doNode($type) { // Create user. @@ -272,61 +298,65 @@ class DBLogTestCase extends DrupalWebTestCase { // Login user. $this->drupalLogin($user); - // Create node using form to generate add content event (which is not triggered by drupalCreateNode). + // Create a node using the form in order to generate an add content event + // (which is not triggered by drupalCreateNode). $edit = $this->getContent($type); $langcode = LANGUAGE_NONE; $title = $edit["title"]; $this->drupalPost('node/add/' . $type, $edit, t('Save')); $this->assertResponse(200); - // Retrieve node object. + // Retrieve the node object. $node = $this->drupalGetNodeByTitle($title); $this->assertTrue($node != NULL, t('Node @title was loaded', array('@title' => $title))); - // Edit node. + // Edit the node. $edit = $this->getContentUpdate($type); $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); $this->assertResponse(200); - // Delete node. + // Delete the node. $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete')); $this->assertResponse(200); - // View node (to generate page not found event). + // View the node (to generate page not found event). $this->drupalGet('node/' . $node->nid); $this->assertResponse(404); - // View the dblog report (to generate access denied event). + // View the database log report (to generate access denied event). $this->drupalGet('admin/reports/dblog'); $this->assertResponse(403); // Login the admin user. $this->drupalLogin($this->big_user); - // View the dblog report. + // View the database log report. $this->drupalGet('admin/reports/dblog'); $this->assertResponse(200); - // Verify events were recorded. - // Content added. + // Verify that node events were recorded. + // Was node content added? $this->assertLogMessage(t('@type: added %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content added]')); - // Content updated. + // Was node content updated? $this->assertLogMessage(t('@type: updated %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content updated]')); - // Content deleted. + // Was node content deleted? $this->assertLogMessage(t('@type: deleted %title.', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content deleted]')); - // View dblog access-denied report node. + // View the database log access-denied report page. $this->drupalGet('admin/reports/access-denied'); $this->assertResponse(200); - // Access denied. + // Verify that the 'access denied' event was recorded. $this->assertText(t('admin/reports/dblog'), t('DBLog event was recorded: [access denied]')); - // View dblog page-not-found report node. + // View the database log page-not-found report page. $this->drupalGet('admin/reports/page-not-found'); $this->assertResponse(200); - // Page not found. + // Verify that the 'page not found' event was recorded. $this->assertText(t('node/@nid', array('@nid' => $node->nid)), t('DBLog event was recorded: [page not found]')); } /** - * Create content based on content type. + * Creates random content based on node content type. * - * @param string $type Content type. - * @return array Content. + * @param string $type + * Node content type (e.g., 'article'). + * + * @return array + * Random content needed by various node types. */ private function getContent($type) { $langcode = LANGUAGE_NONE; @@ -350,10 +380,13 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Create content update based on content type. + * Creates random content as an update based on node content type. + * + * @param string $type + * Node content type (e.g., 'article'). * - * @param string $type Content type. - * @return array Content. + * @return array + * Random content needed by various node types. */ private function getContentUpdate($type) { switch ($type) { @@ -375,11 +408,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Login an admin user, create dblog event, and test clearing dblog functionality through the admin interface. + * Tests the addition and clearing of log events through the admin interface. + * + * Logs in the admin user, creates a database log event, and tests the + * functionality of clearing the database log through the admin interface. */ protected function testDBLogAddAndClear() { global $base_root; - // Get a count of how many watchdog entries there are. + // Get a count of how many watchdog entries already exist. $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(); $log = array( 'type' => 'custom', @@ -396,27 +432,27 @@ class DBLogTestCase extends DrupalWebTestCase { ); // Add a watchdog entry. dblog_watchdog($log); - // Make sure the table count has actually incremented. + // Make sure the table count has actually been incremented. $this->assertEqual($count + 1, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), t('dblog_watchdog() added an entry to the dblog :count', array(':count' => $count))); // Login the admin user. $this->drupalLogin($this->big_user); - // Now post to clear the db table. + // Post in order to clear the database table. $this->drupalPost('admin/reports/dblog', array(), t('Clear log messages')); - // Count rows in watchdog that previously related to the deleted user. + // Count the rows in watchdog that previously related to the deleted user. $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(); $this->assertEqual($count, 0, t('DBLog contains :count records after a clear.', array(':count' => $count))); } /** - * Test the dblog filter on admin/reports/dblog. + * Tests the database log filter functionality at admin/reports/dblog. */ protected function testFilter() { $this->drupalLogin($this->big_user); - // Clear log to ensure that only generated entries are found. + // Clear the log to ensure that only generated entries will be found. db_delete('watchdog')->execute(); - // Generate watchdog entries. + // Generate 9 random watchdog entries. $type_names = array(); $types = array(); for ($i = 0; $i < 3; $i++) { @@ -432,10 +468,10 @@ class DBLogTestCase extends DrupalWebTestCase { } } - // View the dblog. + // View the database log page. $this->drupalGet('admin/reports/dblog'); - // Confirm all the entries are displayed. + // Confirm that all the entries are displayed. $count = $this->getTypeCount($types); foreach ($types as $key => $type) { $this->assertEqual($count[$key], $type['count'], 'Count matched'); @@ -461,8 +497,8 @@ class DBLogTestCase extends DrupalWebTestCase { $this->assertEqual(array_sum($count), $type_count, 'Count matched'); } - // Set filter to match each of the three type attributes and confirm the - // number of entries displayed. + // Set the filter to match each of the two filter-type attributes and + // confirm the correct number of entries are displayed. foreach ($types as $key => $type) { $edit = array( 'type[]' => array($type['type']), @@ -480,10 +516,14 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the log entry information form the page. + * Gets the database log event information from the browser page. * - * @return - * List of entries and their information. + * @return array + * List of log events where each event is an array with following keys: + * - severity: (int) A database log severity constant. + * - type: (string) The type of database log event. + * - message: (string) The message for this database log event. + * - user: (string) The user associated with this database log event. */ protected function getLogEntries() { $entries = array(); @@ -502,11 +542,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the count of entries per type. + * Gets the count of database log entries by database log event type. * - * @param $types + * @param array $types * The type information to compare against. - * @return + * + * @return array * The count of each type keyed by the key of the $types array. */ protected function getTypeCount(array $types) { @@ -524,11 +565,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Get the watchdog severity constant corresponding to the CSS class. + * Gets the watchdog severity constant corresponding to the CSS class. * - * @param $class + * @param string $class * CSS class attribute. - * @return + * + * @return int|null * The watchdog severity constant or NULL if not found. * * @ingroup logging_severity_levels @@ -557,11 +599,12 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Extract the text contained by the element. + * Extracts the text contained by the XHTML element. * - * @param $element + * @param SimpleXMLElement $element * Element to extract text from. - * @return + * + * @return string * Extracted text. */ protected function asText(SimpleXMLElement $element) { @@ -572,21 +615,22 @@ class DBLogTestCase extends DrupalWebTestCase { } /** - * Assert messages appear on the log overview screen. + * Confirms that a log message appears on the database log overview screen. * - * This function should be used only for admin/reports/dblog page, because it - * check for the message link text truncated to 56 characters. Other dblog - * pages have no detail links so contains a full message text. + * This function should only be used for the admin/reports/dblog page, because + * it checks for the message link text truncated to 56 characters. Other log + * pages have no detail links so they contain the full message text. * - * @param $log_message - * The message to check. - * @param $message + * @param string $log_message + * The database log message to check. + * @param string $message * The message to pass to simpletest. */ protected function assertLogMessage($log_message, $message) { $message_text = truncate_utf8(filter_xss($log_message, array()), 56, TRUE, TRUE); - // After filter_xss() HTML entities should be converted to their characters - // because assertLink() uses this string in xpath() to query DOM. + // After filter_xss(), HTML entities should be converted to their character + // equivalents because assertLink() uses this string in xpath() to query the + // Document Object Model (DOM). $this->assertLink(html_entity_decode($message_text), 0, $message); } } diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 5f641173e..822f537bc 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -873,7 +873,7 @@ function hook_field_widget_form(&$form, &$form_state, $field, $instance, $langco '#type' => $instance['widget']['type'], '#default_value' => isset($items[$delta]) ? $items[$delta] : '', ); - return $element; + return array('value' => $element); } /** @@ -1735,11 +1735,14 @@ function hook_field_storage_details_alter(&$details, $field) { * loaded. */ function hook_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 868d7bd75..dec5c20aa 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -283,7 +283,6 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = 'language' => NULL, ); $options += $default_options; - $field_info = field_info_field_by_ids(); $fields = array(); $grouped_instances = array(); @@ -307,7 +306,7 @@ function _field_invoke_multiple($op, $entity_type, $entities, &$a = NULL, &$b = foreach ($instances as $instance) { $field_id = $instance['field_id']; $field_name = $instance['field_name']; - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op; if (function_exists($function)) { // Add the field to the list of fields to invoke the hook on. @@ -614,7 +613,6 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod * non-deleted fields are operated on. */ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; // Merge default options. @@ -692,7 +690,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $ } // Collect the storage backend if the field has not been loaded yet. if (!isset($skip_fields[$field_id])) { - $field = $field_info[$field_id]; + $field = field_info_field_by_id($field_id); $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid; } } @@ -709,7 +707,7 @@ function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $ _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options); // Invoke hook_field_attach_load(): let other modules act on loading the - // entitiy. + // entity. module_invoke_all('field_attach_load', $entity_type, $queried_entities, $age, $options); // Build cache data. diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index f9c96c92b..e1acdd53b 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -319,7 +319,11 @@ function field_read_field($field_name, $include_additional = array()) { * Reads in fields that match an array of conditions. * * @param array $params - * An array of conditions to match against. + * An array of conditions to match against. Keys are columns from the + * 'field_config' table, values are conditions to match. Additionally, + * conditions on the 'entity_type' and 'bundle' columns from the + * 'field_config_instance' table are supported (select fields having an + * instance on a given bundle). * @param array $include_additional * The default behavior of this function is to not return fields that * are inactive or have been deleted. Setting @@ -337,8 +341,21 @@ function field_read_fields($params = array(), $include_additional = array()) { // Turn the conditions into a query. foreach ($params as $key => $value) { + // Allow filtering on the 'entity_type' and 'bundle' columns of the + // field_config_instance table. + if ($key == 'entity_type' || $key == 'bundle') { + if (empty($fci_join)) { + $fci_join = $query->join('field_config_instance', 'fci', 'fc.id = fci.field_id'); + } + $key = 'fci.' . $key; + } + else { + $key = 'fc.' . $key; + } + $query->condition($key, $value); } + if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) { $query ->condition('fc.active', 1) diff --git a/modules/field/field.info b/modules/field/field.info index f8a331a00..fcdca88e6 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -5,6 +5,7 @@ version = VERSION core = 7.x files[] = field.module files[] = field.attach.inc +files[] = field.info.class.inc files[] = tests/field.test dependencies[] = field_sql_storage required = TRUE diff --git a/modules/field/field.info.class.inc b/modules/field/field.info.class.inc new file mode 100644 index 000000000..de50a1bf0 --- /dev/null +++ b/modules/field/field.info.class.inc @@ -0,0 +1,668 @@ +<?php + +/* + * @file + * Definition of the FieldInfo class. + */ + +/** + * Provides field and instance definitions for the current runtime environment. + * + * A FieldInfo object is created and statically persisted through the request + * by the _field_info_field_cache() function. The object properties act as a + * "static cache" of fields and instances definitions. + * + * The preferred way to access definitions is through the getBundleInstances() + * method, which keeps cache entries per bundle, storing both fields and + * instances for a given bundle. Fields used in multiple bundles are duplicated + * in several cache entries, and are merged into a single list in the memory + * cache. Cache entries are loaded for bundles as a whole, optimizing memory + * and CPU usage for the most common pattern of iterating over all instances of + * a bundle rather than accessing a single instance. + * + * The getFields() and getInstances() methods, which return all existing field + * and instance definitions, are kept mainly for backwards compatibility, and + * should be avoided when possible, since they load and persist in memory a + * potentially large array of information. In many cases, the lightweight + * getFieldMap() method should be preferred. + */ +class FieldInfo { + + /** + * Lightweight map of fields across entity types and bundles. + * + * @var array + */ + protected $fieldMap; + + /** + * List of $field structures keyed by ID. Includes deleted fields. + * + * @var array + */ + protected $fieldsById = array(); + + /** + * Mapping of field names to the ID of the corresponding non-deleted field. + * + * @var array + */ + protected $fieldIdsByName = array(); + + /** + * Whether $fieldsById contains all field definitions or a subset. + * + * @var bool + */ + protected $loadedAllFields = FALSE; + + /** + * Separately tracks requested field names or IDs that do not exist. + * + * @var array + */ + protected $unknownFields = array(); + + /** + * Instance definitions by bundle. + * + * @var array + */ + protected $bundleInstances = array(); + + /** + * Whether $bundleInstances contains all instances definitions or a subset. + * + * @var bool + */ + protected $loadedAllInstances = FALSE; + + /** + * Separately tracks requested bundles that are empty (or do not exist). + * + * @var array + */ + protected $emptyBundles = array(); + + /** + * Extra fields by bundle. + * + * @var array + */ + protected $bundleExtraFields = array(); + + /** + * Clears the "static" and persistent caches. + */ + public function flush() { + $this->fieldMap = NULL; + + $this->fieldsById = array(); + $this->fieldIdsByName = array(); + $this->loadedAllFields = FALSE; + $this->unknownFields = array(); + + $this->bundleInstances = array(); + $this->loadedAllInstances = FALSE; + $this->emptyBundles = array(); + + $this->bundleExtraFields = array(); + + cache_clear_all('field_info:', 'cache_field', TRUE); + } + + /** + * Collects a lightweight map of fields across bundles. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with + * entity types as keys and the array of bundle names as values. + */ + public function getFieldMap() { + // Read from the "static" cache. + if ($this->fieldMap !== NULL) { + return $this->fieldMap; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:field_map', 'cache_field')) { + $map = $cached->data; + + // Save in "static" cache. + $this->fieldMap = $map; + + return $map; + } + + $map = array(); + + $query = db_query('SELECT fc.type, fci.field_name, fci.entity_type, fci.bundle FROM {field_config_instance} fci INNER JOIN {field_config} fc ON fc.id = fci.field_id WHERE fc.active = 1 AND fc.storage_active = 1 AND fc.deleted = 0 AND fci.deleted = 0'); + foreach ($query as $row) { + $map[$row->field_name]['bundles'][$row->entity_type][] = $row->bundle; + $map[$row->field_name]['type'] = $row->type; + } + + // Save in "static" and persistent caches. + $this->fieldMap = $map; + cache_set('field_info:field_map', $map, 'cache_field'); + + return $map; + } + + /** + * Returns all active fields, including deleted ones. + * + * @return + * An array of field definitions, keyed by field ID. + */ + public function getFields() { + // Read from the "static" cache. + if ($this->loadedAllFields) { + return $this->fieldsById; + } + + // Read from persistent cache. + if ($cached = cache_get('field_info:fields', 'cache_field')) { + $this->fieldsById = $cached->data; + } + else { + // Collect and prepare fields. + foreach (field_read_fields(array(), array('include_deleted' => TRUE)) as $field) { + $this->fieldsById[$field['id']] = $this->prepareField($field); + } + + // Store in persistent cache. + cache_set('field_info:fields', $this->fieldsById, 'cache_field'); + } + + // Fill the name/ID map. + foreach ($this->fieldsById as $field) { + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + $this->loadedAllFields = TRUE; + + return $this->fieldsById; + } + + /** + * Retrieves all active, non-deleted instances definitions. + * + * @param $entity_type + * (optional) The entity type. + * + * @return + * If $entity_type is not set, all instances keyed by entity type and bundle + * name. If $entity_type is set, all instances for that entity type, keyed + * by bundle name. + */ + public function getInstances($entity_type = NULL) { + // If the full list is not present in "static" cache yet. + if (!$this->loadedAllInstances) { + + // Read from persistent cache. + if ($cached = cache_get('field_info:instances', 'cache_field')) { + $this->bundleInstances = $cached->data; + } + else { + // Collect and prepare instances. + + // We also need to populate the static field cache, since it will not + // be set by subsequent getBundleInstances() calls. + $this->getFields(); + + // Initialize empty arrays for all existing entity types and bundles. + // This is not strictly needed, but is done to preserve the behavior of + // field_info_instances() before http://drupal.org/node/1880666. + foreach (field_info_bundles() as $existing_entity_type => $bundles) { + foreach ($bundles as $bundle => $bundle_info) { + $this->bundleInstances[$existing_entity_type][$bundle] = array(); + } + } + + foreach (field_read_instances() as $instance) { + $field = $this->getField($instance['field_name']); + $instance = $this->prepareInstance($instance, $field['type']); + $this->bundleInstances[$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; + } + + // Store in persistent cache. + cache_set('field_info:instances', $this->bundleInstances, 'cache_field'); + } + + $this->loadedAllInstances = TRUE; + } + + if (isset($entity_type)) { + return isset($this->bundleInstances[$entity_type]) ? $this->bundleInstances[$entity_type] : array(); + } + else { + return $this->bundleInstances; + } + } + + /** + * Returns a field definition from a field name. + * + * This method only retrieves active, non-deleted fields. + * + * @param $field_name + * The field name. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getField($field_name) { + // Read from the "static" cache. + if (isset($this->fieldIdsByName[$field_name])) { + $field_id = $this->fieldIdsByName[$field_name]; + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_name])) { + return; + } + + // Do not check the (large) persistent cache, but read the definition. + + // Cache miss: read from definition. + if ($field = field_read_field($field_name)) { + $field = $this->prepareField($field); + + // Save in the "static" cache. + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + + return $field; + } + else { + $this->unknownFields[$field_name] = TRUE; + } + } + + /** + * Returns a field definition from a field ID. + * + * This method only retrieves active fields, deleted or not. + * + * @param $field_id + * The field ID. + * + * @return + * The field definition, or NULL if no field was found. + */ + public function getFieldById($field_id) { + // Read from the "static" cache. + if (isset($this->fieldsById[$field_id])) { + return $this->fieldsById[$field_id]; + } + if (isset($this->unknownFields[$field_id])) { + return; + } + + // No persistent cache, fields are only persistently cached as part of a + // bundle. + + // Cache miss: read from definition. + if ($fields = field_read_fields(array('id' => $field_id), array('include_deleted' => TRUE))) { + $field = current($fields); + $field = $this->prepareField($field); + + // Store in the static cache. + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + + return $field; + } + else { + $this->unknownFields[$field_id] = TRUE; + } + } + + /** + * Retrieves the instances for a bundle. + * + * The function also populates the corresponding field definitions in the + * "static" cache. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of instance definitions, keyed by field name. + */ + public function getBundleInstances($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleInstances[$entity_type][$bundle])) { + return $this->bundleInstances[$entity_type][$bundle]; + } + if (isset($this->emptyBundles[$entity_type][$bundle])) { + return array(); + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle:$entity_type:$bundle", 'cache_field')) { + $info = $cached->data; + + // Extract the field definitions and save them in the "static" cache. + foreach ($info['fields'] as $field) { + if (!isset($this->fieldsById[$field['id']])) { + $this->fieldsById[$field['id']] = $field; + if (!$field['deleted']) { + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + } + unset($info['fields']); + + // Store the instance definitions in the "static" cache'. Empty (or + // non-existent) bundles are stored separately, so that they do not + // pollute the global list returned by getInstances(). + if ($info['instances']) { + $this->bundleInstances[$entity_type][$bundle] = $info['instances']; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + return $info['instances']; + } + + // Cache miss: collect from the definitions. + + $instances = array(); + + // Collect the fields in the bundle. + $params = array('entity_type' => $entity_type, 'bundle' => $bundle); + $fields = field_read_fields($params); + + // This iterates on non-deleted instances, so deleted fields are kept out of + // the persistent caches. + foreach (field_read_instances($params) as $instance) { + $field = $fields[$instance['field_name']]; + + $instance = $this->prepareInstance($instance, $field['type']); + $instances[$field['field_name']] = $instance; + + // If the field is not in our global "static" list yet, add it. + if (!isset($this->fieldsById[$field['id']])) { + $field = $this->prepareField($field); + + $this->fieldsById[$field['id']] = $field; + $this->fieldIdsByName[$field['field_name']] = $field['id']; + } + } + + // Store in the 'static' cache'. Empty (or non-existent) bundles are stored + // separately, so that they do not pollute the global list returned by + // getInstances(). + if ($instances) { + $this->bundleInstances[$entity_type][$bundle] = $instances; + } + else { + $this->emptyBundles[$entity_type][$bundle] = TRUE; + } + + // The persistent cache additionally contains the definitions of the fields + // involved in the bundle. + $cache = array( + 'instances' => $instances, + 'fields' => array() + ); + foreach ($instances as $instance) { + $cache['fields'][] = $this->fieldsById[$instance['field_id']]; + } + cache_set("field_info:bundle:$entity_type:$bundle", $cache, 'cache_field'); + + return $instances; + } + + /** + * Retrieves the "extra fields" for a bundle. + * + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The array of extra fields. + */ + public function getBundleExtraFields($entity_type, $bundle) { + // Read from the "static" cache. + if (isset($this->bundleExtraFields[$entity_type][$bundle])) { + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Read from the persistent cache. + if ($cached = cache_get("field_info:bundle_extra:$entity_type:$bundle", 'cache_field')) { + $this->bundleExtraFields[$entity_type][$bundle] = $cached->data; + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + // Cache miss: read from hook_field_extra_fields(). Note: given the current + // shape of the hook, we have no other way than collecting extra fields on + // all bundles. + $info = array(); + $extra = module_invoke_all('field_extra_fields'); + drupal_alter('field_extra_fields', $extra); + // Merge in saved settings. + if (isset($extra[$entity_type][$bundle])) { + $info = $this->prepareExtraFields($extra[$entity_type][$bundle], $entity_type, $bundle); + } + + // Store in the 'static' and persistent caches. + $this->bundleExtraFields[$entity_type][$bundle] = $info; + cache_set("field_info:bundle_extra:$entity_type:$bundle", $info, 'cache_field'); + + return $this->bundleExtraFields[$entity_type][$bundle]; + } + + /** + * Prepares a field definition for the current run-time context. + * + * @param $field + * The raw field structure as read from the database. + * + * @return + * The field definition completed for the current runtime context. + */ + public function prepareField($field) { + // Make sure all expected field settings are present. + $field['settings'] += field_info_field_settings($field['type']); + $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); + + // Add storage details. + $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); + drupal_alter('field_storage_details', $details, $field); + $field['storage']['details'] = $details; + + // Populate the list of bundles using the field. + $field['bundles'] = array(); + if (!$field['deleted']) { + $map = $this->getFieldMap(); + if (isset($map[$field['field_name']])) { + $field['bundles'] = $map[$field['field_name']]['bundles']; + } + } + + return $field; + } + + /** + * Prepares an instance definition for the current run-time context. + * + * @param $instance + * The raw instance structure as read from the database. + * @param $field_type + * The field type. + * + * @return + * The field instance array completed for the current runtime context. + */ + public function prepareInstance($instance, $field_type) { + // Make sure all expected instance settings are present. + $instance['settings'] += field_info_instance_settings($field_type); + + // Set a default value for the instance. + if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { + $instance['default_value'] = NULL; + } + + // Prepare widget settings. + $instance['widget'] = $this->prepareInstanceWidget($instance['widget'], $field_type); + + // Prepare display settings. + foreach ($instance['display'] as $view_mode => $display) { + $instance['display'][$view_mode] = $this->prepareInstanceDisplay($display, $field_type); + } + + // Fall back to 'hidden' for view modes configured to use custom display + // settings, and for which the instance has no explicit settings. + $entity_info = entity_get_info($instance['entity_type']); + $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); + $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); + foreach ($view_modes as $view_mode) { + if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { + if (!isset($instance['display'][$view_mode])) { + $instance['display'][$view_mode] = array( + 'type' => 'hidden', + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); + } + } + } + + return $instance; + } + + /** + * Prepares widget properties for the current run-time context. + * + * @param $widget + * Widget specifications as found in $instance['widget']. + * @param $field_type + * The field type. + * + * @return + * The widget properties completed for the current runtime context. + */ + public function prepareInstanceWidget($widget, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $widget += array( + 'type' => $field_type_info['default_widget'], + 'settings' => array(), + 'weight' => 0, + ); + + $widget_type_info = field_info_widget_types($widget['type']); + // Fall back to default formatter if formatter type is not available. + if (!$widget_type_info) { + $widget['type'] = $field_type_info['default_widget']; + $widget_type_info = field_info_widget_types($widget['type']); + } + $widget['module'] = $widget_type_info['module']; + // Fill in default settings for the widget. + $widget['settings'] += field_info_widget_settings($widget['type']); + + return $widget; + } + + /** + * Adapts display specifications to the current run-time context. + * + * @param $display + * Display specifications as found in $instance['display']['a_view_mode']. + * @param $field_type + * The field type. + * + * @return + * The display properties completed for the current runtime context. + */ + public function prepareInstanceDisplay($display, $field_type) { + $field_type_info = field_info_field_types($field_type); + + // Fill in default values. + $display += array( + 'label' => 'above', + 'type' => $field_type_info['default_formatter'], + 'settings' => array(), + 'weight' => 0, + ); + if ($display['type'] != 'hidden') { + $formatter_type_info = field_info_formatter_types($display['type']); + // Fall back to default formatter if formatter type is not available. + if (!$formatter_type_info) { + $display['type'] = $field_type_info['default_formatter']; + $formatter_type_info = field_info_formatter_types($display['type']); + } + $display['module'] = $formatter_type_info['module']; + // Fill in default settings for the formatter. + $display['settings'] += field_info_formatter_settings($display['type']); + } + + return $display; + } + + /** + * Prepares 'extra fields' for the current run-time context. + * + * @param $extra_fields + * The array of extra fields, as collected in hook_field_extra_fields(). + * @param $entity_type + * The entity type. + * @param $bundle + * The bundle name. + * + * @return + * The list of extra fields completed for the current runtime context. + */ + public function prepareExtraFields($extra_fields, $entity_type, $bundle) { + $entity_type_info = entity_get_info($entity_type); + $bundle_settings = field_bundle_settings($entity_type, $bundle); + $extra_fields += array('form' => array(), 'display' => array()); + + $result = array(); + // Extra fields in forms. + foreach ($extra_fields['form'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); + if (isset($settings['weight'])) { + $field_data['weight'] = $settings['weight']; + } + $result['form'][$name] = $field_data; + } + + // Extra fields in displayed entities. + $data = $extra_fields['display']; + foreach ($extra_fields['display'] as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); + $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); + foreach ($view_modes as $view_mode) { + if (isset($settings[$view_mode])) { + $field_data['display'][$view_mode] = $settings[$view_mode]; + } + else { + $field_data['display'][$view_mode] = array( + 'weight' => $field_data['weight'], + 'visible' => TRUE, + ); + } + } + unset($field_data['weight']); + $result['display'][$name] = $field_data; + } + + return $result; + } +} diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc index 9e7ab938d..77113957d 100644 --- a/modules/field/field.info.inc +++ b/modules/field/field.info.inc @@ -6,6 +6,32 @@ */ /** + * Retrieves the FieldInfo object for the current request. + * + * @return FieldInfo + * An instance of the FieldInfo class. + */ +function _field_info_field_cache() { + // Use the advanced drupal_static() pattern, since this is called very often. + static $drupal_static_fast; + + if (!isset($drupal_static_fast)) { + $drupal_static_fast['field_info_field_cache'] = &drupal_static(__FUNCTION__); + } + $field_info = &$drupal_static_fast['field_info_field_cache']; + + if (!isset($field_info)) { + // @todo The registry should save the need for an explicit include, but not + // a couple upgrade tests (DisabledNodeTypeTestCase, + // FilterFormatUpgradePathTestCase...) break in a strange way without it. + include_once dirname(__FILE__) . '/field.info.class.inc'; + $field_info = new FieldInfo(); + } + + return $field_info; +} + +/** * @defgroup field_info Field Info API * @{ * Obtain information about Field API configuration. @@ -34,7 +60,50 @@ function field_info_cache_clear() { entity_info_cache_clear(); _field_info_collate_types(TRUE); - _field_info_collate_fields(TRUE); + _field_info_field_cache()->flush(); +} + +/** + * Collates all information on existing fields and instances. + * + * Deprecated. This function is kept to ensure backwards compatibility, but has + * a serious performance impact, and should be absolutely avoided. + * See http://drupal.org/node/1880666. + * + * Use the regular field_info_*() API functions to access the information, or + * field_info_cache_clear() to clear the cached data. + */ +function _field_info_collate_fields($reset = FALSE) { + if ($reset) { + _field_info_field_cache()->flush(); + return; + } + + $cache = _field_info_field_cache(); + + // Collect fields, and build the array of IDs keyed by field_name. + $fields = $cache->getFields(); + $field_ids = array(); + foreach ($fields as $id => $field) { + if (!$field['deleted']) { + $field_ids[$field['field_name']] = $id; + } + } + + // Collect extra fields for all entity types. + $extra_fields = array(); + foreach (field_info_bundles() as $entity_type => $bundles) { + foreach ($bundles as $bundle => $info) { + $extra_fields[$entity_type][$bundle] = $cache->getBundleExtraFields($entity_type, $bundle); + } + } + + return array( + 'fields' => $fields, + 'field_ids' => $field_ids, + 'instances' => $cache->getInstances(), + 'extra_fields' => $extra_fields, + ); } /** @@ -162,281 +231,68 @@ function _field_info_collate_types($reset = FALSE) { } /** - * Collates all information on existing fields and instances. - * - * @param $reset - * If TRUE, clear the cache. The information will be rebuilt from the - * database next time it is needed. Defaults to FALSE. - * - * @return - * If $reset is TRUE, nothing. - * If $reset is FALSE, an array containing the following elements: - * - fields: Array of existing fields, keyed by field ID. This element - * lists deleted and non-deleted fields, but not inactive ones. - * Each field has an additional element, 'bundles', which is an array - * of all non-deleted instances of that field. - * - field_ids: Array of field IDs, keyed by field name. This element - * only lists non-deleted, active fields. - * - instances: Array of existing instances, keyed by entity type, bundle - * name and field name. This element only lists non-deleted instances - * whose field is active. - */ -function _field_info_collate_fields($reset = FALSE) { - static $info; - - if ($reset) { - $info = NULL; - cache_clear_all('field_info_fields', 'cache_field'); - return; - } - - if (!isset($info)) { - if ($cached = cache_get('field_info_fields', 'cache_field')) { - $info = $cached->data; - } - else { - $definitions = array( - 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)), - 'instances' => field_read_instances(), - ); - - // Populate 'fields' with all fields, keyed by ID. - $info['fields'] = array(); - foreach ($definitions['field_ids'] as $key => $field) { - $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field); - } - - // Build an array of field IDs for non-deleted fields, keyed by name. - $info['field_ids'] = array(); - foreach ($info['fields'] as $key => $field) { - if (!$field['deleted']) { - $info['field_ids'][$field['field_name']] = $key; - } - } - - // Populate 'instances'. Only non-deleted instances are considered. - $info['instances'] = array(); - foreach (field_info_bundles() as $entity_type => $bundles) { - foreach ($bundles as $bundle => $bundle_info) { - $info['instances'][$entity_type][$bundle] = array(); - } - } - foreach ($definitions['instances'] as $instance) { - $field = $info['fields'][$instance['field_id']]; - $instance = _field_info_prepare_instance($instance, $field); - $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; - // Enrich field definitions with the list of bundles where they have - // instances. NOTE: Deleted fields in $info['field_ids'] are not - // enriched because all of their instances are deleted, too, and - // are thus not in $definitions['instances']. - $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle']; - } - - // Populate 'extra_fields'. - $extra = module_invoke_all('field_extra_fields'); - drupal_alter('field_extra_fields', $extra); - // Merge in saved settings. - foreach ($extra as $entity_type => $bundles) { - foreach ($bundles as $bundle => $extra_fields) { - $extra_fields = _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle); - $info['extra_fields'][$entity_type][$bundle] = $extra_fields; - } - } - - cache_set('field_info_fields', $info, 'cache_field'); - } - } - - return $info; -} - -/** * Prepares a field definition for the current run-time context. * - * Since the field was last saved or updated, new field settings can be - * expected. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. * - * @param $field - * The raw field structure as read from the database. + * @see FieldInfo::prepareField() */ function _field_info_prepare_field($field) { - // Make sure all expected field settings are present. - $field['settings'] += field_info_field_settings($field['type']); - $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']); - - // Add storage details. - $details = (array) module_invoke($field['storage']['module'], 'field_storage_details', $field); - drupal_alter('field_storage_details', $details, $field, $instance); - $field['storage']['details'] = $details; - - // Initialize the 'bundles' list. - $field['bundles'] = array(); - - return $field; + $cache = _field_info_field_cache(); + return $cache->prepareField($field); } /** * Prepares an instance definition for the current run-time context. * - * Since the instance was last saved or updated, a number of things might have - * changed: widgets or formatters disabled, new settings expected, new view - * modes added... - * - * @param $instance - * The raw instance structure as read from the database. - * @param $field - * The field structure for the instance. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. * - * @return - * Field instance array. + * @see FieldInfo::prepareInstance() */ function _field_info_prepare_instance($instance, $field) { - // Make sure all expected instance settings are present. - $instance['settings'] += field_info_instance_settings($field['type']); - - // Set a default value for the instance. - if (field_behaviors_widget('default value', $instance) == FIELD_BEHAVIOR_DEFAULT && !isset($instance['default_value'])) { - $instance['default_value'] = NULL; - } - - $instance['widget'] = _field_info_prepare_instance_widget($field, $instance['widget']); - - foreach ($instance['display'] as $view_mode => $display) { - $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display); - } - - // Fallback to 'hidden' for view modes configured to use custom display - // settings, and for which the instance has no explicit settings. - $entity_info = entity_get_info($instance['entity_type']); - $view_modes = array_merge(array('default'), array_keys($entity_info['view modes'])); - $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); - foreach ($view_modes as $view_mode) { - if ($view_mode == 'default' || !empty($view_mode_settings[$view_mode]['custom_settings'])) { - if (!isset($instance['display'][$view_mode])) { - $instance['display'][$view_mode] = array( - 'type' => 'hidden', - 'label' => 'above', - 'settings' => array(), - 'weight' => 0, - ); - } - } - } - - return $instance; + $cache = _field_info_field_cache(); + return $cache->prepareInstance($instance, $field['type']); } /** * Adapts display specifications to the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $display - * Display specifications as found in - * $instance['display']['some_view_mode']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. + * + * @see FieldInfo::prepareInstanceDisplay() */ function _field_info_prepare_instance_display($field, $display) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $display += array( - 'label' => 'above', - 'type' => $field_type['default_formatter'], - 'settings' => array(), - 'weight' => 0, - ); - if ($display['type'] != 'hidden') { - $formatter_type = field_info_formatter_types($display['type']); - // Fallback to default formatter if formatter type is not available. - if (!$formatter_type) { - $display['type'] = $field_type['default_formatter']; - $formatter_type = field_info_formatter_types($display['type']); - } - $display['module'] = $formatter_type['module']; - // Fill in default settings for the formatter. - $display['settings'] += field_info_formatter_settings($display['type']); - } - - return $display; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceDisplay($display, $field['type']); } /** * Prepares widget specifications for the current run-time context. * - * @param $field - * The field structure for the instance. - * @param $widget - * Widget specifications as found in $instance['widget']. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. + * + * @see FieldInfo::prepareInstanceWidget() */ function _field_info_prepare_instance_widget($field, $widget) { - $field_type = field_info_field_types($field['type']); - - // Fill in default values. - $widget += array( - 'type' => $field_type['default_widget'], - 'settings' => array(), - 'weight' => 0, - ); - - $widget_type = field_info_widget_types($widget['type']); - // Fallback to default formatter if formatter type is not available. - if (!$widget_type) { - $widget['type'] = $field_type['default_widget']; - $widget_type = field_info_widget_types($widget['type']); - } - $widget['module'] = $widget_type['module']; - // Fill in default settings for the widget. - $widget['settings'] += field_info_widget_settings($widget['type']); - - return $widget; + $cache = _field_info_field_cache(); + return $cache->prepareInstanceWidget($widget, $field['type']); } /** * Prepares 'extra fields' for the current run-time context. * - * @param $extra_fields - * The array of extra fields, as collected in hook_field_extra_fields(). - * @param $entity_type - * The entity type. - * @param $bundle - * The bundle name. + * The functionality has moved to the FieldInfo class. This function is kept as + * a backwards-compatibility layer. See http://drupal.org/node/1880666. + * + * @see FieldInfo::prepareExtraFields() */ function _field_info_prepare_extra_fields($extra_fields, $entity_type, $bundle) { - $entity_type_info = entity_get_info($entity_type); - $bundle_settings = field_bundle_settings($entity_type, $bundle); - $extra_fields += array('form' => array(), 'display' => array()); - - $result = array(); - // Extra fields in forms. - foreach ($extra_fields['form'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); - if (isset($settings['weight'])) { - $field_data['weight'] = $settings['weight']; - } - $result['form'][$name] = $field_data; - } - - // Extra fields in displayed entities. - $data = $extra_fields['display']; - foreach ($extra_fields['display'] as $name => $field_data) { - $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); - $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); - foreach ($view_modes as $view_mode) { - if (isset($settings[$view_mode])) { - $field_data['display'][$view_mode] = $settings[$view_mode]; - } - else { - $field_data['display'][$view_mode] = array( - 'weight' => $field_data['weight'], - 'visible' => TRUE, - ); - } - } - unset($field_data['weight']); - $result['display'][$name] = $field_data; - } - - return $result; + $cache = _field_info_field_cache(); + return $cache->prepareExtraFields($extra_fields, $entity_type, $bundle); } /** @@ -584,21 +440,50 @@ function field_info_bundles($entity_type = NULL) { } /** + * Returns a lightweight map of fields across bundles. + * + * The function only returns active, non deleted fields. + * + * @return + * An array keyed by field name. Each value is an array with two entries: + * - type: The field type. + * - bundles: The bundles in which the field appears, as an array with entity + * types as keys and the array of bundle names as values. + */ +function field_info_field_map() { + $cache = _field_info_field_cache(); + return $cache->getFieldMap(); +} + +/** * Returns all field definitions. * + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. + * * @return * An array of field definitions, keyed by field name. Each field has an * additional property, 'bundles', which is an array of all the bundles to * which this field belongs keyed by entity type. + * + * @see field_info_field_map() */ function field_info_fields() { + $cache = _field_info_field_cache(); + $info = $cache->getFields(); + $fields = array(); - $info = _field_info_collate_fields(); - foreach ($info['fields'] as $key => $field) { + foreach ($info as $key => $field) { if (!$field['deleted']) { $fields[$field['field_name']] = $field; } } + return $fields; } @@ -620,10 +505,8 @@ function field_info_fields() { * @see field_info_field_by_id() */ function field_info_field($field_name) { - $info = _field_info_collate_fields(); - if (isset($info['field_ids'][$field_name])) { - return $info['fields'][$info['field_ids'][$field_name]]; - } + $cache = _field_info_field_cache(); + return $cache->getField($field_name); } /** @@ -641,17 +524,19 @@ function field_info_field($field_name) { * @see field_info_field() */ function field_info_field_by_id($field_id) { - $info = _field_info_collate_fields(); - if (isset($info['fields'][$field_id])) { - return $info['fields'][$field_id]; - } + $cache = _field_info_field_cache(); + return $cache->getFieldById($field_id); } /** * Returns the same data as field_info_field_by_id() for every field. * - * This function is typically used when handling all fields of some entities - * to avoid thousands of calls to field_info_field_by_id(). + * Use of this function should be avoided when possible, since it loads and + * statically caches a potentially large array of information. + * + * When iterating over the fields present in a given bundle after a call to + * field_info_instances($entity_type, $bundle), it is recommended to use + * field_info_field() on each individual field instead. * * @return * An array, each key is a field ID and the values are field arrays as @@ -662,41 +547,57 @@ function field_info_field_by_id($field_id) { * @see field_info_field_by_id() */ function field_info_field_by_ids() { - $info = _field_info_collate_fields(); - return $info['fields']; + $cache = _field_info_field_cache(); + return $cache->getFields(); } /** * Retrieves information about field instances. * + * Use of this function to retrieve instances across separate bundles (i.e. + * when the $bundle parameter is NULL) should be avoided when possible, since + * it loads and statically caches a potentially large array of information. Use + * field_info_field_map() instead. + * + * When retrieving the instances of a specific bundle (i.e. when both + * $entity_type and $bundle_name are provided), the function also populates a + * static cache with the corresponding field definitions, allowing fast + * retrieval of field_info_field() later in the request. + * * @param $entity_type - * The entity type for which to return instances. + * (optional) The entity type for which to return instances. * @param $bundle_name - * The bundle name for which to return instances. + * (optional) The bundle name for which to return instances. If $entity_type + * is NULL, the $bundle_name parameter is ignored. * * @return * If $entity_type is not set, return all instances keyed by entity type and * bundle name. If $entity_type is set, return all instances for that entity * type, keyed by bundle name. If $entity_type and $bundle_name are set, return * all instances for that bundle. + * + * @see field_info_field_map() */ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { - $info = _field_info_collate_fields(); + $cache = _field_info_field_cache(); - if (isset($entity_type) && isset($bundle_name)) { - return isset($info['instances'][$entity_type][$bundle_name]) ? $info['instances'][$entity_type][$bundle_name] : array(); + if (!isset($entity_type)) { + return $cache->getInstances(); } - elseif (isset($entity_type)) { - return isset($info['instances'][$entity_type]) ? $info['instances'][$entity_type] : array(); - } - else { - return $info['instances']; + if (!isset($bundle_name)) { + return $cache->getInstances($entity_type); } + + return $cache->getBundleInstances($entity_type, $bundle_name); } /** * Returns an array of instance data for a specific field and bundle. * + * The function populates a static cache with all fields and instances used in + * the bundle, allowing fast retrieval of field_info_field() or + * field_info_instance() later in the request. + * * @param $entity_type * The entity type for the instance. * @param $field_name @@ -709,9 +610,10 @@ function field_info_instances($entity_type = NULL, $bundle_name = NULL) { * NULL if the instance does not exist. */ function field_info_instance($entity_type, $field_name, $bundle_name) { - $info = _field_info_collate_fields(); - if (isset($info['instances'][$entity_type][$bundle_name][$field_name])) { - return $info['instances'][$entity_type][$bundle_name][$field_name]; + $cache = _field_info_field_cache(); + $info = $cache->getBundleInstances($entity_type, $bundle_name); + if (isset($info[$field_name])) { + return $info[$field_name]; } } @@ -769,11 +671,10 @@ function field_info_instance($entity_type, $field_name, $bundle_name) { * The array of pseudo-field elements in the bundle. */ function field_info_extra_fields($entity_type, $bundle, $context) { - $info = _field_info_collate_fields(); - if (isset($info['extra_fields'][$entity_type][$bundle][$context])) { - return $info['extra_fields'][$entity_type][$bundle][$context]; - } - return array(); + $cache = _field_info_field_cache(); + $info = $cache->getBundleExtraFields($entity_type, $bundle); + + return isset($info[$context]) ? $info[$context] : array(); } /** diff --git a/modules/field/field.install b/modules/field/field.install index 34d28073d..a4b153481 100644 --- a/modules/field/field.install +++ b/modules/field/field.install @@ -460,5 +460,12 @@ function field_update_7002() { } /** + * Add the FieldInfo class to the class registry. + */ +function field_update_7003() { + // Empty update to force a rebuild of the registry. +} + +/** * @} End of "addtogroup updates-7.x-extra". */ diff --git a/modules/field/field.module b/modules/field/field.module index b6cf05c9b..4331cdf42 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -873,7 +873,8 @@ function field_view_field($entity_type, $entity, $field_name, $display = array() if ($field = field_info_field($field_name)) { if (is_array($display)) { // When using custom display settings, fill in default values. - $display = _field_info_prepare_instance_display($field, $display); + $cache = _field_info_field_cache(); + $display = $cache->prepareInstanceDisplay($display, $field["type"]); } // Hook invocations are done through the _field_invoke() functions in @@ -1197,7 +1198,7 @@ function _element_validate_integer($element, &$form_state) { * Use element_validate_integer_positive() instead. * * @deprecated - * @see element_validate_number_positive() + * @see element_validate_integer_positive() */ function _element_validate_integer_positive($element, &$form_state) { element_validate_integer_positive($element, $form_state); diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module index a75619427..c639f38f2 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.module +++ b/modules/field/modules/field_sql_storage/field_sql_storage.module @@ -324,11 +324,14 @@ function field_sql_storage_field_storage_delete_field($field) { * Implements hook_field_storage_load(). */ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) { - $field_info = field_info_field_by_ids(); $load_current = $age == FIELD_LOAD_CURRENT; foreach ($fields as $field_id => $ids) { - $field = $field_info[$field_id]; + // By the time this hook runs, the relevant field definitions have been + // populated and cached in FieldInfo, so calling field_info_field_by_id() + // on each field individually is more efficient than loading all fields in + // memory upfront with field_info_field_by_ids(). + $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index 8004178eb..9cd8535b5 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -1144,6 +1144,16 @@ class FieldInfoTestCase extends FieldTestCase { $this->assertIdentical($instances, $expected, "field_info_instances('user') returns " . var_export($expected, TRUE) . '.'); $instances = field_info_instances('user', 'user'); $this->assertIdentical($instances, array(), "field_info_instances('user', 'user') returns an empty array."); + + // Test that querying for invalid entity types does not add entries in the + // list returned by field_info_instances(). + field_info_cache_clear(); + field_info_instances('invalid_entity', 'invalid_bundle'); + // Simulate new request by clearing static caches. + drupal_static_reset(); + field_info_instances('invalid_entity', 'invalid_bundle'); + $instances = field_info_instances(); + $this->assertFalse(isset($instances['invalid_entity']), 'field_info_instances() does not contain entries for the invalid entity type that was queried before'); } /** @@ -1254,6 +1264,80 @@ class FieldInfoTestCase extends FieldTestCase { } /** + * Test field_info_field_map(). + */ + function testFieldMap() { + // We will overlook fields created by the 'standard' install profile. + $exclude = field_info_field_map(); + + // Create a new bundle for 'test_entity' entity type. + field_test_create_bundle('test_bundle_2'); + + // Create a couple fields. + $fields = array( + array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ), + array( + 'field_name' => 'field_2', + 'type' => 'hidden_test_field', + ), + ); + foreach ($fields as $field) { + field_create_field($field); + } + + // Create a couple instances. + $instances = array( + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_1', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle_2', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ), + array( + 'field_name' => 'field_2', + 'entity_type' => 'test_cacheable_entity', + 'bundle' => 'test_bundle', + ), + ); + foreach ($instances as $instance) { + field_create_instance($instance); + } + + $expected = array( + 'field_1' => array( + 'type' => 'test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle', 'test_bundle_2'), + ), + ), + 'field_2' => array( + 'type' => 'hidden_test_field', + 'bundles' => array( + 'test_entity' => array('test_bundle'), + 'test_cacheable_entity' => array('test_bundle'), + ), + ), + ); + + // Check that the field map is correct. + $map = field_info_field_map(); + $map = array_diff_key($map, $exclude); + $this->assertEqual($map, $expected); + } + + /** * Test that the field_info settings convenience functions work. */ function testSettingsInfo() { @@ -1277,6 +1361,31 @@ class FieldInfoTestCase extends FieldTestCase { $this->assertIdentical(field_info_formatter_settings($type), $data['settings'], "field_info_formatter_settings returns {$type}'s formatter settings"); } } + + /** + * Tests that the field info cache can be built correctly. + */ + function testFieldInfoCache() { + // Create a test field and ensure it's in the array returned by + // field_info_fields(). + $field_name = drupal_strtolower($this->randomName()); + $field = array( + 'field_name' => $field_name, + 'type' => 'test_field', + ); + field_create_field($field); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is initially found in the array returned by field_info_fields().'); + + // Now rebuild the field info cache, and set a variable which will cause + // the cache to be cleared while it's being rebuilt; see + // field_test_entity_info(). Ensure the test field is still in the returned + // array. + field_info_cache_clear(); + variable_set('field_test_clear_info_cache_in_hook_entity_info', TRUE); + $fields = field_info_fields(); + $this->assertTrue(isset($fields[$field_name]), 'The test field is found in the array returned by field_info_fields() even if its cache is cleared while being rebuilt.'); + } } class FieldFormTestCase extends FieldTestCase { @@ -2179,6 +2288,41 @@ class FieldCrudTestCase extends FieldTestCase { } /** + * Tests reading field definitions. + */ + function testReadFields() { + $field_definition = array( + 'field_name' => 'field_1', + 'type' => 'test_field', + ); + field_create_field($field_definition); + + // Check that 'single column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + + // Check that 'multi column' criteria works. + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => $field_definition['type'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('field_name' => $field_definition['field_name'], 'type' => 'foo')); + $this->assertTrue(empty($fields), 'No field was found.'); + + // Create an instance of the field. + $instance_definition = array( + 'field_name' => $field_definition['field_name'], + 'entity_type' => 'test_entity', + 'bundle' => 'test_bundle', + ); + field_create_instance($instance_definition); + + // Check that criteria spanning over the field_config_instance table work. + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'bundle' => $instance_definition['bundle'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + $fields = field_read_fields(array('entity_type' => $instance_definition['entity_type'], 'field_name' => $instance_definition['field_name'])); + $this->assertTrue(count($fields) == 1 && isset($fields[$field_definition['field_name']]), 'The field was properly read.'); + } + + /** * Test creation of indexes on data column. */ function testFieldIndexes() { diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc index 95af3eeba..c6686ebc2 100644 --- a/modules/field/tests/field_test.entity.inc +++ b/modules/field/tests/field_test.entity.inc @@ -9,6 +9,12 @@ * Implements hook_entity_info(). */ function field_test_entity_info() { + // If requested, clear the field cache while this hook is being called. See + // testFieldInfoCache(). + if (variable_get('field_test_clear_info_cache_in_hook_entity_info', FALSE)) { + field_info_cache_clear(); + } + $bundles = variable_get('field_test_bundles', array('test_bundle' => array('label' => 'Test Bundle'))); $test_entity_modes = array( 'full' => array( diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc index 44770acb9..43bb20276 100644 --- a/modules/field_ui/field_ui.admin.inc +++ b/modules/field_ui/field_ui.admin.inc @@ -1534,7 +1534,7 @@ function field_ui_existing_field_options($entity_type, $bundle) { // - locked fields, // - fields already in the current bundle, // - fields that cannot be added to the entity type, - // - fields that that shoud not be added via user interface. + // - fields that should not be added via user interface. if (empty($field['locked']) && !field_info_instance($entity_type, $field['field_name'], $bundle) @@ -1544,7 +1544,7 @@ function field_ui_existing_field_options($entity_type, $bundle) { 'type' => $field['type'], 'type_label' => $field_types[$field['type']]['label'], 'field' => $field['field_name'], - 'label' => t($instance['label']), + 'label' => $instance['label'], 'widget_type' => $instance['widget']['type'], ); } diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module index 93cbcccc7..5f8bc45ef 100644 --- a/modules/field_ui/field_ui.module +++ b/modules/field_ui/field_ui.module @@ -332,23 +332,30 @@ function _field_ui_bundle_admin_path($entity_type, $bundle_name) { * Identifies inactive fields within a bundle. */ function field_ui_inactive_instances($entity_type, $bundle_name = NULL) { - if (!empty($bundle_name)) { - $inactive = array($bundle_name => array()); - $params = array('bundle' => $bundle_name); + $params = array('entity_type' => $entity_type); + + if (empty($bundle_name)) { + $active = field_info_instances($entity_type); + $inactive = array(); } else { - $inactive = array(); - $params = array(); + // Restrict to the specified bundle. For consistency with the case where + // $bundle_name is NULL, the $active and $inactive arrays are keyed by + // bundle name first. + $params['bundle'] = $bundle_name; + $active = array($bundle_name => field_info_instances($entity_type, $bundle_name)); + $inactive = array($bundle_name => array()); } - $params['entity_type'] = $entity_type; - $active_instances = field_info_instances($entity_type); + // Iterate on existing definitions, and spot those that do not appear in the + // $active list collected earlier. $all_instances = field_read_instances($params, array('include_inactive' => TRUE)); foreach ($all_instances as $instance) { - if (!isset($active_instances[$instance['bundle']][$instance['field_name']])) { + if (!isset($active[$instance['bundle']][$instance['field_name']])) { $inactive[$instance['bundle']][$instance['field_name']] = $instance; } } + if (!empty($bundle_name)) { return $inactive[$bundle_name]; } diff --git a/modules/field_ui/field_ui.test b/modules/field_ui/field_ui.test index d0a822a82..b67b70e2a 100644 --- a/modules/field_ui/field_ui.test +++ b/modules/field_ui/field_ui.test @@ -269,7 +269,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { */ function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') { // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Assert field settings. $field = field_info_field($field_name); $this->assertTrue($field['settings']['test_field_setting'] == $string, t('Field settings were found.')); @@ -360,7 +360,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { $this->fieldUIDeleteField($bundle_path1, $this->field_name, $this->field_label, $this->type); // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Check that the field instance was deleted. $this->assertNull(field_info_instance('node', $this->field_name, $this->type), t('Field instance was deleted.')); // Check that the field was not deleted @@ -370,7 +370,7 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase { $this->fieldUIDeleteField($bundle_path2, $this->field_name, $this->field_label, $type_name2); // Reset the fields info. - _field_info_collate_fields(TRUE); + field_info_cache_clear(); // Check that the field instance was deleted. $this->assertNull(field_info_instance('node', $this->field_name, $type_name2), t('Field instance was deleted.')); // Check that the field was deleted too. diff --git a/modules/filter/filter.admin.inc b/modules/filter/filter.admin.inc index 5a21e6e2e..60284d993 100644 --- a/modules/filter/filter.admin.inc +++ b/modules/filter/filter.admin.inc @@ -2,13 +2,14 @@ /** * @file - * Admin page callbacks for the filter module. + * Administrative page callbacks for the Filter module. */ /** - * Menu callback; Displays a list of all text formats and allows them to be rearranged. + * Page callback: Form constructor for a form to list and reorder text formats. * * @ingroup forms + * @see filter_menu() * @see filter_admin_overview_submit() */ function filter_admin_overview($form) { @@ -45,6 +46,9 @@ function filter_admin_overview($form) { return $form; } +/** + * Form submission handler for filter_admin_overview(). + */ function filter_admin_overview_submit($form, &$form_state) { foreach ($form_state['values']['formats'] as $id => $data) { if (is_array($data) && isset($data['weight'])) { @@ -95,7 +99,26 @@ function theme_filter_admin_overview($variables) { } /** - * Menu callback; Display a text format form. + * Page callback: Displays the text format add/edit form. + * + * @param object|null $format + * (optional) An object representing a format, with the following properties: + * - format: A machine-readable name representing the ID of the text format + * to save. If this corresponds to an existing text format, that format + * will be updated; otherwise, a new format will be created. + * - name: The title of the text format. + * - cache: (optional) An integer indicating whether the text format is + * cacheable (1) or not (0). Defaults to 1. + * - status: (optional) An integer indicating whether the text format is + * enabled (1) or not (0). Defaults to 1. + * - weight: (optional) The weight of the text format, which controls its + * placement in text format lists. If omitted, the weight is set to 0. + * Defaults to NULL. + * + * @return + * A form array. + * + * @see filter_menu() */ function filter_admin_format_page($format = NULL) { if (!isset($format->name)) { @@ -109,11 +132,24 @@ function filter_admin_format_page($format = NULL) { } /** - * Generate a text format form. + * Form constructor for the text format add/edit form. + * + * @param $format + * A format object having the properties: + * - format: A machine-readable name representing the ID of the text format to + * save. If this corresponds to an existing text format, that format will be + * updated; otherwise, a new format will be created. + * - name: The title of the text format. + * - cache: An integer indicating whether the text format is cacheable (1) or + * not (0). Defaults to 1. + * - status: (optional) An integer indicating whether the text format is + * enabled (1) or not (0). Defaults to 1. + * - weight: (optional) The weight of the text format, which controls its + * placement in text format lists. If omitted, the weight is set to 0. * - * @ingroup forms * @see filter_admin_format_form_validate() * @see filter_admin_format_form_submit() + * @ingroup forms */ function filter_admin_format_form($form, &$form_state, $format) { $is_fallback = ($format->format == filter_fallback_format()); @@ -287,7 +323,9 @@ function theme_filter_admin_format_filter_order($variables) { } /** - * Validate text format form submissions. + * Form validation handler for filter_admin_format_form(). + * + * @see filter_admin_format_form_submit() */ function filter_admin_format_form_validate($form, &$form_state) { $format_format = trim($form_state['values']['format']); @@ -304,7 +342,9 @@ function filter_admin_format_form_validate($form, &$form_state) { } /** - * Process text format form submissions. + * Form submission handler for filter_admin_format_form(). + * + * @see filter_admin_format_form_validate() */ function filter_admin_format_form_submit($form, &$form_state) { // Remove unnecessary values. @@ -336,10 +376,14 @@ function filter_admin_format_form_submit($form, &$form_state) { } /** - * Menu callback; confirm deletion of a format. + * Form constructor for the text format deletion confirmation form. * - * @ingroup forms + * @param $format + * An object representing a text format. + * + * @see filter_menu() * @see filter_admin_disable_submit() + * @ingroup forms */ function filter_admin_disable($form, &$form_state, $format) { $form['#format'] = $format; @@ -353,7 +397,7 @@ function filter_admin_disable($form, &$form_state, $format) { } /** - * Process filter disable form submission. + * Form submission handler for filter_admin_disable(). */ function filter_admin_disable_submit($form, &$form_state) { $format = $form['#format']; @@ -362,4 +406,3 @@ function filter_admin_disable_submit($form, &$form_state) { $form_state['redirect'] = 'admin/config/content/formats'; } - diff --git a/modules/filter/filter.install b/modules/filter/filter.install index 9d17eb54b..71ba97b08 100644 --- a/modules/filter/filter.install +++ b/modules/filter/filter.install @@ -2,7 +2,7 @@ /** * @file - * Install, update and uninstall functions for the filter module. + * Install, update, and uninstall functions for the Filter module. */ /** diff --git a/modules/filter/filter.module b/modules/filter/filter.module index 7b451b7e3..182033fe8 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -2,7 +2,7 @@ /** * @file - * Framework for handling filtering of content. + * Framework for handling the filtering of content. */ /** @@ -71,6 +71,7 @@ function filter_theme() { * Implements hook_element_info(). * * @see filter_process_format() + * @see text_format_wrapper() */ function filter_element_info() { $type['text_format'] = array( @@ -132,13 +133,16 @@ function filter_menu() { } /** - * Access callback for deleting text formats. + * Access callback: Checks access for disabling text formats. * * @param $format * A text format object. + * * @return * TRUE if the text format can be disabled by the current user, FALSE * otherwise. + * + * @see filter_menu() */ function _filter_disable_format_access($format) { // The fallback format can never be disabled. @@ -146,7 +150,7 @@ function _filter_disable_format_access($format) { } /** - * Load a text format object from the database. + * Loads a text format object from the database. * * @param $format_id * The format ID. @@ -164,29 +168,32 @@ function filter_format_load($format_id) { } /** - * Save a text format object to the database. + * Saves a text format object to the database. * * @param $format - * A format object using the properties: - * - 'format': A machine-readable name representing the ID of the text format + * A format object having the properties: + * - format: A machine-readable name representing the ID of the text format * to save. If this corresponds to an existing text format, that format * will be updated; otherwise, a new format will be created. - * - 'name': The title of the text format. - * - 'status': (optional) An integer indicating whether the text format is + * - name: The title of the text format. + * - status: (optional) An integer indicating whether the text format is * enabled (1) or not (0). Defaults to 1. - * - 'weight': (optional) The weight of the text format, which controls its + * - weight: (optional) The weight of the text format, which controls its * placement in text format lists. If omitted, the weight is set to 0. - * - 'filters': (optional) An associative, multi-dimensional array of filters + * - filters: (optional) An associative, multi-dimensional array of filters * assigned to the text format, keyed by the name of each filter and using * the properties: - * - 'weight': (optional) The weight of the filter in the text format. If + * - weight: (optional) The weight of the filter in the text format. If * omitted, either the currently stored weight is retained (if there is * one), or the filter is assigned a weight of 10, which will usually * put it at the bottom of the list. - * - 'status': (optional) A boolean indicating whether the filter is + * - status: (optional) A boolean indicating whether the filter is * enabled in the text format. If omitted, the filter will be disabled. - * - 'settings': (optional) An array of configured settings for the filter. + * - settings: (optional) An array of configured settings for the filter. * See hook_filter_info() for details. + * + * @return + * SAVED_NEW or SAVED_UPDATED. */ function filter_format_save($format) { $format->name = trim($format->name); @@ -271,7 +278,7 @@ function filter_format_save($format) { } /** - * Disable a text format. + * Disables a text format. * * There is no core facility to re-enable a disabled format. It is not deleted * to keep information for contrib and to make sure the format ID is never @@ -313,7 +320,15 @@ function filter_format_exists($format_id) { } /** - * Display a text format form title. + * Displays a text format form title. + * + * @param object $format + * A format object. + * + * @return string + * The name of the format. + * + * @see filter_menu() */ function filter_admin_format_title($format) { return $format->name; @@ -350,6 +365,7 @@ function filter_permission() { * * @param $format * An object representing a text format. + * * @return * The machine-readable permission name, or FALSE if the provided text format * is malformed or is the fallback format (which is available to all users). @@ -380,11 +396,13 @@ function filter_modules_disabled($modules) { } /** - * Retrieve a list of text formats, ordered by weight. + * Retrieves a list of text formats, ordered by weight. * * @param $account * (optional) If provided, only those formats that are allowed for this user - * account will be returned. All formats will be returned otherwise. + * account will be returned. All formats will be returned otherwise. Defaults + * to NULL. + * * @return * An array of text format objects, keyed by the format ID and ordered by * weight. @@ -427,7 +445,7 @@ function filter_formats($account = NULL) { } /** - * Resets text format caches. + * Resets the text format caches. * * @see filter_formats() */ @@ -443,6 +461,7 @@ function filter_formats_reset() { * * @param $format * An object representing the text format. + * * @return * An array of role names, keyed by role ID. */ @@ -461,6 +480,7 @@ function filter_get_roles_by_format($format) { * * @param $rid * The user role ID to retrieve text formats for. + * * @return * An array of text format objects that are allowed for the role, keyed by * the text format ID and ordered by weight. @@ -494,7 +514,8 @@ function filter_get_formats_by_role($rid) { * * @param $account * (optional) The user account to check. Defaults to the currently logged-in - * user. + * user. Defaults to NULL. + * * @return * The ID of the user's default text format. * @@ -525,15 +546,18 @@ function filter_default_format($account = NULL) { * format is initialized to output plain text. Installation profiles and site * administrators have the freedom to configure it further. * - * Note that the fallback format is completely distinct from the default - * format, which differs per user and is simply the first format which that - * user has access to. The default and fallback formats are only guaranteed to - * be the same for users who do not have access to any other format; otherwise, - * the fallback format's weight determines its placement with respect to the - * user's other formats. + * Note that the fallback format is completely distinct from the default format, + * which differs per user and is simply the first format which that user has + * access to. The default and fallback formats are only guaranteed to be the + * same for users who do not have access to any other format; otherwise, the + * fallback format's weight determines its placement with respect to the user's + * other formats. * - * Any modules implementing a format deletion functionality must not delete - * this format. + * Any modules implementing a format deletion functionality must not delete this + * format. + * + * @return + * The ID of the fallback text format. * * @see hook_filter_format_disable() * @see filter_default_format() @@ -550,6 +574,9 @@ function filter_fallback_format() { /** * Returns the title of the fallback text format. + * + * @return string + * The title of the fallback text format. */ function filter_fallback_format_title() { $fallback_format = filter_format_load(filter_fallback_format()); @@ -557,7 +584,10 @@ function filter_fallback_format_title() { } /** - * Return a list of all filters provided by modules. + * Returns a list of all filters provided by modules. + * + * @return array + * An array of filter formats. */ function filter_get_filters() { $filters = &drupal_static(__FUNCTION__, array()); @@ -588,14 +618,16 @@ function filter_get_filters() { } /** - * Helper function for sorting the filter list by filter name. + * Sorts an array of filters by filter name. + * + * Callback for uasort() within filter_get_filters(). */ function _filter_list_cmp($a, $b) { return strcmp($a['title'], $b['title']); } /** - * Check if text in a certain text format is allowed to be cached. + * Checks if the text in a certain text format is allowed to be cached. * * This function can be used to check whether the result of the filtering * process can be cached. A text format may allow caching depending on the @@ -603,6 +635,7 @@ function _filter_list_cmp($a, $b) { * * @param $format_id * The text format ID to check. + * * @return * TRUE if the given text format allows caching, FALSE otherwise. */ @@ -619,6 +652,7 @@ function filter_format_allowcache($format_id) { * * @param $format * The text format object to check. + * * @return * TRUE if all the filters enabled in the given text format allow caching, * FALSE otherwise. @@ -640,7 +674,7 @@ function _filter_format_is_cacheable($format) { } /** - * Retrieve a list of filters for a given text format. + * Retrieves a list of filters for a given text format. * * Note that this function returns all associated filters regardless of whether * they are enabled or disabled. All functions working with the filter @@ -694,7 +728,7 @@ function filter_list_format($format_id) { } /** - * Run all the enabled filters on a piece of text. + * Runs all the enabled filters on a piece of text. * * Note: Because filters can inject JavaScript or execute PHP code, security is * vital here. When a user supplies a text format, you should validate it using @@ -705,16 +739,20 @@ function filter_list_format($format_id) { * @param $text * The text to be filtered. * @param $format_id - * The format id of the text to be filtered. If no format is assigned, the - * fallback format will be used. + * (optional) The format ID of the text to be filtered. If no format is + * assigned, the fallback format will be used. Defaults to NULL. * @param $langcode - * Optional: the language code of the text to be filtered, e.g. 'en' for + * (optional) The language code of the text to be filtered, e.g. 'en' for * English. This allows filters to be language aware so language specific - * text replacement can be implemented. + * text replacement can be implemented. Defaults to an empty string. * @param $cache - * Boolean whether to cache the filtered output in the {cache_filter} table. - * The caller may set this to FALSE when the output is already cached - * elsewhere to avoid duplicate cache lookups and storage. + * (optional) A Boolean indicating whether to cache the filtered output in the + * {cache_filter} table. The caller may set this to FALSE when the output is + * already cached elsewhere to avoid duplicate cache lookups and storage. + * Defaults to FALSE. + * + * @return + * The filtered text. * * @ingroup sanitization */ @@ -784,8 +822,8 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) * the text format id specified in #format or the user's default format by * default, if NULL. * - * The resulting value for the element will be an array holding the value and the - * format. For example, the value for the body element will be: + * The resulting value for the element will be an array holding the value and + * the format. For example, the value for the body element will be: * @code * $form_state['values']['body']['value'] = 'foo'; * $form_state['values']['body']['format'] = 'foo'; @@ -795,7 +833,7 @@ function check_markup($text, $format_id = NULL, $langcode = '', $cache = FALSE) * The form element to process. Properties used: * - #base_type: The form element #type to use for the 'value' element. * 'textarea' by default. - * - #format: (optional) The text format id to preselect. If NULL or not set, + * - #format: (optional) The text format ID to preselect. If NULL or not set, * the default format for the current user will be used. * * @return @@ -933,7 +971,7 @@ function filter_process_format($element) { } /** - * #pre_render callback for #type 'text_format' to hide field value from prying eyes. + * Render API callback: Hides the field value of 'text_format' elements. * * To not break form processing and previews if a user does not have access to a * stored text format, the expanded form elements in filter_process_format() are @@ -976,7 +1014,7 @@ function theme_text_format_wrapper($variables) { * An object representing the text format. * @param $account * (optional) The user account to check access for; if omitted, the currently - * logged-in user is used. + * logged-in user is used. Defaults to NULL. * * @return * Boolean TRUE if the user is allowed to access the given format. @@ -998,7 +1036,20 @@ function filter_access($format, $account = NULL) { } /** - * Helper function for fetching filter tips. + * Retrieves the filter tips. + * + * @param $format_id + * The ID of the text format for which to retrieve tips, or -1 to return tips + * for all formats accessible to the current user. + * @param $long + * (optional) Boolean indicating whether the long form of tips should be + * returned. Defaults to FALSE. + * + * @return + * An associative array of filtering tips, keyed by filter name. Each + * filtering tip is an associative array with elements: + * - tip: Tip text. + * - id: Filter ID. */ function _filter_tips($format_id, $long = FALSE) { global $user; @@ -1032,14 +1083,14 @@ function _filter_tips($format_id, $long = FALSE) { /** * Parses an HTML snippet and returns it as a DOM object. * - * This function loads the body part of a partial (X)HTML document - * and returns a full DOMDocument object that represents this document. - * You can use filter_dom_serialize() to serialize this DOMDocument - * back to a XHTML snippet. + * This function loads the body part of a partial (X)HTML document and returns + * a full DOMDocument object that represents this document. You can use + * filter_dom_serialize() to serialize this DOMDocument back to a XHTML + * snippet. * * @param $text - * The partial (X)HTML snippet to load. Invalid mark-up - * will be corrected on import. + * The partial (X)HTML snippet to load. Invalid mark-up will be corrected on + * import. * @return * A DOMDocument that represents the loaded (X)HTML snippet. */ @@ -1054,15 +1105,14 @@ function filter_dom_load($text) { /** * Converts a DOM object back to an HTML snippet. * - * The function serializes the body part of a DOMDocument - * back to an XHTML snippet. - * - * The resulting XHTML snippet will be properly formatted - * to be compatible with HTML user agents. + * The function serializes the body part of a DOMDocument back to an XHTML + * snippet. The resulting XHTML snippet will be properly formatted to be + * compatible with HTML user agents. * * @param $dom_document * A DOMDocument object to serialize, only the tags below * the first <body> node will be converted. + * * @return * A valid (X)HTML snippet, as a string. */ @@ -1099,9 +1149,11 @@ function filter_dom_serialize($dom_document) { * @param $dom_element * The element potentially containing a CDATA node. * @param $comment_start - * String to use as a comment start marker to escape the CDATA declaration. + * (optional) A string to use as a comment start marker to escape the CDATA + * declaration. Defaults to '//'. * @param $comment_end - * String to use as a comment end marker to escape the CDATA declaration. + * (optional) A string to use as a comment end marker to escape the CDATA + * declaration. Defaults to an empty string. */ function filter_dom_serialize_escape_cdata_element($dom_document, $dom_element, $comment_start = '//', $comment_end = '') { foreach ($dom_element->childNodes as $node) { @@ -1156,7 +1208,7 @@ function theme_filter_guidelines($variables) { /** * @defgroup standard_filters Standard filters * @{ - * Filters implemented by the filter.module. + * Filters implemented by the Filter module. */ /** @@ -1204,7 +1256,10 @@ function filter_filter_info() { } /** - * Settings callback for the HTML filter. + * Filter settings callback for the HTML content filter. + * + * See hook_filter_FILTER_settings() for documentation of parameters and return + * value. */ function _filter_html_settings($form, &$form_state, $filter, $format, $defaults) { $filter->settings += $defaults; @@ -1230,7 +1285,7 @@ function _filter_html_settings($form, &$form_state, $filter, $format, $defaults) } /** - * HTML filter. Provides filtering of input into accepted HTML. + * Provides filtering of input into accepted HTML. */ function _filter_html($text, $filter) { $allowed_tags = preg_split('/\s+|<|>/', $filter->settings['allowed_html'], -1, PREG_SPLIT_NO_EMPTY); @@ -1249,7 +1304,9 @@ function _filter_html($text, $filter) { } /** - * Filter tips callback for HTML filter. + * Filter tips callback: Provides help for the HTML filter. + * + * @see filter_filter_info() */ function _filter_html_tips($filter, $format, $long = FALSE) { global $base_url; @@ -1347,7 +1404,9 @@ function _filter_html_tips($filter, $format, $long = FALSE) { } /** - * Settings callback for URL filter. + * Filter URL settings callback: Provides settings for the URL filter. + * + * @see filter_filter_info() */ function _filter_url_settings($form, &$form_state, $filter, $format, $defaults) { $filter->settings += $defaults; @@ -1366,7 +1425,7 @@ function _filter_url_settings($form, &$form_state, $filter, $format, $defaults) } /** - * URL filter. Automatically converts text into hyperlinks. + * Converts text into hyperlinks automatically. * * This filter identifies and makes clickable three types of "links". * - URLs like http://example.com. @@ -1489,7 +1548,9 @@ function _filter_url($text, $filter) { } /** - * preg_replace callback to make links out of absolute URLs. + * Makes links out of absolute URLs. + * + * Callback for preg_replace_callback() within _filter_url(). */ function _filter_url_parse_full_links($match) { // The $i:th parenthesis in the regexp contains the URL. @@ -1502,7 +1563,9 @@ function _filter_url_parse_full_links($match) { } /** - * preg_replace callback to make links out of e-mail addresses. + * Makes links out of e-mail addresses. + * + * Callback for preg_replace_callback() within _filter_url(). */ function _filter_url_parse_email_links($match) { // The $i:th parenthesis in the regexp contains the URL. @@ -1515,7 +1578,9 @@ function _filter_url_parse_email_links($match) { } /** - * preg_replace callback to make links out of domain names starting with "www." + * Makes links out of domain names starting with "www." + * + * Callback for preg_replace_callback() within _filter_url(). */ function _filter_url_parse_partial_links($match) { // The $i:th parenthesis in the regexp contains the URL. @@ -1528,14 +1593,17 @@ function _filter_url_parse_partial_links($match) { } /** - * preg_replace callback to escape contents of HTML comments + * Escapes the contents of HTML comments. + * + * Callback for preg_replace_callback() within _filter_url(). * * @param $match * An array containing matches to replace from preg_replace_callback(), * whereas $match[1] is expected to contain the content to be filtered. * @param $escape - * (optional) Boolean whether to escape (TRUE) or unescape comments (FALSE). - * Defaults to neither. If TRUE, statically cached $comments are reset. + * (optional) A Boolean indicating whether to escape (TRUE) or unescape + * comments (FALSE). Defaults to NULL, indicating neither. If TRUE, statically + * cached $comments are reset. */ function _filter_url_escape_comments($match, $escape = NULL) { static $mode, $comments = array(); @@ -1582,21 +1650,24 @@ function _filter_url_trim($text, $length = NULL) { } /** - * Filter tips callback for URL filter. + * Filter tips callback: Provides help for the URL filter. + * + * @see filter_filter_info() */ function _filter_url_tips($filter, $format, $long = FALSE) { return t('Web page addresses and e-mail addresses turn into links automatically.'); } /** - * Scan input and make sure that all HTML tags are properly closed and nested. + * Scans the input and makes sure that HTML tags are properly closed. */ function _filter_htmlcorrector($text) { return filter_dom_serialize(filter_dom_load($text)); } /** - * Convert line breaks into <p> and <br> in an intelligent fashion. + * Converts line breaks into <p> and <br> in an intelligent fashion. + * * Based on: http://photomatt.net/scripts/autop */ function _filter_autop($text) { @@ -1662,7 +1733,9 @@ function _filter_autop($text) { } /** - * Filter tips callback for auto-paragraph filter. + * Filter tips callback: Provides help for the auto-paragraph filter. + * + * @see filter_filter_info() */ function _filter_autop_tips($filter, $format, $long = FALSE) { if ($long) { @@ -1681,7 +1754,9 @@ function _filter_html_escape($text) { } /** - * Filter tips callback for HTML escaping filter. + * Filter tips callback: Provides help for the HTML escaping filter. + * + * @see filter_filter_info() */ function _filter_html_escape_tips($filter, $format, $long = FALSE) { return t('No HTML tags allowed.'); diff --git a/modules/filter/filter.pages.inc b/modules/filter/filter.pages.inc index dbbbe4c5a..50f81177f 100644 --- a/modules/filter/filter.pages.inc +++ b/modules/filter/filter.pages.inc @@ -2,12 +2,17 @@ /** * @file - * User page callbacks for the filter module. + * User page callbacks for the Filter module. */ - /** - * Menu callback; show a page with long filter tips. + * Page callback: Displays a page with long filter tips. + * + * @return string + * An HTML-formatted string. + * + * @see filter_menu() + * @see theme_filter_tips() */ function filter_tips_long() { $format_id = arg(2); @@ -20,13 +25,12 @@ function filter_tips_long() { return $output; } - /** * Returns HTML for a set of filter tips. * * @param $variables * An associative array containing: - * - tips: An array containing descriptions and a CSS id in the form of + * - tips: An array containing descriptions and a CSS ID in the form of * 'module-name/filter-id' (only used when $long is TRUE) for each * filter in one or more text formats. Example: * @code diff --git a/modules/filter/filter.test b/modules/filter/filter.test index aa1693fba..0c3949517 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -22,7 +22,7 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } /** - * Test CRUD operations for text formats and filters. + * Tests CRUD operations for text formats and filters. */ function testTextFormatCRUD() { // Add a text format with minimum data only. @@ -73,7 +73,7 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } /** - * Verify that a text format is properly stored. + * Verifies that a text format is properly stored. */ function verifyTextFormat($format) { $t_args = array('%format' => $format->name); @@ -111,7 +111,7 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } /** - * Verify that filters are properly stored for a text format. + * Verifies that filters are properly stored for a text format. */ function verifyFilters($format) { // Verify filter database records. @@ -160,6 +160,9 @@ class FilterCRUDTestCase extends DrupalWebTestCase { } } +/** + * Tests the administrative functionality of the Filter module. + */ class FilterAdminTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -185,6 +188,9 @@ class FilterAdminTestCase extends DrupalWebTestCase { $this->drupalLogin($this->admin_user); } + /** + * Tests the format administration functionality. + */ function testFormatAdmin() { // Add text format. $this->drupalGet('admin/config/content/formats'); @@ -249,7 +255,7 @@ class FilterAdminTestCase extends DrupalWebTestCase { } /** - * Test filter administration functionality. + * Tests filter administration functionality. */ function testFilterAdmin() { // URL filter. @@ -413,11 +419,43 @@ class FilterAdminTestCase extends DrupalWebTestCase { } } +/** + * Tests the filter format access functionality in the Filter module. + */ class FilterFormatAccessTestCase extends DrupalWebTestCase { + /** + * A user with administrative permissions. + * + * @var object + */ protected $admin_user; + + /** + * A user with 'administer filters' permission. + * + * @var object + */ protected $filter_admin_user; + + /** + * A user with permission to create and edit own content. + * + * @var object + */ protected $web_user; + + /** + * An object representing an allowed text format. + * + * @var object + */ protected $allowed_format; + + /** + * An object representing a disallowed text format. + * + * @var object + */ protected $disallowed_format; public static function getInfo() { @@ -471,6 +509,9 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { )); } + /** + * Tests the Filter format access permissions functionality. + */ function testFormatPermissions() { // Make sure that a regular user only has access to the text format they // were granted access to, as well to the fallback format. @@ -507,6 +548,9 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { $this->assertTrue(isset($options[filter_fallback_format()]), t('The fallback format appears as an option when adding a new node.')); } + /** + * Tests if text format is available to a role. + */ function testFormatRoles() { // Get the role ID assigned to the regular user; it must be the maximum. $rid = max(array_keys($this->web_user->roles)); @@ -528,13 +572,13 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { } /** - * Test editing a page using a disallowed text format. + * Tests editing a page using a disallowed text format. * - * Verifies that regular users and administrators are able to edit a page, - * but not allowed to change the fields which use an inaccessible text - * format. Also verifies that fields which use a text format that does not - * exist can be edited by administrators only, but that the administrator is - * forced to choose a new format before saving the page. + * Verifies that regular users and administrators are able to edit a page, but + * not allowed to change the fields which use an inaccessible text format. + * Also verifies that fields which use a text format that does not exist can + * be edited by administrators only, but that the administrator is forced to + * choose a new format before saving the page. */ function testFormatWidgetPermissions() { $langcode = LANGUAGE_NONE; @@ -650,7 +694,7 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { } /** - * Rebuild text format and permission caches in the thread running the tests. + * Rebuilds text format and permission caches in the thread running the tests. */ protected function resetFilterCaches() { filter_formats_reset(); @@ -658,6 +702,9 @@ class FilterFormatAccessTestCase extends DrupalWebTestCase { } } +/** + * Tests the default filter functionality in the Filter module. + */ class FilterDefaultFormatTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -667,6 +714,9 @@ class FilterDefaultFormatTestCase extends DrupalWebTestCase { ); } + /** + * Tests if the default text format is accessible to users. + */ function testDefaultTextFormats() { // Create two text formats, and two users. The first user has access to // both formats, but the second user only has access to the second one. @@ -710,7 +760,7 @@ class FilterDefaultFormatTestCase extends DrupalWebTestCase { } /** - * Rebuild text format and permission caches in the thread running the tests. + * Rebuilds text format and permission caches in the thread running the tests. */ protected function resetFilterCaches() { filter_formats_reset(); @@ -718,6 +768,9 @@ class FilterDefaultFormatTestCase extends DrupalWebTestCase { } } +/** + * Tests the behavior of check_markup() when it is called without text format. + */ class FilterNoFormatTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -727,6 +780,12 @@ class FilterNoFormatTestCase extends DrupalWebTestCase { ); } + /** + * Tests text without format. + * + * Tests if text with no format is filtered the same way as text in the + * fallback format. + */ function testCheckMarkupNoFormat() { // Create some text. Include some HTML and line breaks, so we get a good // test of the filtering that is applied to it. @@ -757,7 +816,10 @@ class FilterSecurityTestCase extends DrupalWebTestCase { } /** - * Test that filtered content is emptied when an actively used filter module is disabled. + * Tests removal of filtered content when an active filter is disabled. + * + * Tests that filtered content is emptied when an actively used filter module + * is disabled. */ function testDisableFilterModule() { // Create a new node. @@ -800,7 +862,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test the line break filter. + * Tests the line break filter. */ function testLineBreakFilter() { // Setup dummy filter object. @@ -1060,7 +1122,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test filter settings, defaults, access restrictions and similar. + * Tests filter settings, defaults, access restrictions and similar. * * @todo This is for functions like filter_filter and check_markup, whose * functionality is not completely focused on filtering. Some ideas: @@ -1116,7 +1178,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test the spam deterrent. + * Tests the spam deterrent. */ function testNoFollowFilter() { // Setup dummy filter object. @@ -1147,7 +1209,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { } /** - * Test the loose, admin HTML filter. + * Tests the loose, admin HTML filter. */ function testFilterXSSAdmin() { // DRUPAL-SA-2008-044 @@ -1541,7 +1603,7 @@ www.example.com with a newline in comments --> } /** - * Test the HTML corrector filter. + * Tests the HTML corrector filter. * * @todo This test could really use some validity checking function. */ @@ -1745,9 +1807,9 @@ body {color:red} * @param $needle * Lowercase, plain text to look for. * @param $message - * Message to display if failed. + * (optional) Message to display if failed. Defaults to an empty string. * @param $group - * The group this message belongs to, defaults to 'Other'. + * (optional) The group this message belongs to. Defaults to 'Other'. * @return * TRUE on pass, FALSE on fail. */ @@ -1769,9 +1831,9 @@ body {color:red} * @param $needle * Lowercase, plain text to look for. * @param $message - * Message to display if failed. + * (optional) Message to display if failed. Defaults to an empty string. * @param $group - * The group this message belongs to, defaults to 'Other'. + * (optional) The group this message belongs to. Defaults to 'Other'. * @return * TRUE on pass, FALSE on fail. */ @@ -1781,7 +1843,7 @@ body {color:red} } /** - * Tests for filter hook invocation. + * Tests for Filter's hook invocations. */ class FilterHooksTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1799,7 +1861,10 @@ class FilterHooksTestCase extends DrupalWebTestCase { } /** - * Test that hooks run correctly on creating, editing, and deleting a text format. + * Tests hooks on format management. + * + * Tests that hooks run correctly on creating, editing, and deleting a text + * format. */ function testFilterHooks() { // Add a text format. @@ -1846,6 +1911,11 @@ class FilterHooksTestCase extends DrupalWebTestCase { * Tests filter settings. */ class FilterSettingsTestCase extends DrupalWebTestCase { + /** + * The installation profile to use with this test class. + * + * @var string + */ protected $profile = 'testing'; public static function getInfo() { diff --git a/modules/image/image.admin.inc b/modules/image/image.admin.inc index ab99a49e8..9f0fab254 100644 --- a/modules/image/image.admin.inc +++ b/modules/image/image.admin.inc @@ -326,7 +326,7 @@ function image_style_delete_form_submit($form, &$form_state) { /** * Confirmation form to revert a database style to its default. */ -function image_style_revert_form($form, $form_state, $style) { +function image_style_revert_form($form, &$form_state, $style) { $form_state['image_style'] = $style; return confirm_form( diff --git a/modules/image/image.install b/modules/image/image.install index b7aac7152..1d7bd4ef4 100644 --- a/modules/image/image.install +++ b/modules/image/image.install @@ -391,7 +391,8 @@ function image_update_7002(array &$sandbox) { } // Process the table at the top of the list. - $table = reset(array_keys($sandbox['tables'])); + $keys = array_keys($sandbox['tables']); + $table = reset($keys); $sandbox['processed'] += _image_update_7002_populate_dimensions($table, $sandbox['tables'][$table], $sandbox['last_fid']); // Has the table been fully processed? diff --git a/modules/image/image.module b/modules/image/image.module index a9cc1a545..b7d6cdda3 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -254,7 +254,7 @@ function image_form_system_file_system_settings_alter(&$form, &$form_state) { } /** - * Submit handler for the file system settings form. + * Form submission handler for system_file_system_settings(). * * Adds a menu rebuild after the public file path has been changed, so that the * menu router item depending on that file path will be regenerated. @@ -312,9 +312,9 @@ function image_file_download($uri) { return -1; } - // Private file access for the original files. Note that we only - // check access for non-temporary images, since file.module will - // grant access for all temporary files. + // Private file access for the original files. Note that we only check access + // for non-temporary images, since file.module will grant access for all + // temporary files. $files = file_load_multiple(array(), array('uri' => $uri)); if (count($files)) { $file = reset($files); @@ -537,7 +537,7 @@ function image_field_update_instance($instance, $prior_instance) { } /** - * Clear cached versions of a specific file in all styles. + * Clears cached versions of a specific file in all styles. * * @param $path * The Drupal file path to the original image. @@ -553,7 +553,7 @@ function image_path_flush($path) { } /** - * Get an array of all styles and their settings. + * Gets an array of all styles and their settings. * * @return * An array of styles keyed by the image style ID (isid). @@ -614,7 +614,9 @@ function image_styles() { } /** - * Load a style by style name or ID. May be used as a loader for menu items. + * Loads a style by style name or ID. + * + * May be used as a loader for menu items. * * @param $name * The name of the style. @@ -623,6 +625,7 @@ function image_styles() { * @param $include * If set, this loader will restrict to a specific type of image style, may be * one of the defined Image style storage constants. + * * @return * An image style array containing the following keys: * - "isid": The unique image style ID. @@ -660,12 +663,20 @@ function image_style_load($name = NULL, $isid = NULL, $include = NULL) { } /** - * Save an image style. + * Saves an image style. * - * @param style - * An image style array. - * @return - * An image style array. In the case of a new style, 'isid' will be populated. + * @param array $style + * An image style array containing: + * - name: A unique name for the style. + * - isid: (optional) An image style ID. + * + * @return array + * An image style array containing: + * - name: An unique name for the style. + * - old_name: The original name for the style. + * - isid: An image style ID. + * - is_new: TRUE if this is a new style, and FALSE if it is an existing + * style. */ function image_style_save($style) { if (isset($style['isid']) && is_numeric($style['isid'])) { @@ -692,13 +703,14 @@ function image_style_save($style) { } /** - * Delete an image style. + * Deletes an image style. * * @param $style * An image style array. * @param $replacement_style_name * (optional) When deleting a style, specify a replacement style name so * that existing settings (if any) may be converted to a new style. + * * @return * TRUE on success. */ @@ -717,14 +729,17 @@ function image_style_delete($style, $replacement_style_name = '') { } /** - * Load all the effects for an image style. + * Loads all the effects for an image style. * - * @param $style - * An image style array. - * @return + * @param array $style + * An image style array containing: + * - isid: The unique image style ID that contains this image effect. + * + * @return array * An array of image effects associated with specified image style in the * format array('isid' => array()), or an empty array if the specified style * has no effects. + * @see image_effects() */ function image_style_effects($style) { $effects = image_effects(); @@ -739,10 +754,11 @@ function image_style_effects($style) { } /** - * Get an array of image styles suitable for using as select list options. + * Gets an array of image styles suitable for using as select list options. * * @param $include_empty * If TRUE a <none> option will be inserted in the options array. + * * @return * Array of image styles both key and value are set to style name. */ @@ -763,7 +779,7 @@ function image_style_options($include_empty = TRUE) { } /** - * Menu callback; Given a style and image path, generate a derivative. + * Page callback: Generates a derivative, given a style and image path. * * After generating an image, transfer it to the requesting agent. * @@ -931,7 +947,7 @@ function image_style_transform_dimensions($style_name, array &$dimensions) { } /** - * Flush cached media for a style. + * Flushes cached media for a style. * * @param $style * An image style array. @@ -963,12 +979,13 @@ function image_style_flush($style) { } /** - * Return the URL for an image derivative given a style and image path. + * Returns the URL for an image derivative given a style and image path. * * @param $style_name * The name of the style to be used with this image. * @param $path * The path to the image. + * * @return * The absolute URL where a style image can be downloaded, suitable for use * in an <img> tag. Requesting the URL will cause the image to be created. @@ -979,7 +996,7 @@ function image_style_url($style_name, $path) { // The token query is added even if the 'image_allow_insecure_derivatives' // variable is TRUE, so that the emitted links remain valid if it is changed // back to the default FALSE. - $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, $path)); + $token_query = array(IMAGE_DERIVATIVE_TOKEN => image_style_path_token($style_name, file_stream_wrapper_uri_normalize($path))); // If not using clean URLs, the image derivative callback is only available // with the query string. If the file does not exist, use url() to ensure @@ -1017,7 +1034,7 @@ function image_style_path_token($style_name, $uri) { } /** - * Return the URI of an image when using a style. + * Returns the URI of an image when using a style. * * The path returned by this function may not exist. The default generation * method only creates images when they are requested by a user's browser. @@ -1026,6 +1043,7 @@ function image_style_path_token($style_name, $uri) { * The name of the style to be used with this image. * @param $uri * The URI or path to the image. + * * @return * The URI to an image style image. * @see image_style_url() @@ -1043,10 +1061,11 @@ function image_style_path($style_name, $uri) { } /** - * Save a default image style to the database. + * Saves a default image style to the database. * * @param style * An image style array provided by a module. + * * @return * An image style array. The returned style array will include the new 'isid' * assigned to the style. @@ -1064,7 +1083,7 @@ function image_default_style_save($style) { } /** - * Revert the changes made by users to a default image style. + * Reverts the changes made by users to a default image style. * * @param style * An image style array. @@ -1081,7 +1100,10 @@ function image_default_style_revert($style) { } /** - * Pull in image effects exposed by modules implementing hook_image_effect_info(). + * Returns a set of image effects. + * + * These image effects are exposed by modules implementing + * hook_image_effect_info(). * * @return * An array of image effects to be used when transforming images. @@ -1123,7 +1145,7 @@ function image_effect_definitions() { } /** - * Load the definition for an image effect. + * Loads the definition for an image effect. * * The effect definition is a set of core properties for an image effect, not * containing any user-settings. The definition defines various functions to @@ -1135,6 +1157,7 @@ function image_effect_definitions() { * The name of the effect definition to load. * @param $style * An image style array to which this effect will be added. + * * @return * An array containing the image effect definition with the following keys: * - "effect": The unique name for the effect being performed. Usually prefixed @@ -1162,7 +1185,7 @@ function image_effect_definition_load($effect, $style_name = NULL) { } /** - * Load all image effects from the database. + * Loads all image effects from the database. * * @return * An array of all image effects. @@ -1194,7 +1217,7 @@ function image_effects() { } /** - * Load a single image effect. + * Loads a single image effect. * * @param $ieid * The image effect ID. @@ -1203,6 +1226,7 @@ function image_effects() { * @param $include * If set, this loader will restrict to a specific type of image style, may be * one of the defined Image style storage constants. + * * @return * An image effect array, consisting of the following keys: * - "ieid": The unique image effect ID. @@ -1224,10 +1248,11 @@ function image_effect_load($ieid, $style_name, $include = NULL) { } /** - * Save an image effect. + * Saves an image effect. * * @param $effect * An image effect array. + * * @return * An image effect array. In the case of a new effect, 'ieid' will be set. */ @@ -1244,7 +1269,7 @@ function image_effect_save($effect) { } /** - * Delete an image effect. + * Deletes an image effect. * * @param $effect * An image effect array. @@ -1256,12 +1281,13 @@ function image_effect_delete($effect) { } /** - * Given an image object and effect, perform the effect on the file. + * Applies an image effect to the image object. * * @param $image * An image object returned by image_load(). * @param $effect * An image effect array. + * * @return * TRUE on success. FALSE if unable to perform the image effect on the image. */ @@ -1312,7 +1338,7 @@ function theme_image_style($variables) { } /** - * Accept a keyword (center, top, left, etc) and return it as a pixel offset. + * Accepts a keyword (center, top, left, etc) and returns it as a pixel offset. * * @param $value * @param $current_pixels diff --git a/modules/image/image.test b/modules/image/image.test index 25fddf64c..917677046 100644 --- a/modules/image/image.test +++ b/modules/image/image.test @@ -168,9 +168,16 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { } /** + * Test image_style_url() with a file URL that has an extra slash in it. + */ + function testImageStyleUrlExtraSlash() { + $this->_testImageStyleUrlAndPath('public', TRUE, TRUE); + } + + /** * Test image_style_url(). */ - function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE) { + function _testImageStyleUrlAndPath($scheme, $clean_url = TRUE, $extra_slash = FALSE) { // Make the default scheme neither "public" nor "private" to verify the // functions work for other than the default scheme. variable_set('file_default_scheme', 'temporary'); @@ -196,6 +203,15 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->assertFalse(file_exists($generated_uri), t('Generated file does not exist.')); $generate_url = image_style_url($this->style_name, $original_uri); + // Ensure that the tests still pass when the file is generated by accessing + // a poorly constructed (but still valid) file URL that has an extra slash + // in it. + if ($extra_slash) { + $modified_uri = str_replace('://', ':///', $original_uri); + $this->assertNotEqual($original_uri, $modified_uri, 'An extra slash was added to the generated file URI.'); + $generate_url = image_style_url($this->style_name, $modified_uri); + } + if (!$clean_url) { $this->assertTrue(strpos($generate_url, '?q=') !== FALSE, 'When using non-clean URLS, the system path contains the query string.'); } @@ -224,6 +240,12 @@ class ImageStylesPathAndUrlTestCase extends DrupalWebTestCase { $this->drupalGet($generate_url); $this->assertResponse(200, t('Image was generated at the URL.')); + // Make sure that access is denied for existing style files if we do not + // have access. + variable_del('image_module_test_file_download'); + $this->drupalGet($generate_url); + $this->assertResponse(403, 'Confirmed that access is denied for the private image style.'); + // Repeat this with a different file that we do not have access to and // make sure that access is denied. $file_noaccess = array_shift($files); diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index 253535751..f1a71ddfe 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -388,13 +388,13 @@ function locale_languages_edit_form_validate($form, &$form_state) { form_set_error('prefix', t('Domain and path prefix values should not be set at the same time.')); } if (!empty($form_state['values']['domain']) && $duplicate = db_query("SELECT language FROM {languages} WHERE domain = :domain AND language <> :language", array(':domain' => $form_state['values']['domain'], ':language' => $form_state['values']['langcode']))->fetchField()) { - form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate->language))); + form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate))); } if (empty($form_state['values']['prefix']) && language_default('language') != $form_state['values']['langcode'] && empty($form_state['values']['domain'])) { form_set_error('prefix', t('Only the default language can have both the domain and prefix empty.')); } if (!empty($form_state['values']['prefix']) && $duplicate = db_query("SELECT language FROM {languages} WHERE prefix = :prefix AND language <> :language", array(':prefix' => $form_state['values']['prefix'], ':language' => $form_state['values']['langcode']))->fetchField()) { - form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate->language))); + form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate))); } } diff --git a/modules/locale/locale.module b/modules/locale/locale.module index 94e7cd151..b60c53120 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -386,20 +386,53 @@ function locale_form_node_form_alter(&$form, &$form_state) { /** * Form submit handler for node_form(). * - * Checks if Locale is registered as a translation handler and handle possible - * node language changes. - * * This submit handler needs to run before entity_form_submit_build_entity() * is invoked by node_form_submit_build_node(), because it alters the values of * attached fields. Therefore, it cannot be a hook_node_submit() implementation. */ function locale_field_node_form_submit($form, &$form_state) { - if (field_has_translation_handler('node', 'locale')) { - $node = (object) $form_state['values']; - $current_language = entity_language('node', $node); - list(, , $bundle) = entity_extract_ids('node', $node); + locale_field_entity_form_submit('node', $form, $form_state); +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function locale_form_comment_form_alter(&$form, &$form_state, $form_id) { + // If a content type has multilingual support we set the content language as + // comment language. + if ($form['language']['#value'] == LANGUAGE_NONE && locale_multilingual_node_type($form['#node']->type)) { + global $language_content; + $form['language']['#value'] = $language_content->language; + $submit_callback = 'locale_field_comment_form_submit'; + array_unshift($form['actions']['preview']['#submit'], $submit_callback); + array_unshift($form['#submit'], $submit_callback); + } +} - foreach (field_info_instances('node', $bundle) as $instance) { +/** + * Form submit handler for comment_form(). + * + * This submit handler needs to run before entity_form_submit_build_entity() + * is invoked by comment_form_submit_build_comment(), because it alters the + * values of attached fields. + */ +function locale_field_comment_form_submit($form, &$form_state) { + locale_field_entity_form_submit('comment', $form, $form_state); +} + +/** + * Handles field language on submit for the given entity type. + * + * Checks if Locale is registered as a translation handler and handle possible + * language changes. + */ +function locale_field_entity_form_submit($entity_type, $form, &$form_state ) { + if (field_has_translation_handler($entity_type, 'locale')) { + $entity = (object) $form_state['values']; + $current_language = entity_language($entity_type, $entity); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + + foreach (field_info_instances($entity_type, $bundle) as $instance) { $field_name = $instance['field_name']; $field = field_info_field($field_name); $previous_language = $form[$field_name]['#language']; @@ -407,7 +440,7 @@ function locale_field_node_form_submit($form, &$form_state) { // Handle a possible language change: new language values are inserted, // previous ones are deleted. if ($field['translatable'] && $previous_language != $current_language) { - $form_state['values'][$field_name][$current_language] = $node->{$field_name}[$previous_language]; + $form_state['values'][$field_name][$current_language] = $entity->{$field_name}[$previous_language]; $form_state['values'][$field_name][$previous_language] = array(); } } @@ -491,6 +524,9 @@ function locale_field_language_fallback(&$display_language, $entity, $langcode) */ function locale_entity_info_alter(&$entity_info) { $entity_info['node']['translation']['locale'] = TRUE; + if (isset($entity_info['comment'])) { + $entity_info['comment']['translation']['locale'] = TRUE; + } } /** @@ -1060,15 +1096,3 @@ function locale_url_outbound_alter(&$path, &$options, $original_path) { } } } - -/** - * Implements hook_form_FORM_ID_alter(). - */ -function locale_form_comment_form_alter(&$form, &$form_state, $form_id) { - // If a content type has multilingual support we set the content language as - // comment language. - if ($form['language']['#value'] == LANGUAGE_NONE && locale_multilingual_node_type($form['#node']->type)) { - global $language_content; - $form['language']['#value'] = $language_content->language; - } -} diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 632506e13..d88634e19 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -2758,7 +2758,7 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { parent::setUp('locale', 'locale_test'); // Create and login user. - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'create article content')); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'administer comments', 'create article content')); $this->drupalLogin($admin_user); // Add language. @@ -2787,6 +2787,12 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { // French no matter what path prefix the URLs have. $edit = array('language' => 'fr'); $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save')); + + // Make comment body translatable. + $field = field_info_field('comment_body'); + $field['translatable'] = TRUE; + field_update_field($field); + $this->assertTrue(field_is_translatable('comment', $field), 'Comment body is translatable.'); } /** @@ -2817,22 +2823,46 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { foreach (language_list() as $langcode => $language) { // Post a comment with content language $langcode. $prefix = empty($language->prefix) ? '' : $language->prefix . '/'; - $edit = array("comment_body[$language_none][0][value]" => $this->randomName()); - $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save')); + $comment_values[$node_langcode][$langcode] = $this->randomName(); + // Initially field form widgets have no language. + $edit = array( + 'subject' => $this->randomName(), + "comment_body[$language_none][0][value]" => $comment_values[$node_langcode][$langcode], + ); + $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Preview')); + // After the first submit the submitted entity language is taken into + // account. + $edit = array( + 'subject' => $edit['subject'], + "comment_body[$langcode][0][value]" => $comment_values[$node_langcode][$langcode], + ); + $this->drupalPost(NULL, $edit, t('Save')); // Check that comment language matches the current content language. - $comment = db_select('comment', 'c') - ->fields('c') + $cid = db_select('comment', 'c') + ->fields('c', array('cid')) ->condition('nid', $node->nid) ->orderBy('cid', 'DESC') + ->range(0, 1) ->execute() - ->fetchObject(); + ->fetchField(); + $comment = comment_load($cid); $comment_langcode = entity_language('comment', $comment); $args = array('%node_language' => $node_langcode, '%comment_language' => $comment_langcode, '%langcode' => $langcode); $this->assertEqual($comment_langcode, $langcode, t('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args)); + $this->assertEqual($comment->comment_body[$langcode][0]['value'], $comment_values[$node_langcode][$langcode], 'Comment body correctly stored.'); + } + } + + // Check that comment bodies appear in the administration UI. + $this->drupalGet('admin/content/comment'); + foreach ($comment_values as $node_values) { + foreach ($node_values as $value) { + $this->assertRaw($value); } } } + } /** diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php index 3f3818e17..22d93ef3e 100644 --- a/modules/menu/menu.api.php +++ b/modules/menu/menu.api.php @@ -11,7 +11,7 @@ */ /** - * Informs modules that a custom menu was created. + * Respond to a custom menu creation. * * This hook is used to notify modules that a custom menu has been created. * Contributed modules may use the information to perform actions based on the @@ -34,7 +34,7 @@ function hook_menu_insert($menu) { } /** - * Informs modules that a custom menu was updated. + * Respond to a custom menu update. * * This hook is used to notify modules that a custom menu has been updated. * Contributed modules may use the information to perform actions based on the @@ -59,14 +59,14 @@ function hook_menu_update($menu) { } /** - * Informs modules that a custom menu was deleted. + * Respond to a custom menu deletion. * * This hook is used to notify modules that a custom menu along with all links * contained in it (if any) has been deleted. Contributed modules may use the * information to perform actions based on the information entered into the menu * system. * - * @param $link + * @param $menu * An array representing a custom menu: * - menu_name: The unique name of the custom menu. * - title: The human readable menu title. diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc index 72adc3491..4b722ee02 100644 --- a/modules/node/content_types.inc +++ b/modules/node/content_types.inc @@ -2,7 +2,7 @@ /** * @file - * Content type editing UI. + * Content type editing user interface. */ /** @@ -388,8 +388,7 @@ function node_node_type_update($info) { } /** - * Resets all of the relevant fields of a module-defined node type to their - * default values. + * Resets relevant fields of a module-defined node type to their default values. * * @param $type * The node type to reset. The node type is passed back by reference with its @@ -410,6 +409,8 @@ function node_type_reset($type) { /** * Menu callback; delete a single content type. + * + * @ingroup forms */ function node_type_delete_confirm($form, &$form_state, $type) { $form['type'] = array('#type' => 'value', '#value' => $type->type); @@ -430,6 +431,8 @@ function node_type_delete_confirm($form, &$form_state, $type) { /** * Process content type delete confirm submissions. + * + * @see node_type_delete_confirm() */ function node_type_delete_confirm_submit($form, &$form_state) { node_type_delete($form_state['values']['type']); diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc index 1508bc054..be09b37cc 100644 --- a/modules/node/node.admin.inc +++ b/modules/node/node.admin.inc @@ -7,6 +7,10 @@ /** * Menu callback: confirm rebuilding of permissions. + * + * @see node_configure_rebuild_confirm_submit() + * @see node_menu() + * @ingroup forms */ function node_configure_rebuild_confirm() { return confirm_form(array(), t('Are you sure you want to rebuild the permissions on site content?'), @@ -15,6 +19,8 @@ function node_configure_rebuild_confirm() { /** * Handler for wipe confirmation + * + * @see node_configure_rebuild_confirm() */ function node_configure_rebuild_confirm_submit($form, &$form_state) { node_access_rebuild(TRUE); @@ -66,6 +72,9 @@ function node_node_operations() { /** * List node administration filters that can be applied. + * + * @return + * An associative array of filters. */ function node_filters() { // Regular filters @@ -110,7 +119,7 @@ function node_filters() { } /** - * Apply filters for node administration filters based on session. + * Applies filters for node administration filters based on session. * * @param $query * A SelectQuery to which the filters should be applied. @@ -133,7 +142,16 @@ function node_build_filter_query(SelectQueryInterface $query) { } /** - * Return form for node administration filters. + * Returns the node administration filters form array to node_admin_content(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * + * @ingroup forms */ function node_filter_form() { $session = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array(); @@ -208,7 +226,15 @@ function node_filter_form() { } /** - * Process result from node administration filter form. + * Form submission handler for node_filter_form(). + * + * @see node_admin_content() + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_filter_form_submit($form, &$form_state) { $filters = node_filters(); @@ -240,15 +266,15 @@ function node_filter_form_submit($form, &$form_state) { * Make mass update of nodes, changing all nodes in the $nodes array * to update them with the field values in $updates. * - * IMPORTANT NOTE: This function is intended to work when called - * from a form submit handler. Calling it outside of the form submission - * process may not work correctly. + * IMPORTANT NOTE: This function is intended to work when called from a form + * submission handler. Calling it outside of the form submission process may not + * work correctly. * * @param array $nodes * Array of node nids to update. * @param array $updates - * Array of key/value pairs with node field names and the - * value to update that field to. + * Array of key/value pairs with node field names and the value to update that + * field to. */ function node_mass_update($nodes, $updates) { // We use batch processing to prevent timeout when updating a large number @@ -279,7 +305,17 @@ function node_mass_update($nodes, $updates) { } /** - * Node Mass Update - helper function. + * Updates individual nodes when fewer than 10 are queued. + * + * @param $nid + * ID of node to update. + * @param $updates + * Associative array of updates. + * + * @return object + * An updated node object. + * + * @see node_mass_update() */ function _node_mass_update_helper($nid, $updates) { $node = node_load($nid, NULL, TRUE); @@ -293,7 +329,14 @@ function _node_mass_update_helper($nid, $updates) { } /** - * Node Mass Update Batch operation + * Executes a batch operation for node_mass_update(). + * + * @param array $nodes + * An array of node IDs. + * @param array $updates + * Associative array of updates. + * @param array $context + * An array of contextual key/values. */ function _node_mass_update_batch_process($nodes, $updates, &$context) { if (!isset($context['sandbox']['progress'])) { @@ -324,7 +367,15 @@ function _node_mass_update_batch_process($nodes, $updates, &$context) { } /** - * Node Mass Update Batch 'finished' callback. + * Menu callback: Reports the status of batch operation for node_mass_update(). + * + * @param bool $success + * A boolean indicating whether the batch mass update operation successfully + * concluded. + * @param int $results + * The number of nodes updated via the batch mode process. + * @param array $operations + * An array of function calls (not used in this function). */ function _node_mass_update_batch_finished($success, $results, $operations) { if ($success) { @@ -339,7 +390,17 @@ function _node_mass_update_batch_finished($success, $results, $operations) { } /** - * Menu callback: content administration. + * Page callback: Form constructor for the content administration form. + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_menu() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * @ingroup forms */ function node_admin_content($form, $form_state) { if (isset($form_state['values']['operation']) && $form_state['values']['operation'] == 'delete') { @@ -354,6 +415,15 @@ function node_admin_content($form, $form_state) { /** * Form builder: Builds the node administration overview. + * + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() + * + * @ingroup forms */ function node_admin_nodes() { $admin_access = user_access('administer nodes'); @@ -525,8 +595,15 @@ function node_admin_nodes() { /** * Validate node_admin_nodes form submissions. * - * Check if any nodes have been selected to perform the chosen - * 'Update option' on. + * Checks whether any nodes have been selected to perform the chosen 'Update + * option' on. + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_admin_nodes_validate($form, &$form_state) { // Error if there are no items to select. @@ -538,7 +615,14 @@ function node_admin_nodes_validate($form, &$form_state) { /** * Process node_admin_nodes form submissions. * - * Execute the chosen 'Update option' on the selected nodes. + * Executes the chosen 'Update option' on the selected nodes. + * + * @see node_admin_nodes() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + * @see node_multiple_delete_confirm_submit() */ function node_admin_nodes_submit($form, &$form_state) { $operations = module_invoke_all('node_operations'); @@ -564,6 +648,17 @@ function node_admin_nodes_submit($form, &$form_state) { } } +/** + * Multiple node deletion confirmation form for node_admin_content(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm_submit() + * @ingroup forms + */ function node_multiple_delete_confirm($form, &$form_state, $nodes) { $form['nodes'] = array('#prefix' => '<ul>', '#suffix' => '</ul>', '#tree' => TRUE); // array_filter returns only elements with TRUE values @@ -587,6 +682,16 @@ function node_multiple_delete_confirm($form, &$form_state, $nodes) { t('Delete'), t('Cancel')); } +/** + * Form submission handler for node_multiple_delete_confirm(). + * + * @see node_admin_nodes() + * @see node_admin_nodes_submit() + * @see node_admin_nodes_validate() + * @see node_filter_form() + * @see node_filter_form_submit() + * @see node_multiple_delete_confirm() + */ function node_multiple_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { node_delete_multiple(array_keys($form_state['values']['nodes'])); diff --git a/modules/node/node.api.php b/modules/node/node.api.php index 052effc9b..f8dcfdeff 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -11,8 +11,8 @@ * Functions to define and modify content types. * * Each content type is maintained by a primary module, which is either - * node.module (for content types created in the user interface) or the - * module that implements hook_node_info() to define the content type. + * node.module (for content types created in the user interface) or the module + * that implements hook_node_info() to define the content type. * * During node operations (create, update, view, delete, etc.), there are * several sets of hooks that get invoked to allow modules to modify the base @@ -22,10 +22,10 @@ * function prefix. For example, poll.module defines the base for the Poll * content type as "poll", so during creation of a poll node, hook_insert() is * only invoked by calling poll_insert(). - * - All-module hooks: This set of hooks is invoked on all implementing - * modules, to allow other modules to modify what the primary node module is - * doing. For example, hook_node_insert() is invoked on all modules when - * creating a poll node. + * - All-module hooks: This set of hooks is invoked on all implementing modules, + * to allow other modules to modify what the primary node module is doing. For + * example, hook_node_insert() is invoked on all modules when creating a poll + * node. * - Field hooks: Hooks related to the fields attached to the node. These are * invoked from the field operations functions described below, and can be * either field-type-specific or all-module hooks. @@ -56,16 +56,15 @@ * - hook_entity_update() (all) * - hook_node_access_records() (all) * - hook_node_access_records_alter() (all) - * - Loading a node (calling node_load(), node_load_multiple(), or - * entity_load() with $entity_type of 'node'): + * - Loading a node (calling node_load(), node_load_multiple() or entity_load() + * with $entity_type of 'node'): * - Node and revision information is read from database. * - hook_load() (node-type-specific) * - field_attach_load_revision() and field_attach_load() * - hook_entity_load() (all) * - hook_node_load() (all) * - Viewing a single node (calling node_view() - note that the input to - * node_view() is a loaded node, so the Loading steps above are already - * done): + * node_view() is a loaded node, so the Loading steps above are already done): * - hook_view() (node-type-specific) * - field_attach_prepare_view() * - hook_entity_prepare_view() (all) @@ -97,9 +96,8 @@ * - Revision information is deleted from database * - hook_node_revision_delete() (all) * - field_attach_delete_revision() - * - Preparing a node for editing (calling node_form() - note that if it's - * an existing node, it will already be loaded; see the Loading section - * above): + * - Preparing a node for editing (calling node_form() - note that if it is an + * existing node, it will already be loaded; see the Loading section above): * - hook_prepare() (node-type-specific) * - hook_node_prepare() (all) * - hook_form() (node-type-specific) @@ -137,16 +135,16 @@ * associated with permission to view, edit, and delete individual nodes. * * The realms and grant IDs can be arbitrarily defined by your node access - * module; it is common to use role IDs as grant IDs, but that is not - * required. Your module could instead maintain its own list of users, where - * each list has an ID. In that case, the return value of this hook would be - * an array of the list IDs that this user is a member of. + * module; it is common to use role IDs as grant IDs, but that is not required. + * Your module could instead maintain its own list of users, where each list has + * an ID. In that case, the return value of this hook would be an array of the + * list IDs that this user is a member of. * - * A node access module may implement as many realms as necessary to - * properly define the access privileges for the nodes. Note that the system - * makes no distinction between published and unpublished nodes. It is the - * module's responsibility to provide appropriate realms to limit access to - * unpublished content. + * A node access module may implement as many realms as necessary to properly + * define the access privileges for the nodes. Note that the system makes no + * distinction between published and unpublished nodes. It is the module's + * responsibility to provide appropriate realms to limit access to unpublished + * content. * * Node access records are stored in the {node_access} table and define which * grants are required to access a node. There is a special case for the view @@ -183,7 +181,7 @@ * @param $account * The user object whose grants are requested. * @param $op - * The node operation to be performed, such as "view", "update", or "delete". + * The node operation to be performed, such as 'view', 'update', or 'delete'. * * @return * An array whose keys are "realms" of grants, and whose values are arrays of @@ -264,6 +262,7 @@ function hook_node_grants($account, $op) { * @return * An array of grants as defined above. * + * @see hook_node_access_records_alter() * @ingroup node_access */ function hook_node_access_records($node) { @@ -350,12 +349,11 @@ function hook_node_access_records_alter(&$grants, $node) { * Alter user access rules when trying to view, edit or delete a node. * * Node access modules establish rules for user access to content. - * hook_node_grants() defines permissions for a user to view, edit or - * delete nodes by building a $grants array that indicates the permissions - * assigned to the user by each node access module. This hook is called to allow - * modules to modify the $grants array by reference, so the interaction of - * multiple node access modules can be altered or advanced business logic can be - * applied. + * hook_node_grants() defines permissions for a user to view, edit or delete + * nodes by building a $grants array that indicates the permissions assigned to + * the user by each node access module. This hook is called to allow modules to + * modify the $grants array by reference, so the interaction of multiple node + * access modules can be altered or advanced business logic can be applied. * * @see hook_node_grants() * @@ -374,8 +372,8 @@ function hook_node_access_records_alter(&$grants, $node) { * @param $op * The operation being performed, 'view', 'update' or 'delete'. * - * Developers may use this hook to either add additional grants to a user - * or to remove existing grants. These rules are typically based on either the + * Developers may use this hook to either add additional grants to a user or to + * remove existing grants. These rules are typically based on either the * permissions assigned to a user role, or specific attributes of a user * account. * @@ -412,10 +410,10 @@ function hook_node_grants_alter(&$grants, $account, $op) { * @return * An array of operations. Each operation is an associative array that may * contain the following key-value pairs: - * - 'label': Required. The label for the operation, displayed in the dropdown + * - label: (required) The label for the operation, displayed in the dropdown * menu. - * - 'callback': Required. The function to call for the operation. - * - 'callback arguments': Optional. An array of additional arguments to pass + * - callback: (required) The function to call for the operation. + * - callback arguments: (optional) An array of additional arguments to pass * to the callback function. */ function hook_node_operations() { @@ -528,11 +526,10 @@ function hook_node_insert($node) { /** * Act on arbitrary nodes being loaded from the database. * - * This hook should be used to add information that is not in the node or - * node revisions table, not to replace information that is in these tables - * (which could interfere with the entity cache). For performance reasons, - * information for all available nodes should be loaded in a single query where - * possible. + * This hook should be used to add information that is not in the node or node + * revisions table, not to replace information that is in these tables (which + * could interfere with the entity cache). For performance reasons, information + * for all available nodes should be loaded in a single query where possible. * * This hook is invoked during node loading, which is handled by entity_load(), * via classes NodeController and DrupalDefaultEntityController. After the node @@ -572,15 +569,15 @@ function hook_node_load($nodes, $types) { * Modules may implement this hook if they want to have a say in whether or not * a given user has access to perform a given operation on a node. * - * The administrative account (user ID #1) always passes any access check, - * so this hook is not called in that case. Users with the "bypass node access" + * The administrative account (user ID #1) always passes any access check, so + * this hook is not called in that case. Users with the "bypass node access" * permission may always view and edit content through the administrative * interface. * - * Note that not all modules will want to influence access on all - * node types. If your module does not want to actively grant or - * block access, return NODE_ACCESS_IGNORE or simply return nothing. - * Blindly returning FALSE will break other node access modules. + * Note that not all modules will want to influence access on all node types. If + * your module does not want to actively grant or block access, return + * NODE_ACCESS_IGNORE or simply return nothing. Blindly returning FALSE will + * break other node access modules. * * Also note that this function isn't called for node listings (e.g., RSS feeds, * the default home page at path 'node', a recent content block, etc.) See @@ -651,17 +648,17 @@ function hook_node_prepare($node) { /** * Act on a node being displayed as a search result. * - * This hook is invoked from node_search_execute(), after node_load() - * and node_view() have been called. + * This hook is invoked from node_search_execute(), after node_load() and + * node_view() have been called. * * @param $node * The node being displayed in a search result. * * @return array * Extra information to be displayed with search result. This information - * should be presented as an associative array. It will be concatenated - * with the post information (last updated, author) in the default search - * result theming. + * should be presented as an associative array. It will be concatenated with + * the post information (last updated, author) in the default search result + * theming. * * @see template_preprocess_search_result() * @see search-result.tpl.php @@ -724,8 +721,8 @@ function hook_node_update($node) { /** * Act on a node being indexed for searching. * - * This hook is invoked during search indexing, after node_load(), and after - * the result of node_view() is added as $node->rendered to the node object. + * This hook is invoked during search indexing, after node_load(), and after the + * result of node_view() is added as $node->rendered to the node object. * * @param $node * The node being indexed. @@ -756,8 +753,8 @@ function hook_node_update_index($node) { * * Note: Changes made to the $node object within your hook implementation will * have no effect. The preferred method to change a node's content is to use - * hook_node_presave() instead. If it is really necessary to change - * the node at the validate stage, you can use form_set_value(). + * hook_node_presave() instead. If it is really necessary to change the node at + * the validate stage, you can use form_set_value(). * * @param $node * The node being validated. @@ -874,8 +871,8 @@ function hook_node_view_alter(&$build) { * * This hook allows a module to define one or more of its own node types. For * example, the blog module uses it to define a blog node-type named "Blog - * entry." The name and attributes of each desired node type are specified in - * an array returned by the hook. + * entry." The name and attributes of each desired node type are specified in an + * array returned by the hook. * * Only module-provided node types should be defined through this hook. User- * provided (or 'custom') node types should be defined only in the 'node_type' @@ -887,22 +884,22 @@ function hook_node_view_alter(&$build) { * contains a sub-array for each node type, with the machine-readable type * name as the key. Each sub-array has up to 10 attributes. Possible * attributes: - * - "name": the human-readable name of the node type. Required. - * - "base": the base string used to construct callbacks corresponding to - * this node type. - * (i.e. if base is defined as example_foo, then example_foo_insert will - * be called when inserting a node of that type). This string is usually - * the name of the module, but not always. Required. - * - "description": a brief description of the node type. Required. - * - "help": help information shown to the user when creating a node of - * this type.. Optional (defaults to ''). - * - "has_title": boolean indicating whether or not this node type has a title - * field. Optional (defaults to TRUE). - * - "title_label": the label for the title field of this content type. - * Optional (defaults to 'Title'). - * - "locked": boolean indicating whether the administrator can change the - * machine name of this type. FALSE = changeable (not locked), - * TRUE = unchangeable (locked). Optional (defaults to TRUE). + * - name: (required) The human-readable name of the node type. + * - base: (required) The base string used to construct callbacks + * corresponding to this node type (for example, if base is defined as + * example_foo, then example_foo_insert will be called when inserting a node + * of that type). This string is usually the name of the module, but not + * always. + * - description: (required) A brief description of the node type. + * - help: (optional) Help information shown to the user when creating a node + * of this type. + * - has_title: (optional) A Boolean indicating whether or not this node type + * has a title field. + * - title_label: (optional) The label for the title field of this content + * type. + * - locked: (optional) A Boolean indicating whether the administrator can + * change the machine name of this type. FALSE = changeable (not locked), + * TRUE = unchangeable (locked). * * The machine name of a node type should contain only letters, numbers, and * underscores. Underscores will be converted into hyphens for the purpose of @@ -950,20 +947,20 @@ function hook_node_info() { * corresponding to the internal name of the ranking mechanism, such as * 'recent', or 'comments'. The values should be arrays themselves, with the * following keys available: - * - "title": the human readable name of the ranking mechanism. Required. - * - "join": part of a query string to join to any additional necessary - * table. This is not necessary if the table required is already joined to - * by the base query, such as for the {node} table. Other tables should use - * the full table name as an alias to avoid naming collisions. Optional. - * - "score": part of a query string to calculate the score for the ranking - * mechanism based on values in the database. This does not need to be - * wrapped in parentheses, as it will be done automatically; it also does - * not need to take the weighted system into account, as it will be done - * automatically. It does, however, need to calculate a decimal between + * - title: (required) The human readable name of the ranking mechanism. + * - join: (optional) The part of a query string to join to any additional + * necessary table. This is not necessary if the table required is already + * joined to by the base query, such as for the {node} table. Other tables + * should use the full table name as an alias to avoid naming collisions. + * - score: (required) The part of a query string to calculate the score for + * the ranking mechanism based on values in the database. This does not need + * to be wrapped in parentheses, as it will be done automatically; it also + * does not need to take the weighted system into account, as it will be + * done automatically. It does, however, need to calculate a decimal between * 0 and 1; be careful not to cast the entire score to an integer by - * inadvertently introducing a variable argument. Required. - * - "arguments": if any arguments are required for the score, they can be - * specified in an array here. + * inadvertently introducing a variable argument. + * - arguments: (optional) If any arguments are required for the score, they + * can be specified in an array here. * * @ingroup node_api_hooks */ @@ -990,8 +987,8 @@ function hook_ranking() { /** * Respond to node type creation. * - * This hook is invoked from node_type_save() after the node type is added - * to the database. + * This hook is invoked from node_type_save() after the node type is added to + * the database. * * @param $info * The node type object that is being created. @@ -1003,8 +1000,8 @@ function hook_node_type_insert($info) { /** * Respond to node type updates. * - * This hook is invoked from node_type_save() after the node type is updated - * in the database. + * This hook is invoked from node_type_save() after the node type is updated in + * the database. * * @param $info * The node type object that is being updated. @@ -1258,25 +1255,24 @@ function hook_validate($node, $form, &$form_state) { * This hook is invoked only on the module that defines the node's content type * (use hook_node_view() to act on all node views). * - * This hook is invoked during node viewing after the node is fully loaded, - * so that the node type module can define a custom method for display, or - * add to the default display. + * This hook is invoked during node viewing after the node is fully loaded, so + * that the node type module can define a custom method for display, or add to + * the default display. * * @param $node * The node to be displayed, as returned by node_load(). * @param $view_mode * View mode, e.g. 'full', 'teaser', ... * @return - * $node. The passed $node parameter should be modified as necessary and - * returned so it can be properly presented. Nodes are prepared for display - * by assembling a structured array, formatted as in the Form API, in - * $node->content. As with Form API arrays, the #weight property can be - * used to control the relative positions of added elements. After this - * hook is invoked, node_view() calls field_attach_view() to add field - * views to $node->content, and then invokes hook_node_view() and - * hook_node_view_alter(), so if you want to affect the final - * view of the node, you might consider implementing one of these hooks - * instead. + * The passed $node parameter should be modified as necessary and returned so + * it can be properly presented. Nodes are prepared for display by assembling + * a structured array, formatted as in the Form API, in $node->content. As + * with Form API arrays, the #weight property can be used to control the + * relative positions of added elements. After this hook is invoked, + * node_view() calls field_attach_view() to add field views to $node->content, + * and then invokes hook_node_view() and hook_node_view_alter(), so if you + * want to affect the final view of the node, you might consider implementing + * one of these hooks instead. * * @ingroup node_api_hooks */ diff --git a/modules/node/node.install b/modules/node/node.install index 16d3dbaa0..43bfd531f 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -914,6 +914,7 @@ function node_update_7012() { * Change {node}.vid default value from 0 to NULL to avoid deadlock issues on MySQL. */ function node_update_7013() { + db_drop_unique_key('node', 'vid'); db_change_field('node', 'vid', 'vid', array( 'description' => 'The current {node_revision}.vid version identifier.', 'type' => 'int', @@ -921,6 +922,7 @@ function node_update_7013() { 'not null' => FALSE, 'default' => NULL, )); + db_add_unique_key('node', 'vid', array('vid')); } /** diff --git a/modules/node/node.module b/modules/node/node.module index d86c74d2d..abcd4e0c5 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -200,8 +200,8 @@ function node_entity_info() { ), ); - // Search integration is provided by node.module, so search-related - // view modes for nodes are defined here and not in search.module. + // Search integration is provided by node.module, so search-related view modes + // for nodes are defined here and not in search.module. if (module_exists('search')) { $return['node']['view modes'] += array( 'search_index' => array( @@ -244,6 +244,12 @@ function node_field_display_node_alter(&$display, $context) { /** * Entity URI callback. + * + * @param $node + * A node entity. + * + * @return array + * An array with 'path' as the key and the path to the node as its value. */ function node_uri($node) { return array( @@ -296,7 +302,7 @@ function node_title_list($result, $title = NULL) { } /** - * Update the 'last viewed' timestamp of the specified node for current user. + * Updates the 'last viewed' timestamp of the specified node for current user. * * @param $node * A node object. @@ -315,8 +321,14 @@ function node_tag_new($node) { } /** - * Retrieves the timestamp at which the current user last viewed the - * specified node. + * Retrieves the timestamp for the current user's last view of a specified node. + * + * @param $nid + * A node ID. + * + * @return + * If a node has been previously viewed by the user, the timestamp in seconds + * of when the last view occurred; otherwise, zero. */ function node_last_viewed($nid) { global $user; @@ -330,12 +342,13 @@ function node_last_viewed($nid) { } /** - * Decide on the type of marker to be displayed for a given node. + * Determines the type of marker to be displayed for a given node. * * @param $nid * Node ID whose history supplies the "last viewed" timestamp. * @param $timestamp * Time which is compared against node's "last viewed" timestamp. + * * @return * One of the MARK constants. */ @@ -359,7 +372,7 @@ function node_mark($nid, $timestamp) { } /** - * Extract the type name. + * Extracts the type name. * * @param $node * Either a string or object, containing the node type information. @@ -461,6 +474,8 @@ function node_type_get_name($node) { * node_type_save(), and obsolete ones are deleted via a call to * node_type_delete(). See _node_types_build() for an explanation of the new * and obsolete types. + * + * @see _node_types_build() */ function node_types_rebuild() { _node_types_build(TRUE); @@ -483,11 +498,34 @@ function node_type_load($name) { /** * Saves a node type to the database. * - * @param $info - * The node type to save, as an object. - * - * @return - * Status flag indicating outcome of the operation. + * @param object $info + * The node type to save; an object with the following properties: + * - type: A string giving the machine name of the node type. + * - name: A string giving the human-readable name of the node type. + * - base: A string that indicates the base string for hook functions. For + * example, 'node_content' is the value used by the UI when creating a new + * node type. + * - description: A string that describes the node type. + * - help: A string giving the help information shown to the user when + * creating a node of this type. + * - custom: TRUE or FALSE indicating whether this type is defined by a module + * (FALSE) or by a user (TRUE) via Add Content Type. + * - modified: TRUE or FALSE indicating whether this type has been modified by + * an administrator. Currently not used in any way. + * - locked: TRUE or FALSE indicating whether the administrator can change the + * machine name of this type. + * - disabled: TRUE or FALSE indicating whether this type has been disabled. + * - has_title: TRUE or FALSE indicating whether this type uses the node title + * field. + * - title_label: A string containing the label for the title. + * - module: A string giving the module defining this type of node. + * - orig_type: A string giving the original machine-readable name of this + * node type. This may be different from the current type name if the + * 'locked' key is FALSE. + * + * @return int + * A status flag indicating the outcome of the operation, either SAVED_NEW or + * SAVED_UPDATED. */ function node_type_save($info) { $existing_type = !empty($info->old_type) ? $info->old_type : $info->type; @@ -540,7 +578,7 @@ function node_type_save($info) { } /** - * Add default body field to a node type. + * Adds default body field to a node type. * * @param $type * A node type object. @@ -655,6 +693,7 @@ function node_type_update_nodes($old_type, $type) { * * @param $rebuild * TRUE to rebuild node types. Equivalent to calling node_types_rebuild(). + * * @return * An object with two properties: * - names: Associative array of the names of node types, keyed by the type. @@ -761,8 +800,9 @@ function node_type_cache_reset() { * which prevents users from changing the machine name of the type. * * @param $info - * An object or array containing values to override the defaults. See - * hook_node_info() for details on what the array elements mean. + * (optional) An object or array containing values to override the defaults. + * See hook_node_info() for details on what the array elements mean. Defaults + * to an empty array. * * @return * A node type object, with missing values in $info set to their defaults. @@ -845,12 +885,13 @@ function node_rdf_mapping() { } /** - * Determine whether a node hook exists. + * Determines whether a node hook exists. * * @param $node * A node object or a string containing the node type. * @param $hook * A string containing the name of the hook. + * * @return * TRUE if the $hook exists in the node type of $node. */ @@ -860,7 +901,7 @@ function node_hook($node, $hook) { } /** - * Invoke a node hook. + * Invokes a node hook. * * @param $node * A node object or a string containing the node type. @@ -868,6 +909,7 @@ function node_hook($node, $hook) { * A string containing the name of the hook. * @param $a2, $a3, $a4 * Arguments to pass on to the hook, after the $node argument. + * * @return * The returned value of the invoked hook. */ @@ -880,11 +922,11 @@ function node_invoke($node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) { } /** - * Load node entities from the database. + * Loads node entities from the database. * * This function should be used whenever you need to load more than one node - * from the database. Nodes are loaded into memory and will not require - * database access if loaded again during the same page request. + * from the database. Nodes are loaded into memory and will not require database + * access if loaded again during the same page request. * * @see entity_load() * @see EntityFieldQuery @@ -910,7 +952,7 @@ function node_load_multiple($nids = array(), $conditions = array(), $reset = FAL } /** - * Load a node object from the database. + * Loads a node object from the database. * * @param $nid * The node ID. @@ -934,6 +976,9 @@ function node_load($nid = NULL, $vid = NULL, $reset = FALSE) { * * Fills in a few default values, and then invokes hook_prepare() on the node * type module, and hook_node_prepare() on all modules. + * + * @param $node + * A node object. */ function node_object_prepare($node) { // Set up default values, if required. @@ -963,7 +1008,9 @@ function node_object_prepare($node) { } /** - * Perform validation checks on the given node. + * Implements hook_validate(). + * + * Performs validation checks on the given node. */ function node_validate($node, $form, &$form_state) { $type = node_type_get_type($node); @@ -1000,7 +1047,13 @@ function node_validate($node, $form, &$form_state) { } /** - * Prepare node for saving by populating author and creation date. + * Prepares node for saving by populating author and creation date. + * + * @param $node + * A node object. + * + * @return + * An updated node object. */ function node_submit($node) { // A user might assign the node author by entering a user name in the node @@ -1021,7 +1074,7 @@ function node_submit($node) { } /** - * Save changes to a node or add a new node. + * Saves changes to a node or adds a new node. * * @param $node * The $node object to be saved. If $node->nid is @@ -1159,6 +1212,13 @@ function node_save($node) { * Helper function to save a revision with the uid of the current user. * * The resulting revision ID is available afterward in $node->vid. + * + * @param $node + * A node object. + * @param $uid + * The current user's UID. + * @param $update + * (optional) An array of primary keys' field names to update. */ function _node_save_revision($node, $uid, $update = NULL) { $temp_uid = $node->uid; @@ -1174,7 +1234,7 @@ function _node_save_revision($node, $uid, $update = NULL) { } /** - * Delete a node. + * Deletes a node. * * @param $nid * A node ID. @@ -1184,7 +1244,7 @@ function node_delete($nid) { } /** - * Delete multiple nodes. + * Deletes multiple nodes. * * @param $nids * An array of node IDs. @@ -1237,7 +1297,7 @@ function node_delete_multiple($nids) { } /** - * Delete a node revision. + * Deletes a node revision. * * @param $revision_id * The revision ID to delete. @@ -1262,7 +1322,7 @@ function node_revision_delete($revision_id) { } /** - * Generate an array for rendering the given node. + * Generates an array for rendering the given node. * * @param $node * A node object. @@ -1367,8 +1427,8 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) { entity_prepare_view('node', array($node->nid => $node), $langcode); $node->content += field_attach_view('node', $node, $view_mode, $langcode); - // Always display a read more link on teasers because we have no way - // to know when a teaser view is different than a full view. + // Always display a read more link on teasers because we have no way to know + // when a teaser view is different than a full view. $links = array(); $node->content['links'] = array( '#theme' => 'links__node', @@ -1400,12 +1460,13 @@ function node_build_content($node, $view_mode = 'full', $langcode = NULL) { } /** - * Generate an array which displays a node detail page. + * Generates an array which displays a node detail page. * * @param $node * A node object. * @param $message * A flag which sets a page title relevant to the revision being viewed. + * * @return * A $page element suitable for use by drupal_render(). */ @@ -1428,6 +1489,9 @@ function node_show($node, $message = FALSE) { * * @param $node * A node object. + * + * @return + * The ID of the node if this is a full page view, otherwise FALSE. */ function node_is_page($node) { $page_node = menu_get_object(); @@ -1435,7 +1499,7 @@ function node_is_page($node) { } /** - * Process variables for node.tpl.php + * Processes variables for node.tpl.php * * Most themes utilize their own copy of node.tpl.php. The default is located * inside "modules/node/node.tpl.php". Look in there for the full list of @@ -1557,7 +1621,7 @@ function node_permission() { } /** - * Gather the rankings from the the hook_ranking implementations. + * Gathers the rankings from the the hook_ranking() implementations. * * @param $query * A query object that has been extended with the Search DB Extender. @@ -1804,6 +1868,7 @@ function node_user_delete($account) { * An associative array containing: * - form: A render element representing the form. * + * @see node_search_admin() * @ingroup themeable */ function theme_node_search_admin($variables) { @@ -1872,11 +1937,11 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { $node_current_revision = node_load($node->nid); $is_current_revision = $node_current_revision->vid == $node->vid; - // There should be at least two revisions. If the vid of the given node - // and the vid of the current revision differ, then we already have two + // There should be at least two revisions. If the vid of the given node and + // the vid of the current revision differ, then we already have two // different revisions so there is no need for a separate database check. - // Also, if you try to revert to or delete the current revision, that's - // not good. + // Also, if you try to revert to or delete the current revision, that's not + // good. if ($is_current_revision && (db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() == 1 || $op == 'update' || $op == 'delete')) { $access[$cid] = FALSE; } @@ -1884,8 +1949,8 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { $access[$cid] = TRUE; } else { - // First check the access to the current revision and finally, if the - // node passed in is not the current revision then access to that, too. + // First check the access to the current revision and finally, if the node + // passed in is not the current revision then access to that, too. $access[$cid] = node_access($op, $node_current_revision, $account) && ($is_current_revision || node_access($op, $node, $account)); } } @@ -1893,6 +1958,14 @@ function _node_revision_access($node, $op = 'view', $account = NULL) { return $access[$cid]; } +/** + * Access callback: Checks whether the user has permission to add a node. + * + * @return + * TRUE if the user has add permission, otherwise FALSE. + * + * @see node_menu() + */ function _node_add_access() { $types = node_type_get_types(); foreach ($types as $type) { @@ -2110,14 +2183,30 @@ function node_menu_local_tasks_alter(&$data, $router_item, $root_path) { } /** - * Title callback for a node type. + * Title callback: Returns the unsanitized title of the node type edit form. + * + * @param $type + * The node type object. + * + * @return string + * An unsanitized string that is the title of the node type edit form. + * + * @see node_menu() */ function node_type_page_title($type) { return $type->name; } /** - * Title callback. + * Title callback: Returns the title of the node. + * + * @param $node + * The node object. + * + * @return + * An unsanitized string that is the title of the node. + * + * @see node_menu() */ function node_page_title($node) { return $node->title; @@ -2137,7 +2226,13 @@ function node_last_changed($nid) { } /** - * Return a list of all the existing revision numbers. + * Returns a list of all the existing revision numbers. + * + * @param Drupal\node\Node $node + * The node entity. + * + * @return + * An associative array keyed by node revision number. */ function node_revision_list($node) { $revisions = array(); @@ -2223,16 +2318,16 @@ function node_block_save($delta = '', $edit = array()) { * (optional) The maximum number of nodes to find. Defaults to 10. * * @return - * An array of partial node objects or an empty array if there are no recent - * nodes visible to the current user. + * An array of node entities or an empty array if there are no recent nodes + * visible to the current user. */ function node_get_recent($number = 10) { $query = db_select('node', 'n'); if (!user_access('bypass node access')) { - // If the user is able to view their own unpublished nodes, allow them - // to see these in addition to published nodes. Check that they actually - // have some unpublished nodes to view before adding the condition. + // If the user is able to view their own unpublished nodes, allow them to + // see these in addition to published nodes. Check that they actually have + // some unpublished nodes to view before adding the condition. if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) { $query->condition(db_or() ->condition('n.status', NODE_PUBLISHED) @@ -2362,7 +2457,7 @@ function node_form_block_admin_configure_alter(&$form, &$form_state) { } /** - * Form submit handler for block configuration form. + * Form submission handler for node_form_block_admin_configure_alter(). * * @see node_form_block_admin_configure_alter() */ @@ -2394,7 +2489,7 @@ function node_form_block_custom_block_delete_alter(&$form, &$form_state) { } /** - * Form submit handler for custom block delete form. + * Form submission handler for node_form_block_custom_block_delete_alter(). * * @see node_form_block_custom_block_delete_alter() */ @@ -2419,8 +2514,8 @@ function node_modules_uninstalled($modules) { /** * Implements hook_block_list_alter(). * - * Check the content type specific visibilty settings. - * Remove the block if the visibility conditions are not met. + * Check the content type specific visibilty settings. Remove the block if the + * visibility conditions are not met. */ function node_block_list_alter(&$blocks) { global $theme_key; @@ -2485,7 +2580,8 @@ function node_block_list_alter(&$blocks) { * @param $channel * An associative array containing title, link, description and other keys, * to be parsed by format_rss_channel() and format_xml_elements(). - * A list of channel elements can be found at the @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink + * A list of channel elements can be found at the + * @link http://cyber.law.harvard.edu/rss/rss.html RSS 2.0 Specification. @endlink * The link should be an absolute URL. */ function node_feed($nids = FALSE, $channel = array()) { @@ -2559,7 +2655,7 @@ function node_feed($nids = FALSE, $channel = array()) { } /** - * Construct a drupal_render() style array from an array of loaded nodes. + * Constructs a drupal_render() style array from an array of loaded nodes. * * @param $nodes * An array of nodes as returned by node_load_multiple(). @@ -2568,8 +2664,8 @@ function node_feed($nids = FALSE, $channel = array()) { * @param $weight * An integer representing the weight of the first node in the list. * @param $langcode - * (optional) A language code to use for rendering. Defaults to the global - * content language of the current request. + * (optional) A language code to use for rendering. Defaults to NULL which is + * the global content language of the current request. * * @return * An array in the format expected by drupal_render(). @@ -2588,7 +2684,12 @@ function node_view_multiple($nodes, $view_mode = 'teaser', $weight = 0, $langcod } /** - * Menu callback; Generate a listing of promoted nodes. + * Menu callback: Generates a listing of promoted nodes. + * + * @return array + * An array in the format expected by drupal_render(). + * + * @see node_menu() */ function node_page_default() { $select = db_select('node', 'n') @@ -2638,7 +2739,15 @@ function node_page_default() { } /** - * Menu callback; view a single node. + * Menu callback: Displays a single node. + * + * @param $node + * The node object. + * + * @return + * A page array suitable for use by drupal_render(). + * + * @see node_menu() */ function node_page_view($node) { // If there is a menu link to this node, the link becomes the last part @@ -2667,7 +2776,7 @@ function node_update_index() { } /** - * Index a single node. + * Indexes a single node. * * @param $node * The node to index. @@ -2771,7 +2880,7 @@ function node_form_search_form_alter(&$form, $form_state) { } /** - * Form API callback for the search form. Registered in node_form_alter(). + * Form validation handler for node_form_alter(). */ function node_search_validate($form, &$form_state) { // Initialize using any existing basic search keywords. @@ -2819,8 +2928,8 @@ function node_search_validate($form, &$form_state) { * @{ * The node access system determines who can do what to which nodes. * - * In determining access rights for a node, node_access() first checks - * whether the user has the "bypass node access" permission. Such users have + * In determining access rights for a node, node_access() first checks whether + * the user has the "bypass node access" permission. Such users have * unrestricted access to all nodes. user 1 will always pass this check. * * Next, all implementations of hook_node_access() will be called. Each @@ -2858,8 +2967,7 @@ function node_search_validate($form, &$form_state) { */ /** - * Determine whether the current user may perform the given operation on the - * specified node. + * Determines whether the current user may perform the operation on the node. * * @param $op * The operation to be performed on the node. Possible values are: @@ -2873,6 +2981,7 @@ function node_search_validate($form, &$form_state) { * @param $account * Optional, a user object representing the user for whom the operation is to * be performed. Determines access for a user other than the current user. + * * @return * TRUE if the operation may be performed, FALSE otherwise. */ @@ -3005,6 +3114,7 @@ function node_node_access($node, $op, $account) { * * @param $type * The machine-readable name of the node type. + * * @return array * An array of permission names and descriptions. */ @@ -3038,11 +3148,11 @@ function node_list_permissions($type) { * * By default, this will include all node types in the system. To exclude a * specific node from getting permissions defined for it, set the - * node_permissions_$type variable to 0. Core does not provide an interface - * for doing so, however, contrib modules may exclude their own nodes in + * node_permissions_$type variable to 0. Core does not provide an interface for + * doing so. However, contrib modules may exclude their own nodes in * hook_install(). Alternatively, contrib modules may configure all node types - * at once, or decide to apply some other hook_node_access() implementation - * to some or all node types. + * at once, or decide to apply some other hook_node_access() implementation to + * some or all node types. * * @return * An array of node types managed by this module. @@ -3061,21 +3171,22 @@ function node_permissions_get_configured_types() { } /** - * Fetch an array of permission IDs granted to the given user ID. + * Fetches an array of permission IDs granted to the given user ID. * * The implementation here provides only the universal "all" grant. A node - * access module should implement hook_node_grants() to provide a grant - * list for the user. + * access module should implement hook_node_grants() to provide a grant list for + * the user. * - * After the default grants have been loaded, we allow modules to alter - * the grants array by reference. This hook allows for complex business - * logic to be applied when integrating multiple node access modules. + * After the default grants have been loaded, we allow modules to alter the + * grants array by reference. This hook allows for complex business logic to be + * applied when integrating multiple node access modules. * * @param $op * The operation that the user is trying to perform. * @param $account * The user object for the user performing the operation. If omitted, the * current user is used. + * * @return * An associative array in which the keys are realms, and the values are * arrays of grants for those realms. @@ -3163,11 +3274,10 @@ function node_access_view_all_nodes($account = NULL) { /** * Implements hook_query_TAG_alter(). * - * This is the hook_query_alter() for queries tagged with 'node_access'. - * It adds node access checks for the user account given by the 'account' - * meta-data (or global $user if not provided), for an operation given by - * the 'op' meta-data (or 'view' if not provided; other possible values are - * 'update' and 'delete'). + * This is the hook_query_alter() for queries tagged with 'node_access'. It adds + * node access checks for the user account given by the 'account' meta-data (or + * global $user if not provided), for an operation given by the 'op' meta-data + * (or 'view' if not provided; other possible values are 'update' and 'delete'). */ function node_query_node_access_alter(QueryAlterableInterface $query) { _node_query_node_access_alter($query, 'node'); @@ -3206,8 +3316,8 @@ function _node_query_node_access_alter($query, $type) { } // If $account can bypass node access, or there are no node access modules, - // or the operation is 'view' and the $acount has a global view grant (i.e., - // a view grant for node ID 0), we don't need to alter the query. + // or the operation is 'view' and the $account has a global view grant + // (such as a view grant for node ID 0), we don't need to alter the query. if (user_access('bypass node access', $account)) { return; } @@ -3394,15 +3504,14 @@ function node_access_acquire_grants($node, $delete = TRUE) { * * If a realm is provided, it will only delete grants from that realm, but it * will always delete a grant from the 'all' realm. Modules that utilize - * node_access can use this function when doing mass updates due to widespread + * node_access() can use this function when doing mass updates due to widespread * permission changes. * * Note: Don't call this function directly from a contributed module. Call * node_access_acquire_grants() instead. * * @param $node - * The $node being written to. All that is necessary is that it contains a - * nid. + * The node whose grants are being written. * @param $grants * A list of grants to write. Each grant is an array that must contain the * following keys: realm, gid, grant_view, grant_update, grant_delete. @@ -3410,10 +3519,14 @@ function node_access_acquire_grants($node, $delete = TRUE) { * is a module-defined id to define grant privileges. each grant_* field * is a boolean value. * @param $realm - * If provided, only read/write grants for that realm. + * (optional) If provided, read/write grants for that realm only. Defaults to + * NULL. * @param $delete - * If false, do not delete records. This is only for optimization purposes, - * and assumes the caller has already performed a mass delete of some form. + * (optional) If false, does not delete records. This is only for optimization + * purposes, and assumes the caller has already performed a mass delete of + * some form. Defaults to TRUE. + * + * @see node_access_acquire_grants() */ function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) { if ($delete) { @@ -3442,21 +3555,23 @@ function node_access_write_grants($node, $grants, $realm = NULL, $delete = TRUE) } /** - * Flag / unflag the node access grants for rebuilding, or read the current - * value of the flag. + * Flags or unflags the node access grants for rebuilding. * + * If the argument isn't specified, the current value of the flag is returned. * When the flag is set, a message is displayed to users with 'access * administration pages' permission, pointing to the 'rebuild' confirm form. * This can be used as an alternative to direct node_access_rebuild calls, * allowing administrators to decide when they want to perform the actual - * (possibly time consuming) rebuild. - * When unsure the current user is an administrator, node_access_rebuild - * should be used instead. + * (possibly time consuming) rebuild. When unsure if the current user is an + * administrator, node_access_rebuild() should be used instead. * * @param $rebuild * (Optional) The boolean value to be written. - * @return - * (If no value was provided for $rebuild) The current value of the flag. + * + * @return + * The current value of the flag if no value was provided for $rebuild. + * + * @see node_access_rebuild() */ function node_access_needs_rebuild($rebuild = NULL) { if (!isset($rebuild)) { @@ -3471,15 +3586,15 @@ function node_access_needs_rebuild($rebuild = NULL) { } /** - * Rebuild the node access database. This is occasionally needed by modules - * that make system-wide changes to access levels. + * Rebuilds the node access database. * - * When the rebuild is required by an admin-triggered action (e.g module - * settings form), calling node_access_needs_rebuild(TRUE) instead of + * This is occasionally needed by modules that make system-wide changes to + * access levels. When the rebuild is required by an admin-triggered action (e.g + * module settings form), calling node_access_needs_rebuild(TRUE) instead of * node_access_rebuild() lets the user perform his changes and actually * rebuild only once he is done. * - * Note : As of Drupal 6, node access modules are not required to (and actually + * Note: As of Drupal 6, node access modules are not required to (and actually * should not) call node_access_rebuild() in hook_enable/disable anymore. * * @see node_access_needs_rebuild() @@ -3543,11 +3658,14 @@ function node_access_rebuild($batch_mode = FALSE) { } /** - * Batch operation for node_access_rebuild_batch. + * Performs batch operation for node_access_rebuild(). + * + * This is a multistep operation: we go through all nodes by packs of 20. The + * batch processing engine interrupts processing and sends progress feedback + * after 1 second execution time. * - * This is a multistep operation : we go through all nodes by packs of 20. - * The batch processing engine interrupts processing and sends progress - * feedback after 1 second execution time. + * @param array $context + * An array of contextual key/value information for rebuild batch process. */ function _node_access_rebuild_batch_operation(&$context) { if (empty($context['sandbox'])) { @@ -3578,7 +3696,14 @@ function _node_access_rebuild_batch_operation(&$context) { } /** - * Post-processing for node_access_rebuild_batch. + * Performs post-processing for node_access_rebuild(). + * + * @param bool $success + * A boolean indicating whether the re-build process has completed. + * @param array $results + * An array of results information. + * @param array $operations + * An array of function calls (not used in this function). */ function _node_access_rebuild_batch_finished($success, $results, $operations) { if ($success) { @@ -3595,7 +3720,6 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { * @} End of "defgroup node_access". */ - /** * @defgroup node_content Hook implementations for user-created content types * @{ @@ -3631,6 +3755,7 @@ function node_content_form($node, $form_state) { /** * Implements hook_forms(). + * * All node forms share the same form handler. */ function node_forms() { @@ -3715,6 +3840,12 @@ function node_action_info() { /** * Sets the status of a node to 1 (published). * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_publish_action($node, $context = array()) { @@ -3725,6 +3856,12 @@ function node_publish_action($node, $context = array()) { /** * Sets the status of a node to 0 (unpublished). * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_unpublish_action($node, $context = array()) { @@ -3735,6 +3872,12 @@ function node_unpublish_action($node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 1. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_make_sticky_action($node, $context = array()) { @@ -3745,6 +3888,12 @@ function node_make_sticky_action($node, $context = array()) { /** * Sets the sticky-at-top-of-list property of a node to 0. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_make_unsticky_action($node, $context = array()) { @@ -3755,6 +3904,12 @@ function node_make_unsticky_action($node, $context = array()) { /** * Sets the promote property of a node to 1. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_promote_action($node, $context = array()) { @@ -3765,6 +3920,12 @@ function node_promote_action($node, $context = array()) { /** * Sets the promote property of a node to 0. * + * @param $node + * A node object. + * @param $context + * (optional) Array of additional information about what triggered the action. + * Not used for this action. + * * @ingroup actions */ function node_unpromote_action($node, $context = array()) { @@ -3775,6 +3936,9 @@ function node_unpromote_action($node, $context = array()) { /** * Saves a node. * + * @param $node + * The node to be saved. + * * @ingroup actions */ function node_save_action($node) { @@ -3791,6 +3955,9 @@ function node_save_action($node) { * Array with the following elements: * - 'owner_uid': User ID to assign to the node. * + * @see node_assign_owner_action_form() + * @see node_assign_owner_action_validate() + * @see node_assign_owner_action_submit() * @ingroup actions */ function node_assign_owner_action($node, $context) { @@ -3801,6 +3968,16 @@ function node_assign_owner_action($node, $context) { /** * Generates the settings form for node_assign_owner_action(). + * + * @param $context + * Array of additional information about what triggered the action. Includes + * the following elements: + * - 'owner_uid': User ID to assign to the node. + * + * @see node_assign_owner_action_submit() + * @see node_assign_owner_action_validate() + * + * @ingroup forms */ function node_assign_owner_action_form($context) { $description = t('The username of the user to which you would like to assign ownership.'); @@ -3841,6 +4018,8 @@ function node_assign_owner_action_form($context) { /** * Validates settings form for node_assign_owner_action(). + * + * @see node_assign_owner_action_submit() */ function node_assign_owner_action_validate($form, $form_state) { $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField(); @@ -3851,6 +4030,8 @@ function node_assign_owner_action_validate($form, $form_state) { /** * Saves settings form for node_assign_owner_action(). + * + * @see node_assign_owner_action_validate() */ function node_assign_owner_action_submit($form, $form_state) { // Username can change, so we need to store the ID, not the username. @@ -3860,6 +4041,14 @@ function node_assign_owner_action_submit($form, $form_state) { /** * Generates settings form for node_unpublish_by_keyword_action(). + * + * @param array $context + * Array of additional information about what triggered this action. + * + * @return array + * A form array. + * + * @see node_unpublish_by_keyword_action_submit() */ function node_unpublish_by_keyword_action_form($context) { $form['keywords'] = array( diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index c6cb1bcc2..dee92e16c 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -5,7 +5,6 @@ * Page callbacks for adding, editing, deleting, and revisions management for content. */ - /** * Menu callback; presents the node editing form. */ @@ -63,6 +62,12 @@ function theme_node_add_list($variables) { /** * Returns a node submission form. + * + * @param $type + * The node type for the submitted node. + * + * @return + * The themed form. */ function node_add($type) { global $user; @@ -75,6 +80,12 @@ function node_add($type) { return $output; } +/** + * Form validation handler for node_form(). + * + * @see node_form() + * @see node_form_submit() + */ function node_form_validate($form, &$form_state) { // $form_state['node'] contains the actual entity being edited, but we must // not update it with form values that have not yet been validated, so we @@ -85,7 +96,13 @@ function node_form_validate($form, &$form_state) { } /** - * Generate the node add/edit form array. + * Form constructor for the node add/edit form. + * + * @see node_form_validate() + * @see node_form_submit() + * @see node_form_build_preview() + * @see node_form_delete_submit() + * @ingroup forms */ function node_form($form, &$form_state, $node) { global $user; @@ -311,7 +328,12 @@ function node_form($form, &$form_state, $node) { } /** - * Button submit function: handle the 'Delete' button on the node form. + * Form submission handler for node_form(). + * + * Handles the 'Delete' button on the node form. + * + * @see node_form() + * @see node_form_validate() */ function node_form_delete_submit($form, &$form_state) { $destination = array(); @@ -323,7 +345,14 @@ function node_form_delete_submit($form, &$form_state) { $form_state['redirect'] = array('node/' . $node->nid . '/delete', array('query' => $destination)); } - +/** + * Form submission handler for node_form(). + * + * Handles the 'Preview' button on the node form. + * + * @see node_form() + * @see node_form_validate() + */ function node_form_build_preview($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); $form_state['node_preview'] = node_preview($node); @@ -331,7 +360,15 @@ function node_form_build_preview($form, &$form_state) { } /** - * Generate a node preview. + * Generates a node preview. + * + * @param $node + * The node to preview. + * + * @return + * An HTML-formatted string of a node preview. + * + * @see node_form_build_preview() */ function node_preview($node) { if (node_access('create', $node) || node_access('update', $node)) { @@ -377,6 +414,7 @@ function node_preview($node) { * An associative array containing: * - node: The node object which is being previewed. * + * @see node_preview() * @ingroup themeable */ function theme_node_preview($variables) { @@ -407,6 +445,12 @@ function theme_node_preview($variables) { return $output; } +/** + * Form submission handler for node_form(). + * + * @see node_form() + * @see node_form_validate() + */ function node_form_submit($form, &$form_state) { $node = node_form_submit_build_node($form, $form_state); $insert = empty($node->nid); @@ -472,7 +516,9 @@ function node_form_submit_build_node($form, &$form_state) { } /** - * Menu callback -- ask for confirmation of node deletion + * Form constructor for the node deletion confirmation form. + * + * @see node_delete_confirm_submit() */ function node_delete_confirm($form, &$form_state, $node) { $form['#node'] = $node; @@ -488,7 +534,9 @@ function node_delete_confirm($form, &$form_state, $node) { } /** - * Execute node deletion + * Executes node deletion. + * + * @see node_delete_confirm() */ function node_delete_confirm_submit($form, &$form_state) { if ($form_state['values']['confirm']) { @@ -502,7 +550,15 @@ function node_delete_confirm_submit($form, &$form_state) { } /** - * Generate an overview table of older revisions of a node. + * Generates an overview table of older revisions of a node. + * + * @param $node + * A node object. + * + * @return array + * An array as expected by drupal_render(). + * + * @see node_menu() */ function node_revision_overview($node) { drupal_set_title(t('Revisions for %title', array('%title' => $node->title)), PASS_THROUGH); @@ -553,13 +609,26 @@ function node_revision_overview($node) { } /** - * Ask for confirmation of the reversion to prevent against CSRF attacks. + * Asks for confirmation of the reversion to prevent against CSRF attacks. + * + * @param int $node_revision + * The node revision ID. + * + * @return array + * An array as expected by drupal_render(). + * + * @see node_menu() + * @see node_revision_revert_confirm_submit() + * @ingroup forms */ function node_revision_revert_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel')); } +/** + * Form submission handler for node_revision_revert_confirm(). + */ function node_revision_revert_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; $node_revision->revision = 1; @@ -572,11 +641,29 @@ function node_revision_revert_confirm_submit($form, &$form_state) { $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions'; } +/** + * Form constructor for the revision deletion confirmation form. + * + * This form prevents against CSRF attacks. + * + * @param $node_revision + * The node revision ID. + * + * @return + * An array as expected by drupal_render(). + * + * @see node_menu() + * @see node_revision_delete_confirm_submit() + * @ingroup forms + */ function node_revision_delete_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel')); } +/** + * Form submission handler for node_revision_delete_confirm(). + */ function node_revision_delete_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; node_revision_delete($node_revision->vid); diff --git a/modules/node/node.test b/modules/node/node.test index d789d3c0e..2180f5838 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -149,6 +149,9 @@ class NodeLoadHooksTestCase extends DrupalWebTestCase { } } +/** + * Tests the node revision functionality. + */ class NodeRevisionsTestCase extends DrupalWebTestCase { protected $nodes; protected $logs; @@ -198,7 +201,7 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { } /** - * Check node revision related operations. + * Checks node revision related operations. */ function testRevisions() { $nodes = $this->nodes; @@ -282,6 +285,9 @@ class NodeRevisionsTestCase extends DrupalWebTestCase { } } +/** + * Tests the node edit functionality. + */ class PageEditTestCase extends DrupalWebTestCase { protected $web_user; protected $admin_user; @@ -302,7 +308,7 @@ class PageEditTestCase extends DrupalWebTestCase { } /** - * Check node edit functionality. + * Checks node edit functionality. */ function testPageEdit() { $this->drupalLogin($this->web_user); @@ -369,7 +375,7 @@ class PageEditTestCase extends DrupalWebTestCase { } /** - * Check changing node authored by fields. + * Tests changing a node's "authored by" field. */ function testPageAuthoredBy() { $this->drupalLogin($this->admin_user); @@ -414,6 +420,9 @@ class PageEditTestCase extends DrupalWebTestCase { } } +/** + * Tests the node entity preview functionality. + */ class PagePreviewTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -431,7 +440,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { } /** - * Check the node preview functionality. + * Checks the node preview functionality. */ function testPagePreview() { $langcode = LANGUAGE_NONE; @@ -455,7 +464,7 @@ class PagePreviewTestCase extends DrupalWebTestCase { } /** - * Check the node preview functionality, when using revisions. + * Checks the node preview functionality, when using revisions. */ function testPagePreviewWithRevisions() { $langcode = LANGUAGE_NONE; @@ -485,6 +494,9 @@ class PagePreviewTestCase extends DrupalWebTestCase { } } +/** + * Tests creating and saving a node. + */ class NodeCreationTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -503,7 +515,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { } /** - * Create a "Basic page" node and verify its consistency in the database. + * Creates a "Basic page" node and verifies its consistency in the database. */ function testNodeCreation() { // Create a node. @@ -522,7 +534,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { } /** - * Create a page node and verify that a transaction rolls back the failed creation + * Verifies that a transaction rolls back the failed creation. */ function testFailedPageCreation() { // Create a node. @@ -563,6 +575,9 @@ class NodeCreationTestCase extends DrupalWebTestCase { } } +/** + * Tests the functionality of node entity edit permissions. + */ class PageViewTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -573,7 +588,7 @@ class PageViewTestCase extends DrupalWebTestCase { } /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + * Tests an anonymous and unpermissioned user attempting to edit the node. */ function testPageView() { // Create a node to view. @@ -602,6 +617,9 @@ class PageViewTestCase extends DrupalWebTestCase { } } +/** + * Tests the summary length functionality. + */ class SummaryLengthTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -612,7 +630,7 @@ class SummaryLengthTestCase extends DrupalWebTestCase { } /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + * Tests the node summary length functionality. */ function testSummaryLength() { // Create a node to view. @@ -644,6 +662,9 @@ class SummaryLengthTestCase extends DrupalWebTestCase { } } +/** + * Tests XSS functionality with a node entity. + */ class NodeTitleXSSTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -653,6 +674,9 @@ class NodeTitleXSSTestCase extends DrupalWebTestCase { ); } + /** + * Tests XSS functionality with a node entity. + */ function testNodeTitleXSS() { // Prepare a user to do the stuff. $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content')); @@ -678,6 +702,9 @@ class NodeTitleXSSTestCase extends DrupalWebTestCase { } } +/** + * Tests the availability of the syndicate block. + */ class NodeBlockTestCase extends DrupalWebTestCase { public static function getInfo() { return array( @@ -709,7 +736,7 @@ class NodeBlockTestCase extends DrupalWebTestCase { } /** - * Check that the post information displays when enabled for a content type. + * Checks that the post information displays when enabled for a content type. */ class NodePostSettingsTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -728,7 +755,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Set "Basic page" content type to display post information and confirm its presence on a new node. + * Confirms "Basic page" content type and post information is on a new node. */ function testPagePostInfo() { @@ -751,7 +778,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Set "Basic page" content type to not display post information and confirm its absence on a new node. + * Confirms absence of post information on a new node. */ function testPageNotPostInfo() { @@ -774,7 +801,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { } /** - * Ensure that data added to nodes by other modules appears in RSS feeds. + * Ensures that data added to nodes by other modules appears in RSS feeds. * * Create a node, enable the node_test module to ensure that extra data is * added to the node->content array, then verify that the data appears on the @@ -801,8 +828,7 @@ class NodeRSSContentTestCase extends DrupalWebTestCase { } /** - * Create a new node and ensure that it includes the custom data when added - * to an RSS feed. + * Ensures that a new node includes the custom data when added to an RSS feed. */ function testNodeRSSContent() { // Create a node. @@ -841,9 +867,11 @@ class NodeRSSContentTestCase extends DrupalWebTestCase { } /** - * Test case to verify basic node_access functionality. + * Tests basic node_access functionality. + * + * Note that hook_node_access_records() is covered in another test class. + * * @todo Cover hook_node_access in a separate test class. - * hook_node_access_records is covered in another test class. */ class NodeAccessTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -855,7 +883,7 @@ class NodeAccessTestCase extends DrupalWebTestCase { } /** - * Asserts node_access correctly grants or denies access. + * Asserts node_access() correctly grants or denies access. */ function assertNodeAccess($ops, $node, $account) { foreach ($ops as $op => $result) { @@ -910,7 +938,7 @@ class NodeAccessTestCase extends DrupalWebTestCase { } /** - * Test case to verify hook_node_access_records functionality. + * Tests hook_node_access_records() functionality. */ class NodeAccessRecordsTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -929,7 +957,7 @@ class NodeAccessRecordsTestCase extends DrupalWebTestCase { } /** - * Create a node and test the creation of node access rules. + * Creates a node and tests the creation of node access rules. */ function testNodeAccessRecords() { // Create an article node. @@ -1005,9 +1033,6 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { ); } - /** - * Enable modules and create user with specific permissions. - */ public function setUp() { parent::setUp('node_access_test'); node_access_rebuild(); @@ -1015,7 +1040,7 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { } /** - * Test the "private" node access. + * Tests the "private" node access functionality. * * - Create 2 users with "access content" and "create article" permissions. * - Each user creates one private and one not private article. @@ -1152,7 +1177,7 @@ class NodeAccessBaseTableTestCase extends DrupalWebTestCase { } /** - * Test case to check node save related functionality, including import-save + * Tests node save related functionality, including import-save. */ class NodeSaveTestCase extends DrupalWebTestCase { @@ -1173,7 +1198,8 @@ class NodeSaveTestCase extends DrupalWebTestCase { } /** - * Import test, to check if custom node ids are saved properly. + * Checks whether custom node IDs are saved properly during an import operation. + * * Workflow: * - first create a piece of content * - save the content @@ -1207,8 +1233,7 @@ class NodeSaveTestCase extends DrupalWebTestCase { } /** - * Check that the "created" and "changed" timestamps are set correctly when - * saving a new node or updating an existing node. + * Verifies accuracy of the "created" and "changed" timestamp functionality. */ function testTimestamps() { // Use the default timestamps. @@ -1307,7 +1332,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Ensure that node type functions (node_type_get_*) work correctly. + * Ensures that node type functions (node_type_get_*) work correctly. * * Load available node types and validate the returned data. */ @@ -1326,7 +1351,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test creating a content type programmatically and via a form. + * Tests creating a content type programmatically and via a form. */ function testNodeTypeCreation() { // Create a content type programmaticaly. @@ -1356,7 +1381,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test editing a node type using the UI. + * Tests editing a node type using the UI. */ function testNodeTypeEditing() { $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); @@ -1409,7 +1434,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test that node_types_rebuild() correctly handles the 'disabled' flag. + * Tests that node_types_rebuild() correctly handles the 'disabled' flag. */ function testNodeTypeStatus() { // Enable all core node modules, and all types should be active. @@ -1470,7 +1495,7 @@ class NodeTypePersistenceTestCase extends DrupalWebTestCase { } /** - * Test node type customizations persist through disable and uninstall. + * Tests that node type customizations persist through disable and uninstall. */ function testNodeTypeCustomizationPersistence() { $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules')); @@ -1534,7 +1559,7 @@ class NodeTypePersistenceTestCase extends DrupalWebTestCase { } /** - * Rebuild the node_access table. + * Verifies the rebuild functionality for the node_access table. */ class NodeAccessRebuildTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1553,6 +1578,9 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { $this->web_user = $web_user; } + /** + * Tests rebuilding the node access permissions table. + */ function testNodeAccessRebuild() { $this->drupalGet('admin/reports/status'); $this->clickLink(t('Rebuild permissions')); @@ -1562,7 +1590,7 @@ class NodeAccessRebuildTestCase extends DrupalWebTestCase { } /** - * Test node administration page functionality. + * Tests node administration page functionality. */ class NodeAdminTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -1630,6 +1658,7 @@ class NodeAdminTestCase extends DrupalWebTestCase { * Tests content overview with different user permissions. * * Taxonomy filters are tested separately. + * * @see TaxonomyNodeFilterTestCase */ function testContentAdminPages() { @@ -1727,7 +1756,7 @@ class NodeAdminTestCase extends DrupalWebTestCase { } /** - * Test node title. + * Tests node title functionality. */ class NodeTitleTestCase extends DrupalWebTestCase { protected $admin_user; @@ -1747,7 +1776,7 @@ class NodeTitleTestCase extends DrupalWebTestCase { } /** - * Create one node and test if the node title has the correct value. + * Creates one node and tests if the node title has the correct value. */ function testNodeTitle() { // Create "Basic page" content with title. @@ -1790,7 +1819,7 @@ class NodeFeedTestCase extends DrupalWebTestCase { } /** - * Ensure that node_feed accepts and prints extra channel elements. + * Ensures that node_feed() accepts and prints extra channel elements. */ function testNodeFeedExtraChannelElements() { ob_start(); @@ -1822,7 +1851,7 @@ class NodeBlockFunctionalTest extends DrupalWebTestCase { } /** - * Test the recent comments block. + * Tests the recent comments block. */ function testRecentNodeBlock() { $this->drupalLogin($this->admin_user); @@ -1935,7 +1964,7 @@ class NodeBlockFunctionalTest extends DrupalWebTestCase { } } /** - * Test multistep node forms basic options. + * Tests basic options of multi-step node forms. */ class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase { public static function getInfo() { @@ -1953,7 +1982,7 @@ class MultiStepNodeFormBasicOptionsTest extends DrupalWebTestCase { } /** - * Change the default values of basic options to ensure they persist. + * Tests changing the default values of basic options to ensure they persist. */ function testMultiStepNodeFormBasicOptions() { $edit = array( @@ -1985,7 +2014,7 @@ class NodeBuildContent extends DrupalWebTestCase { } /** - * Test to ensure that a node's content array is rebuilt on every call to node_build_content(). + * Ensures that content array is rebuilt on every call to node_build_content(). */ function testNodeRebuildContent() { $node = $this->drupalCreateNode(); @@ -2065,10 +2094,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for user with access. + * Tests 'node_access' query alter, for user with access. * - * Verifies that a non-standard table alias can be used, and that a - * user with node access can view the nodes. + * Verifies that a non-standard table alias can be used, and that a user with + * node access can view the nodes. */ function testNodeQueryAlterLowLevelWithAccess() { // User with access should be able to view 4 nodes. @@ -2088,10 +2117,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for user without access. + * Tests 'node_access' query alter, for user without access. * - * Verifies that a non-standard table alias can be used, and that a - * user without node access cannot view the nodes. + * Verifies that a non-standard table alias can be used, and that a user + * without node access cannot view the nodes. */ function testNodeQueryAlterLowLevelNoAccess() { // User without access should be able to view 0 nodes. @@ -2111,10 +2140,10 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter, for edit access. + * Tests 'node_access' query alter, for edit access. * - * Verifies that a non-standard table alias can be used, and that a - * user with view-only node access cannot edit the nodes. + * Verifies that a non-standard table alias can be used, and that a user with + * view-only node access cannot edit the nodes. */ function testNodeQueryAlterLowLevelEditAccess() { // User with view-only access should not be able to edit nodes. @@ -2136,13 +2165,13 @@ class NodeQueryAlter extends DrupalWebTestCase { } /** - * Lower-level test of 'node_access' query alter override. + * Tests 'node_access' query alter override. * * Verifies that node_access_view_all_nodes() is called from - * node_query_node_access_alter(). We do this by checking that - * a user which normally would not have view privileges is able - * to view the nodes when we add a record to {node_access} paired - * with a corresponding privilege in hook_node_grants(). + * node_query_node_access_alter(). We do this by checking that a user who + * normally would not have view privileges is able to view the nodes when we + * add a record to {node_access} paired with a corresponding privilege in + * hook_node_grants(). */ function testNodeQueryAlterOverride() { $record = array( diff --git a/modules/node/tests/node_access_test.module b/modules/node/tests/node_access_test.module index 813bf929b..ec35c41f1 100644 --- a/modules/node/tests/node_access_test.module +++ b/modules/node/tests/node_access_test.module @@ -2,7 +2,9 @@ /** * @file - * Dummy module implementing node access related hooks to test API interaction + * A dummy module implementing node access related hooks for testing purposes. + * + * A dummy module implementing node access related hooks to test API interaction * with the Node module. This module restricts view permission to those with * a special 'node test view' permission. */ @@ -140,6 +142,8 @@ function node_access_test_page() { * database query is shown, and a list of the node IDs, for debugging purposes. * And if there is a query exception, the page says "Exception" and gives the * error. + * + * @see node_access_test_menu() */ function node_access_entity_test_page() { $output = ''; diff --git a/modules/node/tests/node_test.module b/modules/node/tests/node_test.module index a52c1fad0..fb6678521 100644 --- a/modules/node/tests/node_test.module +++ b/modules/node/tests/node_test.module @@ -2,8 +2,10 @@ /** * @file - * Dummy module implementing node related hooks to test API interaction with - * the Node module. + * A dummy module for testing node related hooks. + * + * This is a dummy module that implements node related hooks to test API + * interaction with the Node module. */ /** diff --git a/modules/node/tests/node_test_exception.module b/modules/node/tests/node_test_exception.module index 0fe9f35ea..66bc71747 100644 --- a/modules/node/tests/node_test_exception.module +++ b/modules/node/tests/node_test_exception.module @@ -2,8 +2,7 @@ /** * @file - * Dummy module implementing node related hooks to test API interaction with - * the Node module. + * A module implementing node related hooks to test API interaction. */ /** diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module index c07cc6cfa..728198680 100644 --- a/modules/overlay/overlay.module +++ b/modules/overlay/overlay.module @@ -704,7 +704,7 @@ function overlay_overlay_child_initialize() { } /** - * Requests that the overlay overlay closes when the page is displayed. + * Requests that the overlay closes when the page is displayed. * * @param $redirect * (optional) The path that should open in the parent window after the diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index e3cab62a2..5c65e12f4 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -2646,10 +2646,9 @@ class DrupalWebTestCase extends DrupalTestCase { /** * Follows a link by name. * - * Will click the first link found with this link text by default, or a - * later one if an index is given. Match is case insensitive with - * normalized space. The label is translated label. There is an assert - * for successful click. + * Will click the first link found with this link text by default, or a later + * one if an index is given. Match is case sensitive with normalized space. + * The label is translated label. There is an assert for successful click. * * @param $label * Text between the anchor tags. @@ -3149,6 +3148,42 @@ class DrupalWebTestCase extends DrupalTestCase { } /** + * Asserts themed output. + * + * @param $callback + * The name of the theme function to invoke; e.g. 'links' for theme_links(). + * @param $variables + * An array of variables to pass to the theme function. + * @param $expected + * The expected themed output string. + * @param $message + * (optional) A message to display with the assertion. Do not translate + * messages: use format_string() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * @param $group + * (optional) The group this message is in, which is displayed in a column + * in test output. Use 'Debug' to indicate this is debugging output. Do not + * translate this string. Defaults to 'Other'; most tests do not override + * this default. + * + * @return + * TRUE on pass, FALSE on fail. + */ + protected function assertThemeOutput($callback, array $variables = array(), $expected, $message = '', $group = 'Other') { + $output = theme($callback, $variables); + $this->verbose('Variables:' . '<pre>' . check_plain(var_export($variables, TRUE)) . '</pre>' + . '<hr />' . 'Result:' . '<pre>' . check_plain(var_export($output, TRUE)) . '</pre>' + . '<hr />' . 'Expected:' . '<pre>' . check_plain(var_export($expected, TRUE)) . '</pre>' + . '<hr />' . $output + ); + if (!$message) { + $message = '%callback rendered correctly.'; + } + $message = format_string($message, array('%callback' => 'theme_' . $callback . '()')); + return $this->assertIdentical($output, $expected, $message, $group); + } + + /** * Asserts that a field exists in the current page by the given XPath. * * @param $xpath diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index e8e403330..cc4606951 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -2298,6 +2298,12 @@ class FormatDateUnitTest extends DrupalWebTestCase { $edit = array('date_format' => $admin_date_format); $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + // Add a new date format which just differs in the case. + $admin_date_format_uppercase = 'j M Y'; + $edit = array('date_format' => $admin_date_format_uppercase); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + $this->assertText(t('Custom date format added.')); + // Add new date type. $edit = array( 'date_type' => 'Example Style', @@ -2306,8 +2312,18 @@ class FormatDateUnitTest extends DrupalWebTestCase { ); $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type')); + // Add a second date format with a different case than the first. + $edit = array( + 'machine_name' => 'example_style_uppercase', + 'date_type' => 'Example Style Uppercase', + 'date_format' => $admin_date_format_uppercase, + ); + $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type')); + $this->assertText(t('New date type added successfully.')); + $timestamp = strtotime('2007-03-10T00:00:00+00:00'); $this->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07', t('Test format_date() using an admin-defined date type.')); + $this->assertIdentical(format_date($timestamp, 'example_style_uppercase', '', 'America/Los_Angeles'), '9 Mar 2007', 'Test format_date() using an admin-defined date type with different case.'); $this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'medium'), t('Test format_date() defaulting to medium when $type not found.')); } diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 6e1d15979..b58578e99 100644 --- a/modules/simpletest/tests/database_test.test +++ b/modules/simpletest/tests/database_test.test @@ -48,7 +48,7 @@ class DatabaseTestCase extends DrupalWebTestCase { } foreach ($schema as $name => $data) { - $this->assertTrue(db_table_exists($name), t('Table @name created successfully.', array('@name' => $name))); + $this->assertTrue(db_table_exists($name), format_string('Table @name created successfully.', array('@name' => $name))); } } @@ -191,25 +191,25 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { $db1 = Database::getConnection('default', 'default'); $db2 = Database::getConnection('slave', 'default'); - $this->assertNotNull($db1, t('default connection is a real connection object.')); - $this->assertNotNull($db2, t('slave connection is a real connection object.')); - $this->assertNotIdentical($db1, $db2, t('Each target refers to a different connection.')); + $this->assertNotNull($db1, 'default connection is a real connection object.'); + $this->assertNotNull($db2, 'slave connection is a real connection object.'); + $this->assertNotIdentical($db1, $db2, 'Each target refers to a different connection.'); // Try to open those targets another time, that should return the same objects. $db1b = Database::getConnection('default', 'default'); $db2b = Database::getConnection('slave', 'default'); - $this->assertIdentical($db1, $db1b, t('A second call to getConnection() returns the same object.')); - $this->assertIdentical($db2, $db2b, t('A second call to getConnection() returns the same object.')); + $this->assertIdentical($db1, $db1b, 'A second call to getConnection() returns the same object.'); + $this->assertIdentical($db2, $db2b, 'A second call to getConnection() returns the same object.'); // Try to open an unknown target. $unknown_target = $this->randomName(); $db3 = Database::getConnection($unknown_target, 'default'); - $this->assertNotNull($db3, t('Opening an unknown target returns a real connection object.')); - $this->assertIdentical($db1, $db3, t('An unknown target opens the default connection.')); + $this->assertNotNull($db3, 'Opening an unknown target returns a real connection object.'); + $this->assertIdentical($db1, $db3, 'An unknown target opens the default connection.'); // Try to open that unknown target another time, that should return the same object. $db3b = Database::getConnection($unknown_target, 'default'); - $this->assertIdentical($db3, $db3b, t('A second call to getConnection() returns the same object.')); + $this->assertIdentical($db3, $db3b, 'A second call to getConnection() returns the same object.'); } /** @@ -227,7 +227,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { $db1 = Database::getConnection('default', 'default'); $db2 = Database::getConnection('slave', 'default'); - $this->assertIdentical($db1, $db2, t('Both targets refer to the same connection.')); + $this->assertIdentical($db1, $db2, 'Both targets refer to the same connection.'); } /** @@ -242,7 +242,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { $db2 = Database::getConnection('default', 'default'); // Opening a connection after closing it should yield an object different than the original. - $this->assertNotIdentical($db1, $db2, t('Opening the default connection after it is closed returns a new object.')); + $this->assertNotIdentical($db1, $db2, 'Opening the default connection after it is closed returns a new object.'); } /** @@ -257,8 +257,8 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { // In the MySQL driver, the port can be different, so check individual // options. - $this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], t('The default connection info driver matches the current connection options driver.')); - $this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], t('The default connection info database matches the current connection options database.')); + $this->assertEqual($connection_info['default']['driver'], $connectionOptions['driver'], 'The default connection info driver matches the current connection options driver.'); + $this->assertEqual($connection_info['default']['database'], $connectionOptions['database'], 'The default connection info database matches the current connection options database.'); // Set up identical slave and confirm connection options are identical. Database::addConnectionInfo('default', 'slave', $connection_info['default']); @@ -267,7 +267,7 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { // Get a fresh copy of the default connection options. $connectionOptions = $db->getConnectionOptions(); - $this->assertIdentical($connectionOptions, $connectionOptions2, t('The default and slave connection options are identical.')); + $this->assertIdentical($connectionOptions, $connectionOptions2, 'The default and slave connection options are identical.'); // Set up a new connection with different connection info. $test = $connection_info['default']; @@ -277,7 +277,46 @@ class DatabaseConnectionTestCase extends DatabaseTestCase { // Get a fresh copy of the default connection options. $connectionOptions = $db->getConnectionOptions(); - $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], t('The test connection info database does not match the current connection options database.')); + $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.'); + } +} + +/** + * Test cloning Select queries. + */ +class DatabaseSelectCloneTest extends DatabaseTestCase { + + public static function getInfo() { + return array( + 'name' => 'Select tests, cloning', + 'description' => 'Test cloning Select queries.', + 'group' => 'Database', + ); + } + + /** + * Test that subqueries as value within conditions are cloned properly. + */ + function testSelectConditionSubQueryCloning() { + $subquery = db_select('test', 't'); + $subquery->addField('t', 'id', 'id'); + $subquery->condition('age', 28, '<'); + + $query = db_select('test', 't'); + $query->addField('t', 'name', 'name'); + $query->condition('id', $subquery, 'IN'); + + $clone = clone $query; + // Cloned query should not be altered by the following modification + // happening on original query. + $subquery->condition('age', 25, '>'); + + $clone_result = $clone->countQuery()->execute()->fetchField(); + $query_result = $query->countQuery()->execute()->fetchField(); + + // Make sure the cloned query has not been modified + $this->assertEqual(3, $clone_result, 'The cloned query returns the expected number of rows'); + $this->assertEqual(2, $query_result, 'The query returns the expected number of rows'); } } @@ -302,14 +341,14 @@ class DatabaseFetchTestCase extends DatabaseTestCase { function testQueryFetchDefault() { $records = array(); $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25)); - $this->assertTrue($result instanceof DatabaseStatementInterface, t('Result set is a Drupal statement object.')); + $this->assertTrue($result instanceof DatabaseStatementInterface, 'Result set is a Drupal statement object.'); foreach ($result as $record) { $records[] = $record; - $this->assertTrue(is_object($record), t('Record is an object.')); - $this->assertIdentical($record->name, 'John', t('25 year old is John.')); + $this->assertTrue(is_object($record), 'Record is an object.'); + $this->assertIdentical($record->name, 'John', '25 year old is John.'); } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -320,11 +359,11 @@ class DatabaseFetchTestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_OBJ)); foreach ($result as $record) { $records[] = $record; - $this->assertTrue(is_object($record), t('Record is an object.')); - $this->assertIdentical($record->name, 'John', t('25 year old is John.')); + $this->assertTrue(is_object($record), 'Record is an object.'); + $this->assertIdentical($record->name, 'John', '25 year old is John.'); } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -335,12 +374,12 @@ class DatabaseFetchTestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue(is_array($record), t('Record is an array.'))) { - $this->assertIdentical($record['name'], 'John', t('Record can be accessed associatively.')); + if ($this->assertTrue(is_array($record), 'Record is an array.')) { + $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.'); } } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -353,12 +392,12 @@ class DatabaseFetchTestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => 'FakeRecord')); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue($record instanceof FakeRecord, t('Record is an object of class FakeRecord.'))) { - $this->assertIdentical($record->name, 'John', t('25 year old is John.')); + if ($this->assertTrue($record instanceof FakeRecord, 'Record is an object of class FakeRecord.')) { + $this->assertIdentical($record->name, 'John', '25 year old is John.'); } } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } } @@ -387,8 +426,8 @@ class DatabaseFetch2TestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_NUM)); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue(is_array($record), t('Record is an array.'))) { - $this->assertIdentical($record[0], 'John', t('Record can be accessed numerically.')); + if ($this->assertTrue(is_array($record), 'Record is an array.')) { + $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.'); } } @@ -403,13 +442,13 @@ class DatabaseFetch2TestCase extends DatabaseTestCase { $result = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 25), array('fetch' => PDO::FETCH_BOTH)); foreach ($result as $record) { $records[] = $record; - if ($this->assertTrue(is_array($record), t('Record is an array.'))) { - $this->assertIdentical($record[0], 'John', t('Record can be accessed numerically.')); - $this->assertIdentical($record['name'], 'John', t('Record can be accessed associatively.')); + if ($this->assertTrue(is_array($record), 'Record is an array.')) { + $this->assertIdentical($record[0], 'John', 'Record can be accessed numerically.'); + $this->assertIdentical($record['name'], 'John', 'Record can be accessed associatively.'); } } - $this->assertIdentical(count($records), 1, t('There is only one record.')); + $this->assertIdentical(count($records), 1, 'There is only one record.'); } /** @@ -419,12 +458,12 @@ class DatabaseFetch2TestCase extends DatabaseTestCase { $records = array(); $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25)); $column = $result->fetchCol(); - $this->assertIdentical(count($column), 3, t('fetchCol() returns the right number of records.')); + $this->assertIdentical(count($column), 3, 'fetchCol() returns the right number of records.'); $result = db_query('SELECT name FROM {test} WHERE age > :age', array(':age' => 25)); $i = 0; foreach ($result as $record) { - $this->assertIdentical($record->name, $column[$i++], t('Column matches direct accesss.')); + $this->assertIdentical($record->name, $column[$i++], 'Column matches direct accesss.'); } } } @@ -456,9 +495,9 @@ class DatabaseInsertTestCase extends DatabaseTestCase { $query->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before + 1, (int) $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Yoko'))->fetchField(); - $this->assertIdentical($saved_age, '29', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '29', 'Can retrieve after inserting.'); } /** @@ -485,13 +524,13 @@ class DatabaseInsertTestCase extends DatabaseTestCase { $query->execute(); $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before + 3, $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical($num_records_before + 3, $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); - $this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); - $this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); } /** @@ -520,13 +559,13 @@ class DatabaseInsertTestCase extends DatabaseTestCase { $query->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical((int) $num_records_before + 3, (int) $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); - $this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); - $this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); } /** @@ -542,11 +581,11 @@ class DatabaseInsertTestCase extends DatabaseTestCase { ->values(array('Moe', '32')) ->execute(); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Larry'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Curly'))->fetchField(); - $this->assertIdentical($saved_age, '31', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '31', 'Can retrieve after inserting.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Moe'))->fetchField(); - $this->assertIdentical($saved_age, '32', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '32', 'Can retrieve after inserting.'); } /** @@ -560,7 +599,7 @@ class DatabaseInsertTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertIdentical($id, '5', t('Auto-increment ID returned successfully.')); + $this->assertIdentical($id, '5', 'Auto-increment ID returned successfully.'); } /** @@ -586,7 +625,7 @@ class DatabaseInsertTestCase extends DatabaseTestCase { ->execute(); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Meredith'))->fetchField(); - $this->assertIdentical($saved_age, '30', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '30', 'Can retrieve after inserting.'); } } @@ -608,12 +647,12 @@ class DatabaseInsertLOBTestCase extends DatabaseTestCase { */ function testInsertOneBlob() { $data = "This is\000a test."; - $this->assertTrue(strlen($data) === 15, t('Test data contains a NULL.')); + $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.'); $id = db_insert('test_one_blob') ->fields(array('blob1' => $data)) ->execute(); $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === $data, t('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); + $this->assertTrue($r['blob1'] === $data, format_string('Can insert a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); } /** @@ -627,7 +666,7 @@ class DatabaseInsertLOBTestCase extends DatabaseTestCase { )) ->execute(); $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', t('Can insert multiple blobs per row.')); + $this->assertTrue($r['blob1'] === 'This is' && $r['blob2'] === 'a test', 'Can insert multiple blobs per row.'); } } @@ -654,7 +693,7 @@ class DatabaseInsertDefaultsTestCase extends DatabaseTestCase { $schema = drupal_get_schema('test'); $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField(); - $this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.')); + $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.'); } /** @@ -666,13 +705,13 @@ class DatabaseInsertDefaultsTestCase extends DatabaseTestCase { try { $result = db_insert('test')->execute(); // This is only executed if no exception has been thrown. - $this->fail(t('Expected exception NoFieldsException has not been thrown.')); + $this->fail('Expected exception NoFieldsException has not been thrown.'); } catch (NoFieldsException $e) { - $this->pass(t('Expected exception NoFieldsException has been thrown.')); + $this->pass('Expected exception NoFieldsException has been thrown.'); } $num_records_after = (int) db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before, $num_records_after, t('Do nothing as no fields are specified.')); + $this->assertIdentical($num_records_before, $num_records_after, 'Do nothing as no fields are specified.'); } /** @@ -687,7 +726,7 @@ class DatabaseInsertDefaultsTestCase extends DatabaseTestCase { $schema = drupal_get_schema('test'); $job = db_query('SELECT job FROM {test} WHERE id = :id', array(':id' => $id))->fetchField(); - $this->assertEqual($job, $schema['fields']['job']['default'], t('Default field value is set.')); + $this->assertEqual($job, $schema['fields']['job']['default'], 'Default field value is set.'); } } @@ -712,10 +751,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('name' => 'Tiffany')) ->condition('id', 1) ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $saved_name = db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 1))->fetchField(); - $this->assertIdentical($saved_name, 'Tiffany', t('Updated name successfully.')); + $this->assertIdentical($saved_name, 'Tiffany', 'Updated name successfully.'); } /** @@ -727,10 +766,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('age' => NULL)) ->condition('name', 'Kermit') ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $saved_age = db_query('SELECT age FROM {test_null} WHERE name = :name', array(':name' => 'Kermit'))->fetchField(); - $this->assertNull($saved_age, t('Updated name successfully.')); + $this->assertNull($saved_age, 'Updated name successfully.'); } /** @@ -741,10 +780,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('job', 'Singer') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -755,10 +794,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('age', 26, '>') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -769,10 +808,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->where('age > :age', array(':age' => 26)) ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -784,10 +823,10 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { ->where('age > :age', array(':age' => 26)) ->condition('name', 'Ringo'); $num_updated = $update->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); } /** @@ -807,7 +846,21 @@ class DatabaseUpdateTestCase extends DatabaseTestCase { $num_rows = db_update('test') ->expression('age', 'age * age') ->execute(); - $this->assertIdentical($num_rows, 3, t('Number of affected rows are returned.')); + $this->assertIdentical($num_rows, 3, 'Number of affected rows are returned.'); + } + + /** + * Confirm that we can update the primary key of a record successfully. + */ + function testPrimaryKeyUpdate() { + $num_updated = db_update('test') + ->fields(array('id' => 42, 'name' => 'John')) + ->condition('id', 1) + ->execute(); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); + + $saved_name= db_query('SELECT name FROM {test} WHERE id = :id', array(':id' => 42))->fetchField(); + $this->assertIdentical($saved_name, 'John', 'Updated primary key successfully.'); } } @@ -835,10 +888,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->condition('name', 'Paul') ); $num_updated = $update->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -849,10 +902,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('name', array('John', 'Paul'), 'IN') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -865,10 +918,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('name', array('John', 'Paul', 'George'), 'NoT IN') ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); } /** @@ -879,10 +932,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('age', array(25, 26), 'BETWEEN') ->execute(); - $this->assertIdentical($num_updated, 2, t('Updated 2 records.')); + $this->assertIdentical($num_updated, 2, 'Updated 2 records.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '2', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '2', 'Updated fields successfully.'); } /** @@ -893,10 +946,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->condition('name', '%ge%', 'LIKE') ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); } /** @@ -910,15 +963,15 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->fields(array('job' => 'Musician')) ->expression('age', 'age + :age', array(':age' => 4)) ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $num_matches = db_query('SELECT COUNT(*) FROM {test} WHERE job = :job', array(':job' => 'Musician'))->fetchField(); - $this->assertIdentical($num_matches, '1', t('Updated fields successfully.')); + $this->assertIdentical($num_matches, '1', 'Updated fields successfully.'); $person = db_query('SELECT * FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetch(); - $this->assertEqual($person->name, 'Ringo', t('Name set correctly.')); - $this->assertEqual($person->age, $before_age + 4, t('Age set correctly.')); - $this->assertEqual($person->job, 'Musician', t('Job set correctly.')); + $this->assertEqual($person->name, 'Ringo', 'Name set correctly.'); + $this->assertEqual($person->age, $before_age + 4, 'Age set correctly.'); + $this->assertEqual($person->job, 'Musician', 'Job set correctly.'); $GLOBALS['larry_test'] = 0; } @@ -931,10 +984,10 @@ class DatabaseUpdateComplexTestCase extends DatabaseTestCase { ->condition('name', 'Ringo') ->expression('age', 'age + :age', array(':age' => 4)) ->execute(); - $this->assertIdentical($num_updated, 1, t('Updated 1 record.')); + $this->assertIdentical($num_updated, 1, 'Updated 1 record.'); $after_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'Ringo'))->fetchField(); - $this->assertEqual($before_age + 4, $after_age, t('Age updated correctly')); + $this->assertEqual($before_age + 4, $after_age, 'Age updated correctly'); } } @@ -956,7 +1009,7 @@ class DatabaseUpdateLOBTestCase extends DatabaseTestCase { */ function testUpdateOneBlob() { $data = "This is\000a test."; - $this->assertTrue(strlen($data) === 15, t('Test data contains a NULL.')); + $this->assertTrue(strlen($data) === 15, 'Test data contains a NULL.'); $id = db_insert('test_one_blob') ->fields(array('blob1' => $data)) ->execute(); @@ -968,7 +1021,7 @@ class DatabaseUpdateLOBTestCase extends DatabaseTestCase { ->execute(); $r = db_query('SELECT * FROM {test_one_blob} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === $data, t('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); + $this->assertTrue($r['blob1'] === $data, format_string('Can update a blob: id @id, @data.', array('@id' => $id, '@data' => serialize($r)))); } /** @@ -988,7 +1041,7 @@ class DatabaseUpdateLOBTestCase extends DatabaseTestCase { ->execute(); $r = db_query('SELECT * FROM {test_two_blobs} WHERE id = :id', array(':id' => $id))->fetchAssoc(); - $this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', t('Can update multiple blobs per row.')); + $this->assertTrue($r['blob1'] === 'and so' && $r['blob2'] === 'is this', 'Can update multiple blobs per row.'); } } @@ -1028,10 +1081,10 @@ class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { ->condition('pid', $subquery, 'IN'); $num_deleted = $delete->execute(); - $this->assertEqual($num_deleted, 1, t("Deleted 1 record.")); + $this->assertEqual($num_deleted, 1, "Deleted 1 record."); $num_records_after = db_query('SELECT COUNT(*) FROM {test_task}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after + $num_deleted, t('Deletion adds up.')); + $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.'); } /** @@ -1043,10 +1096,10 @@ class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { $num_deleted = db_delete('test') ->condition('id', 1) ->execute(); - $this->assertIdentical($num_deleted, 1, t('Deleted 1 record.')); + $this->assertIdentical($num_deleted, 1, 'Deleted 1 record.'); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after + $num_deleted, t('Deletion adds up.')); + $this->assertEqual($num_records_before, $num_records_after + $num_deleted, 'Deletion adds up.'); } /** @@ -1058,7 +1111,7 @@ class DatabaseDeleteTruncateTestCase extends DatabaseTestCase { db_truncate('test')->execute(); $num_records_after = db_query("SELECT COUNT(*) FROM {test}")->fetchField(); - $this->assertEqual(0, $num_records_after, t('Truncate really deletes everything.')); + $this->assertEqual(0, $num_records_after, 'Truncate really deletes everything.'); } } @@ -1089,15 +1142,15 @@ class DatabaseMergeTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertEqual($result, MergeQuery::STATUS_INSERT, t('Insert status returned.')); + $this->assertEqual($result, MergeQuery::STATUS_INSERT, 'Insert status returned.'); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before + 1, $num_records_after, t('Merge inserted properly.')); + $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, 31, t('Age set correctly.')); - $this->assertEqual($person->job, 'Presenter', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, 31, 'Age set correctly.'); + $this->assertEqual($person->job, 'Presenter', 'Job set correctly.'); } /** @@ -1114,15 +1167,15 @@ class DatabaseMergeTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertEqual($result, MergeQuery::STATUS_UPDATE, t('Update status returned.')); + $this->assertEqual($result, MergeQuery::STATUS_UPDATE, 'Update status returned.'); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, 31, t('Age set correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, 31, 'Age set correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1138,12 +1191,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1164,12 +1217,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Joe', t('Name set correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Joe', 'Name set correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1193,12 +1246,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge updated properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge updated properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Tiffany', t('Name set correctly.')); - $this->assertEqual($person->age, $age_before + 4, t('Age updated correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job set correctly.')); + $this->assertEqual($person->name, 'Tiffany', 'Name set correctly.'); + $this->assertEqual($person->age, $age_before + 4, 'Age updated correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job set correctly.'); } /** @@ -1212,12 +1265,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before + 1, $num_records_after, t('Merge inserted properly.')); + $this->assertEqual($num_records_before + 1, $num_records_after, 'Merge inserted properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Presenter'))->fetch(); - $this->assertEqual($person->name, '', t('Name set correctly.')); - $this->assertEqual($person->age, 0, t('Age set correctly.')); - $this->assertEqual($person->job, 'Presenter', t('Job set correctly.')); + $this->assertEqual($person->name, '', 'Name set correctly.'); + $this->assertEqual($person->age, 0, 'Age set correctly.'); + $this->assertEqual($person->job, 'Presenter', 'Job set correctly.'); } /** @@ -1231,12 +1284,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge skipped properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Meredith', t('Name skipped correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job skipped correctly.')); + $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.'); db_merge('test_people') ->key(array('job' => 'Speaker')) @@ -1244,12 +1297,12 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField(); - $this->assertEqual($num_records_before, $num_records_after, t('Merge skipped properly.')); + $this->assertEqual($num_records_before, $num_records_after, 'Merge skipped properly.'); $person = db_query('SELECT * FROM {test_people} WHERE job = :job', array(':job' => 'Speaker'))->fetch(); - $this->assertEqual($person->name, 'Meredith', t('Name skipped correctly.')); - $this->assertEqual($person->age, 30, t('Age skipped correctly.')); - $this->assertEqual($person->job, 'Speaker', t('Job skipped correctly.')); + $this->assertEqual($person->name, 'Meredith', 'Name skipped correctly.'); + $this->assertEqual($person->age, 30, 'Age skipped correctly.'); + $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.'); } /** @@ -1266,10 +1319,10 @@ class DatabaseMergeTestCase extends DatabaseTestCase { ->execute(); } catch (InvalidMergeQueryException $e) { - $this->pass(t('InvalidMergeQueryException thrown for invalid query.')); + $this->pass('InvalidMergeQueryException thrown for invalid query.'); return; } - $this->fail(t('No InvalidMergeQueryException thrown')); + $this->fail('No InvalidMergeQueryException thrown'); } } @@ -1300,7 +1353,7 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -1320,8 +1373,8 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $query = (string)$query; $expected = "/* Testing query comments */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); - $this->assertEqual($query, $expected, t('The flattened query contains the comment string.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); + $this->assertEqual($query, $expected, 'The flattened query contains the comment string.'); } /** @@ -1341,8 +1394,8 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $query = (string)$query; $expected = "/* Testing query comments SELECT nid FROM {node}; -- */ SELECT test.name AS name, test.age AS age\nFROM \n{test} test"; - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); - $this->assertEqual($query, $expected, t('The flattened query contains the sanitised comment string.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); + $this->assertEqual($query, $expected, 'The flattened query contains the sanitised comment string.'); } /** @@ -1356,13 +1409,13 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $result = $query->execute(); // Check that the aliases are being created the way we want. - $this->assertEqual($name_field, 'name', t('Name field alias is correct.')); - $this->assertEqual($age_field, 'age', t('Age field alias is correct.')); + $this->assertEqual($name_field, 'name', 'Name field alias is correct.'); + $this->assertEqual($age_field, 'age', 'Age field alias is correct.'); // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27, t('Fetched age is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27, 'Fetched age is correct.'); } /** @@ -1376,13 +1429,13 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $result = $query->execute(); // Check that the aliases are being created the way we want. - $this->assertEqual($name_field, 'name', t('Name field alias is correct.')); - $this->assertEqual($age_field, 'double_age', t('Age field alias is correct.')); + $this->assertEqual($name_field, 'name', 'Name field alias is correct.'); + $this->assertEqual($age_field, 'double_age', 'Age field alias is correct.'); // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27*2, t('Fetched age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27*2, 'Fetched age expression is correct.'); } /** @@ -1397,14 +1450,14 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $result = $query->execute(); // Check that the aliases are being created the way we want. - $this->assertEqual($age_double_field, 'expression', t('Double age field alias is correct.')); - $this->assertEqual($age_triple_field, 'expression_2', t('Triple age field alias is correct.')); + $this->assertEqual($age_double_field, 'expression', 'Double age field alias is correct.'); + $this->assertEqual($age_triple_field, 'expression_2', 'Triple age field alias is correct.'); // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_double_field, 27*2, t('Fetched double age expression is correct.')); - $this->assertEqual($record->$age_triple_field, 27*3, t('Fetched triple age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_double_field, 27*2, 'Fetched double age expression is correct.'); + $this->assertEqual($record->$age_triple_field, 27*3, 'Fetched triple age expression is correct.'); } /** @@ -1417,17 +1470,17 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->execute()->fetchObject(); // Check that all fields we asked for are present. - $this->assertNotNull($record->id, t('ID field is present.')); - $this->assertNotNull($record->name, t('Name field is present.')); - $this->assertNotNull($record->age, t('Age field is present.')); - $this->assertNotNull($record->job, t('Job field is present.')); + $this->assertNotNull($record->id, 'ID field is present.'); + $this->assertNotNull($record->name, 'Name field is present.'); + $this->assertNotNull($record->age, 'Age field is present.'); + $this->assertNotNull($record->job, 'Job field is present.'); // Ensure that we got the right record. // Check that all fields we asked for are present. - $this->assertEqual($record->id, 2, t('ID field has the correct value.')); - $this->assertEqual($record->name, 'George', t('Name field has the correct value.')); - $this->assertEqual($record->age, 27, t('Age field has the correct value.')); - $this->assertEqual($record->job, 'Singer', t('Job field has the correct value.')); + $this->assertEqual($record->id, 2, 'ID field has the correct value.'); + $this->assertEqual($record->name, 'George', 'Name field has the correct value.'); + $this->assertEqual($record->age, 27, 'Age field has the correct value.'); + $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.'); } /** @@ -1440,17 +1493,17 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->execute()->fetchObject(); // Check that all fields we asked for are present. - $this->assertNotNull($record->id, t('ID field is present.')); - $this->assertNotNull($record->name, t('Name field is present.')); - $this->assertNotNull($record->age, t('Age field is present.')); - $this->assertNotNull($record->job, t('Job field is present.')); + $this->assertNotNull($record->id, 'ID field is present.'); + $this->assertNotNull($record->name, 'Name field is present.'); + $this->assertNotNull($record->age, 'Age field is present.'); + $this->assertNotNull($record->job, 'Job field is present.'); // Ensure that we got the right record. // Check that all fields we asked for are present. - $this->assertEqual($record->id, 2, t('ID field has the correct value.')); - $this->assertEqual($record->name, 'George', t('Name field has the correct value.')); - $this->assertEqual($record->age, 27, t('Age field has the correct value.')); - $this->assertEqual($record->job, 'Singer', t('Job field has the correct value.')); + $this->assertEqual($record->id, 2, 'ID field has the correct value.'); + $this->assertEqual($record->name, 'George', 'Name field has the correct value.'); + $this->assertEqual($record->age, 27, 'Age field has the correct value.'); + $this->assertEqual($record->job, 'Singer', 'Job field has the correct value.'); } /** @@ -1464,8 +1517,8 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->isNull('age') ->execute()->fetchCol(); - $this->assertEqual(count($names), 1, t('Correct number of records found with NULL age.')); - $this->assertEqual($names[0], 'Fozzie', t('Correct record returned for NULL age.')); + $this->assertEqual(count($names), 1, 'Correct number of records found with NULL age.'); + $this->assertEqual($names[0], 'Fozzie', 'Correct record returned for NULL age.'); } /** @@ -1480,9 +1533,9 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderBy('name') ->execute()->fetchCol(); - $this->assertEqual(count($names), 2, t('Correct number of records found withNOT NULL age.')); - $this->assertEqual($names[0], 'Gonzo', t('Correct record returned for NOT NULL age.')); - $this->assertEqual($names[1], 'Kermit', t('Correct record returned for NOT NULL age.')); + $this->assertEqual(count($names), 2, 'Correct number of records found withNOT NULL age.'); + $this->assertEqual($names[0], 'Gonzo', 'Correct record returned for NOT NULL age.'); + $this->assertEqual($names[1], 'Kermit', 'Correct record returned for NOT NULL age.'); } /** @@ -1503,10 +1556,10 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $names = $query_1->execute()->fetchCol(); // Ensure we only get 2 records. - $this->assertEqual(count($names), 2, t('UNION correctly discarded duplicates.')); + $this->assertEqual(count($names), 2, 'UNION correctly discarded duplicates.'); - $this->assertEqual($names[0], 'George', t('First query returned correct name.')); - $this->assertEqual($names[1], 'Ringo', t('Second query returned correct name.')); + $this->assertEqual($names[0], 'George', 'First query returned correct name.'); + $this->assertEqual($names[1], 'Ringo', 'Second query returned correct name.'); } /** @@ -1526,11 +1579,11 @@ class DatabaseSelectTestCase extends DatabaseTestCase { $names = $query_1->execute()->fetchCol(); // Ensure we get all 3 records. - $this->assertEqual(count($names), 3, t('UNION ALL correctly preserved duplicates.')); + $this->assertEqual(count($names), 3, 'UNION ALL correctly preserved duplicates.'); - $this->assertEqual($names[0], 'George', t('First query returned correct first name.')); - $this->assertEqual($names[1], 'Ringo', t('Second query returned correct second name.')); - $this->assertEqual($names[2], 'Ringo', t('Third query returned correct name.')); + $this->assertEqual($names[0], 'George', 'First query returned correct first name.'); + $this->assertEqual($names[1], 'Ringo', 'Second query returned correct second name.'); + $this->assertEqual($names[2], 'Ringo', 'Third query returned correct name.'); } /** @@ -1565,7 +1618,7 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderBy('id') ->execute() ->fetchCol(); - $this->assertEqual($ordered_ids, $expected_ids, t('A query without random ordering returns IDs in the correct order.')); + $this->assertEqual($ordered_ids, $expected_ids, 'A query without random ordering returns IDs in the correct order.'); // Now perform the same query, but instead choose a random ordering. We // expect this to contain a differently ordered version of the original @@ -1576,10 +1629,10 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderRandom() ->execute() ->fetchCol(); - $this->assertNotEqual($randomized_ids, $ordered_ids, t('A query with random ordering returns an unordered set of IDs.')); + $this->assertNotEqual($randomized_ids, $ordered_ids, 'A query with random ordering returns an unordered set of IDs.'); $sorted_ids = $randomized_ids; sort($sorted_ids); - $this->assertEqual($sorted_ids, $ordered_ids, t('After sorting the random list, the result matches the original query.')); + $this->assertEqual($sorted_ids, $ordered_ids, 'After sorting the random list, the result matches the original query.'); // Now perform the exact same query again, and make sure the order is // different. @@ -1589,10 +1642,10 @@ class DatabaseSelectTestCase extends DatabaseTestCase { ->orderRandom() ->execute() ->fetchCol(); - $this->assertNotEqual($randomized_ids_second_set, $randomized_ids, t('Performing the query with random ordering a second time returns IDs in a different order.')); + $this->assertNotEqual($randomized_ids_second_set, $randomized_ids, 'Performing the query with random ordering a second time returns IDs in a different order.'); $sorted_ids_second_set = $randomized_ids_second_set; sort($sorted_ids_second_set); - $this->assertEqual($sorted_ids_second_set, $sorted_ids, t('After sorting the second random list, the result matches the sorted version of the first random list.')); + $this->assertEqual($sorted_ids_second_set, $sorted_ids, 'After sorting the second random list, the result matches the sorted version of the first random list.'); } /** @@ -1649,7 +1702,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // WHERE tt.task = 'code' $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 1, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); } } @@ -1676,7 +1729,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // INNER JOIN test t ON t.id=tt.pid $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 1, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 1, 'Returned the correct number of rows.'); } /** @@ -1699,7 +1752,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // FROM test tt2 // WHERE tt2.pid IN (SELECT tt.pid AS pid FROM test_task tt WHERE tt.priority=1) $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 5, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 5, 'Returned the correct number of rows.'); } /** @@ -1723,7 +1776,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // INNER JOIN (SELECT tt.pid AS pid FROM test_task tt WHERE priority=1) tt ON t.id=tt.pid $people = $select->execute()->fetchCol(); - $this->assertEqual(count($people), 2, t('Returned the correct number of rows.')); + $this->assertEqual(count($people), 2, 'Returned the correct number of rows.'); } /** @@ -1753,7 +1806,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->name, 'George', t('Fetched name is correct using EXISTS query.')); + $this->assertEqual($record->name, 'George', 'Fetched name is correct using EXISTS query.'); } /** @@ -1783,7 +1836,7 @@ class DatabaseSelectSubqueryTestCase extends DatabaseTestCase { // Ensure that we got the right number of records. $people = $query->execute()->fetchCol(); - $this->assertEqual(count($people), 3, t('NOT EXISTS query returned the correct results.')); + $this->assertEqual(count($people), 3, 'NOT EXISTS query returned the correct results.'); } } @@ -1814,11 +1867,11 @@ class DatabaseSelectOrderedTestCase extends DatabaseTestCase { $last_age = 0; foreach ($result as $record) { $num_records++; - $this->assertTrue($record->age >= $last_age, t('Results returned in correct order.')); + $this->assertTrue($record->age >= $last_age, 'Results returned in correct order.'); $last_age = $record->age; } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -1845,11 +1898,11 @@ class DatabaseSelectOrderedTestCase extends DatabaseTestCase { $num_records++; foreach ($record as $kk => $col) { if ($expected[$k][$kk] != $results[$k][$kk]) { - $this->assertTrue(FALSE, t('Results returned in correct order.')); + $this->assertTrue(FALSE, 'Results returned in correct order.'); } } } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -1866,11 +1919,11 @@ class DatabaseSelectOrderedTestCase extends DatabaseTestCase { $last_age = 100000000; foreach ($result as $record) { $num_records++; - $this->assertTrue($record->age <= $last_age, t('Results returned in correct order.')); + $this->assertTrue($record->age <= $last_age, 'Results returned in correct order.'); $last_age = $record->age; } - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } } @@ -1904,12 +1957,12 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $last_priority = 0; foreach ($result as $record) { $num_records++; - $this->assertTrue($record->$priority_field >= $last_priority, t('Results returned in correct order.')); - $this->assertNotEqual($record->$name_field, 'Ringo', t('Taskless person not selected.')); + $this->assertTrue($record->$priority_field >= $last_priority, 'Results returned in correct order.'); + $this->assertNotEqual($record->$name_field, 'Ringo', 'Taskless person not selected.'); $last_priority = $record->$priority_field; } - $this->assertEqual($num_records, 7, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 7, 'Returned the correct number of rows.'); } /** @@ -1930,11 +1983,11 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { foreach ($result as $record) { $num_records++; - $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, t('Results returned in correct order.')); + $this->assertTrue(strcmp($record->$name_field, $last_name) >= 0, 'Results returned in correct order.'); $last_priority = $record->$name_field; } - $this->assertEqual($num_records, 8, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 8, 'Returned the correct number of rows.'); } /** @@ -1953,7 +2006,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $records = array(); foreach ($result as $record) { $num_records++; - $this->assertTrue($record->$count_field >= $last_count, t('Results returned in correct order.')); + $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.'); $last_count = $record->$count_field; $records[$record->$task_field] = $record->$count_field; } @@ -1967,10 +2020,10 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { ); foreach ($correct_results as $task => $count) { - $this->assertEqual($records[$task], $count, t("Correct number of '@task' records found.", array('@task' => $task))); + $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task))); } - $this->assertEqual($num_records, 6, t('Returned the correct number of total rows.')); + $this->assertEqual($num_records, 6, 'Returned the correct number of total rows.'); } /** @@ -1990,8 +2043,8 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $records = array(); foreach ($result as $record) { $num_records++; - $this->assertTrue($record->$count_field >= 2, t('Record has the minimum count.')); - $this->assertTrue($record->$count_field >= $last_count, t('Results returned in correct order.')); + $this->assertTrue($record->$count_field >= 2, 'Record has the minimum count.'); + $this->assertTrue($record->$count_field >= $last_count, 'Results returned in correct order.'); $last_count = $record->$count_field; $records[$record->$task_field] = $record->$count_field; } @@ -2001,10 +2054,10 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { ); foreach ($correct_results as $task => $count) { - $this->assertEqual($records[$task], $count, t("Correct number of '@task' records found.", array('@task' => $task))); + $this->assertEqual($records[$task], $count, format_string("Correct number of '@task' records found.", array('@task' => $task))); } - $this->assertEqual($num_records, 1, t('Returned the correct number of total rows.')); + $this->assertEqual($num_records, 1, 'Returned the correct number of total rows.'); } /** @@ -2022,7 +2075,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 2, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 2, 'Returned the correct number of rows.'); } /** @@ -2039,7 +2092,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 6, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 6, 'Returned the correct number of rows.'); } /** @@ -2053,13 +2106,13 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 4, t('Counted the correct number of records.')); + $this->assertEqual($count, 4, 'Counted the correct number of records.'); // Now make sure we didn't break the original query! We should still have // all of the fields we asked for. $record = $query->execute()->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Correct data retrieved.')); - $this->assertEqual($record->$age_field, 27, t('Correct data retrieved.')); + $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.'); + $this->assertEqual($record->$age_field, 27, 'Correct data retrieved.'); } function testHavingCountQuery() { @@ -2070,7 +2123,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $query->addField('test', 'age'); $query->addExpression('age + 1'); $count = count($query->execute()->fetchCol()); - $this->assertEqual($count, 4, t('Counted the correct number of records.')); + $this->assertEqual($count, 4, 'Counted the correct number of records.'); } /** @@ -2085,20 +2138,20 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { // Check that the 'all_fields' statement is handled properly. $tables = $query->getTables(); - $this->assertEqual($tables['test']['all_fields'], 1, t('Query correctly sets \'all_fields\' statement.')); + $this->assertEqual($tables['test']['all_fields'], 1, 'Query correctly sets \'all_fields\' statement.'); $tables = $count->getTables(); - $this->assertFalse(isset($tables['test']['all_fields']), t('Count query correctly unsets \'all_fields\' statement.')); + $this->assertFalse(isset($tables['test']['all_fields']), 'Count query correctly unsets \'all_fields\' statement.'); // Check that the ordering clause is handled properly. $orderby = $query->getOrderBy(); - $this->assertEqual($orderby['name'], 'ASC', t('Query correctly sets ordering clause.')); + $this->assertEqual($orderby['name'], 'ASC', 'Query correctly sets ordering clause.'); $orderby = $count->getOrderBy(); - $this->assertFalse(isset($orderby['name']), t('Count query correctly unsets ordering caluse.')); + $this->assertFalse(isset($orderby['name']), 'Count query correctly unsets ordering caluse.'); // Make sure that the count query works. $count = $count->execute()->fetchField(); - $this->assertEqual($count, 4, t('Counted the correct number of records.')); + $this->assertEqual($count, 4, 'Counted the correct number of records.'); } @@ -2113,11 +2166,11 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { // records in the {test} table). $query = db_select('test'); $query->fields('test', array('fail')); - $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), t('Count Query removed fields')); + $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed fields'); $query = db_select('test'); $query->addExpression('fail'); - $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), t('Count Query removed expressions')); + $this->assertEqual(4, $query->countQuery()->execute()->fetchField(), 'Count Query removed expressions'); } /** @@ -2130,7 +2183,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 6, t('Counted the correct number of records.')); + $this->assertEqual($count, 6, 'Counted the correct number of records.'); } /** @@ -2143,7 +2196,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 3, t('Counted the correct number of records.')); + $this->assertEqual($count, 3, 'Counted the correct number of records.'); // Use a column alias as, without one, the query can succeed for the wrong // reason. @@ -2155,7 +2208,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $count = $query->countQuery()->execute()->fetchField(); - $this->assertEqual($count, 3, t('Counted the correct number of records.')); + $this->assertEqual($count, 3, 'Counted the correct number of records.'); } /** @@ -2172,7 +2225,7 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $query->condition(db_or()->condition('age', 26)->condition('age', 27)); $job = $query->execute()->fetchField(); - $this->assertEqual($job, 'Songwriter', t('Correct data retrieved.')); + $this->assertEqual($job, 'Songwriter', 'Correct data retrieved.'); } /** @@ -2185,8 +2238,8 @@ class DatabaseSelectComplexTestCase extends DatabaseTestCase { $query->addField($alias, 'job', 'otherjob'); $query->where("$alias.name <> test.name"); $crowded_job = $query->execute()->fetch(); - $this->assertEqual($crowded_job->job, $crowded_job->otherjob, t('Correctly joined same table twice.')); - $this->assertNotEqual($crowded_job->name, $crowded_job->othername, t('Correctly joined same table twice.')); + $this->assertEqual($crowded_job->job, $crowded_job->otherjob, 'Correctly joined same table twice.'); + $this->assertNotEqual($crowded_job->name, $crowded_job->othername, 'Correctly joined same table twice.'); } } @@ -2251,7 +2304,7 @@ class DatabaseSelectComplexTestCase2 extends DatabaseTestCase { // Verify that the string only has one copy of condition placeholder 0. $pos = strpos($str, 'db_condition_placeholder_0', 0); $pos2 = strpos($str, 'db_condition_placeholder_0', $pos + 1); - $this->assertFalse($pos2, "Condition placeholder is not repeated"); + $this->assertFalse($pos2, 'Condition placeholder is not repeated.'); } } @@ -2295,7 +2348,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $correct_number = $count - ($limit * $page); } - $this->assertEqual(count($data->names), $correct_number, t('Correct number of records returned by pager: @number', array('@number' => $correct_number))); + $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number))); } } @@ -2329,7 +2382,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $correct_number = $count - ($limit * $page); } - $this->assertEqual(count($data->names), $correct_number, t('Correct number of records returned by pager: @number', array('@number' => $correct_number))); + $this->assertEqual(count($data->names), $correct_number, format_string('Correct number of records returned by pager: @number', array('@number' => $correct_number))); } } @@ -2351,7 +2404,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $ages = $outer_query ->execute() ->fetchCol(); - $this->assertEqual($ages, array(25, 26, 27, 28), t('Inner pager query returned the correct ages.')); + $this->assertEqual($ages, array(25, 26, 27, 28), 'Inner pager query returned the correct ages.'); } /** @@ -2371,7 +2424,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { $ages = $query ->execute() ->fetchCol(); - $this->assertEqual($ages, array('George', 'Ringo'), t('Pager query with having expression returned the correct ages.')); + $this->assertEqual($ages, array('George', 'Ringo'), 'Pager query with having expression returned the correct ages.'); } /** @@ -2387,7 +2440,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { ->limit(1) ->execute() ->fetchField(); - $this->assertEqual($name, 'Paul', t('Pager query #1 with a specified element ID returned the correct results.')); + $this->assertEqual($name, 'Paul', 'Pager query #1 with a specified element ID returned the correct results.'); // Setting an element smaller than the previous one // should not overwrite the pager $maxElement with a smaller value. @@ -2398,7 +2451,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { ->limit(1) ->execute() ->fetchField(); - $this->assertEqual($name, 'George', t('Pager query #2 with a specified element ID returned the correct results.')); + $this->assertEqual($name, 'George', 'Pager query #2 with a specified element ID returned the correct results.'); $name = db_select('test', 't')->extend('PagerDefault') ->fields('t', array('name')) @@ -2406,7 +2459,7 @@ class DatabaseSelectPagerDefaultTestCase extends DatabaseTestCase { ->limit(1) ->execute() ->fetchField(); - $this->assertEqual($name, 'John', t('Pager query #3 with a generated element ID returned the correct results.')); + $this->assertEqual($name, 'John', 'Pager query #3 with a generated element ID returned the correct results.'); unset($_GET['page']); } @@ -2446,8 +2499,8 @@ class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase { $first = array_shift($data->tasks); $last = array_pop($data->tasks); - $this->assertEqual($first->task, $sort['first'], t('Items appear in the correct order.')); - $this->assertEqual($last->task, $sort['last'], t('Items appear in the correct order.')); + $this->assertEqual($first->task, $sort['first'], 'Items appear in the correct order.'); + $this->assertEqual($last->task, $sort['last'], 'Items appear in the correct order.'); } } @@ -2472,8 +2525,8 @@ class DatabaseSelectTableSortDefaultTestCase extends DatabaseTestCase { $first = array_shift($data->tasks); $last = array_pop($data->tasks); - $this->assertEqual($first->task, $sort['first'], t('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); - $this->assertEqual($last->task, $sort['last'], t('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); + $this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); + $this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort']))); } } @@ -2513,8 +2566,8 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addTag('test'); - $this->assertTrue($query->hasTag('test'), t('hasTag() returned true.')); - $this->assertFalse($query->hasTag('other'), t('hasTag() returned false.')); + $this->assertTrue($query->hasTag('test'), 'hasTag() returned true.'); + $this->assertFalse($query->hasTag('other'), 'hasTag() returned false.'); } /** @@ -2528,8 +2581,8 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addTag('test'); $query->addTag('other'); - $this->assertTrue($query->hasAllTags('test', 'other'), t('hasAllTags() returned true.')); - $this->assertFalse($query->hasAllTags('test', 'stuff'), t('hasAllTags() returned false.')); + $this->assertTrue($query->hasAllTags('test', 'other'), 'hasAllTags() returned true.'); + $this->assertFalse($query->hasAllTags('test', 'stuff'), 'hasAllTags() returned false.'); } /** @@ -2542,8 +2595,8 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addTag('test'); - $this->assertTrue($query->hasAnyTag('test', 'other'), t('hasAnyTag() returned true.')); - $this->assertFalse($query->hasAnyTag('other', 'stuff'), t('hasAnyTag() returned false.')); + $this->assertTrue($query->hasAnyTag('test', 'other'), 'hasAnyTag() returned true.'); + $this->assertFalse($query->hasAnyTag('other', 'stuff'), 'hasAnyTag() returned false.'); } /** @@ -2564,10 +2617,10 @@ class DatabaseTaggingTestCase extends DatabaseTestCase { $query->addMetaData('test', $data); $return = $query->getMetaData('test'); - $this->assertEqual($data, $return, t('Corect metadata returned.')); + $this->assertEqual($data, $return, 'Corect metadata returned.'); $return = $query->getMetaData('nothere'); - $this->assertNull($return, t('Non-existent key returned NULL.')); + $this->assertNull($return, 'Non-existent key returned NULL.'); } } @@ -2602,7 +2655,7 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $num_records++; } - $this->assertEqual($num_records, 2, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 2, 'Returned the correct number of rows.'); } /** @@ -2619,14 +2672,14 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $records = $result->fetchAll(); - $this->assertEqual(count($records), 2, t('Returned the correct number of rows.')); + $this->assertEqual(count($records), 2, 'Returned the correct number of rows.'); - $this->assertEqual($records[0]->name, 'George', t('Correct data retrieved.')); - $this->assertEqual($records[0]->$tid_field, 4, t('Correct data retrieved.')); - $this->assertEqual($records[0]->$task_field, 'sing', t('Correct data retrieved.')); - $this->assertEqual($records[1]->name, 'George', t('Correct data retrieved.')); - $this->assertEqual($records[1]->$tid_field, 5, t('Correct data retrieved.')); - $this->assertEqual($records[1]->$task_field, 'sleep', t('Correct data retrieved.')); + $this->assertEqual($records[0]->name, 'George', 'Correct data retrieved.'); + $this->assertEqual($records[0]->$tid_field, 4, 'Correct data retrieved.'); + $this->assertEqual($records[0]->$task_field, 'sing', 'Correct data retrieved.'); + $this->assertEqual($records[1]->name, 'George', 'Correct data retrieved.'); + $this->assertEqual($records[1]->$tid_field, 5, 'Correct data retrieved.'); + $this->assertEqual($records[1]->$task_field, 'sleep', 'Correct data retrieved.'); } /** @@ -2647,11 +2700,11 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $records = $result->fetchAll(); - $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); - $this->assertEqual($records[0]->$name_field, 'John', t('Correct data retrieved.')); - $this->assertEqual($records[0]->$tid_field, 2, t('Correct data retrieved.')); - $this->assertEqual($records[0]->$pid_field, 1, t('Correct data retrieved.')); - $this->assertEqual($records[0]->$task_field, 'sleep', t('Correct data retrieved.')); + $this->assertEqual(count($records), 1, 'Returned the correct number of rows.'); + $this->assertEqual($records[0]->$name_field, 'John', 'Correct data retrieved.'); + $this->assertEqual($records[0]->$tid_field, 2, 'Correct data retrieved.'); + $this->assertEqual($records[0]->$pid_field, 1, 'Correct data retrieved.'); + $this->assertEqual($records[0]->$task_field, 'sleep', 'Correct data retrieved.'); } /** @@ -2665,8 +2718,8 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $query->addTag('database_test_alter_change_fields'); $record = $query->execute()->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Correct data retrieved.')); - $this->assertFalse(isset($record->$age_field), t('Age field not found, as intended.')); + $this->assertEqual($record->$name_field, 'George', 'Correct data retrieved.'); + $this->assertFalse(isset($record->$age_field), 'Age field not found, as intended.'); } /** @@ -2683,8 +2736,8 @@ class DatabaseAlterTestCase extends DatabaseTestCase { // Ensure that we got the right record. $record = $result->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27*3, t('Fetched age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.'); } /** @@ -2699,7 +2752,7 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $num_records = count($query->execute()->fetchAll()); - $this->assertEqual($num_records, 4, t('Returned the correct number of rows.')); + $this->assertEqual($num_records, 4, 'Returned the correct number of rows.'); } /** @@ -2723,8 +2776,8 @@ class DatabaseAlterTestCase extends DatabaseTestCase { $name_field = $query->addField('pq', 'name'); $record = $query->execute()->fetch(); - $this->assertEqual($record->$name_field, 'George', t('Fetched name is correct.')); - $this->assertEqual($record->$age_field, 27*3, t('Fetched age expression is correct.')); + $this->assertEqual($record->$name_field, 'George', 'Fetched name is correct.'); + $this->assertEqual($record->$age_field, 27*3, 'Fetched age expression is correct.'); } } @@ -2758,31 +2811,31 @@ class DatabaseRegressionTestCase extends DatabaseTestCase { ))->execute(); $from_database = db_query('SELECT name FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); - $this->assertIdentical($name, $from_database, t("The database handles UTF-8 characters cleanly.")); + $this->assertIdentical($name, $from_database, "The database handles UTF-8 characters cleanly."); } /** * Test the db_table_exists() function. */ function testDBTableExists() { - $this->assertIdentical(TRUE, db_table_exists('node'), t('Returns true for existent table.')); - $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), t('Returns false for nonexistent table.')); + $this->assertIdentical(TRUE, db_table_exists('node'), 'Returns true for existent table.'); + $this->assertIdentical(FALSE, db_table_exists('nosuchtable'), 'Returns false for nonexistent table.'); } /** * Test the db_field_exists() function. */ function testDBFieldExists() { - $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), t('Returns true for existent column.')); - $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), t('Returns false for nonexistent column.')); + $this->assertIdentical(TRUE, db_field_exists('node', 'nid'), 'Returns true for existent column.'); + $this->assertIdentical(FALSE, db_field_exists('node', 'nosuchcolumn'), 'Returns false for nonexistent column.'); } /** * Test the db_index_exists() function. */ function testDBIndexExists() { - $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), t('Returns true for existent index.')); - $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), t('Returns false for nonexistent index.')); + $this->assertIdentical(TRUE, db_index_exists('node', 'node_created'), 'Returns true for existent index.'); + $this->assertIdentical(FALSE, db_index_exists('node', 'nosuchindex'), 'Returns false for nonexistent index.'); } } @@ -2813,10 +2866,10 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries = Database::getLog('testing', 'default'); - $this->assertEqual(count($queries), 3, t('Correct number of queries recorded.')); + $this->assertEqual(count($queries), 3, 'Correct number of queries recorded.'); foreach ($queries as $query) { - $this->assertEqual($query['caller']['function'], __FUNCTION__, t('Correct function in query log.')); + $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.'); } } @@ -2835,8 +2888,8 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); $queries2 = Database::getLog('testing2'); - $this->assertEqual(count($queries1), 2, t('Correct number of queries recorded for log 1.')); - $this->assertEqual(count($queries2), 1, t('Correct number of queries recorded for log 2.')); + $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.'); + $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.'); } /** @@ -2856,9 +2909,9 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); - $this->assertEqual(count($queries1), 2, t('Recorded queries from all targets.')); - $this->assertEqual($queries1[0]['target'], 'default', t('First query used default target.')); - $this->assertEqual($queries1[1]['target'], 'slave', t('Second query used slave target.')); + $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.'); + $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.'); + $this->assertEqual($queries1[1]['target'], 'slave', 'Second query used slave target.'); } /** @@ -2882,9 +2935,9 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); - $this->assertEqual(count($queries1), 2, t('Recorded queries from all targets.')); - $this->assertEqual($queries1[0]['target'], 'default', t('First query used default target.')); - $this->assertEqual($queries1[1]['target'], 'default', t('Second query used default target as fallback.')); + $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.'); + $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.'); + $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.'); } /** @@ -2910,8 +2963,8 @@ class DatabaseLoggingTestCase extends DatabaseTestCase { $queries1 = Database::getLog('testing1'); $queries2 = Database::getLog('testing1', 'test2'); - $this->assertEqual(count($queries1), 1, t('Correct number of queries recorded for first connection.')); - $this->assertEqual(count($queries2), 1, t('Correct number of queries recorded for second connection.')); + $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.'); + $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.'); } } @@ -2938,7 +2991,7 @@ class DatabaseSerializeQueryTestCase extends DatabaseTestCase { // assertion. $query = unserialize(serialize($query)); $results = $query->execute()->fetchCol(); - $this->assertEqual($results[0], 28, t('Query properly executed after unserialization.')); + $this->assertEqual($results[0], 28, 'Query properly executed after unserialization.'); } } @@ -2964,12 +3017,12 @@ class DatabaseRangeQueryTestCase extends DrupalWebTestCase { function testRangeQuery() { // Test if return correct number of rows. $range_rows = db_query_range("SELECT name FROM {system} ORDER BY name", 2, 3)->fetchAll(); - $this->assertEqual(count($range_rows), 3, t('Range query work and return correct number of rows.')); + $this->assertEqual(count($range_rows), 3, 'Range query work and return correct number of rows.'); // Test if return target data. $raw_rows = db_query('SELECT name FROM {system} ORDER BY name')->fetchAll(); $raw_rows = array_slice($raw_rows, 2, 3); - $this->assertEqual($range_rows, $raw_rows, t('Range query work and return target data.')); + $this->assertEqual($range_rows, $raw_rows, 'Range query work and return target data.'); } } @@ -3003,19 +3056,19 @@ class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase { $this->drupalGet('database_test/db_query_temporary'); $data = json_decode($this->drupalGetContent()); if ($data) { - $this->assertEqual($this->countTableRows("system"), $data->row_count, t('The temporary table contains the correct amount of rows.')); - $this->assertFalse(db_table_exists($data->table_name), t('The temporary table is, indeed, temporary.')); + $this->assertEqual($this->countTableRows("system"), $data->row_count, 'The temporary table contains the correct amount of rows.'); + $this->assertFalse(db_table_exists($data->table_name), 'The temporary table is, indeed, temporary.'); } else { - $this->fail(t("The creation of the temporary table failed.")); + $this->fail("The creation of the temporary table failed."); } // Now try to run two db_query_temporary() in the same request. $table_name_system = db_query_temporary('SELECT status FROM {system}', array()); $table_name_users = db_query_temporary('SELECT uid FROM {users}', array()); - $this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), t('A temporary table was created successfully in this request.')); - $this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), t('A second temporary table was created successfully in this request.')); + $this->assertEqual($this->countTableRows($table_name_system), $this->countTableRows("system"), 'A temporary table was created successfully in this request.'); + $this->assertEqual($this->countTableRows($table_name_users), $this->countTableRows("users"), 'A second temporary table was created successfully in this request.'); } } @@ -3050,7 +3103,7 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ':a4' => ' a ', ':a5' => 'test.', )); - $this->assertIdentical($result->fetchField(), 'This is a test.', t('Basic CONCAT works.')); + $this->assertIdentical($result->fetchField(), 'This is a test.', 'Basic CONCAT works.'); } /** @@ -3063,7 +3116,7 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ':a3' => '.', ':age' => 25, )); - $this->assertIdentical($result->fetchField(), 'The age of John is 25.', t('Field CONCAT works.')); + $this->assertIdentical($result->fetchField(), 'The age of John is 25.', 'Field CONCAT works.'); } /** @@ -3082,14 +3135,14 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '2', t('Found 2 records.')); + $this->assertIdentical($num_matches, '2', 'Found 2 records.'); // Match only "Ring_" using a LIKE expression with no wildcards. $num_matches = db_select('test', 't') ->condition('name', db_like('Ring_'), 'LIKE') ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '1', t('Found 1 record.')); + $this->assertIdentical($num_matches, '1', 'Found 1 record.'); } /** @@ -3113,14 +3166,14 @@ class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '2', t('Found 2 records.')); + $this->assertIdentical($num_matches, '2', 'Found 2 records.'); // Match only the former using a LIKE expression with no wildcards. $num_matches = db_select('test', 't') ->condition('name', db_like('abc%\_'), 'LIKE') ->countQuery() ->execute() ->fetchField(); - $this->assertIdentical($num_matches, '1', t('Found 1 record.')); + $this->assertIdentical($num_matches, '1', 'Found 1 record.'); } } @@ -3151,9 +3204,9 @@ class DatabaseCaseSensitivityTestCase extends DatabaseTestCase { ->execute(); $num_records_after = db_query('SELECT COUNT(*) FROM {test}')->fetchField(); - $this->assertIdentical($num_records_before + 1, (int) $num_records_after, t('Record inserts correctly.')); + $this->assertIdentical($num_records_before + 1, (int) $num_records_after, 'Record inserts correctly.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'john'))->fetchField(); - $this->assertIdentical($saved_age, '2', t('Can retrieve after inserting.')); + $this->assertIdentical($saved_age, '2', 'Can retrieve after inserting.'); } } @@ -3196,7 +3249,7 @@ class DatabaseInvalidDataTestCase extends DatabaseTestCase { 'job' => 'Singer', )) ->execute(); - $this->fail(t('Insert succeedded when it should not have.')); + $this->fail('Insert succeedded when it should not have.'); } catch (Exception $e) { // Check if the first record was inserted. @@ -3208,14 +3261,14 @@ class DatabaseInvalidDataTestCase extends DatabaseTestCase { // Database engines that don't support transactions can leave partial // inserts in place when an error occurs. This is the case for MySQL // when running on a MyISAM table. - $this->pass(t("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions")); + $this->pass("The whole transaction has not been rolled-back when a duplicate key insert occurs, this is expected because the database doesn't support transactions"); } else { - $this->fail(t('The whole transaction is rolled back when a duplicate key insert occurs.')); + $this->fail('The whole transaction is rolled back when a duplicate key insert occurs.'); } } else { - $this->pass(t('The whole transaction is rolled back when a duplicate key insert occurs.')); + $this->pass('The whole transaction is rolled back when a duplicate key insert occurs.'); } // Ensure the other values were not inserted. @@ -3224,7 +3277,7 @@ class DatabaseInvalidDataTestCase extends DatabaseTestCase { ->condition('age', array(17, 75), 'IN') ->execute()->fetchObject(); - $this->assertFalse($record, t('The rest of the insert aborted as expected.')); + $this->assertFalse($record, 'The rest of the insert aborted as expected.'); } } @@ -3252,7 +3305,7 @@ class DatabaseQueryTestCase extends DatabaseTestCase { function testArraySubstitution() { $names = db_query('SELECT name FROM {test} WHERE age IN (:ages) ORDER BY age', array(':ages' => array(25, 26, 27)))->fetchAll(); - $this->assertEqual(count($names), 3, t('Correct number of names returned')); + $this->assertEqual(count($names), 3, 'Correct number of names returned'); } } @@ -3320,19 +3373,19 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertTrue($connection->inTransaction(), t('In transaction before calling nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction before calling nested transaction.'); // We're already in a transaction, but we call ->transactionInnerLayer // to nest another transaction inside the current one. $this->transactionInnerLayer($suffix, $rollback, $ddl_statement); - $this->assertTrue($connection->inTransaction(), t('In transaction after calling nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction after calling nested transaction.'); if ($rollback) { // Roll back the transaction, if requested. // This rollback should propagate to the last savepoint. $txn->rollback(); - $this->assertTrue(($connection->transactionDepth() == $depth), t('Transaction has rolled back to the last savepoint after calling rollback().')); + $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().'); } } @@ -3358,7 +3411,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { $txn = db_transaction(); $depth2 = $connection->transactionDepth(); - $this->assertTrue($depth < $depth2, t('Transaction depth is has increased with new transaction.')); + $this->assertTrue($depth < $depth2, 'Transaction depth is has increased with new transaction.'); // Insert a single row into the testing table. db_insert('test') @@ -3368,7 +3421,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { )) ->execute(); - $this->assertTrue($connection->inTransaction(), t('In transaction inside nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.'); if ($ddl_statement) { $table = array( @@ -3383,14 +3436,14 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { ); db_create_table('database_test_1', $table); - $this->assertTrue($connection->inTransaction(), t('In transaction inside nested transaction.')); + $this->assertTrue($connection->inTransaction(), 'In transaction inside nested transaction.'); } if ($rollback) { // Roll back the transaction, if requested. // This rollback should propagate to the last savepoint. $txn->rollback(); - $this->assertTrue(($connection->transactionDepth() == $depth), t('Transaction has rolled back to the last savepoint after calling rollback().')); + $this->assertTrue(($connection->transactionDepth() == $depth), 'Transaction has rolled back to the last savepoint after calling rollback().'); } } @@ -3411,9 +3464,9 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Neither of the rows we inserted in the two transaction layers // should be present in the tables post-rollback. $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField(); - $this->assertNotIdentical($saved_age, '24', t('Cannot retrieve DavidB row after commit.')); + $this->assertNotIdentical($saved_age, '24', 'Cannot retrieve DavidB row after commit.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField(); - $this->assertNotIdentical($saved_age, '19', t('Cannot retrieve DanielB row after commit.')); + $this->assertNotIdentical($saved_age, '19', 'Cannot retrieve DanielB row after commit.'); } catch (Exception $e) { $this->fail($e->getMessage()); @@ -3437,9 +3490,9 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Because our current database claims to not support transactions, // the inserted rows should be present despite the attempt to roll back. $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidB'))->fetchField(); - $this->assertIdentical($saved_age, '24', t('DavidB not rolled back, since transactions are not supported.')); + $this->assertIdentical($saved_age, '24', 'DavidB not rolled back, since transactions are not supported.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielB'))->fetchField(); - $this->assertIdentical($saved_age, '19', t('DanielB not rolled back, since transactions are not supported.')); + $this->assertIdentical($saved_age, '19', 'DanielB not rolled back, since transactions are not supported.'); } catch (Exception $e) { $this->fail($e->getMessage()); @@ -3459,9 +3512,9 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Because we committed, both of the inserted rows should be present. $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DavidA'))->fetchField(); - $this->assertIdentical($saved_age, '24', t('Can retrieve DavidA row after commit.')); + $this->assertIdentical($saved_age, '24', 'Can retrieve DavidA row after commit.'); $saved_age = db_query('SELECT age FROM {test} WHERE name = :name', array(':name' => 'DanielA'))->fetchField(); - $this->assertIdentical($saved_age, '19', t('Can retrieve DanielA row after commit.')); + $this->assertIdentical($saved_age, '19', 'Can retrieve DanielA row after commit.'); } catch (Exception $e) { $this->fail($e->getMessage()); @@ -3553,7 +3606,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // $this->fail(t('Rolling back a transaction containing DDL should fail.')); } catch (DatabaseTransactionNoActiveException $e) { - $this->pass(t('Rolling back a transaction containing DDL should fail.')); + $this->pass('Rolling back a transaction containing DDL should fail.'); } $this->assertRowPresent('row'); } @@ -3606,7 +3659,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { */ function assertRowPresent($name, $message = NULL) { if (!isset($message)) { - $message = t('Row %name is present.', array('%name' => $name)); + $message = format_string('Row %name is present.', array('%name' => $name)); } $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); return $this->assertTrue($present, $message); @@ -3622,7 +3675,7 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { */ function assertRowAbsent($name, $message = NULL) { if (!isset($message)) { - $message = t('Row %name is absent.', array('%name' => $name)); + $message = format_string('Row %name is absent.', array('%name' => $name)); } $present = (boolean) db_query('SELECT 1 FROM {test} WHERE name = :name', array(':name' => $name))->fetchField(); return $this->assertFalse($present, $message); @@ -3646,10 +3699,10 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { $this->insertRow('inner'); // Pop the inner transaction. unset($transaction2); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the inner transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the inner transaction'); // Pop the outer transaction. unset($transaction); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the outer transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the outer transaction'); $this->assertRowPresent('outer'); $this->assertRowPresent('inner'); @@ -3662,10 +3715,10 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Pop the outer transaction, nothing should happen. unset($transaction); $this->insertRow('inner-after-outer-commit'); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the outer transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); // Pop the inner transaction, the whole transaction should commit. unset($transaction2); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the inner transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); $this->assertRowPresent('outer'); $this->assertRowPresent('inner'); $this->assertRowPresent('inner-after-outer-commit'); @@ -3679,11 +3732,11 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { // Now rollback the inner transaction. $transaction2->rollback(); unset($transaction2); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the outer transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); // Pop the outer transaction, it should commit. $this->insertRow('outer-after-inner-rollback'); unset($transaction); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the inner transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); $this->assertRowPresent('outer'); $this->assertRowAbsent('inner'); $this->assertRowPresent('outer-after-inner-rollback'); @@ -3696,11 +3749,11 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { $this->insertRow('inner'); // Pop the outer transaction, nothing should happen. unset($transaction); - $this->assertTrue($database->inTransaction(), t('Still in a transaction after popping the outer transaction')); + $this->assertTrue($database->inTransaction(), 'Still in a transaction after popping the outer transaction'); // Now rollback the inner transaction, it should rollback. $transaction2->rollback(); unset($transaction2); - $this->assertFalse($database->inTransaction(), t('Transaction closed after popping the inner transaction')); + $this->assertFalse($database->inTransaction(), 'Transaction closed after popping the inner transaction'); $this->assertRowPresent('outer'); $this->assertRowAbsent('inner'); @@ -3718,23 +3771,23 @@ class DatabaseTransactionTestCase extends DatabaseTestCase { try { $transaction->rollback(); unset($transaction); - $this->fail(t('Rolling back the outer transaction while the inner transaction is active resulted in an exception.')); + $this->fail('Rolling back the outer transaction while the inner transaction is active resulted in an exception.'); } catch (DatabaseTransactionOutOfOrderException $e) { - $this->pass(t('Rolling back the outer transaction while the inner transaction is active resulted in an exception.')); + $this->pass('Rolling back the outer transaction while the inner transaction is active resulted in an exception.'); } - $this->assertFalse($database->inTransaction(), t('No more in a transaction after rolling back the outer transaction')); + $this->assertFalse($database->inTransaction(), 'No more in a transaction after rolling back the outer transaction'); // Try to commit one inner transaction. unset($transaction3); - $this->pass(t('Trying to commit an inner transaction resulted in an exception.')); + $this->pass('Trying to commit an inner transaction resulted in an exception.'); // Try to rollback one inner transaction. try { $transaction->rollback(); unset($transaction2); - $this->fail(t('Trying to commit an inner transaction resulted in an exception.')); + $this->fail('Trying to commit an inner transaction resulted in an exception.'); } catch (DatabaseTransactionNoActiveException $e) { - $this->pass(t('Trying to commit an inner transaction resulted in an exception.')); + $this->pass('Trying to commit an inner transaction resulted in an exception.'); } $this->assertRowAbsent('outer'); $this->assertRowAbsent('inner'); @@ -3764,9 +3817,9 @@ class DatabaseNextIdCase extends DrupalWebTestCase { // We can test for exact increase in here because we know there is no // other process operating on these tables -- normally we could only // expect $second > $first. - $this->assertEqual($first + 1, $second, t('The second call from a sequence provides a number increased by one.')); + $this->assertEqual($first + 1, $second, 'The second call from a sequence provides a number increased by one.'); $result = db_next_id(1000); - $this->assertEqual($result, 1001, t('Sequence provides a larger number than the existing ID.')); + $this->assertEqual($result, 1001, 'Sequence provides a larger number than the existing ID.'); } } @@ -3788,8 +3841,8 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { function testEmpty() { $result = new DatabaseStatementEmpty(); - $this->assertTrue($result instanceof DatabaseStatementInterface, t('Class implements expected interface')); - $this->assertNull($result->fetchObject(), t('Null result returned.')); + $this->assertTrue($result instanceof DatabaseStatementInterface, 'Class implements expected interface'); + $this->assertNull($result->fetchObject(), 'Null result returned.'); } /** @@ -3799,11 +3852,11 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { $result = new DatabaseStatementEmpty(); foreach ($result as $record) { - $this->fail(t('Iterating empty result set should not iterate.')); + $this->fail('Iterating empty result set should not iterate.'); return; } - $this->pass(t('Iterating empty result set skipped iteration.')); + $this->pass('Iterating empty result set skipped iteration.'); } /** @@ -3812,6 +3865,225 @@ class DatabaseEmptyStatementTestCase extends DrupalWebTestCase { function testEmptyFetchAll() { $result = new DatabaseStatementEmpty(); - $this->assertEqual($result->fetchAll(), array(), t('Empty array returned from empty result set.')); + $this->assertEqual($result->fetchAll(), array(), 'Empty array returned from empty result set.'); + } +} + +/** + * Tests management of database connections. + */ +class ConnectionUnitTest extends DrupalUnitTestCase { + + protected $key; + protected $target; + + protected $monitor; + protected $originalCount; + + public static function getInfo() { + return array( + 'name' => 'Connection unit tests', + 'description' => 'Tests management of database connections.', + 'group' => 'Database', + ); + } + + function setUp() { + parent::setUp(); + + $this->key = 'default'; + $this->originalTarget = 'default'; + $this->target = 'DatabaseConnectionUnitTest'; + + // Determine whether the database driver is MySQL. If it is not, the test + // methods will not be executed. + // @todo Make this test driver-agnostic, or find a proper way to skip it. + // @see http://drupal.org/node/1273478 + $connection_info = Database::getConnectionInfo('default'); + $this->skipTest = (bool) $connection_info['default']['driver'] != 'mysql'; + if ($this->skipTest) { + // Insert an assertion to prevent Simpletest from interpreting the test + // as failure. + $this->pass('This test is only compatible with MySQL.'); + } + + // Create an additional connection to monitor the connections being opened + // and closed in this test. + // @see TestBase::changeDatabasePrefix() + $connection_info = Database::getConnectionInfo('default'); + Database::addConnectionInfo('default', 'monitor', $connection_info['default']); + global $databases; + $databases['default']['monitor'] = $connection_info['default']; + $this->monitor = Database::getConnection('monitor'); + } + + /** + * Adds a new database connection info to Database. + */ + protected function addConnection() { + // Add a new target to the connection, by cloning the current connection. + $connection_info = Database::getConnectionInfo($this->key); + Database::addConnectionInfo($this->key, $this->target, $connection_info[$this->originalTarget]); + + // Verify that the new target exists. + $info = Database::getConnectionInfo($this->key); + // Note: Custom assertion message to not expose database credentials. + $this->assertIdentical($info[$this->target], $connection_info[$this->key], 'New connection info found.'); + } + + /** + * Returns the connection ID of the current test connection. + * + * @return integer + */ + protected function getConnectionID() { + return (int) Database::getConnection($this->target, $this->key)->query('SELECT CONNECTION_ID()')->fetchField(); } + + /** + * Asserts that a connection ID exists. + * + * @param integer $id + * The connection ID to verify. + */ + protected function assertConnection($id) { + $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); + return $this->assertTrue(isset($list[$id]), format_string('Connection ID @id found.', array('@id' => $id))); + } + + /** + * Asserts that a connection ID does not exist. + * + * @param integer $id + * The connection ID to verify. + */ + protected function assertNoConnection($id) { + $list = $this->monitor->query('SHOW PROCESSLIST')->fetchAllKeyed(0, 0); + return $this->assertFalse(isset($list[$id]), format_string('Connection ID @id not found.', array('@id' => $id))); + } + + /** + * Tests Database::closeConnection() without query. + * + * @todo getConnectionID() executes a query. + */ + function testOpenClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a query. + */ + function testOpenQueryClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES'); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a query and custom prefetch method. + */ + function testOpenQueryPrefetchClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Execute a query. + Database::getConnection($this->target, $this->key)->query('SHOW TABLES')->fetchCol(); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + + /** + * Tests Database::closeConnection() with a select query. + */ + function testOpenSelectQueryClose() { + if ($this->skipTest) { + return; + } + // Add and open a new connection. + $this->addConnection(); + $id = $this->getConnectionID(); + Database::getConnection($this->target, $this->key); + + // Verify that there is a new connection. + $this->assertConnection($id); + + // Create a table. + $name = 'foo'; + Database::getConnection($this->target, $this->key)->schema()->createTable($name, array( + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + ), + ), + )); + + // Execute a query. + Database::getConnection($this->target, $this->key)->select('foo', 'f') + ->fields('f', array('name')) + ->execute() + ->fetchAll(); + + // Drop the table. + Database::getConnection($this->target, $this->key)->schema()->dropTable($name); + + // Close the connection. + Database::closeConnection($this->target, $this->key); + // Wait 20ms to give the database engine sufficient time to react. + usleep(20000); + + // Verify that we are back to the original connection count. + $this->assertNoConnection($id); + } + } diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index 3df31ba5f..ebaa0c034 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -2579,6 +2579,15 @@ class FileNameMungingTest extends FileTestCase { } /** + * Tests munging with a null byte in the filename. + */ + function testMungeNullByte() { + $prefix = $this->randomName(); + $filename = $prefix . '.' . $this->bad_extension . "\0.txt"; + $this->assertEqual(file_munge_filename($filename, ''), $prefix . '.' . $this->bad_extension . '_.txt', 'A filename with a null byte is correctly munged to remove the null byte.'); + } + + /** * If the allow_insecure_uploads variable evaluates to true, the file should * come out untouched, no matter how evil the filename. */ diff --git a/modules/simpletest/tests/upgrade/upgrade.test b/modules/simpletest/tests/upgrade/upgrade.test index 9df8ec779..cc849aa79 100644 --- a/modules/simpletest/tests/upgrade/upgrade.test +++ b/modules/simpletest/tests/upgrade/upgrade.test @@ -566,6 +566,20 @@ class BasicMinimalUpdatePath extends UpdatePathTestCase { // Confirm that no {menu_links} entry exists for user/autocomplete. $result = db_query('SELECT COUNT(*) FROM {menu_links} WHERE link_path = :user_autocomplete', array(':user_autocomplete' => 'user/autocomplete'))->fetchField(); $this->assertFalse($result, t('No {menu_links} entry exists for user/autocomplete')); + + // Confirm that a date format that just differs in the case can be added. + $admin_date_format = 'j M y'; + $edit = array('date_format' => $admin_date_format); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + + // Add a new date format which just differs in the case. + $admin_date_format_uppercase = 'j M Y'; + $edit = array('date_format' => $admin_date_format_uppercase); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + $this->assertText(t('Custom date format added.')); + + // Verify that the unique key on {date_formats}.format still exists. + $this->assertTrue(db_index_exists('date_formats', 'formats'), 'Unique key on {date_formats} exists'); } } diff --git a/modules/system/language.api.php b/modules/system/language.api.php index 671479309..d868b6fef 100644 --- a/modules/system/language.api.php +++ b/modules/system/language.api.php @@ -62,16 +62,22 @@ function hook_language_switch_links_alter(array &$links, $type, $path) { } /** - * Allow modules to define their own language types. + * Define language types. * * @return - * An array of language type definitions. Each language type has an identifier - * key. The language type definition is an associative array that may contain - * the following key-value pairs: - * - "name": The human-readable language type identifier. - * - "description": A description of the language type. - * - "fixed": An array of language provider identifiers. Defining this key - * makes the language type non-configurable. + * An associative array of language type definitions. The keys are the + * identifiers, which are also used as names for global variables representing + * the types in the bootstrap phase. The values are associative arrays that + * may contain the following elements: + * - name: The human-readable language type identifier. + * - description: A description of the language type. + * - fixed: A fixed array of language negotiation provider identifiers to use + * to initialize this language. Defining this key makes the language type + * non-configurable, so it will always use the specified providers in the + * given priority order. Omit to make the language type configurable. + * + * @see hook_language_types_info_alter() + * @ingroup language_negotiation */ function hook_language_types_info() { return array( @@ -90,6 +96,9 @@ function hook_language_types_info() { * * @param $language_types * Array of language type definitions. + * + * @see hook_language_types_info() + * @ingroup language_negotiation */ function hook_language_types_info_alter(array &$language_types) { if (isset($language_types['custom_language_type'])) { @@ -98,31 +107,35 @@ function hook_language_types_info_alter(array &$language_types) { } /** - * Allow modules to define their own language providers. + * Define language negotiation providers. * * @return - * An array of language provider definitions. Each language provider has an - * identifier key. The language provider definition is an associative array - * that may contain the following key-value pairs: - * - "types": An array of allowed language types. If a language provider does - * not specify which language types it should be used with, it will be - * available for all the configurable language types. - * - "callbacks": An array of functions that will be called to perform various - * tasks. Possible key-value pairs are: - * - "language": Required. The callback that will determine the language - * value. - * - "switcher": The callback that will determine the language switch links - * associated to the current language provider. - * - "url_rewrite": The callback that will provide URL rewriting. - * - "file": A file that will be included before the callback is invoked; this - * allows callback functions to be in separate files. - * - "weight": The default weight the language provider has. - * - "name": A human-readable identifier. - * - "description": A description of the language provider. - * - "config": An internal path pointing to the language provider - * configuration page. - * - "cache": The value Drupal's page cache should be set to for the current - * language provider to be invoked. + * An associative array of language negotiation provider definitions. The keys + * are provider identifiers, and the values are associative arrays definining + * each provider, with the following elements: + * - types: An array of allowed language types. If a language negotiation + * provider does not specify which language types it should be used with, it + * will be available for all the configurable language types. + * - callbacks: An associative array of functions that will be called to + * perform various tasks. Possible elements are: + * - negotiation: (required) Name of the callback function that determines + * the language value. + * - language_switch: (optional) Name of the callback function that + * determines links for a language switcher block associated with this + * provider. See language_switcher_url() for an example. + * - url_rewrite: (optional) Name of the callback function that provides URL + * rewriting, if needed by this provider. + * - file: The file where callback functions are defined (this file will be + * included before the callbacks are invoked). + * - weight: The default weight of the provider. + * - name: The translated human-readable name for the provider. + * - description: A translated longer description of the provider. + * - config: An internal path pointing to the provider's configuration page. + * - cache: The value Drupal's page cache should be set to for the current + * provider to be invoked. + * + * @see hook_language_negotiation_info_alter() + * @ingroup language_negotiation */ function hook_language_negotiation_info() { return array( @@ -135,18 +148,21 @@ function hook_language_negotiation_info() { 'file' => drupal_get_path('module', 'custom') . '/custom.module', 'weight' => -4, 'types' => array('custom_language_type'), - 'name' => t('Custom language provider'), - 'description' => t('This is a custom language provider.'), + 'name' => t('Custom language negotiation provider'), + 'description' => t('This is a custom language negotiation provider.'), 'cache' => 0, ), ); } /** - * Perform alterations on language providers. + * Perform alterations on language negoiation providers. * * @param $language_providers - * Array of language provider definitions. + * Array of language negotiation provider definitions. + * + * @see hook_language_negotiation_info() + * @ingroup language_negotiation */ function hook_language_negotiation_info_alter(array &$language_providers) { if (isset($language_providers['custom_language_provider'])) { diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 061898c85..05543be6a 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1594,6 +1594,7 @@ function system_site_information_settings_validate($form, &$form_state) { * @ingroup forms */ function system_cron_settings() { + global $base_url; $form['description'] = array( '#markup' => '<p>' . t('Cron takes care of running periodic tasks like checking for updates and indexing content for search.') . '</p>', ); @@ -1606,6 +1607,11 @@ function system_cron_settings() { $form['status'] = array( '#markup' => $status, ); + + $form['cron_url'] = array( + '#markup' => '<p>' . t('To run cron from outside the site, go to <a href="!cron">!cron</a>', array('!cron' => url($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => variable_get('cron_key', 'drupal')))))) . '</p>', + ); + $form['cron'] = array( '#type' => 'fieldset', ); diff --git a/modules/system/system.api.php b/modules/system/system.api.php index adef26141..195bc8354 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -154,7 +154,10 @@ function hook_hook_info_alter(&$hooks) { * the name of the bundle object. * - bundles: An array describing all bundles for this object type. Keys are * bundles machine names, as found in the objects' 'bundle' property - * (defined in the 'entity keys' entry above). Elements: + * (defined in the 'entity keys' entry above). This entry can be omitted if + * this entity type exposes a single bundle (all entities have the same + * collection of fields). The name of this single bundle will be the same as + * the entity type. Elements: * - label: The human-readable name of the bundle. * - uri callback: Same as the 'uri callback' key documented above for the * entity type, but for the bundle only. When determining the URI of an @@ -1201,6 +1204,10 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) { * same weight are ordered alphabetically. * - "menu_name": Optional. Set this to a custom menu if you don't want your * item to be placed in Navigation. + * - "expanded": Optional. If set to TRUE, and if a menu link is provided for + * this menu item (as a result of other properties), then the menu link is + * always expanded, equivalent to its 'always expanded' checkbox being set + * in the UI. * - "context": (optional) Defines the context a tab may appear in. By * default, all tabs are only displayed as local tasks when being rendered * in a page context. All tabs that should be accessible as contextual links @@ -1412,7 +1419,7 @@ function hook_menu_link_delete($link) { * - #link: An associative array containing: * - title: The localized title of the link. * - href: The system path to link to. - * - localized_options: An array of options to pass to url(). + * - localized_options: An array of options to pass to l(). * - #active: Whether the link should be marked as 'active'. * * @param $data @@ -1929,8 +1936,9 @@ function hook_image_toolkits() { * The drupal_mail() id of the message. Look at module source code or * drupal_mail() for possible id values. * - 'to': - * The address or addresses the message will be sent to. The - * formatting of this string must comply with RFC 2822. + * The address or addresses the message will be sent to. The formatting of + * this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * - 'from': * The address the message will be marked as being from, which is * either a custom address or the site-wide default email address. @@ -2100,7 +2108,9 @@ function hook_permission() { * specify how a particular render array is to be rendered as HTML (this is * usually the case if the theme function is assigned to the render array's * #theme property), or they return the HTML that should be returned by an - * invocation of theme(). + * invocation of theme(). See + * @link http://drupal.org/node/933976 Using the theme layer Drupal 7.x @endlink + * for more information on how to implement theme hooks. * * The following parameters are all optional. * @@ -2196,6 +2206,8 @@ function hook_permission() { * 'module', 'theme_engine', or 'theme'. * - theme path: (automatically derived) The directory path of the theme or * module, so that it doesn't need to be looked up. + * + * @see hook_theme_registry_alter() */ function hook_theme($existing, $type, $theme, $path) { return array( @@ -2290,7 +2302,8 @@ function hook_theme_registry_alter(&$theme_registry) { * @return * The machine-readable name of the theme that should be used for the current * page request. The value returned from this function will only have an - * effect if it corresponds to a currently-active theme on the site. + * effect if it corresponds to a currently-active theme on the site. Do not + * return a value if you do not wish to set a custom theme. */ function hook_custom_theme() { // Allow the user to request a particular theme via a query parameter. @@ -2476,8 +2489,9 @@ function hook_watchdog(array $log_entry) { * An array to be filled in. Elements in this array include: * - id: An ID to identify the mail sent. Look at module source code * or drupal_mail() for possible id values. - * - to: The address or addresses the message will be sent to. The - * formatting of this string must comply with RFC 2822. + * - to: The address or addresses the message will be sent to. The formatting + * of this string will be validated with the + * @link http://php.net/manual/filter.filters.validate.php PHP e-mail validation filter. @endlink * - subject: Subject of the e-mail to be sent. This must not contain any * newline characters, or the mail may not be sent properly. drupal_mail() * sets this to an empty string when the hook is invoked. @@ -3092,44 +3106,48 @@ function hook_schema() { 'description' => 'The primary identifier for a node.', 'type' => 'serial', 'unsigned' => TRUE, - 'not null' => TRUE), + 'not null' => TRUE, + ), 'vid' => array( 'description' => 'The current {node_revision}.vid version identifier.', 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, - 'default' => 0), + 'default' => 0, + ), 'type' => array( 'description' => 'The {node_type} of this node.', 'type' => 'varchar', 'length' => 32, 'not null' => TRUE, - 'default' => ''), + 'default' => '', + ), 'title' => array( 'description' => 'The title of this node, always treated as non-markup plain text.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, - 'default' => ''), + 'default' => '', ), + ), 'indexes' => array( 'node_changed' => array('changed'), 'node_created' => array('created'), - ), + ), 'unique keys' => array( 'nid_vid' => array('nid', 'vid'), - 'vid' => array('vid') - ), + 'vid' => array('vid'), + ), 'foreign keys' => array( 'node_revision' => array( 'table' => 'node_revision', 'columns' => array('vid' => 'vid'), - ), + ), 'node_author' => array( 'table' => 'users', - 'columns' => array('uid' => 'uid') - ), - ), + 'columns' => array('uid' => 'uid'), + ), + ), 'primary key' => array('nid'), ); return $schema; @@ -3236,8 +3254,7 @@ function hook_query_TAG_alter(QueryAlterableInterface $query) { * a hook_update_N() is added to the module, this function needs to be updated * to reflect the current version of the database schema. * - * See the Schema API documentation at - * @link http://drupal.org/node/146843 http://drupal.org/node/146843 @endlink + * See the @link http://drupal.org/node/146843 Schema API documentation @endlink * for details on hook_schema and how database tables are defined. * * Note that since this function is called from a full bootstrap, all functions @@ -3621,6 +3638,9 @@ function hook_registry_files_alter(&$files, $modules) { * inspect later. It is important to remove any temporary variables using * variable_del() before your last task has completed and control is handed * back to the installer. + * + * @param array $install_state + * An array of information about the current installation state. * * @return * A keyed array of tasks the profile will perform during the final stage of @@ -3679,7 +3699,7 @@ function hook_registry_files_alter(&$files, $modules) { * @see install_state_defaults() * @see batch_set() */ -function hook_install_tasks() { +function hook_install_tasks(&$install_state) { // Here, we define a variable to allow tasks to indicate that a particular, // processor-intensive batch process needs to be triggered later on in the // installation. diff --git a/modules/system/system.install b/modules/system/system.install index 7b667678e..59bb2f14a 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -744,6 +744,7 @@ function system_schema() { 'type' => 'varchar', 'length' => 100, 'not null' => TRUE, + 'binary' => TRUE, ), 'type' => array( 'description' => 'The date format type, e.g. medium.', @@ -3032,6 +3033,7 @@ function system_update_7073() { 'default' => '', 'binary' => TRUE, )); + db_drop_unique_key('file_managed', 'uri'); db_change_field('file_managed', 'uri', 'uri', array( 'description' => 'The URI to access the file (either local or remote).', 'type' => 'varchar', @@ -3040,6 +3042,7 @@ function system_update_7073() { 'default' => '', 'binary' => TRUE, )); + db_add_unique_key('file_managed', 'uri', array('uri')); } /** @@ -3086,6 +3089,21 @@ function system_update_7077() { )); } + +/** + * Add binary to {date_formats}.format. + */ +function system_update_7078() { + db_drop_unique_key('date_formats', 'formats'); + db_change_field('date_formats', 'format', 'format', array( + 'description' => 'The date format string.', + 'type' => 'varchar', + 'length' => 100, + 'not null' => TRUE, + 'binary' => TRUE, + ), array('unique keys' => array('formats' => array('format', 'type')))); +} + /** * @} End of "defgroup updates-7.x-extra". * The next series of updates should start at 8000. diff --git a/modules/system/system.updater.inc b/modules/system/system.updater.inc index 0df1ad955..a14d788b1 100644 --- a/modules/system/system.updater.inc +++ b/modules/system/system.updater.inc @@ -73,8 +73,12 @@ class ModuleUpdater extends Updater implements DrupalUpdaterInterface { return array(); } + /** + * Returns a list of post install actions. + */ public function postInstallTasks() { return array( + l(t('Install another module'), 'admin/modules/install'), l(t('Enable newly added modules'), 'admin/modules'), l(t('Administration pages'), 'admin'), ); diff --git a/modules/update/update.module b/modules/update/update.module index 85c0968d5..d5728be3e 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -139,10 +139,10 @@ function update_init() { if (!empty($verbose)) { if (isset($status[$type]['severity'])) { if ($status[$type]['severity'] == REQUIREMENT_ERROR) { - drupal_set_message($status[$type]['description'], 'error'); + drupal_set_message($status[$type]['description'], 'error', FALSE); } elseif ($status[$type]['severity'] == REQUIREMENT_WARNING) { - drupal_set_message($status[$type]['description'], 'warning'); + drupal_set_message($status[$type]['description'], 'warning', FALSE); } } } @@ -152,7 +152,7 @@ function update_init() { if (isset($status[$type]) && isset($status[$type]['reason']) && $status[$type]['reason'] === UPDATE_NOT_SECURE) { - drupal_set_message($status[$type]['description'], 'error'); + drupal_set_message($status[$type]['description'], 'error', FALSE); } } } diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index 1cc2c4a24..932c20593 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -5,6 +5,21 @@ * Admin page callback file for the user module. */ +/** + * Page callback: Generates the appropriate user administration form. + * + * This function generates the user registration, multiple user cancellation, + * or filtered user list admin form, depending on the argument and the POST + * form values. + * + * @param string $callback_arg + * (optional) Indicates which form to build. Defaults to '', which will + * trigger the user filter form. If the POST value 'op' is present, this + * function uses that value as the callback argument. + * + * @return string + * A renderable form array for the respective request. + */ function user_admin($callback_arg = '') { $op = isset($_POST['op']) ? $_POST['op'] : $callback_arg; diff --git a/modules/user/user.module b/modules/user/user.module index 622fe4d25..c1c7ec218 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -1517,15 +1517,33 @@ function theme_user_list($variables) { return theme('item_list', array('items' => $items, 'title' => $title)); } +/** + * Determines if the current user is anonymous. + * + * @return bool + * TRUE if the user is anonymous, FALSE if the user is authenticated. + */ function user_is_anonymous() { // Menu administrators can see items for anonymous when administering. return !$GLOBALS['user']->uid || !empty($GLOBALS['menu_admin']); } +/** + * Determines if the current user is logged in. + * + * @return bool + * TRUE if the user is logged in, FALSE if the user is anonymous. + */ function user_is_logged_in() { return (bool) $GLOBALS['user']->uid; } +/** + * Determines if the current user has access to the user registration page. + * + * @return bool + * TRUE if the user is not already logged in and can register for an account. + */ function user_register_access() { return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); } @@ -3353,7 +3371,7 @@ function user_filters() { $options = array(); foreach (module_implements('permission') as $module) { $function = $module . '_permission'; - if ($permissions = $function('permission')) { + if ($permissions = $function()) { asort($permissions); foreach ($permissions as $permission => $description) { $options[t('@module module', array('@module' => $module))][$permission] = t($permission); diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 2b207f224..40f552e1d 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -147,7 +147,7 @@ * 'authmap' => 'shared_', * ), * @endcode - * You can also use a reference to a schema/database as a prefix. This maybe + * You can also use a reference to a schema/database as a prefix. This may be * useful if your Drupal installation exists in a schema that is not the default * or you want to access several databases from the same code base at the same * time. @@ -435,7 +435,7 @@ ini_set('session.cookie_lifetime', 2000000); /** * String overrides: * - * To override specific strings on your site with or without enabling locale + * To override specific strings on your site with or without enabling the Locale * module, add an entry to this list. This functionality allows you to change * a small number of your site's default English language interface strings. * diff --git a/update.php b/update.php index 0c2aaf850..f9be98edf 100644 --- a/update.php +++ b/update.php @@ -1,7 +1,7 @@ <?php /** - * Root directory of Drupal installation. + * Defines the root directory of the Drupal installation. */ define('DRUPAL_ROOT', getcwd()); @@ -27,6 +27,9 @@ define('DRUPAL_ROOT', getcwd()); */ define('MAINTENANCE_MODE', 'update'); +/** + * Renders a form with a list of available database updates. + */ function update_selection_page() { drupal_set_title('Drupal database update'); $elements = drupal_get_form('update_script_selection_form'); @@ -37,6 +40,9 @@ function update_selection_page() { return $output; } +/** + * Form constructor for the list of available database module updates. + */ function update_script_selection_form($form, &$form_state) { $count = 0; $incompatible_count = 0; @@ -141,6 +147,9 @@ function update_script_selection_form($form, &$form_state) { return $form; } +/** + * Provides links to the homepage and administration pages. + */ function update_helpful_links() { // NOTE: we can't use l() here because the URL would point to // 'update.php?q=admin'. @@ -151,6 +160,9 @@ function update_helpful_links() { return $links; } +/** + * Displays results of the update script with any accompanying errors. + */ function update_results_page() { drupal_set_title('Drupal database update'); $links = update_helpful_links(); @@ -231,6 +243,15 @@ function update_results_page() { return $output; } +/** + * Provides an overview of the Drupal database update. + * + * This page provides cautionary suggestions that should happen before + * proceeding with the update to ensure data integrity. + * + * @return + * Rendered HTML form. + */ function update_info_page() { // Change query-strings on css/js files to enforce reload for all users. _drupal_flush_css_js(); @@ -256,6 +277,12 @@ function update_info_page() { return $output; } +/** + * Renders a 403 access denied page for update.php. + * + * @return + * Rendered HTML warning with 403 status. + */ function update_access_denied_page() { drupal_add_http_header('Status', '403 Forbidden'); watchdog('access denied', 'update.php', NULL, WATCHDOG_WARNING); @@ -294,7 +321,7 @@ function update_access_allowed() { } /** - * Add the update task list to the current page. + * Adds the update task list to the current page. */ function update_task_list($active = NULL) { // Default list of tasks. @@ -310,8 +337,7 @@ function update_task_list($active = NULL) { } /** - * Returns (and optionally stores) extra requirements that only apply during - * particular parts of the update.php process. + * Returns and stores extra requirements that apply during the update process. */ function update_extra_requirements($requirements = NULL) { static $extra_requirements = array(); @@ -322,7 +348,7 @@ function update_extra_requirements($requirements = NULL) { } /** - * Check update requirements and report any errors or (optionally) warnings. + * Checks update requirements and reports errors and (optionally) warnings. * * @param $skip_warnings * (optional) If set to TRUE, requirement warnings will be ignored, and a |