diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 527bee7..27942f8 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,4 @@ -// $Id: CHANGELOG.txt,v 1.340 2009/09/05 06:03:29 dries Exp $ +// $Id: CHANGELOG.txt,v 1.344 2009/09/25 23:48:23 dries Exp $ Drupal 7.0, xxxx-xx-xx (development version) ---------------------- @@ -43,6 +43,7 @@ Drupal 7.0, xxxx-xx-xx (development version) * Redesigned the add content type screen. * Highlight duplicate URL aliases. * Renamed "input formats" to "text formats". + * Moved text format permissions to the main permissions page. * Added configurable ability for users to cancel their own accounts. * Added "vertical tabs", a reusable interface component that features automatic summaries and increases usability. @@ -74,15 +75,14 @@ Drupal 7.0, xxxx-xx-xx (development version) to time zone names, e.g. Africa/Abidjan. * In some cases the upgrade and install scripts do not choose the preferred site default time zone. The automatically-selected time zone can be - corrected at admin/settings/date-time. + corrected at admin/config/regional/settings. * If your site is being upgraded from Drupal 6 and you do not have the contributed date or event modules installed, user time zone settings will fallback to the system time zone and will have to be reconfigured by each user. - Filter system: * Revamped the filter API and text format storage. + * Added support for default text formats to be assigned on a per-role basis. * Refactored the HTML corrector to take advantage of PHP 5 features. -- Removed ping module: - * Contributed modules with similar functionality are available. - User system: * Added clean API functions for creating, loading, updating, and deleting user roles and permissions. @@ -94,9 +94,6 @@ Drupal 7.0, xxxx-xx-xx (development version) at the operating system level. * Removed per-user themes: Contributed modules with similar functionality are available. -- Removed throttle module: - * Alternative methods for improving performance are available in other core and - contributed modules. - Added code registry: * Using the registry, modules declare their includable files via their .info file, allowing Drupal to lazy-load classes and interfaces as needed. @@ -171,10 +168,14 @@ Drupal 7.0, xxxx-xx-xx (development version) * Upgraded the jQuery Forms library to 2.21. * Added jQuery UI 1.7.2, which allows improvements to Drupal's user experience. -- Better module version support. +- Better module version support * Modules now can specify which version of another module they depend on. -- Blog API - * This module has been removed from core. +- Removed modules from core + * The following modules have been removed from core, because contributed + modules with similar functionality are available: + * Blog API module + * Ping module + * Throttle module - Improved node access control system. * All modules may now influence the access to a node at runtime, not just the module that defined a node. @@ -182,6 +183,15 @@ Drupal 7.0, xxxx-xx-xx (development version) them complete access to the site. * Access control affects both published and unpublished nodes. * Numerous other improvements to the node access system. +- Actions system + * Simplified definitions of actions and triggers. + * Removed dependency on the combination of hooks and operations. Triggers + now directly map to module hooks. +- Task handling + * Added a queue API to process many or long-running tasks. + * Added queue API support to cron API. + * Added a locking framework to coordinate long-running operations across + requests. Drupal 6.0, 2008-02-13 ---------------------- diff --git a/CVS/Entries b/CVS/Entries index 12fe0c2..2d63dcd 100644 --- a/CVS/Entries +++ b/CVS/Entries @@ -10,14 +10,14 @@ D/themes//// /INSTALL.mysql.txt/1.11/Thu Aug 27 22:12:13 2009// /INSTALL.pgsql.txt/1.8/Thu Aug 27 22:12:13 2009// /INSTALL.sqlite.txt/1.1/Thu Aug 27 22:12:13 2009// -/INSTALL.txt/1.75/Thu Aug 27 22:12:13 2009// /LICENSE.txt/1.7/Thu Aug 27 22:12:13 2009// -/MAINTAINERS.txt/1.32/Thu Aug 27 22:12:13 2009// -/UPGRADE.txt/1.15/Thu Aug 27 22:12:13 2009// /cron.php/1.42/Thu Aug 27 22:12:13 2009// /index.php/1.98/Thu Aug 27 22:12:14 2009// -/install.php/1.202/Thu Aug 27 22:12:14 2009// -/robots.txt/1.13/Thu Aug 27 22:12:24 2009// -/update.php/1.301/Thu Aug 27 22:12:25 2009// /xmlrpc.php/1.17/Thu Aug 27 22:12:25 2009// -/CHANGELOG.txt/1.340/Sat Sep 5 13:40:15 2009// +/CHANGELOG.txt/1.344/Fri Oct 2 19:50:11 2009// +/INSTALL.txt/1.76/Fri Oct 2 19:50:11 2009// +/MAINTAINERS.txt/1.33/Fri Oct 2 19:50:12 2009// +/UPGRADE.txt/1.16/Fri Oct 2 19:50:12 2009// +/install.php/1.211/Fri Oct 2 19:50:12 2009// +/robots.txt/1.14/Fri Oct 2 19:50:12 2009// +/update.php/1.305/Fri Oct 2 19:50:12 2009// diff --git a/INSTALL.txt b/INSTALL.txt index ee67fc1..30dd140 100644 --- a/INSTALL.txt +++ b/INSTALL.txt @@ -1,4 +1,4 @@ -// $Id: INSTALL.txt,v 1.75 2009/08/22 16:01:10 dries Exp $ +// $Id: INSTALL.txt,v 1.76 2009/09/14 07:33:55 dries Exp $ CONTENTS OF THIS FILE --------------------- @@ -119,8 +119,8 @@ INSTALLATION (e.g., http://www.example.com). You will be guided through several screens to set up the database, - create tables, add the first user account and provide basic web - site settings. + create tables, add the site maintenance account (the first user, also known + as user/1), and provide basic web site settings. The install script will attempt to create a files storage directory in the default location at sites/default/files (the location of the @@ -152,8 +152,8 @@ INSTALLATION 5. CONFIGURE DRUPAL When the install script succeeds, you will be directed to the "Welcome" - page, and you will be logged in as the administrator already. Proceed with - the initial configuration steps suggested on the "Welcome" page. + page logged in with the site maintenance account. Proceed with the initial + configuration steps suggested on the "Welcome" page. If the default Drupal theme is not displaying properly and links on the page result in "Page Not Found" errors, try manually setting the $base_url variable diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt index 43db315..1771e90 100644 --- a/MAINTAINERS.txt +++ b/MAINTAINERS.txt @@ -1,4 +1,4 @@ -// $Id: MAINTAINERS.txt,v 1.32 2009/08/25 10:58:31 dries Exp $ +// $Id: MAINTAINERS.txt,v 1.33 2009/09/26 16:51:23 dries Exp $ List of maintainers -------------------------------------------------------------------------------- @@ -49,6 +49,7 @@ Joon Park 'dvessel' UPDATE MODULE Derek Wright 'dww' +Dave Reid 'davereid' USER EXPERIENCE AND USABILITY Roy Scholten 'yoroy' diff --git a/UPGRADE.txt b/UPGRADE.txt index 5ef4321..755d4fb 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -1,4 +1,4 @@ -// $Id: UPGRADE.txt,v 1.15 2009/08/11 17:26:33 webchick Exp $ +// $Id: UPGRADE.txt,v 1.16 2009/09/14 07:33:55 dries Exp $ UPGRADING --------- @@ -36,10 +36,10 @@ Let's begin! More information on multisite configuration is located in INSTALL.txt. 2. If possible, log on as the user with user ID 1, which is the first account - created and the main administrator account. User ID 1 will be able to - automatically access update.php in step #10. There are special instructions - in step #10 if you are unable to log on as user ID 1. Do not close your - browser until the final step is complete. + created - also known as the site maintenance account. Only this account will + be able to automatically access update.php in step #10. There are special + instructions in step #10 if you are unable to log on as user ID 1. Do not + close your browser until the final step is complete. 3. Place the site in "Offline" mode, to let the database updates run without interruption and avoid displaying errors to end users of the site. This diff --git a/includes/CVS/Entries b/includes/CVS/Entries index dff7d7b..3daae65 100644 --- a/includes/CVS/Entries +++ b/includes/CVS/Entries @@ -1,39 +1,39 @@ D/database//// D/filetransfer//// -/batch.inc/1.37/Thu Aug 27 22:12:13 2009// /browser.inc/1.3/Tue Sep 1 10:21:14 2009// -/cache-install.inc/1.3/Thu Aug 27 22:12:13 2009// /entity.inc/1.1/Thu Aug 27 22:12:14 2009// -/file.inc/1.190/Tue Sep 1 10:21:15 2009// /file.mimetypes.inc/1.4/Tue Sep 1 10:21:16 2009// /graph.inc/1.3/Thu Aug 27 22:12:14 2009// -/install.inc/1.110/Thu Aug 27 22:12:14 2009// /language.inc/1.19/Thu Aug 27 22:12:14 2009// -/locale.inc/1.226/Thu Aug 27 22:12:14 2009// /lock.inc/1.1/Thu Aug 27 22:12:14 2009// -/menu.inc/1.341/Thu Aug 27 22:12:14 2009// -/module.inc/1.157/Thu Aug 27 22:12:14 2009// -/pager.inc/1.71/Thu Aug 27 22:12:14 2009// /password.inc/1.6/Thu Aug 27 22:12:14 2009// /path.inc/1.44/Thu Aug 27 22:12:14 2009// /registry.inc/1.24/Thu Aug 27 22:12:14 2009// /stream_wrappers.inc/1.6/Tue Sep 1 10:21:19 2009// -/tablesort.inc/1.53/Thu Aug 27 22:12:14 2009// -/theme.maintenance.inc/1.39/Tue Sep 1 10:21:20 2009// /token.inc/1.5/Thu Aug 27 22:12:14 2009// /unicode.entities.inc/1.2/Thu Aug 27 22:12:14 2009// -/unicode.inc/1.39/Thu Aug 27 22:12:14 2009// /xmlrpc.inc/1.61/Thu Aug 27 22:12:14 2009// -/xmlrpcs.inc/1.31/Thu Aug 27 22:12:14 2009// -/actions.inc/1.30/Thu Sep 3 08:52:44 2009// -/cache.inc/1.39/Thu Sep 3 08:52:45 2009// /image.inc/1.38/Thu Sep 3 08:52:45 2009// /mail.inc/1.25/Thu Sep 3 08:52:46 2009// -/theme.inc/1.519/Tue Sep 1 21:00:22 2009// -/update.inc/1.6/Thu Sep 3 08:52:46 2009// -/bootstrap.inc/1.303/Sat Sep 5 13:40:15 2009// /iso.inc/1.5/Sat Sep 5 13:40:15 2009// -/session.inc/1.71/Sat Sep 5 13:40:15 2009// -/ajax.inc/1.8/Sat Sep 5 15:25:49 2009// -/common.inc/1.984/Sat Sep 5 15:25:49 2009// -/form.inc/1.370/Sat Sep 5 15:25:49 2009// +/actions.inc/1.32/Fri Oct 2 19:50:12 2009// +/ajax.inc/1.12/Fri Oct 2 19:50:12 2009// +/batch.inc/1.39/Fri Oct 2 19:50:12 2009// +/bootstrap.inc/1.307/Fri Oct 2 19:50:12 2009// +/cache-install.inc/1.5/Fri Oct 2 19:50:12 2009// +/cache.inc/1.40/Fri Oct 2 19:50:12 2009// +/common.inc/1.1004/Fri Oct 2 19:50:12 2009// +/file.inc/1.192/Fri Oct 2 19:50:12 2009// +/form.inc/1.376/Fri Oct 2 19:50:12 2009// +/install.inc/1.113/Fri Oct 2 19:50:12 2009// +/locale.inc/1.229/Fri Oct 2 19:50:12 2009// +/menu.inc/1.348/Fri Oct 2 19:50:12 2009// +/module.inc/1.159/Fri Oct 2 19:50:12 2009// +/pager.inc/1.73/Fri Oct 2 19:50:12 2009// +/session.inc/1.72/Fri Oct 2 19:50:12 2009// +/tablesort.inc/1.55/Fri Oct 2 19:50:12 2009// +/theme.inc/1.527/Fri Oct 2 19:50:12 2009// +/theme.maintenance.inc/1.41/Fri Oct 2 19:50:12 2009// +/unicode.inc/1.40/Fri Oct 2 19:50:12 2009// +/update.inc/1.11/Fri Oct 2 19:50:12 2009// +/xmlrpcs.inc/1.32/Fri Oct 2 19:50:12 2009// diff --git a/includes/actions.inc b/includes/actions.inc index 396c9cb..092fc7f 100644 --- a/includes/actions.inc +++ b/includes/actions.inc @@ -1,5 +1,5 @@ $action_ids))->fetchObject(); $function = $action->callback; - $context = array_merge($context, unserialize($action->parameters)); - $actions_result[$action_ids] = $function($object, $context, $a1, $a2); + if (function_exists($function)) { + $context = array_merge($context, unserialize($action->parameters)); + $actions_result[$action_ids] = $function($object, $context, $a1, $a2); + } + else { + $actions_result[$action_ids] = FALSE; + } } // Singleton action; $action_ids is the function name. else { @@ -109,49 +115,20 @@ function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a } /** - * Discover all action functions by invoking hook_action_info(). - * - * @code - * mymodule_action_info() { - * return array( - * 'mymodule_functiondescription_action' => array( - * 'type' => 'node', - * 'description' => t('Save node'), - * 'configurable' => FALSE, - * 'hooks' => array( - * 'node' => array('delete', 'insert', 'update', 'view'), - * 'comment' => array('delete', 'insert', 'update', 'view'), - * ) - * ) - * ); - * } - * @endcode + * Discovers all available actions by invoking hook_action_info(). * - * The description is used in presenting possible actions to the user for - * configuration. The type is used to present these actions in a logical - * grouping and to denote context. Some types are 'node', 'user', 'comment', - * and 'system'. If an action is configurable it will provide form, - * validation and submission functions. The hooks the action supports - * are declared in the 'hooks' array. + * This function contrasts with actions_get_all_actions(); see the + * documentation of actions_get_all_actions() for an explanation. * * @param $reset * Reset the action info static cache. - * * @return - * An associative array keyed on function name. The value of each key is - * an array containing information about the action, such as type of - * action and description of the action, e.g.: - * @code - * $actions['node_publish_action'] = array( - * 'type' => 'node', - * 'description' => t('Publish post'), - * 'configurable' => FALSE, - * 'hooks' => array( - * 'node' => array('presave', 'insert', 'update', 'view'), - * 'comment' => array('delete', 'insert', 'update', 'view'), - * ), - * ); - * @endcode + * An associative array keyed on action function name, with the same format + * as the return value of hook_action_info(), containing all + * modules' hook_action_info() return values as modified by any + * hook_action_info_alter() implementations. + * + * @see hook_action_info() */ function actions_list($reset = FALSE) { static $actions; @@ -165,19 +142,24 @@ function actions_list($reset = FALSE) { } /** - * Retrieve all action instances from the database. + * Retrieves all action instances from the database. * - * Compare with actions_list() which gathers actions by invoking - * hook_action_info(). The two are synchronized by visiting - * /admin/structure/actions (when actions.module is enabled) which runs - * actions_synchronize(). + * This function differs from the actions_list() function, which gathers + * actions by invoking hook_action_info(). The actions returned by this + * function and the actions returned by actions_list() are partially + * synchronized. Non-configurable actions from hook_action_info() + * implementations are put into the database when actions_synchronize() is + * called, which happens when admin/config/system/actions is visited. Configurable + * actions are not added to the database until they are configured in the + * user interface, in which case a database row is created for each + * configuration of each action. * * @return - * Associative array keyed by action ID. Each value is an associative array - * with keys 'callback', 'description', 'type' and 'configurable'. + * Associative array keyed by numeric action ID. Each value is an associative + * array with keys 'callback', 'label', 'type' and 'configurable'. */ function actions_get_all_actions() { - $actions = db_query("SELECT aid, type, callback, parameters, description FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC); + $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC); foreach ($actions as &$action) { $action['configurable'] = (bool) $action['parameters']; unset($action['parameters']); @@ -187,28 +169,26 @@ function actions_get_all_actions() { } /** - * Create an associative array keyed by md5 hashes of function names. + * Creates an associative array keyed by md5 hashes of function names or IDs. * * Hashes are used to prevent actual function names from going out into HTML * forms and coming back. * * @param $actions - * An associative array with function names as keys and associative arrays - * with keys 'description', 'type', etc. as values. Generally the output of - * actions_list() or actions_get_all_actions() is given as input to this - * function. - * + * An associative array with function names or action IDs as keys + * and associative arrays with keys 'label', 'type', etc. as values. + * This is usually the output of actions_list() or actions_get_all_actions(). * @return - * An associative array keyed on md5 hash of function names. The value of - * each key is an associative array of function, description, and type for - * the action. + * An associative array whose keys are md5 hashes of the input array keys, and + * whose corresponding values are associative arrays with components + * 'callback', 'label', 'type', and 'configurable' from the input array. */ function actions_actions_map($actions) { $actions_map = array(); foreach ($actions as $callback => $array) { $key = md5($callback); $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback; - $actions_map[$key]['description'] = $array['description']; + $actions_map[$key]['label'] = $array['label']; $actions_map[$key]['type'] = $array['type']; $actions_map[$key]['configurable'] = $array['configurable']; } @@ -216,17 +196,19 @@ function actions_actions_map($actions) { } /** - * Given an md5 hash of a function name, return the function name. + * Given an md5 hash of an action array key, returns the key (function or ID). * - * Faster than actions_actions_map() when you only need the function name. + * Faster than actions_actions_map() when you only need the function name or ID. * * @param $hash - * MD5 hash of a function name. - * + * MD5 hash of a function name or action ID array key. The array key + * is a key into the return value of actions_list() (array key is the action + * function name) or actions_get_all_actions() (array key is the action ID). * @return - * The corresponding function name or FALSE if none is found. + * The corresponding array key, or FALSE if no match is found. */ function actions_function_lookup($hash) { + // Check for a function name match. $actions_list = actions_list(); foreach ($actions_list as $function => $array) { if (md5($function) == $hash) { @@ -234,26 +216,26 @@ function actions_function_lookup($hash) { } } - // Must be an instance; must check database. + // Must be a configurable action; check database. return db_query("SELECT aid FROM {actions} WHERE MD5(aid) = :hash AND parameters <> ''", array(':hash' => $hash))->fetchField(); } /** - * Synchronize actions that are provided by modules. + * Synchronizes actions that are provided by modules in hook_action_info(). * - * Actions provided by modules are synchronized with actions that are stored in - * the actions table. This is necessary so that actions that do not require - * configuration can receive action IDs. This is not necessarily the best - * approach, but it is the most straightforward. + * Actions provided by modules in hook_action_info() implementations are + * synchronized with actions that are stored in the actions database table. + * This is necessary so that actions that do not require configuration can + * receive action IDs. * * @param $delete_orphans - * Boolean if TRUE, any actions that exist in the database but are no longer + * If TRUE, any actions that exist in the database but are no longer * found in the code (for example, because the module that provides them has * been disabled) will be deleted. */ function actions_synchronize($delete_orphans = FALSE) { $actions_in_code = actions_list(TRUE); - $actions_in_db = db_query("SELECT aid, callback, description FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC); + $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC); // Go through all the actions provided by modules. foreach ($actions_in_code as $callback => $array) { @@ -272,10 +254,10 @@ function actions_synchronize($delete_orphans = FALSE) { 'type' => $array['type'], 'callback' => $callback, 'parameters' => '', - 'description' => $array['description'], + 'label' => $array['label'], )) ->execute(); - watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['description']))); + watchdog('actions', "Action '%action' added.", array('%action' => filter_xss_admin($array['label']))); } } } @@ -285,10 +267,10 @@ function actions_synchronize($delete_orphans = FALSE) { $orphaned = array_keys($actions_in_db); if ($delete_orphans) { - $actions = db_query('SELECT aid, description FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll(); + $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll(); foreach ($actions as $action) { actions_delete($action->aid); - watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->description))); + watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => filter_xss_admin($action->label))); } } else { @@ -301,7 +283,7 @@ function actions_synchronize($delete_orphans = FALSE) { } /** - * Save an action and its associated user-supplied parameter values to the database. + * Saves an action and its user-supplied parameter values to the database. * * @param $function * The name of the function to be called when this action is performed. @@ -311,16 +293,15 @@ function actions_synchronize($delete_orphans = FALSE) { * @param $params * An associative array with parameter names as keys and parameter values as * values. - * @param $desc - * A user-supplied description of this particular action, e.g., 'Send e-mail + * @param $label + * A user-supplied label of this particular action, e.g., 'Send e-mail * to Jim'. * @param $aid * The ID of this action. If omitted, a new action is created. - * * @return * The ID of the action. */ -function actions_save($function, $type, $params, $desc, $aid = NULL) { +function actions_save($function, $type, $params, $label, $aid = NULL) { // aid is the callback for singleton actions so we need to keep a separate // table for numeric aids. if (!$aid) { @@ -333,29 +314,28 @@ function actions_save($function, $type, $params, $desc, $aid = NULL) { 'callback' => $function, 'type' => $type, 'parameters' => serialize($params), - 'description' => $desc, + 'label' => $label, )) ->execute(); - watchdog('actions', 'Action %action saved.', array('%action' => $desc)); + watchdog('actions', 'Action %action saved.', array('%action' => $label)); return $aid; } /** - * Retrieve a single action from the database. + * Retrieves a single action from the database. * * @param $aid * The ID of the action to retrieve. - * * @return * The appropriate action row from the database as an object. */ function actions_load($aid) { - return db_query("SELECT aid, type, callback, parameters, description FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject(); + return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject(); } /** - * Delete a single action from the database. + * Deletes a single action from the database. * * @param $aid * The ID of the action to delete. diff --git a/includes/ajax.inc b/includes/ajax.inc index 95fbcf0..86f2110 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -1,5 +1,5 @@ command is the type of command and will * be used to find the method (it will correlate directly to a method in * the Drupal.ajax[command] space). The object may contain any other data that @@ -83,7 +80,6 @@ * * Commands are usually created with a couple of helper functions, so they * look like this: - * * @code * $commands = array(); * // Replace the content of '#object-1' on the page with 'some html here'. @@ -91,8 +87,29 @@ * // Add a visual "changed" marker to the '#object-1' element. * $commands[] = ajax_command_changed('#object-1'); * // Output new markup to the browser and end the request. + * // Note: Only custom AJAX paths/page callbacks need to do this manually. * ajax_render($commands); * @endcode + * + * When the system's default #ajax['path'] is used, the invoked callback + * function can either return a HTML string or an AJAX command structure. + * + * In case an AJAX callback returns a HTML string instead of an AJAX command + * structure, ajax_form_callback() automatically replaces the original container + * by using the ajax_command_replace() command and additionally prepends the + * returned output with any status messages. + * + * When returning an AJAX command structure, it is likely that any status + * messages shall be output with the given HTML. To achieve the same result + * using an AJAX command structure, the AJAX callback may use the following: + * @code + * $commands = array(); + * $commands[] = ajax_command_replace(NULL, $output); + * $commands[] = ajax_command_prepend(NULL, theme('status_messages')); + * return $commands; + * @endcode + * + * See @link ajax_commands AJAX framework commands @endlink */ /** @@ -105,8 +122,8 @@ * A list of macro commands generated by the use of ajax_command_*() * functions. * @param $header - * If set to FALSE the 'text/javascript' header used by drupal_json() will - * not be used, which is necessary when using an IFRAME. If set to + * If set to FALSE the 'text/javascript' header used by drupal_json_output() + * will not be used, which is necessary when using an IFRAME. If set to * 'multipart' the output will be wrapped in a textarea, which can also be * used as an alternative method when uploading files. */ @@ -123,17 +140,17 @@ function ajax_render($commands = array(), $header = TRUE) { // Use === here so that bool TRUE doesn't match 'multipart'. if ($header === 'multipart') { - // We do not use drupal_json() here because the header is not true. We are - // not really returning JSON, strictly-speaking, but rather JSON content - // wrapped in a textarea as per the "file uploads" example here: + // We do not use drupal_json_output() here because the header is not true. + // We are not really returning JSON, strictly-speaking, but rather JSON + // content wrapped in a textarea as per the "file uploads" example here: // http://malsup.com/jquery/form/#code-samples - print ''; + print ''; } else if ($header) { - drupal_json($commands); + drupal_json_output($commands); } else { - print drupal_to_js($commands); + print drupal_json_encode($commands); } exit; } @@ -184,7 +201,7 @@ function ajax_get_form() { } // Since some of the submit handlers are run, redirects need to be disabled. - $form['#redirect'] = FALSE; + $form_state['no_redirect'] = TRUE; // The form needs to be processed; prepare for that by setting a few internal // variables. @@ -196,9 +213,37 @@ function ajax_get_form() { } /** - * Menu callback for AJAX callbacks through the #ajax['callback'] Form API property. + * Menu callback; handles AJAX requests for the #ajax Form API property. + * + * This rebuilds the form from cache and invokes the defined #ajax['callback'] + * to return an AJAX command structure for JavaScript. In case no 'callback' has + * been defined, nothing will happen. + * + * The Form API #ajax property can be set both for buttons and other input + * elements. + * + * ajax_process_form() defines an additional 'formPath' JavaScript setting + * that is used by Drupal.ajax.prototype.beforeSubmit() to automatically inject + * an additional field 'ajax_triggering_element' to the submitted form values, + * which contains the array #parents of the element in the form structure. + * This additional field allows ajax_form_callback() to determine which + * element triggered the action, as non-submit form elements do not + * provide this information in $form_state['clicked_button'], which can + * also be used to determine triggering element, but only submit-type + * form elements. + * + * This function is also the canonical example of how to implement + * #ajax['path']. If processing is required that cannot be accomplished with + * a callback, re-implement this function and set #ajax['path'] to the + * enhanced function. */ function ajax_form_callback() { + // Find the triggering element, which was set up for us on the client side. + if (!empty($_REQUEST['ajax_triggering_element'])) { + $triggering_element_path = $_REQUEST['ajax_triggering_element']; + // Remove the value for form validation. + unset($_REQUEST['ajax_triggering_element']); + } list($form, $form_state, $form_id, $form_build_id) = ajax_get_form(); // Build, validate and if possible, submit the form. @@ -208,20 +253,49 @@ function ajax_form_callback() { // drupal_process_form() set up. $form = drupal_rebuild_form($form_id, $form_state, $form_build_id); - // Get the callback function from the clicked button. - $ajax = $form_state['clicked_button']['#ajax']; - $callback = $ajax['callback']; - if (function_exists($callback)) { + // $triggering_element_path in a simple form might just be 'myselect', which + // would mean we should use the element $form['myselect']. For nested form + // elements we need to recurse into the form structure to find the triggering + // element, so we can retrieve the #ajax['callback'] from it. + if (!empty($triggering_element_path)) { + if (!isset($form['#access']) || $form['#access']) { + $triggering_element = $form; + foreach (explode('/', $triggering_element_path) as $key) { + if (!empty($triggering_element[$key]) && (!isset($triggering_element[$key]['#access']) || $triggering_element[$key]['#access'])) { + $triggering_element = $triggering_element[$key]; + } + else { + // We did not find the $triggering_element or do not have #access, + // so break out and do not provide it. + $triggering_element = NULL; + break; + } + } + } + } + if (empty($triggering_element)) { + $triggering_element = $form_state['clicked_button']; + } + // Now that we have the element, get a callback if there is one. + if (!empty($triggering_element)) { + $callback = $triggering_element['#ajax']['callback']; + } + if (!empty($callback) && function_exists($callback)) { $html = $callback($form, $form_state); - // If the returned value is a string, assume it is HTML and create - // a command object to return automatically. + // If the returned value is a string, assume it is HTML, add the status + // messages, and create a command object to return automatically. We want + // the status messages inside the new wrapper, so that they get replaced + // on subsequent AJAX calls for the same wrapper. if (is_string($html)) { $commands = array(); $commands[] = ajax_command_replace(NULL, $html); + $commands[] = ajax_command_prepend(NULL, theme('status_messages')); } // Otherwise, $html is supposed to be an array of commands, suitable for - // Drupal.ajax, so we pass it on as is. + // Drupal.ajax, so we pass it on as is. In this situation, the callback is + // doing something fancy, so let it decide how to handle status messages + // without second guessing it. else { $commands = $html; } @@ -305,6 +379,7 @@ function ajax_process_form($element) { 'method' => empty($element['#ajax']['method']) ? 'replace' : $element['#ajax']['method'], 'progress' => empty($element['#ajax']['progress']) ? array('type' => 'throbber') : $element['#ajax']['progress'], 'button' => isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE, + 'formPath' => implode('/', $element['#array_parents']), ); // Convert a simple #ajax['progress'] type string into an array. @@ -335,7 +410,6 @@ function ajax_process_form($element) { /** * @defgroup ajax_commands AJAX framework commands * @{ - * @ingroup ajax */ /** diff --git a/includes/batch.inc b/includes/batch.inc index 6b2e186..7d7c349 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -1,5 +1,5 @@ TRUE, 'percentage' => $percentage, 'message' => $message)); + drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); } /** @@ -426,29 +426,24 @@ function _batch_finished() { if ($_batch['progressive']) { // Revert the 'destination' that was saved in batch_process(). if (isset($_batch['destination'])) { - $_REQUEST['destination'] = $_batch['destination']; + $_GET['destination'] = $_batch['destination']; } // Determine the target path to redirect to. - if (isset($_batch['form_state']['redirect'])) { - $redirect = $_batch['form_state']['redirect']; - } - elseif (isset($_batch['redirect'])) { - $redirect = $_batch['redirect']; - } - else { - $redirect = $_batch['source_page']; + if (!isset($_batch['form_state']['redirect'])) { + if (isset($_batch['redirect'])) { + $_batch['form_state']['redirect'] = $_batch['redirect']; + } + else { + $_batch['form_state']['redirect'] = $_batch['source_page']; + } } // Use drupal_redirect_form() to handle the redirection logic. - $form = isset($batch['form']) ? $batch['form'] : array(); - if (empty($_batch['form_state']['rebuild']) && empty($_batch['form_state']['storage'])) { - drupal_redirect_form($form, $redirect); - } + drupal_redirect_form($_batch['form_state']); - // We get here if $form['#redirect'] was FALSE, or if the form is a - // multi-step form. We save the final $form_state value to be retrieved - // by drupal_get_form(), and redirect to the originating page. + // If no redirection happened, save the final $form_state value to be + // retrieved by drupal_get_form() and redirect to the originating page. $_SESSION['batch_form_state'] = $_batch['form_state']; drupal_goto($_batch['source_page']); } diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 33b6b03..40282d8 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -1,5 +1,5 @@ '127.0.0.1', 'REQUEST_METHOD' => 'GET', 'SERVER_NAME' => NULL, - 'SERVER_SOFTWARE' => 'PHP CLI', + 'SERVER_SOFTWARE' => NULL, 'HTTP_USER_AGENT' => NULL, ); // Replace elements of the $_SERVER array, as appropriate. @@ -790,7 +782,7 @@ function drupal_page_is_cacheable($allow_caching = NULL) { } return $allow_caching_static && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') - && $_SERVER['SERVER_SOFTWARE'] !== 'PHP CLI'; + && !drupal_is_cli(); } /** @@ -851,7 +843,7 @@ function drupal_load($type, $name) { * @param $append * Whether to append the value to an existing header or to replace it. */ -function drupal_set_header($name = NULL, $value = NULL, $append = FALSE) { +function drupal_add_http_header($name = NULL, $value = NULL, $append = FALSE) { // The headers as name/value pairs. $headers = &drupal_static(__FUNCTION__, array()); @@ -893,8 +885,8 @@ function drupal_set_header($name = NULL, $value = NULL, $append = FALSE) { * A string containing the header value, or FALSE if the header has been set, * or NULL if the header has not been set. */ -function drupal_get_header($name = NULL) { - $headers = drupal_set_header(); +function drupal_get_http_header($name = NULL) { + $headers = drupal_add_http_header(); if (isset($name)) { $name = strtolower($name); return isset($headers[$name]) ? $headers[$name] : NULL; @@ -918,9 +910,9 @@ function _drupal_set_preferred_header_name($name = NULL) { } /** - * Send the HTTP response headers previously set using drupal_set_header(). + * Send the HTTP response headers previously set using drupal_add_http_header(). * Add default headers, unless they have been replaced or unset using - * drupal_set_header(). + * drupal_add_http_header(). * * @param $default_headers * An array of headers as name/value pairs. @@ -929,7 +921,7 @@ function _drupal_set_preferred_header_name($name = NULL) { */ function drupal_send_headers($default_headers = array(), $only_default = FALSE) { $headers_sent = &drupal_static(__FUNCTION__, FALSE); - $headers = drupal_get_header(); + $headers = drupal_get_http_header(); if ($only_default && $headers_sent) { $headers = array(); } @@ -1002,7 +994,7 @@ function drupal_page_header() { * * The headers allow as much as possible in proxies and browsers without any * particular knowledge about the pages. Modules can override these headers - * using drupal_set_header(). + * using drupal_add_http_header(). * * If the request is conditional (using If-Modified-Since and If-None-Match), * and the conditions match those currently in the cache, a 304 Not Modified @@ -1014,10 +1006,10 @@ function drupal_serve_page_from_cache(stdClass $cache) { $return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; // Get headers set in hook_boot(). Keys are lower-case. - $hook_boot_headers = drupal_get_header(); + $hook_boot_headers = drupal_get_http_header(); // Headers generated in this function, that may be replaced or unset using - // drupal_set_headers(). Keys are mixed-case. + // drupal_add_http_headers(). Keys are mixed-case. $default_headers = array(); foreach ($cache->headers as $name => $value) { @@ -1026,7 +1018,7 @@ function drupal_serve_page_from_cache(stdClass $cache) { // headers set in hook_boot(). $name_lower = strtolower($name); if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($hook_boot_headers[$name_lower])) { - drupal_set_header($name, $value); + drupal_add_http_header($name, $value); unset($cache->headers[$name]); } } @@ -1058,7 +1050,7 @@ function drupal_serve_page_from_cache(stdClass $cache) { // Send the remaining headers. foreach ($cache->headers as $name => $value) { - drupal_set_header($name, $value); + drupal_add_http_header($name, $value); } $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created); @@ -1392,18 +1384,7 @@ function drupal_anonymous_user($session = '') { * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE). * * @param $phase - * A constant. Allowed values are: - * DRUPAL_BOOTSTRAP_CONFIGURATION: initialize configuration. - * DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE: try to call a non-database cache fetch routine. - * DRUPAL_BOOTSTRAP_DATABASE: initialize database layer. - * DRUPAL_BOOTSTRAP_ACCESS: identify and reject banned hosts. - * DRUPAL_BOOTSTRAP_SESSION: initialize session handling. - * DRUPAL_BOOTSTRAP_VARIABLES: initialize variable handling. - * DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start - * the variable system and try to serve a page from the cache. - * DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page. - * DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request. - * DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data. + * A constant. Allowed values are the DRUPAL_BOOTSTRAP_* constants. * @param $new_phase * A boolean, set to FALSE if calling drupal_bootstrap from inside a * function called from drupal_bootstrap (recursion). @@ -1549,7 +1530,7 @@ function _drupal_bootstrap($phase) { require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc'); lock_initialize(); - if ($_SERVER['SERVER_SOFTWARE'] !== 'PHP CLI') { + if (!drupal_is_cli()) { ob_start(); drupal_page_header(); } @@ -2002,3 +1983,10 @@ function &drupal_static($name, $default_value = NULL, $reset = FALSE) { function drupal_static_reset($name = NULL) { drupal_static($name, NULL, TRUE); } + +/** + * Detect whether the current script is running in a command-line environment. + */ +function drupal_is_cli() { + return (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0))); +} diff --git a/includes/cache-install.inc b/includes/cache-install.inc index e684d0a..e397bf5 100644 --- a/includes/cache-install.inc +++ b/includes/cache-install.inc @@ -1,5 +1,5 @@ clear($cid, $wildcard); } +/** + * Check if a cache bin is empty. + * + * A cache bin is considered empty if it does not contain any valid data for any + * cache ID. + * + * @param $bin + * The cache bin to check. + * @return + * TRUE if the cache bin specified is empty. + */ +function cache_is_empty($bin) { + return _cache_get_object($bin)->isEmpty(); +} + /** * Interface for cache implementations. * @@ -260,6 +275,17 @@ interface DrupalCacheInterface { * match. If '*' is given as $cid, the bin $bin will be emptied. */ function clear($cid = NULL, $wildcard = FALSE); + + /** + * Check if a cache bin is empty. + * + * A cache bin is considered empty if it does not contain any valid data for + * any cache ID. + * + * @return + * TRUE if the cache bin specified is empty. + */ + function isEmpty(); } /** @@ -449,4 +475,14 @@ class DrupalDatabaseCache implements DrupalCacheInterface { } } } + + function isEmpty() { + $this->garbageCollection(); + $query = db_select($this->bin); + $query->addExpression('1'); + $result = $query->range(0, 1) + ->execute() + ->fetchField(); + return empty($result); + } } diff --git a/includes/common.inc b/includes/common.inc index c9290fb..b122e05 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1,5 +1,5 @@ $value) { - $key = rawurlencode($key); - if ($parent) { - $key = $parent . '[' . $key . ']'; + $string_key = ($parent ? $parent . '[' . $key . ']' : $key); + if (isset($exclude[$string_key])) { + continue; } - if (in_array($key, $exclude)) { - continue; + if (is_array($value)) { + $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key); } + else { + $params[$key] = $value; + } + } + + return $params; +} + +/** + * Parse an array into a valid, rawurlencoded query string. + * + * This differs from http_build_query() as we need to rawurlencode() (instead of + * urlencode()) all query parameters. + * + * @param $query + * The query parameter array to be processed, e.g. $_GET. + * @param $parent + * Internal use only. Used to build the $query array key for nested items. + * + * @return + * A rawurlencoded string which can be used as or appended to the URL query + * string. + * + * @see drupal_get_query_parameters() + * @ingroup php_wrappers + */ +function drupal_http_build_query(array $query, $parent = '') { + $params = array(); + + foreach ($query as $key => $value) { + $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key)); + // Recurse into children. if (is_array($value)) { - $params[] = drupal_query_string_encode($value, $exclude, $key); + $params[] = drupal_http_build_query($value, $key); + } + // If a query parameter value is NULL, only append its key. + elseif (!isset($value)) { + $params[] = $key; } else { - $params[] = $key . '=' . rawurlencode($value); + // For better readability of paths in query strings, we decode slashes. + // @see drupal_encode_path() + $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value)); } } @@ -354,7 +435,7 @@ function drupal_query_string_encode($query, $exclude = array(), $parent = '') { } /** - * Prepare a destination query string for use in combination with drupal_goto(). + * Prepare a 'destination' URL query parameter for use in combination with drupal_goto(). * * Used to direct the user back to the referring page after completing a form. * By default the current URL is returned. If a destination exists in the @@ -364,17 +445,129 @@ function drupal_query_string_encode($query, $exclude = array(), $parent = '') { * @see drupal_goto() */ function drupal_get_destination() { - if (isset($_REQUEST['destination'])) { - return 'destination=' . urlencode($_REQUEST['destination']); + $destination = &drupal_static(__FUNCTION__); + + if (isset($destination)) { + return $destination; + } + + if (isset($_GET['destination'])) { + $destination = array('destination' => $_GET['destination']); } else { - // Use $_GET here to retrieve the original path in source form. - $path = isset($_GET['q']) ? $_GET['q'] : ''; - $query = drupal_query_string_encode($_GET, array('q')); + $path = $_GET['q']; + $query = drupal_http_build_query(drupal_get_query_parameters()); if ($query != '') { $path .= '?' . $query; } - return 'destination=' . urlencode($path); + $destination = array('destination' => $path); + } + return $destination; +} + +/** + * Wrapper around parse_url() to parse a given URL into an associative array, suitable for url(). + * + * The returned array contains a 'path' that may be passed separately to url(). + * For example: + * @code + * $options = drupal_parse_url($_GET['destination']); + * $my_url = url($options['path'], $options); + * $my_link = l('Example link', $options['path'], $options); + * @endcode + * + * This is required, because url() does not support relative URLs containing a + * query string or fragment in its $path argument. Instead, any query string + * needs to be parsed into an associative query parameter array in + * $options['query'] and the fragment into $options['fragment']. + * + * @param $url + * The URL string to parse, f.e. $_GET['destination']. + * + * @return + * An associative array containing the keys: + * - 'path': The path of the URL. If the given $url is external, this includes + * the scheme and host. + * - 'query': An array of query parameters of $url, if existent. + * - 'fragment': The fragment of $url, if existent. + * + * @see url() + * @see drupal_goto() + * @ingroup php_wrappers + */ +function drupal_parse_url($url) { + $options = array( + 'path' => NULL, + 'query' => array(), + 'fragment' => '', + ); + + // External URLs: not using parse_url() here, so we do not have to rebuild + // the scheme, host, and path without having any use for it. + if (strpos($url, '://') !== FALSE) { + // Split off everything before the query string into 'path'. + $parts = explode('?', $url); + $options['path'] = $parts[0]; + // If there is a query string, transform it into keyed query parameters. + if (isset($parts[1])) { + $query_parts = explode('#', $parts[1]); + parse_str($query_parts[0], $options['query']); + // Take over the fragment, if there is any. + if (isset($query_parts[1])) { + $options['fragment'] = $query_parts[1]; + } + } + } + // Internal URLs. + else { + // parse_url() does not support relative URLs, so make it absolute. E.g. the + // relative URL "foo/bar:1" isn't properly parsed. + $parts = parse_url('http://example.com/' . $url); + // Strip the leading slash that was just added. + $options['path'] = substr($parts['path'], 1); + if (isset($parts['query'])) { + parse_str($parts['query'], $options['query']); + } + if (isset($parts['fragment'])) { + $options['fragment'] = $parts['fragment']; + } + } + + return $options; +} + +/** + * Encode a path for usage in a URL. + * + * Wrapper around rawurlencode() which avoids Apache quirks. Should be used when + * placing arbitrary data into the path component of an URL. + * + * Do not use this function to pass a path to url(). url() properly handles + * and encodes paths internally. + * This function should only be used on paths, not on query string arguments. + * Otherwise, unwanted double encoding will occur. + * + * Notes: + * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature' + * in Apache where it 404s on any path containing '%2F'. + * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean + * URLs are used, which are interpreted as delimiters by PHP. These + * characters are double escaped so PHP will still see the encoded version. + * - With clean URLs, Apache changes '//' to '/', so every second slash is + * double escaped. + * + * @param $path + * The URL path component to encode. + */ +function drupal_encode_path($path) { + if (!empty($GLOBALS['conf']['clean_url'])) { + return str_replace(array('%2F', '%26', '%23', '//'), + array('/', '%2526', '%2523', '/%252F'), + rawurlencode($path) + ); + } + else { + return str_replace('%2F', '/', rawurlencode($path)); } } @@ -417,10 +610,9 @@ function drupal_get_destination() { * supported. * @see drupal_get_destination() */ -function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { - - if (isset($_REQUEST['destination'])) { - extract(parse_url(urldecode($_REQUEST['destination']))); +function drupal_goto($path = '', array $query = array(), $fragment = NULL, $http_response_code = 302) { + if (isset($_GET['destination'])) { + extract(drupal_parse_url(urldecode($_GET['destination']))); } $args = array( @@ -456,7 +648,7 @@ function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response */ function drupal_site_offline() { drupal_maintenance_theme(); - drupal_set_header('503 Service unavailable'); + drupal_add_http_header('503 Service unavailable'); drupal_set_title(t('Site under maintenance')); print theme('maintenance_page', filter_xss_admin(variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))); @@ -466,13 +658,13 @@ function drupal_site_offline() { * Generates a 404 error if the request can not be handled. */ function drupal_not_found() { - drupal_set_header('404 Not Found'); + drupal_add_http_header('404 Not Found'); watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); // Keep old path for reference, and to allow forms to redirect to it. - if (!isset($_REQUEST['destination'])) { - $_REQUEST['destination'] = $_GET['q']; + if (!isset($_GET['destination'])) { + $_GET['destination'] = $_GET['q']; } $path = drupal_get_normal_path(variable_get('site_404', '')); @@ -498,12 +690,12 @@ function drupal_not_found() { * Generates a 403 error if the request is not allowed. */ function drupal_access_denied() { - drupal_set_header('403 Forbidden'); + drupal_add_http_header('403 Forbidden'); watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); // Keep old path for reference, and to allow forms to redirect to it. - if (!isset($_REQUEST['destination'])) { - $_REQUEST['destination'] = $_GET['q']; + if (!isset($_GET['destination'])) { + $_GET['destination'] = $_GET['q']; } $path = drupal_get_normal_path(variable_get('site_403', '')); @@ -578,11 +770,13 @@ function drupal_http_request($url, array $options = array()) { if ($uri == FALSE) { $result->error = 'unable to parse URL'; + $result->code = -1001; return $result; } if (!isset($uri['scheme'])) { $result->error = 'missing schema'; + $result->code = -1002; return $result; } @@ -611,6 +805,7 @@ function drupal_http_request($url, array $options = array()) { break; default: $result->error = 'invalid schema ' . $uri['scheme']; + $result->code = -1003; return $result; } @@ -878,7 +1073,7 @@ function _drupal_decode_exception($exception) { // The first element in the stack is the call, the second element gives us the caller. // We skip calls that occurred in one of the classes of the database layer // or in one of its global functions. - $db_functions = array('db_query', 'pager_query', 'db_query_range', 'db_query_temporary', 'update_sql'); + $db_functions = array('db_query', 'db_query_range'); while (!empty($backtrace[1]) && ($caller = $backtrace[1]) && ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) || in_array($caller['function'], $db_functions))) { @@ -947,7 +1142,7 @@ function _drupal_log_error($error, $fatal = FALSE) { } if ($fatal) { - drupal_set_header('500 Service unavailable (with message)'); + drupal_add_http_header('500 Service unavailable (with message)'); } if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { @@ -2130,42 +2325,35 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { */ /** - * Generate a URL from a Drupal menu path. Will also pass-through existing URLs. + * Generate a URL. * * @param $path - * The Drupal path being linked to, such as "admin/content", or an - * existing URL like "http://drupal.org/". The special path - * '' may also be given and will generate the site's base URL. + * The Drupal path being linked to, such as "admin/content", or an existing + * URL like "http://drupal.org/". The special path '' may also be given + * and will generate the site's base URL. * @param $options * An associative array of additional options, with the following keys: - * - 'query' - * A URL-encoded query string to append to the link, or an array of query - * key/value-pairs without any URL-encoding. - * - 'fragment' - * A fragment identifier (or named anchor) to append to the link. - * Do not include the '#' character. - * - 'absolute' (default FALSE) - * Whether to force the output to be an absolute link (beginning with - * http:). Useful for links that will be displayed outside the site, such - * as in an RSS feed. - * - 'alias' (default FALSE) - * Whether the given path is an alias already. - * - 'external' - * Whether the given path is an external URL. - * - 'language' - * An optional language object. Used to build the URL to link to and - * look up the proper alias for the link. - * - 'https' - * Whether this URL should point to a secure location. If not specified, - * the current scheme is used, so the user stays on http or https - * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS - * can only be enforced when the variable 'https' is set to TRUE. - * - 'base_url' - * Only used internally, to modify the base URL when a language dependent - * URL requires so. - * - 'prefix' - * Only used internally, to modify the path when a language dependent URL - * requires so. + * - 'query': An array of query key/value-pairs (without any URL-encoding) to + * append to the link. + * - 'fragment': A fragment identifier (or named anchor) to append to the + * link. Do not include the leading '#' character. + * - 'absolute': Defaults to FALSE. Whether to force the output to be an + * absolute link (beginning with http:). Useful for links that will be + * displayed outside the site, such as in a RSS feed. + * - 'alias': Defaults to FALSE. Whether the given path is a URL alias + * already. + * - 'external': Whether the given path is an external URL. + * - 'language': An optional language object. Used to build the URL to link to + * and look up the proper alias for the link. + * - 'https': Whether this URL should point to a secure location. If not + * specified, the current scheme is used, so the user stays on http or https + * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can + * only be enforced when the variable 'https' is set to TRUE. + * - 'base_url': Only used internally, to modify the base URL when a language + * dependent URL requires so. + * - 'prefix': Only used internally, to modify the path when a language + * dependent URL requires so. + * * @return * A string containing a URL to the given path. * @@ -2176,7 +2364,7 @@ function url($path = NULL, array $options = array()) { // Merge in defaults. $options += array( 'fragment' => '', - 'query' => '', + 'query' => array(), 'absolute' => FALSE, 'alias' => FALSE, 'https' => FALSE, @@ -2197,21 +2385,19 @@ function url($path = NULL, array $options = array()) { if ($options['fragment']) { $options['fragment'] = '#' . $options['fragment']; } - if (is_array($options['query'])) { - $options['query'] = drupal_query_string_encode($options['query']); - } if ($options['external']) { // Split off the fragment. if (strpos($path, '#') !== FALSE) { list($path, $old_fragment) = explode('#', $path, 2); + // If $options contains no fragment, take it over from the path. if (isset($old_fragment) && !$options['fragment']) { $options['fragment'] = '#' . $old_fragment; } } // Append the query. if ($options['query']) { - $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query']; + $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($options['query']); } // Reassemble. return $path . $options['fragment']; @@ -2262,28 +2448,31 @@ function url($path = NULL, array $options = array()) { $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; - $path = drupal_encode_path($prefix . $path); - if (variable_get('clean_url', '0')) { - // With Clean URLs. + // With Clean URLs. + if (!empty($GLOBALS['conf']['clean_url'])) { + $path = drupal_encode_path($prefix . $path); if ($options['query']) { - return $base . $path . '?' . $options['query'] . $options['fragment']; + return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; } else { return $base . $path . $options['fragment']; } } + // Without Clean URLs. else { - // Without Clean URLs. - $variables = array(); + $path = $prefix . $path; + $query = array(); if (!empty($path)) { - $variables[] = 'q=' . $path; + $query['q'] = $path; } - if (!empty($options['query'])) { - $variables[] = $options['query']; + if ($options['query']) { + // We do not use array_merge() here to prevent overriding $path via query + // parameters. + $query += $options['query']; } - if ($query = join('&', $variables)) { - return $base . $script . '?' . $query . $options['fragment']; + if ($query) { + return $base . $script . '?' . drupal_http_build_query($query) . $options['fragment']; } else { return $base . $options['fragment']; @@ -2403,6 +2592,7 @@ function drupal_page_footer() { _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); drupal_cache_system_paths(); + module_implements_write_cache(); } /** @@ -2463,6 +2653,8 @@ function drupal_map_assoc($array, $function = NULL) { * @param $time_limit * An integer specifying the new time limit, in seconds. A value of 0 * indicates unlimited execution time. + * + * @ingroup php_wrappers */ function drupal_set_time_limit($time_limit) { if (function_exists('set_time_limit')) { @@ -2863,9 +3055,6 @@ function drupal_load_stylesheet($file, $optimize = NULL) { * Contents of the stylesheet including the imported stylesheets. */ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { - // Replaces @import commands with the actual stylesheet content. - // This happens recursively but omits external files. - $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents); // Remove multiple charset declarations for standards compliance (and fixing Safari problems). $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents); @@ -2877,6 +3066,10 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { [\n\r] # Remove line breaks. >x', '\1', $contents); } + + // Replaces @import commands with the actual stylesheet content. + // This happens recursively but omits external files. + $contents = preg_replace_callback('/@import\s*(?:url\()?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\)?;/', '_drupal_load_stylesheet', $contents); return $contents; } @@ -3165,7 +3358,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { foreach ($items as $item) { switch ($item['type']) { case 'setting': - $output .= '\n"; + $output .= '\n"; break; case 'inline': @@ -3221,7 +3414,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { * callback function and each value an array of arguments. For example: * * @code - * $build['#attached']['drupal_set_header'] = array( + * $build['#attached']['drupal_add_http_header'] = array( * array('Content-Type', 'application/rss+xml; charset=utf-8'), * ); * @endcode @@ -3246,7 +3439,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL) { * @see drupal_render(). */ function drupal_process_attached($elements, $weight = JS_DEFAULT, $dependency_check = FALSE) { - // If there is nothing to process then return. + // If there is nothing to process then return. if (empty($elements['#attached'])) { return; } @@ -3583,7 +3776,7 @@ function drupal_clear_js_cache() { * * We use HTML-safe strings, i.e. with <, > and & escaped. */ -function drupal_to_js($var) { +function drupal_json_encode($var) { // json_encode() does not escape <, > and &, so we do it with str_replace() return str_replace(array("<", ">", "&"), array('\x3c', '\x3e', '\x26'), json_encode($var)); } @@ -3597,44 +3790,12 @@ function drupal_to_js($var) { * @param $var * (optional) If set, the variable will be converted to JSON and output. */ -function drupal_json($var = NULL) { +function drupal_json_output($var = NULL) { // We are returning JavaScript, so tell the browser. - drupal_set_header('Content-Type', 'text/javascript; charset=utf-8'); + drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8'); if (isset($var)) { - echo drupal_to_js($var); - } -} - -/** - * Wrapper around urlencode() which avoids Apache quirks. - * - * Should be used when placing arbitrary data in an URL. Note that Drupal paths - * are urlencoded() when passed through url() and do not require urlencoding() - * of individual components. - * - * Notes: - * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature' - * in Apache where it 404s on any path containing '%2F'. - * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean - * URLs are used, which are interpreted as delimiters by PHP. These - * characters are double escaped so PHP will still see the encoded version. - * - With clean URLs, Apache changes '//' to '/', so every second slash is - * double escaped. - * - This function should only be used on paths, not on query string arguments, - * otherwise unwanted double encoding will occur. - * - * @param $text - * String to encode - */ -function drupal_encode_path($text) { - if (variable_get('clean_url', '0')) { - return str_replace(array('%2F', '%26', '%23', '//'), - array('/', '%2526', '%2523', '/%252F'), - rawurlencode($text)); - } - else { - return str_replace('%2F', '/', rawurlencode($text)); + echo drupal_json_encode($var); } } @@ -3744,7 +3905,7 @@ function _drupal_bootstrap_full() { set_exception_handler('_drupal_exception_handler'); // Emit the correct charset HTTP header. - drupal_set_header('Content-Type', 'text/html; charset=utf-8'); + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); // Detect string handling method unicode_check(); // Undo magic quotes @@ -3760,7 +3921,10 @@ function _drupal_bootstrap_full() { ini_set('log_errors', 1); ini_set('error_log', file_directory_path() . '/error.log'); } - + // Set a custom theme for the current page, if there is one. We need to run + // this before invoking hook_init(), since any modules which initialize the + // theme system will prevent a custom theme from being correctly set later. + menu_set_custom_theme(); // Let all modules take action before menu system handles the request // We do not want this while running update.php. if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { @@ -3796,9 +3960,9 @@ function drupal_page_set_cache() { ); // Restore preferred header names based on the lower-case names returned - // by drupal_get_header(). + // by drupal_get_http_header(). $header_names = _drupal_set_preferred_header_name(); - foreach (drupal_get_header() as $name_lower => $value) { + foreach (drupal_get_http_header() as $name_lower => $value) { $cache->headers[$header_names[$name_lower]] = $value; } @@ -3836,6 +4000,11 @@ function drupal_cron_run() { // Fetch the cron semaphore $semaphore = variable_get('cron_semaphore', FALSE); + $return = FALSE; + // Grab the defined cron queues. + $queues = module_invoke_all('cron_queue_info'); + drupal_alter('cron_queue_info', $queues); + if ($semaphore) { if (REQUEST_TIME - $semaphore > 3600) { // Either cron has been running for more than an hour or the semaphore @@ -3851,6 +4020,11 @@ function drupal_cron_run() { } } else { + // Make sure every queue exists. There is no harm in trying to recreate an + // existing queue. + foreach ($queues as $queue_name => $info) { + DrupalQueue::get($queue_name)->createQueue(); + } // Register shutdown callback register_shutdown_function('drupal_cron_cleanup'); @@ -3868,8 +4042,19 @@ function drupal_cron_run() { variable_del('cron_semaphore'); // Return TRUE so other functions can check if it did run successfully - return TRUE; + $return = TRUE; } + + foreach ($queues as $queue_name => $info) { + $function = $info['worker callback']; + $end = time() + (isset($info['time']) ? $info['time'] : 15); + $queue = DrupalQueue::get($queue_name); + while (time() < $end && ($item = $queue->claimItem())) { + $function($item->data); + $queue->deleteItem($item); + } + } + return $return; } /** @@ -4126,7 +4311,7 @@ function drupal_render(&$elements) { } // Try to fetch the element's markup from cache and return. - if ($cached_output = drupal_render_cache_get($elements)) { + if (isset($elements['#cache']) && $cached_output = drupal_render_cache_get($elements)) { return $cached_output; } @@ -4287,7 +4472,7 @@ function show(&$element) { * * @see drupal_render() * @see drupal_render_cache_set() - * + * * @param $elements * A renderable array. * @return @@ -4324,7 +4509,7 @@ function drupal_render_cache_get($elements) { * A renderable array. */ function drupal_render_cache_set($markup, $elements) { - // Create the cache ID for the element + // Create the cache ID for the element. if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) { return FALSE; } @@ -4424,25 +4609,16 @@ function element_info($type) { if (!isset($cache)) { $basic_defaults = element_basic_defaults(); - $cache = array(); - - foreach (module_implements('elements') as $module) { - $elements = module_invoke($module, 'elements'); - if (isset($elements) && is_array($elements)) { - $cache = array_merge_recursive($cache, $elements); - } - } - if (!empty($cache)) { - foreach ($cache as $element_type => $info) { - $cache[$element_type] = array_merge_recursive($basic_defaults, $info); - $cache[$element_type]['#type'] = $element_type; - } + $cache = module_invoke_all('element_info'); + foreach ($cache as $element_type => $info) { + $cache[$element_type] = array_merge_recursive($basic_defaults, $info); + $cache[$element_type]['#type'] = $element_type; } // Allow modules to alter the element type defaults. drupal_alter('element_info', $cache); } - return $cache[$type]; + return isset($cache[$type]) ? $cache[$type] : array(); } /** @@ -4541,6 +4717,10 @@ function drupal_common_theme() { 'placeholder' => array( 'arguments' => array('text' => NULL) ), + 'html' => array( + 'arguments' => array('page' => NULL), + 'template' => 'html', + ), 'page' => array( 'arguments' => array('page' => NULL), 'template' => 'page', @@ -4636,15 +4816,12 @@ function drupal_common_theme() { 'arguments' => array('form' => NULL), ), // from menu.inc - 'menu_item_link' => array( - 'arguments' => array('item' => NULL), + 'menu_link' => array( + 'arguments' => array('element' => NULL), ), 'menu_tree' => array( 'arguments' => array('tree' => NULL), ), - 'menu_item' => array( - 'arguments' => array('link' => NULL, 'has_children' => NULL, 'menu' => ''), - ), 'menu_local_task' => array( 'arguments' => array('link' => NULL, 'active' => FALSE), ), @@ -4741,11 +4918,9 @@ function drupal_install_schema($module) { $schema = drupal_get_schema_unprocessed($module); _drupal_schema_initialize($module, $schema); - $ret = array(); foreach ($schema as $name => $table) { - db_create_table($ret, $name, $table); + db_create_table($name, $table); } - return $ret; } /** @@ -4766,13 +4941,11 @@ function drupal_uninstall_schema($module) { $schema = drupal_get_schema_unprocessed($module); _drupal_schema_initialize($module, $schema); - $ret = array(); foreach ($schema as $table) { if (db_table_exists($table['name'])) { - db_drop_table($ret, $table['name']); + db_drop_table($table['name']); } } - return $ret; } /** @@ -4807,9 +4980,10 @@ function drupal_get_schema_unprocessed($module, $table = NULL) { if (!is_null($table) && isset($schema[$table])) { return $schema[$table]; } - else { + else if (!empty($schema)) { return $schema; } + return array(); } /** @@ -5024,72 +5198,87 @@ function drupal_write_record($table, &$object, $primary_keys = array()) { */ /** - * Parse Drupal info file format. + * Parse Drupal module and theme info file format. * - * Files should use an ini-like format to specify values. - * White-space generally doesn't matter, except inside values. - * e.g. + * Info files are NOT for placing arbitrary theme and module-specific settings. + * Use variable_get() and variable_set() for that. + * + * Information stored in a module .info file: + * - name: The real name of the module for display purposes. + * - description: A brief description of the module. + * - dependencies: An array of shortnames of other modules this module requires. + * - package: The name of the package of modules this module belongs to. + * + * @see forum.info + * + * Information stored in a theme .info file: + * - name: The real name of the theme for display purposes + * - description: Brief description + * - screenshot: Path to screenshot relative to the theme's .info file. + * - engine: Theme engine, typically: engine = phptemplate + * - base: Name of a base theme, if applicable, eg: base = zen + * - regions: Listed regions eg: region[left] = Left sidebar + * - features: Features available eg: features[] = logo + * - stylesheets: Theme stylesheets eg: stylesheets[all][] = my-style.css + * - scripts: Theme scripts eg: scripts[] = my-script.css + * + * @see garland.info + * + * @param $filename + * The file we are parsing. Accepts file with relative or absolute path. + * @return + * The info array. + * + * @see drupal_parse_info_format() + */ +function drupal_parse_info_file($filename) { + if (!file_exists($filename)) { + return array(); + } + + $data = file_get_contents($filename); + return drupal_parse_info_format($data); +} + +/** + * Parse data in Drupal's .info format. * - * @verbatim + * Data should be in an .ini-like format to specify values. White-space + * generally doesn't matter, except inside values: + * @code * key = value * key = "value" * key = 'value' * key = "multi-line - * * value" * key = 'multi-line - * * value' * key * = * 'value' - * @endverbatim - * - * Arrays are created using a GET-like syntax: + * @endcode * - * @verbatim + * Arrays are created using a HTTP GET alike syntax: + * @code * key[] = "numeric array" * key[index] = "associative array" * key[index][] = "nested numeric array" * key[index][index] = "nested associative array" - * @endverbatim - * - * PHP constants are substituted in, but only when used as the entire value: + * @endcode * + * PHP constants are substituted in, but only when used as the entire value. * Comments should start with a semi-colon at the beginning of a line. * - * This function is NOT for placing arbitrary module-specific settings. Use - * variable_get() and variable_set() for that. - * - * Information stored in the module.info file: - * - name: The real name of the module for display purposes. - * - description: A brief description of the module. - * - dependencies: An array of shortnames of other modules this module requires. - * - package: The name of the package of modules this module belongs to. - * - * Example of .info file: - * @verbatim - * name = Forum - * description = Enables threaded discussions about general topics. - * dependencies[] = taxonomy - * dependencies[] = comment - * package = Core - * version = VERSION - * @endverbatim - * - * @param $filename - * The file we are parsing. Accepts file with relative or absolute path. + * @param $data + * A string to parse. * @return * The info array. + * + * @see drupal_parse_info_file() */ -function drupal_parse_info_file($filename) { +function drupal_parse_info_format($data) { $info = array(); - if (!file_exists($filename)) { - return $info; - } - - $data = file_get_contents($filename); if (preg_match_all(' @^\s* # Start at the beginning of a line, ignoring leading whitespace ((?: @@ -5229,7 +5418,7 @@ function drupal_flush_all_caches() { } drupal_theme_rebuild(); - // Rebuild content types, menu will be rebuilt as well. + menu_rebuild(); node_types_rebuild(); // Don't clear cache_form - in-progress form submissions may break. // Ordered so clearing the page cache will always be the last action. diff --git a/includes/database/CVS/Entries b/includes/database/CVS/Entries index 3c61fde..dc47a53 100644 --- a/includes/database/CVS/Entries +++ b/includes/database/CVS/Entries @@ -1,9 +1,9 @@ D/mysql//// D/pgsql//// D/sqlite//// -/database.inc/1.74/Thu Aug 27 22:12:13 2009// /log.inc/1.6/Thu Aug 27 22:12:13 2009// /prefetch.inc/1.6/Thu Aug 27 22:12:14 2009// /query.inc/1.31/Tue Sep 1 10:21:20 2009// -/schema.inc/1.20/Tue Sep 1 10:21:20 2009// -/select.inc/1.21/Tue Sep 1 10:21:20 2009// +/database.inc/1.78/Fri Oct 2 19:50:13 2009// +/schema.inc/1.21/Fri Oct 2 19:50:13 2009// +/select.inc/1.24/Fri Oct 2 19:50:13 2009// diff --git a/includes/database/database.inc b/includes/database/database.inc index b38f354..2e09bc8 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1,5 +1,5 @@ query($query, $args, $options); } @@ -1830,30 +1829,26 @@ function db_query($query, $args = array(), $options = array()) { * The prepared statement query to run. Although it will accept both * named and unnamed placeholders, named placeholders are strongly preferred * as they are more self-documenting. + * @param $from + * The first record from the result set to return. + * @param $limit + * The number of records to return from the result set. * @param $args * An array of values to substitute into the query. If the query uses named * placeholders, this is an associative array in any order. If the query uses * unnamed placeholders (?), this is an indexed array and the order must match * the order of placeholders in the query string. - * @param $from - * The first record from the result set to return. - * @param $limit - * The number of records to return from the result set. * @param $options * An array of options to control how the query operates. * @return * A prepared statement object, already executed. */ -function db_query_range($query, $args, $from = 0, $count = 0, $options = array()) { - if (!is_array($args)) { - $args = func_get_args(); - array_shift($args); - $count = array_pop($args); - $from = array_pop($args); +function db_query_range($query, $from, $count, array $args = array(), array $options = array()) { + if (empty($options['target'])) { + $options['target'] = 'default'; } - list($query, $args, $options) = _db_query_process_args($query, $args, $options); - return Database::getConnection($options['target'])->queryRange($query, $args, $from, $count, $options); + return Database::getConnection($options['target'])->queryRange($query, $from, $count, $args, $options); } /** @@ -1874,12 +1869,10 @@ function db_query_range($query, $args, $from = 0, $count = 0, $options = array() * @return * The name of the temporary table. */ -function db_query_temporary($query, $args, $options = array()) { - if (!is_array($args)) { - $args = func_get_args(); - array_shift($args); +function db_query_temporary($query, array $args = array(), array $options = array()) { + if (empty($options['target'])) { + $options['target'] = 'default'; } - list($query, $args, $options) = _db_query_process_args($query, $args, $options); return Database::getConnection($options['target'])->queryTemporary($query, $args, $options); } @@ -2048,42 +2041,6 @@ function db_escape_table($table) { return Database::getConnection()->escapeTable($table); } -/** - * Perform an SQL query and return success or failure. - * - * @param $sql - * A string containing a complete SQL query. %-substitution - * parameters are not supported. - * @return - * An array containing the keys: - * success: a boolean indicating whether the query succeeded - * query: the SQL query executed, passed through check_plain() - */ -function update_sql($sql) { - $result = Database::getConnection()->query($sql); - return array('success' => $result !== FALSE, 'query' => check_plain($sql)); -} - -/** - * Wraps the given table.field entry with a DISTINCT(). The wrapper is added to - * the SELECT list entry of the given query and the resulting query is returned. - * This function only applies the wrapper if a DISTINCT doesn't already exist in - * the query. - * - * @todo Remove this. - * @param $table - * Table containing the field to set as DISTINCT - * @param $field - * Field to set as DISTINCT - * @param $query - * Query to apply the wrapper to - * @return - * SQL query with the DISTINCT wrapper surrounding the given table.field. - */ -function db_distinct_field($table, $field, $query) { - return Database::getConnection()->distinctField($table, $field, $query); -} - /** * Retrieve the name of the currently active database driver, such as * "mysql" or "pgsql". @@ -2122,15 +2079,15 @@ function db_close(array $options = array()) { /** * Create a new table from a Drupal table definition. * - * @param $ret - * Array to which query results will be added. * @param $name * The name of the table to create. * @param $table * A Schema API table definition array. */ -function db_create_table(&$ret, $name, $table) { - return Database::getConnection()->schema()->createTable($ret, $name, $table); +function db_create_table($name, $table) { + if (!db_table_exists($name)) { + return Database::getConnection()->schema()->createTable($name, $table); + } } /** @@ -2190,34 +2147,28 @@ function db_type_map() { /** * Rename a table. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be renamed. * @param $new_name * The new name for the table. */ -function db_rename_table(&$ret, $table, $new_name) { - return Database::getConnection()->schema()->renameTable($ret, $table, $new_name); +function db_rename_table($table, $new_name) { + return Database::getConnection()->schema()->renameTable($table, $new_name); } /** * Drop a table. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be dropped. */ -function db_drop_table(&$ret, $table) { - return Database::getConnection()->schema()->dropTable($ret, $table); +function db_drop_table($table) { + return Database::getConnection()->schema()->dropTable($table); } /** * Add a new field to a table. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table to be altered. * @param $field @@ -2237,29 +2188,25 @@ function db_drop_table(&$ret, $table) { * explanation why. * @see db_change_field() */ -function db_add_field(&$ret, $table, $field, $spec, $keys_new = array()) { - return Database::getConnection()->schema()->addField($ret, $table, $field, $spec, $keys_new); +function db_add_field($table, $field, $spec, $keys_new = array()) { + return Database::getConnection()->schema()->addField($table, $field, $spec, $keys_new); } /** * Drop a field. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field * The field to be dropped. */ -function db_drop_field(&$ret, $table, $field) { - return Database::getConnection()->schema()->dropField($ret, $table, $field); +function db_drop_field($table, $field) { + return Database::getConnection()->schema()->dropField($table, $field); } /** * Set the default value for a field. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field @@ -2267,55 +2214,47 @@ function db_drop_field(&$ret, $table, $field) { * @param $default * Default value to be set. NULL for 'default NULL'. */ -function db_field_set_default(&$ret, $table, $field, $default) { - return Database::getConnection()->schema()->fieldSetDefault($ret, $table, $field, $default); +function db_field_set_default($table, $field, $default) { + return Database::getConnection()->schema()->fieldSetDefault($table, $field, $default); } /** * Set a field to have no default value. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field * The field to be altered. */ -function db_field_set_no_default(&$ret, $table, $field) { - return Database::getConnection()->schema()->fieldSetNoDefault($ret, $table, $field); +function db_field_set_no_default($table, $field) { + return Database::getConnection()->schema()->fieldSetNoDefault($table, $field); } /** * Add a primary key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $fields * Fields for the primary key. */ -function db_add_primary_key(&$ret, $table, $fields) { - return Database::getConnection()->schema()->addPrimaryKey($ret, $table, $fields); +function db_add_primary_key($table, $fields) { + return Database::getConnection()->schema()->addPrimaryKey($table, $fields); } /** * Drop the primary key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. */ -function db_drop_primary_key(&$ret, $table) { - return Database::getConnection()->schema()->dropPrimaryKey($ret, $table); +function db_drop_primary_key($table) { + return Database::getConnection()->schema()->dropPrimaryKey($table); } /** * Add a unique key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name @@ -2323,29 +2262,25 @@ function db_drop_primary_key(&$ret, $table) { * @param $fields * An array of field names. */ -function db_add_unique_key(&$ret, $table, $name, $fields) { - return Database::getConnection()->schema()->addUniqueKey($ret, $table, $name, $fields); +function db_add_unique_key($table, $name, $fields) { + return Database::getConnection()->schema()->addUniqueKey($table, $name, $fields); } /** * Drop a unique key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name * The name of the key. */ -function db_drop_unique_key(&$ret, $table, $name) { - return Database::getConnection()->schema()->dropUniqueKey($ret, $table, $name); +function db_drop_unique_key($table, $name) { + return Database::getConnection()->schema()->dropUniqueKey($table, $name); } /** * Add an index. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name @@ -2353,22 +2288,20 @@ function db_drop_unique_key(&$ret, $table, $name) { * @param $fields * An array of field names. */ -function db_add_index(&$ret, $table, $name, $fields) { - return Database::getConnection()->schema()->addIndex($ret, $table, $name, $fields); +function db_add_index($table, $name, $fields) { + return Database::getConnection()->schema()->addIndex($table, $name, $fields); } /** * Drop an index. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name * The name of the index. */ -function db_drop_index(&$ret, $table, $name) { - return Database::getConnection()->schema()->dropIndex($ret, $table, $name); +function db_drop_index($table, $name) { + return Database::getConnection()->schema()->dropIndex($table, $name); } /** @@ -2394,8 +2327,8 @@ function db_drop_index(&$ret, $table, $name) { * and you want to change foo.bar to be type serial, leaving it as the * primary key. The correct sequence is: * @code - * db_drop_primary_key($ret, 'foo'); - * db_change_field($ret, 'foo', 'bar', 'bar', + * db_drop_primary_key('foo'); + * db_change_field('foo', 'bar', 'bar', * array('type' => 'serial', 'not null' => TRUE), * array('primary key' => array('bar'))); * @endcode @@ -2418,8 +2351,6 @@ function db_drop_index(&$ret, $table, $name) { * unless you are converting a field to be type serial. You can use * the $keys_new argument in all cases. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table. * @param $field @@ -2433,9 +2364,8 @@ function db_drop_index(&$ret, $table, $name) { * table along with changing the field. The format is the same as a * table specification but without the 'fields' element. */ - -function db_change_field(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) { - return Database::getConnection()->schema()->changeField($ret, $table, $field, $field_new, $spec, $keys_new); +function db_change_field($table, $field, $field_new, $spec, $keys_new = array()) { + return Database::getConnection()->schema()->changeField($table, $field, $field_new, $spec, $keys_new); } /** @@ -2451,7 +2381,7 @@ function _db_error_page($error = '') { global $db_type; drupal_language_initialize(); drupal_maintenance_theme(); - drupal_set_header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable'); + drupal_add_http_header($_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable'); drupal_set_title('Site offline'); } @@ -2473,24 +2403,6 @@ function db_ignore_slave() { } } -/** - * @ingroup database-legacy - * - * These functions are no longer necessary, as the DatabaseStatementInterface interface - * offers this and much more functionality. They are kept temporarily for backward - * compatibility during conversion and should be removed as soon as possible. - * - * @{ - */ - -function db_fetch_object(DatabaseStatementInterface $statement) { - return $statement->fetch(PDO::FETCH_OBJ); -} - -function db_result(DatabaseStatementInterface $statement) { - return $statement->fetchField(); -} - /** * Redirect the user to the installation script if Drupal has not been * installed yet (i.e., if no $databases array has been defined in the @@ -2502,152 +2414,4 @@ function _db_check_install_needed() { include_once DRUPAL_ROOT . '/includes/install.inc'; install_goto('install.php'); } -} - -/** - * Backward-compatibility utility. - * - * This function should be removed after all queries have been converted - * to the new API. It is temporary only. - * - * @todo Remove this once the query conversion is complete. - */ -function _db_query_process_args($query, $args, $options) { - - if (!is_array($options)) { - $options = array(); - } - if (empty($options['target'])) { - $options['target'] = 'default'; - } - - // Temporary backward-compatibility hacks. Remove later. - $old_query = $query; - $query = str_replace(array('%n', '%d', '%f', '%b', "'%s'", '%s'), '?', $old_query); - if ($old_query !== $query) { - $args = array_values($args); // The old system allowed named arrays, but PDO doesn't if you use ?. - } - - return array($query, $args, $options); -} - -/** - * Helper function for db_rewrite_sql. - * - * Collects JOIN and WHERE statements via hook_db_rewrite_sql() - * Decides whether to select primary_key or DISTINCT(primary_key) - * - * @todo Remove this function when all code has been converted to query_alter. - * @param $query - * Query to be rewritten. - * @param $primary_table - * Name or alias of the table which has the primary key field for this query. - * Typical table names would be: {block}, {comment}, {forum}, {node}, - * {menu}, {taxonomy_term_data} or {taxonomy_vocabulary}. However, in most cases the usual - * table alias (b, c, f, n, m, t or v) is used instead of the table name. - * @param $primary_field - * Name of the primary field. - * @param $args - * Array of additional arguments. - * @return - * An array: join statements, where statements, field or DISTINCT(field). - */ -function _db_rewrite_sql($query = '', $primary_table = 'n', $primary_field = 'nid', $args = array()) { - $where = array(); - $join = array(); - $distinct = FALSE; - foreach (module_implements('db_rewrite_sql') as $module) { - $result = module_invoke($module, 'db_rewrite_sql', $query, $primary_table, $primary_field, $args); - if (isset($result) && is_array($result)) { - if (isset($result['where'])) { - $where[] = $result['where']; - } - if (isset($result['join'])) { - $join[] = $result['join']; - } - if (isset($result['distinct']) && $result['distinct']) { - $distinct = TRUE; - } - } - elseif (isset($result)) { - $where[] = $result; - } - } - - $where = empty($where) ? '' : '(' . implode(') AND (', $where) . ')'; - $join = empty($join) ? '' : implode(' ', $join); - - return array($join, $where, $distinct); -} - -/** - * Rewrites node, taxonomy and comment queries. Use it for listing queries. Do not - * use FROM table1, table2 syntax, use JOIN instead. - * - * @todo Remove this function when all code has been converted to query_alter. - * @param $query - * Query to be rewritten. - * @param $primary_table - * Name or alias of the table which has the primary key field for this query. - * Typical table names would be: {block}, {comment}, {forum}, {node}, - * {menu}, {taxonomy_term_data} or {taxonomy_vocabulary}. However, it is more common to use the - * the usual table aliases: b, c, f, n, m, t or v. - * @param $primary_field - * Name of the primary field. - * @param $args - * An array of arguments, passed to the implementations of hook_db_rewrite_sql. - * @return - * The original query with JOIN and WHERE statements inserted from - * hook_db_rewrite_sql implementations. nid is rewritten if needed. - */ -function db_rewrite_sql($query, $primary_table = 'n', $primary_field = 'nid', $args = array()) { - list($join, $where, $distinct) = _db_rewrite_sql($query, $primary_table, $primary_field, $args); - - if ($distinct) { - $query = db_distinct_field($primary_table, $primary_field, $query); - } - - if (!empty($where) || !empty($join)) { - $pattern = '{ - # Beginning of the string - ^ - ((?P - # Everything within this set of parentheses is named "anonymous view" - (?: - [^()]++ # anything not parentheses - | - \( (?P>anonymous_view) \) # an open parenthesis, more "anonymous view" and finally a close parenthesis. - )* - )[^()]+WHERE) - }x'; - preg_match($pattern, $query, $matches); - if ($where) { - $n = strlen($matches[1]); - $second_part = substr($query, $n); - $first_part = substr($matches[1], 0, $n - 5) . " $join WHERE $where AND ( "; - foreach (array('GROUP', 'ORDER', 'LIMIT') as $needle) { - $pos = strrpos($second_part, $needle); - if ($pos !== FALSE) { - // All needles are five characters long. - $pos += 5; - break; - } - } - if ($pos === FALSE) { - $query = $first_part . $second_part . ')'; - } - else { - $query = $first_part . substr($second_part, 0, -$pos) . ')' . substr($second_part, -$pos); - } - } - else { - $query = $matches[1] . " $join " . substr($query, strlen($matches[1])); - } - } - - return $query; -} - -/** - * @} End of "ingroup database-legacy". - */ +} \ No newline at end of file diff --git a/includes/database/mysql/CVS/Entries b/includes/database/mysql/CVS/Entries index 574a49e..0c18224 100644 --- a/includes/database/mysql/CVS/Entries +++ b/includes/database/mysql/CVS/Entries @@ -1,5 +1,5 @@ /install.inc/1.3/Thu Aug 27 22:12:14 2009// /query.inc/1.13/Thu Aug 27 22:12:14 2009// -/schema.inc/1.26/Tue Sep 1 10:21:21 2009// -/database.inc/1.19/Sat Sep 5 13:40:15 2009// +/database.inc/1.20/Fri Oct 2 19:50:13 2009// +/schema.inc/1.27/Fri Oct 2 19:50:13 2009// D diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index 1e87911..b547836 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -1,5 +1,5 @@ exec("SET sql_mode='ANSI,TRADITIONAL'"); } - public function queryRange($query, array $args, $from, $count, array $options = array()) { + public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { return $this->query($query . ' LIMIT ' . $from . ', ' . $count, $args, $options); } - public function queryTemporary($query, array $args, array $options = array()) { + public function queryTemporary($query, array $args = array(), array $options = array()) { $tablename = $this->generateTemporaryTableName(); $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY SELECT', $query), $args, $options); return $tablename; @@ -68,15 +68,6 @@ class DatabaseConnection_mysql extends DatabaseConnection { // We don't want to override any of the defaults. return NULL; } - - /** - * @todo Remove this as soon as db_rewrite_sql() has been exterminated. - */ - public function distinctField($table, $field, $query) { - $field_to_select = 'DISTINCT(' . $table . '.' . $field . ')'; - // (?connection->query('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}'); } - public function dropTable(&$ret, $table) { - $ret[] = update_sql('DROP TABLE {' . $table . '}'); + public function dropTable($table) { + $this->connection->query('DROP TABLE {' . $table . '}'); } - public function addField(&$ret, $table, $field, $spec, $keys_new = array()) { + public function addField($table, $field, $spec, $keys_new = array()) { $fixnull = FALSE; if (!empty($spec['not null']) && !isset($spec['default'])) { $fixnull = TRUE; @@ -287,24 +287,23 @@ class DatabaseSchema_mysql extends DatabaseSchema { if (count($keys_new)) { $query .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new)); } - $ret[] = update_sql($query); + $this->connection->query($query); if (isset($spec['initial'])) { - // All this because update_sql does not support %-placeholders. - $sql = 'UPDATE {' . $table . '} SET ' . $field . ' = :value'; - $result = db_query($sql, array(':value' => $spec['initial'])); - $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql . ' (' . $spec['initial'] . ')')); + $this->connection->update($table) + ->fields(array($field, $spec['initial'])) + ->execute(); } if ($fixnull) { $spec['not null'] = TRUE; - $this->changeField($ret, $table, $field, $field, $spec); + $this->changeField($table, $field, $field, $spec); } } - public function dropField(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP `' . $field . '`'); + public function dropField($table, $field) { + $this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`'); } - public function fieldSetDefault(&$ret, $table, $field, $default) { + public function fieldSetDefault($table, $field, $default) { if (is_null($default)) { $default = 'NULL'; } @@ -312,44 +311,43 @@ class DatabaseSchema_mysql extends DatabaseSchema { $default = is_string($default) ? "'$default'" : $default; } - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $default); + $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $default); } - public function fieldSetNoDefault(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT'); + public function fieldSetNoDefault($table, $field) { + $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT'); } - public function addPrimaryKey(&$ret, $table, $fields) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')'); + public function addPrimaryKey($table, $fields) { + $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')'); } - public function dropPrimaryKey(&$ret, $table) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP PRIMARY KEY'); + public function dropPrimaryKey($table) { + $this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY'); } - public function addUniqueKey(&$ret, $table, $name, $fields) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')'); + public function addUniqueKey($table, $name, $fields) { + $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')'); } - public function dropUniqueKey(&$ret, $table, $name) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`'); + public function dropUniqueKey($table, $name) { + $this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`'); } - public function addIndex(&$ret, $table, $name, $fields) { - $query = 'ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')'; - $ret[] = update_sql($query); + public function addIndex($table, $name, $fields) { + $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')'); } - public function dropIndex(&$ret, $table, $name) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`'); + public function dropIndex($table, $name) { + $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`'); } - public function changeField(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) { + public function changeField($table, $field, $field_new, $spec, $keys_new = array()) { $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec)); if (count($keys_new)) { $sql .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new)); } - $ret[] = update_sql($sql); + $this->connection->query($sql); } public function prepareComment($comment, $length = NULL) { @@ -374,11 +372,11 @@ class DatabaseSchema_mysql extends DatabaseSchema { $condition->condition('column_name', $column); $condition->compile($this->connection, $this); // Don't use {} around information_schema.columns table. - return db_query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField(); + return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField(); } $condition->compile($this->connection, $this); // Don't use {} around information_schema.tables table. - $comment = db_query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField(); + $comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField(); // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379 return preg_replace('/; InnoDB free:.*$/', '', $comment); } diff --git a/includes/database/pgsql/CVS/Entries b/includes/database/pgsql/CVS/Entries index e98d3a4..13518d9 100644 --- a/includes/database/pgsql/CVS/Entries +++ b/includes/database/pgsql/CVS/Entries @@ -1,5 +1,5 @@ -/database.inc/1.29/Thu Aug 27 22:12:14 2009// /install.inc/1.4/Thu Aug 27 22:12:14 2009// -/query.inc/1.15/Tue Sep 1 10:21:21 2009// -/schema.inc/1.20/Thu Aug 27 22:12:14 2009// +/database.inc/1.30/Fri Oct 2 19:50:13 2009// +/query.inc/1.16/Fri Oct 2 19:50:13 2009// +/schema.inc/1.21/Fri Oct 2 19:50:13 2009// D diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index e08543f..bf14ad3 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -1,5 +1,5 @@ query($query . ' LIMIT ' . $count . ' OFFSET ' . $from, $args, $options); } - public function queryTemporary($query, array $args, array $options = array()) { + public function queryTemporary($query, array $args = array(), array $options = array()) { $tablename = $this->generateTemporaryTableName(); $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options); return $tablename; @@ -126,15 +126,6 @@ class DatabaseConnection_pgsql extends DatabaseConnection { return isset($specials[$operator]) ? $specials[$operator] : NULL; } - - /** - * @todo Remove this as soon as db_rewrite_sql() has been exterminated. - */ - public function distinctField($table, $field, $query) { - $field_to_select = 'DISTINCT(' . $table . '.' . $field . ')'; - // (?rowCount(); } } + +class SelectQuery_pgsql extends SelectQuery { + + public function orderRandom() { + $alias = $this->addExpression('RANDOM()', 'random_field'); + $this->orderBy($alias); + return $this; + } + +} diff --git a/includes/database/pgsql/schema.inc b/includes/database/pgsql/schema.inc index 6818534..c7c6979 100644 --- a/includes/database/pgsql/schema.inc +++ b/includes/database/pgsql/schema.inc @@ -1,5 +1,5 @@ array(), ); // Don't use {} around information_schema.columns table. - $result = db_query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(':schema' => $schema, ':table' => $table_name, ':default' => '%nextval%')); + $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array( + ':schema' => $schema, + ':table' => $table_name, + ':default' => '%nextval%', + )); foreach ($result as $column) { if ($column->data_type == 'bytea') { $table_information->blob_fields[$column->column_name] = TRUE; @@ -245,7 +249,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { 'date:normal' => 'date', 'datetime:normal' => 'timestamp without time zone', - + 'time:normal' => 'time without time zone', 'serial:tiny' => 'serial', @@ -258,49 +262,29 @@ class DatabaseSchema_pgsql extends DatabaseSchema { } protected function _createKeySql($fields) { - $ret = array(); + $return = array(); foreach ($fields as $field) { if (is_array($field)) { - $ret[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')'; + $return[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')'; } else { - $ret[] = '"' . $field . '"'; + $return[] = '"' . $field . '"'; } } - return implode(', ', $ret); + return implode(', ', $return); } - /** - * Rename a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be renamed. - * @param $new_name - * The new name for the table. - */ - function renameTable(&$ret, $table, $new_name) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}'); + function renameTable($table, $new_name) { + $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}'); } - /** - * Drop a table. - * - * @param $ret - * Array to which query results will be added. - * @param $table - * The table to be dropped. - */ - public function dropTable(&$ret, $table) { - $ret[] = update_sql('DROP TABLE {' . $table . '}'); + public function dropTable($table) { + $this->connection->query('DROP TABLE {' . $table . '}'); } /** * Add a new field to a table. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table to be altered. * @param $field @@ -321,7 +305,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * * @see db_change_field() */ - public function addField(&$ret, $table, $field, $spec, $new_keys = array()) { + public function addField($table, $field, $spec, $new_keys = array()) { $fixnull = FALSE; if (!empty($spec['not null']) && !isset($spec['default'])) { $fixnull = TRUE; @@ -329,44 +313,39 @@ class DatabaseSchema_pgsql extends DatabaseSchema { } $query = 'ALTER TABLE {' . $table . '} ADD COLUMN '; $query .= $this->createFieldSql($field, $this->processField($spec)); - $ret[] = update_sql($query); + $this->connection->query($query); if (isset($spec['initial'])) { - // All this because update_sql does not support %-placeholders. - $sql = 'UPDATE {' . $table . '} SET ' . $field . ' = :value'; - $result = db_query($sql, array(':value' => $spec['initial'])); - $ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql . ' (' . $spec['initial'] . ')')); + $this->connection->update($table) + ->fields(array($field, $spec['initial'])) + ->execute(); } if ($fixnull) { - $ret[] = update_sql("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL"); + $this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL"); } if (isset($new_keys)) { - $this->_createKeys($ret, $table, $new_keys); + $this->_createKeys($table, $new_keys); } // Add column comment. if (!empty($spec['description'])) { - $ret[] = update_sql('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description'])); + $this->connection->query('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description'])); } } /** * Drop a field. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field * The field to be dropped. */ - public function dropField(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"'); + public function dropField($table, $field) { + $this->connection->query('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"'); } /** * Set the default value for a field. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field @@ -374,7 +353,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * @param $default * Default value to be set. NULL for 'default NULL'. */ - public function fieldSetDefault(&$ret, $table, $field, $default) { + public function fieldSetDefault($table, $field, $default) { if (is_null($default)) { $default = 'NULL'; } @@ -382,54 +361,46 @@ class DatabaseSchema_pgsql extends DatabaseSchema { $default = is_string($default) ? "'$default'" : $default; } - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default); + $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default); } /** * Set a field to have no default value. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field * The field to be altered. */ - public function fieldSetNoDefault(&$ret, $table, $field) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT'); + public function fieldSetNoDefault($table, $field) { + $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT'); } /** * Add a primary key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $fields * Fields for the primary key. */ - public function addPrimaryKey(&$ret, $table, $fields) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')'); + public function addPrimaryKey($table, $fields) { + $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')'); } /** * Drop the primary key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. */ - public function dropPrimaryKey(&$ret, $table) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP CONSTRAINT {' . $table . '}_pkey'); + public function dropPrimaryKey($table) { + $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT {' . $table . '}_pkey'); } /** * Add a unique key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name @@ -437,31 +408,27 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * @param $fields * An array of field names. */ - function addUniqueKey(&$ret, $table, $name, $fields) { + function addUniqueKey($table, $name, $fields) { $name = '{' . $table . '}_' . $name . '_key'; - $ret[] = update_sql('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $name . '" UNIQUE (' . implode(',', $fields) . ')'); + $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $name . '" UNIQUE (' . implode(',', $fields) . ')'); } /** * Drop a unique key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name * The name of the key. */ - public function dropUniqueKey(&$ret, $table, $name) { + public function dropUniqueKey($table, $name) { $name = '{' . $table . '}_' . $name . '_key'; - $ret[] = update_sql('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $name . '"'); + $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $name . '"'); } /** * Add an index. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name @@ -469,23 +436,21 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * @param $fields * An array of field names. */ - public function addIndex(&$ret, $table, $name, $fields) { - $ret[] = update_sql($this->_createIndexSql($table, $name, $fields)); + public function addIndex($table, $name, $fields) { + $this->connection->query($this->_createIndexSql($table, $name, $fields)); } /** * Drop an index. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name * The name of the index. */ - public function dropIndex(&$ret, $table, $name) { + public function dropIndex($table, $name) { $name = '{' . $table . '}_' . $name . '_idx'; - $ret[] = update_sql('DROP INDEX ' . $name); + $this->connection->query('DROP INDEX ' . $name); } /** @@ -511,8 +476,8 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * and you want to change foo.bar to be type serial, leaving it as the * primary key. The correct sequence is: * @code - * db_drop_primary_key($ret, 'foo'); - * db_change_field($ret, 'foo', 'bar', 'bar', + * db_drop_primary_key('foo'); + * db_change_field('foo', 'bar', 'bar', * array('type' => 'serial', 'not null' => TRUE), * array('primary key' => array('bar'))); * @endcode @@ -535,8 +500,6 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * unless you are converting a field to be type serial. You can use * the $new_keys argument in all cases. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table. * @param $field @@ -550,15 +513,15 @@ class DatabaseSchema_pgsql extends DatabaseSchema { * table along with changing the field. The format is the same as a * table specification but without the 'fields' element. */ - public function changeField(&$ret, $table, $field, $field_new, $spec, $new_keys = array()) { - $ret[] = update_sql('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field . '_old"'); + public function changeField($table, $field, $field_new, $spec, $new_keys = array()) { + $this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field . '_old"'); $not_null = isset($spec['not null']) ? $spec['not null'] : FALSE; unset($spec['not null']); if (!array_key_exists('size', $spec)) { $spec['size'] = 'normal'; } - $this->addField($ret, $table, "$field_new", $spec); + $this->addField($table, "$field_new", $spec); // We need to typecast the new column to best be able to transfer the data // Schema_pgsql::getFieldTypeMap() will return possibilities that are not @@ -568,16 +531,16 @@ class DatabaseSchema_pgsql extends DatabaseSchema { if (in_array($typecast, array('serial', 'bigserial', 'numeric'))) { $typecast = 'int'; } - $ret[] = update_sql("UPDATE {" . $table . "} SET $field_new = CAST(" . $field . "_old as " . $typecast . ")"); + $this->connection->query("UPDATE {" . $table . "} SET $field_new = CAST(" . $field . "_old as " . $typecast . ")"); if ($not_null) { - $ret[] = update_sql("ALTER TABLE {" . $table . "} ALTER $field_new SET NOT NULL"); + $this->connection->query("ALTER TABLE {" . $table . "} ALTER $field_new SET NOT NULL"); } - $this->dropField($ret, $table, $field . '_old'); + $this->dropField($table, $field . '_old'); if (isset($new_keys)) { - $this->_createKeys($ret, $table, $new_keys); + $this->_createKeys($table, $new_keys); } } @@ -587,18 +550,18 @@ class DatabaseSchema_pgsql extends DatabaseSchema { return $query; } - protected function _createKeys(&$ret, $table, $new_keys) { + protected function _createKeys($table, $new_keys) { if (isset($new_keys['primary key'])) { - $this->addPrimaryKey($ret, $table, $new_keys['primary key']); + $this->addPrimaryKey($table, $new_keys['primary key']); } if (isset($new_keys['unique keys'])) { foreach ($new_keys['unique keys'] as $name => $fields) { - $this->addUniqueKey($ret, $table, $name, $fields); + $this->addUniqueKey($table, $name, $fields); } } if (isset($new_keys['indexes'])) { foreach ($new_keys['indexes'] as $name => $fields) { - $this->addIndex($ret, $table, $name, $fields); + $this->addIndex($table, $name, $fields); } } } @@ -610,8 +573,8 @@ class DatabaseSchema_pgsql extends DatabaseSchema { $table = $this->connection->prefixTables('{' . $table . '}'); // Don't use {} around pg_class, pg_attribute tables. if (isset($column)) { - return db_query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($table, $column))->fetchField(); + $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($table, $column))->fetchField(); } - return db_query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $table))->fetchField(); + $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $table))->fetchField(); } } diff --git a/includes/database/schema.inc b/includes/database/schema.inc index 0f4382a..435f348 100644 --- a/includes/database/schema.inc +++ b/includes/database/schema.inc @@ -1,5 +1,5 @@ 'serial', 'not null' => TRUE), * array('primary key' => array('bar'))); * @endcode @@ -471,8 +446,6 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { * unless you are converting a field to be type serial. You can use * the $keys_new argument in all cases. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table. * @param $field @@ -486,22 +459,20 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { * table along with changing the field. The format is the same as a * table specification but without the 'fields' element. */ - abstract public function changeField(&$ret, $table, $field, $field_new, $spec, $keys_new = array()); + abstract public function changeField($table, $field, $field_new, $spec, $keys_new = array()); /** * Create a new table from a Drupal table definition. * - * @param $ret - * Array to which query results will be added. * @param $name * The name of the table to create. * @param $table * A Schema API table definition array. */ - public function createTable(&$ret, $name, $table) { - $statements = $this->createTableSql($name, $table); + public function createTable($name, $table) { + $statements = $this->createTableSql($name, $table); foreach ($statements as $statement) { - $ret[] = update_sql($statement); + $this->connection->query($statement); } } @@ -517,16 +488,16 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { * An array of field names. */ public function fieldNames($fields) { - $ret = array(); + $return = array(); foreach ($fields as $field) { if (is_array($field)) { - $ret[] = $field[0]; + $return[] = $field[0]; } else { - $ret[] = $field; + $return[] = $field; } } - return $ret; + return $return; } /** diff --git a/includes/database/select.inc b/includes/database/select.inc index 8ec197a..ff51b52 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -1,5 +1,5 @@ getUnion(); + * @endcode + * + * @return + * A reference to the union query array structure. + */ + public function &getUnion(); + /** * Compiles and returns an associative array of the arguments for this prepared statement. * @@ -328,6 +347,28 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn */ public function orderBy($field, $direction = 'ASC'); + /** + * Orders the result set by a random value. + * + * This may be stacked with other orderBy() calls. If so, the query will order + * by each specified field, including this one, in the order called. Although + * this method may be called multiple times on the same query, doing so + * is not particularly useful. + * + * Note: The method used by most drivers may not scale to very large result + * sets. If you need to work with extremely large data sets, you may create + * your own database driver by subclassing off of an existing driver and + * implementing your own randomization mechanism. See + * + * http://jan.kneschke.de/projects/mysql/order-by-rand/ + * + * for an example of such an alternate sorting mechanism. + * + * @return + * The called object + */ + public function orderRandom(); + /** * Restricts a query to a given range in the result set. * @@ -344,6 +385,31 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn */ public function range($start = NULL, $length = NULL); + /** + * Add another Select query to UNION to this one. + * + * Union queries consist of two or more queries whose + * results are effectively concatenated together. Queries + * will be UNIONed in the order they are specified, with + * this object's query coming first. Duplicate columns will + * be discarded. All forms of UNION are supported, using + * the second '$type' argument. + * + * Note: All queries UNIONed together must have the same + * field structure, in the same order. It is up to the + * caller to ensure that they match properly. If they do + * not, an SQL syntax error will result. + * + * @param $query + * The query to UNION to this query. + * @param $type + * The type of UNION to add to the query. Defaults to plain + * UNION. + * @return + * The called object. + */ + public function union(SelectQueryInterface $query, $type = ''); + /** * Groups the result set by the specified field. * @@ -524,6 +590,10 @@ class SelectQueryExtender implements SelectQueryInterface { return $this->query->getTables(); } + public function &getUnion() { + return $this->query->getUnion(); + } + public function getArguments(QueryPlaceholderInterface $queryPlaceholder = NULL) { return $this->query->getArguments($queryPlaceholder); } @@ -595,11 +665,21 @@ class SelectQueryExtender implements SelectQueryInterface { return $this; } + public function orderRandom() { + $this->query->orderRandom(); + return $this; + } + public function range($start = NULL, $length = NULL) { $this->query->range($start, $length); return $this; } + public function union(SelectQueryInterface $query, $type = '') { + $this->query->union($query, $type); + return $this; + } + public function groupBy($field) { $this->query->groupBy($field); return $this; @@ -765,6 +845,19 @@ class SelectQuery extends Query implements SelectQueryInterface { */ protected $range; + /** + * An array whose elements specify a query to UNION, and the UNION type. The + * 'type' key may be '', 'ALL', or 'DISTINCT' to represent a 'UNION', + * 'UNION ALL', or 'UNION DISTINCT' statement, respectively. + * + * All entries in this array will be applied from front to back, with the + * first query to union on the right of the original query, the second union + * to the right of the first, etc. + * + * @var array + */ + protected $union = array(); + /** * Indicates if preExecute() has already been called. * @var boolean @@ -910,6 +1003,10 @@ class SelectQuery extends Query implements SelectQueryInterface { return $this->tables; } + public function &getUnion() { + return $this->union; + } + public function getArguments(QueryPlaceholderInterface $queryPlaceholder = NULL) { if (!isset($queryPlaceholder)) { $queryPlaceholder = $this; @@ -917,6 +1014,7 @@ class SelectQuery extends Query implements SelectQueryInterface { $this->where->compile($this->connection, $queryPlaceholder); $this->having->compile($this->connection, $queryPlaceholder); $args = $this->where->arguments() + $this->having->arguments(); + foreach ($this->tables as $table) { if ($table['arguments']) { $args += $table['arguments']; @@ -926,12 +1024,19 @@ class SelectQuery extends Query implements SelectQueryInterface { $args += $table['table']->getArguments($queryPlaceholder); } } + foreach ($this->expressions as $expression) { if ($expression['arguments']) { $args += $expression['arguments']; } } + // If there are any dependent queries to UNION, + // incorporate their arguments recursively. + foreach ($this->union as $union) { + $args += $union['query']->getArguments($queryPlaceholder); + } + return $args; } @@ -979,7 +1084,7 @@ class SelectQuery extends Query implements SelectQueryInterface { $args = $this->getArguments(); if (!empty($this->range)) { - return $this->connection->queryRange((string)$this, $args, $this->range['start'], $this->range['length'], $this->queryOptions); + return $this->connection->queryRange((string)$this, $this->range['start'], $this->range['length'], $args, $this->queryOptions); } return $this->connection->query((string)$this, $args, $this->queryOptions); } @@ -1104,11 +1209,39 @@ class SelectQuery extends Query implements SelectQueryInterface { return $this; } + public function orderRandom() { + $alias = $this->addExpression('RAND()', 'random_field'); + $this->orderBy($alias); + return $this; + } + public function range($start = NULL, $length = NULL) { $this->range = func_num_args() ? array('start' => $start, 'length' => $length) : array(); return $this; } + public function union(SelectQueryInterface $query, $type = '') { + // Handle UNION aliasing. + switch ($type) { + // Fold UNION DISTINCT to UNION for better cross database support. + case 'DISTINCT': + case '': + $type = 'UNION'; + break; + + case 'ALL': + $type = 'UNION ALL'; + default: + } + + $this->union[] = array( + 'type' => $type, + 'query' => $query, + ); + + return $this; + } + public function groupBy($field) { $this->group[] = $field; return $this; @@ -1223,16 +1356,28 @@ class SelectQuery extends Query implements SelectQueryInterface { } // RANGE is database specific, so we can't do it here. + + // UNION is a little odd, as the select queries to combine are passed into + // this query, but syntactically they all end up on the same level. + if ($this->union) { + foreach ($this->union as $union) { + $query .= ' ' . $union['type'] . ' ' . (string) $union['query']; + } + } + return $query; } public function __clone() { - // On cloning, also clone the conditional objects. However, we do not + // On cloning, also clone the dependent objects. However, we do not // want to clone the database connection object as that would duplicate the // connection itself. $this->where = clone($this->where); $this->having = clone($this->having); + foreach ($this->union as $key => $aggregate) { + $this->union[$key]['query'] = clone($aggregate['query']); + } } } diff --git a/includes/database/sqlite/CVS/Entries b/includes/database/sqlite/CVS/Entries index 1db4fc9..be4439c 100644 --- a/includes/database/sqlite/CVS/Entries +++ b/includes/database/sqlite/CVS/Entries @@ -1,5 +1,5 @@ -/database.inc/1.19/Thu Aug 27 22:12:14 2009// /install.inc/1.2/Thu Aug 27 22:12:14 2009// /query.inc/1.8/Tue Sep 1 10:21:21 2009// -/schema.inc/1.9/Thu Aug 27 22:12:14 2009// +/database.inc/1.20/Fri Oct 2 19:50:13 2009// +/schema.inc/1.10/Fri Oct 2 19:50:13 2009// D diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index 9b52b27..fbbbb79 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -1,5 +1,5 @@ query($query . ' LIMIT ' . $from . ', ' . $count, $args, $options); } - public function queryTemporary($query, array $args, array $options = array()) { + public function queryTemporary($query, array $args = array(), array $options = array()) { $tablename = $this->generateTemporaryTableName(); $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options); return $tablename; @@ -164,15 +164,6 @@ class DatabaseConnection_sqlite extends DatabaseConnection { // DatabaseStatement_sqlite::execute() and cannot be cached. return $this->prepare($this->prefixTables($query)); } - - /** - * @todo Remove this as soon as db_rewrite_sql() has been exterminated. - */ - public function distinctField($table, $field, $query) { - $field_to_select = 'DISTINCT(' . $table . '.' . $field . ')'; - // (?connection->query('ALTER TABLE {' . $table . '} RENAME TO {' . $new_name . '}'); } /** * Drop a table. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be dropped. */ - public function dropTable(&$ret, $table) { - $ret[] = update_sql('DROP TABLE {' . $table . '}'); + public function dropTable($table) { + $this->connection->query('DROP TABLE {' . $table . '}'); } /** * Add a new field to a table. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table to be altered. * @param $field @@ -249,11 +243,11 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * @param $spec * The field specification array, as taken from a schema definition. */ - public function addField(&$ret, $table, $field, $spec, $keys_new = array()) { + public function addField($table, $field, $spec, $keys_new = array()) { // TODO: $keys_new is not supported yet. $query = 'ALTER TABLE {' . $table . '} ADD '; $query .= $this->createFieldSql($field, $this->processField($spec)); - $ret[] = update_sql($query); + $this->connection->query($query); } /** @@ -262,30 +256,32 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * As SQLite does not support ALTER TABLE (with a few exceptions) it is * necessary to create a new table and copy over the old content. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table to be altered. * @param $new_schema * The new schema array for the table. */ - protected function alterTable(&$ret, $table, $new_schema) { + protected function alterTable($table, $new_schema) { $i = 0; do { $new_table = $table . '_' . $i++; } while ($this->tableExists($new_table)); - $this->createTable($ret, $new_table, $new_schema); - $fields = implode(', ', array_keys($new_schema['fields'])); - $ret[] = update_sql('INSERT INTO {' . $new_table . "} ($fields) SELECT $fields FROM {" . $table . '}'); - $old_count = db_query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField(); - $new_count = db_query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField(); + + $this->createTable($new_table, $new_schema); + + $select = $this->connection->select($table)->fields($new_schema['fields']); + $this->connection->insert($new_table) + ->from($select) + ->execute(); + $old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField(); + $new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField(); if ($old_count == $new_count) { do { $temp_table = $table . '_' . $i++; } while ($this->tableExists($temp_table)); - $this->renameTable($ret, $table, $temp_table); - $this->renameTable($ret, $new_table, $table); - $this->dropTable($ret, $temp_table); + $this->renameTable($table, $temp_table); + $this->renameTable($new_table, $table); + $this->dropTable($temp_table); } } @@ -305,7 +301,8 @@ class DatabaseSchema_sqlite extends DatabaseSchema { protected function introspectSchema($table) { $mapped_fields = array_flip($this->getFieldTypeMap()); $schema = array(); - foreach (db_query("PRAGMA table_info('{" . $table . "}')") as $row) { + $result = $this->connection->query("PRAGMA table_info('{" . $table . "}')"); + foreach ($result as $row) { if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) { $type = $matches[1]; $length = $matches[2]; @@ -334,7 +331,8 @@ class DatabaseSchema_sqlite extends DatabaseSchema { } } $indexes = array(); - foreach (db_query("PRAGMA index_list('{" . $table . "}')") as $row) { + $result = $this->connection->query("PRAGMA index_list('{" . $table . "}')"); + foreach ($result as $row) { if (strpos($row->name, 'sqlite_autoindex_') !== 0) { $indexes[] = array( 'schema_key' => $row->unique ? 'unique keys' : 'indexes', @@ -346,7 +344,8 @@ class DatabaseSchema_sqlite extends DatabaseSchema { foreach ($indexes as $index) { $name = $index['name']; $index_name = substr($name, $n); - foreach (db_query("PRAGMA index_info('$name')") as $row) { + $result = $this->connection->query("PRAGMA index_info('$name')"); + foreach ($result as $row) { $schema[$index['schema_key']][$index_name][] = $row->name; } } @@ -359,14 +358,12 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * This implementation can't use ALTER TABLE directly, because SQLite only * supports a limited subset of that command. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field * The field to be dropped. */ - public function dropField(&$ret, $table, $field) { + public function dropField($table, $field) { $new_schema = $this->introspectSchema($table); unset($new_schema['fields'][$field]); foreach ($new_schema['indexes'] as $index => $fields) { @@ -380,7 +377,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema { unset($new_schema['indexes'][$index]); } } - $this->alterTable($ret, $table, $new_schema); + $this->alterTable($table, $new_schema); } /** @@ -389,8 +386,6 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * This implementation can't use ALTER TABLE directly, because SQLite only * supports a limited subset of that command. * - * @param $ret - * Array to which query results will be added. * @param $table * Name of the table. * @param $field @@ -404,7 +399,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * table along with changing the field. The format is the same as a * table specification but without the 'fields' element. */ - public function changeField(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) { + public function changeField($table, $field, $field_new, $spec, $keys_new = array()) { $new_schema = $this->introspectSchema($table); unset($new_schema['fields'][$field]); $new_schema['fields'][$field_new] = $spec; @@ -417,14 +412,12 @@ class DatabaseSchema_sqlite extends DatabaseSchema { $new_schema[$k] = $keys_new[$k] + $new_schema[$k]; } } - $this->alterTable($ret, $table, $new_schema); + $this->alterTable($table, $new_schema); } /** * Add an index. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name @@ -432,33 +425,29 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * @param $fields * An array of field names. */ - public function addIndex(&$ret, $table, $name, $fields) { + public function addIndex($table, $name, $fields) { $schema['indexes'][$name] = $fields; $statements = $this->createIndexSql($table, $schema); foreach ($statements as $statement) { - $ret[] = update_sql($statement); + $this->connection->query($statement); } } /** * Drop an index. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name * The name of the index. */ - public function dropIndex(&$ret, $table, $name) { - $ret[] = update_sql('DROP INDEX ' . '{' . $table . '}_' . $name); + public function dropIndex($table, $name) { + $this->connection->query('DROP INDEX ' . '{' . $table . '}_' . $name); } /** * Add a unique key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name @@ -466,26 +455,24 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * @param $fields * An array of field names. */ - public function addUniqueKey(&$ret, $table, $name, $fields) { + public function addUniqueKey($table, $name, $fields) { $schema['unique keys'][$name] = $fields; $statements = $this->createIndexSql($table, $schema); foreach ($statements as $statement) { - $ret[] = update_sql($statement); + $this->connection->query($statement); } } /** * Drop a unique key. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $name * The name of the key. */ - public function dropUniqueKey(&$ret, $table, $name) { - $ret[] = update_sql('DROP INDEX ' . '{' . $table . '}_' . $name); + public function dropUniqueKey($table, $name) { + $this->connection->query('DROP INDEX ' . '{' . $table . '}_' . $name); } /** @@ -494,17 +481,15 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * This implementation can't use ALTER TABLE directly, because SQLite only * supports a limited subset of that command. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $fields * Fields for the primary key. */ - public function addPrimaryKey(&$ret, $table, $fields) { + public function addPrimaryKey($table, $fields) { $new_schema = $this->introspectSchema($table); $new_schema['primary key'] = $fields; - $this->alterTable($ret, $table, $new_schema); + $this->alterTable($table, $new_schema); } /** @@ -513,15 +498,13 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * This implementation can't use ALTER TABLE directly, because SQLite only * supports a limited subset of that command.` * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. */ - public function dropPrimaryKey(&$ret, $table) { + public function dropPrimaryKey($table) { $new_schema = $this->introspectSchema($table); unset($new_schema['primary key']); - $this->alterTable($ret, $table, $new_schema); + $this->alterTable($table, $new_schema); } /** @@ -530,8 +513,6 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * This implementation can't use ALTER TABLE directly, because SQLite only * supports a limited subset of that command. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field @@ -539,10 +520,10 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * @param $default * Default value to be set. NULL for 'default NULL'. */ - public function fieldSetDefault(&$ret, $table, $field, $default) { + public function fieldSetDefault($table, $field, $default) { $new_schema = $this->introspectSchema($table); $new_schema['fields'][$field]['default'] = $default; - $this->alterTable($ret, $table, $new_schema); + $this->alterTable($table, $new_schema); } /** @@ -551,17 +532,15 @@ class DatabaseSchema_sqlite extends DatabaseSchema { * This implementation can't use ALTER TABLE directly, because SQLite only * supports a limited subset of that command. * - * @param $ret - * Array to which query results will be added. * @param $table * The table to be altered. * @param $field * The field to be altered. */ - public function fieldSetNoDefault(&$ret, $table, $field) { + public function fieldSetNoDefault($table, $field) { $new_schema = $this->introspectSchema($table); unset($new_schema['fields'][$field]['default']); - $this->alterTable($ret, $table, $new_schema); + $this->alterTable($table, $new_schema); } /** diff --git a/includes/file.inc b/includes/file.inc index 6f82e01..cb17dcb 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -1,5 +1,5 @@ $value) { - drupal_set_header($name, $value); + drupal_add_http_header($name, $value); } drupal_send_headers(); $scheme = file_uri_scheme($uri); @@ -1725,6 +1725,8 @@ function file_get_mimetype($uri, $mapping = NULL) { * more information. * @return * TRUE for success, FALSE in the event of an error. + * + * @ingroup php_wrappers */ function drupal_chmod($uri, $mode = NULL) { if (!isset($mode)) { @@ -1775,6 +1777,7 @@ function drupal_chmod($uri, $mode = NULL) { * The absolute pathname, or FALSE on failure. * * @see realpath() + * @ingroup php_wrappers */ function drupal_realpath($uri) { // If this URI is a stream, pass it off to the appropriate stream wrapper. @@ -1804,6 +1807,7 @@ function drupal_realpath($uri) { * A string containing the directory name. * * @see dirname() + * @ingroup php_wrappers */ function drupal_dirname($uri) { $scheme = file_uri_scheme($uri); @@ -1844,6 +1848,7 @@ function drupal_dirname($uri) { * Boolean TRUE on success, or FALSE on failure. * * @see mkdir() + * @ingroup php_wrappers */ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { @@ -1878,6 +1883,7 @@ function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { * The new temporary fillename, or FALSE on failure. * * @see tempnam() + * @ingroup php_wrappers */ function drupal_tempnam($directory, $prefix) { $scheme = file_uri_scheme($directory); diff --git a/includes/form.inc b/includes/form.inc index 9df168e..c8320a6 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1,5 +1,5 @@ 'textarea', * '#title' => t('Body'), - * '#text_format' => isset($node->format) ? $node->format : FILTER_FORMAT_DEFAULT, + * '#text_format' => isset($node->format) ? $node->format : filter_default_format(), * ); * @endcode * @@ -1932,7 +1977,7 @@ function form_process_radios($element) { * $form_state['values']['body_format'] = 1; * @endcode * - * @see system_elements(), filter_form() + * @see system_element_info(), filter_form() */ function form_process_text_format($element) { if (isset($element['#text_format'])) { @@ -2890,6 +2935,8 @@ function batch_set($batch_definition) { function batch_process($redirect = NULL, $url = NULL) { $batch =& batch_get(); + drupal_theme_initialize(); + if (isset($batch)) { // Add process information $url = isset($url) ? $url : 'batch'; @@ -2899,16 +2946,16 @@ function batch_process($redirect = NULL, $url = NULL) { 'url' => isset($url) ? $url : 'batch', 'source_page' => $_GET['q'], 'redirect' => $redirect, + 'theme' => $GLOBALS['theme_key'], ); $batch += $process_info; if ($batch['progressive']) { - // Clear the way for the drupal_goto redirection to the batch processing - // page, by saving and unsetting the 'destination' if any, on both places - // drupal_goto looks for it. - if (isset($_REQUEST['destination'])) { - $batch['destination'] = $_REQUEST['destination']; - unset($_REQUEST['destination']); + // Clear the way for the drupal_goto() redirection to the batch processing + // page, by saving and unsetting the 'destination', if there is any. + if (isset($_GET['destination'])) { + $batch['destination'] = $_GET['destination']; + unset($_GET['destination']); } // Initiate db storage in order to get a batch id. We have to provide @@ -2937,7 +2984,7 @@ function batch_process($redirect = NULL, $url = NULL) { // Set the batch number in the session to guarantee that it will stay alive. $_SESSION['batches'][$batch['id']] = TRUE; - drupal_goto($batch['url'], 'op=start&id=' . $batch['id']); + drupal_goto($batch['url'], array('op' => 'start', 'id' => $batch['id'])); } else { // Non-progressive execution: bypass the whole progressbar workflow diff --git a/includes/install.inc b/includes/install.inc index e80c848..35b3c9e 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -1,5 +1,5 @@ .+)_update_(?P\d+)$/'; + $functions = get_defined_functions(); + // Narrow this down to functions ending with an integer, since all + // hook_update_N() functions end this way, and there are other + // possible functions which match '_update_'. We use preg_grep() here + // instead of foreaching through all defined functions, since the loop + // through all PHP functions can take significant page execution time + // and this function is called on every administrative page via + // system_requirements(). + foreach (preg_grep('/_\d+$/', $functions['user']) as $function) { + // If this function is a module update function, add it to the list of + // module updates. + if (preg_match($regexp, $function, $matches)) { + $updates[$matches['module']][] = $matches['version']; } } + // Ensure that updates are applied in numerical order. + foreach ($updates as &$module_updates) { + sort($module_updates, SORT_NUMERIC); + } } - if (count($updates) == 0) { - return FALSE; - } - - // Make sure updates are run in numeric order, not in definition order. - sort($updates, SORT_NUMERIC); - - return $updates; + return isset($updates[$module]) ? $updates[$module] : FALSE; } /** @@ -586,8 +594,9 @@ function drupal_install_modules($module_list = array(), $disable_modules_install */ function _drupal_install_module($module) { if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { - module_load_install($module); drupal_load('module', $module); + drupal_install_schema($module); + // Now allow the module to perform install tasks. module_invoke($module, 'install'); $versions = drupal_get_schema_versions($module); drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED); @@ -663,6 +672,8 @@ function drupal_uninstall_modules($module_list = array()) { // Uninstall the module. module_load_install($module); module_invoke($module, 'uninstall'); + drupal_uninstall_schema($module); + watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); // Now remove the menu links for all paths declared by this module. diff --git a/includes/locale.inc b/includes/locale.inc index c2bf744..f8a08c7 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -1,5 +1,5 @@ 'fieldset', '#title' => t('Predefined language'), '#collapsible' => TRUE, @@ -186,8 +185,7 @@ function locale_languages_predefined_form() { /** * Custom language addition form. */ -function locale_languages_custom_form() { - $form = array(); +function locale_languages_custom_form($form) { $form['custom language'] = array('#type' => 'fieldset', '#title' => t('Custom language'), '#collapsible' => TRUE, @@ -210,9 +208,8 @@ function locale_languages_custom_form() { * @param $langcode * Language code of the language to edit. */ -function locale_languages_edit_form(&$form_state, $langcode) { +function locale_languages_edit_form($form, &$form_state, $langcode) { if ($language = db_query("SELECT * FROM {languages} WHERE language = :language", array(':language' => $langcode))->fetchObject()) { - $form = array(); _locale_languages_common_controls($form, $language); $form['submit'] = array( '#type' => 'submit', @@ -406,7 +403,7 @@ function locale_languages_edit_form_submit($form, &$form_state) { /** * User interface for the language deletion confirmation screen. */ -function locale_languages_delete_form(&$form_state, $langcode) { +function locale_languages_delete_form($form, &$form_state, $langcode) { // Do not allow deletion of English locale. if ($langcode == 'en') { @@ -698,7 +695,7 @@ function locale_translation_filter_form_submit($form, &$form_state) { /** * User interface for the translation import screen. */ -function locale_translate_import_form() { +function locale_translate_import_form($form) { // Get all languages, except English drupal_static_reset('language_list'); $names = locale_language_list('name'); @@ -716,7 +713,6 @@ function locale_translate_import_form() { $default = key($names); } - $form = array(); $form['import'] = array('#type' => 'fieldset', '#title' => t('Import translation'), ); @@ -769,7 +765,7 @@ function locale_translate_import_form_submit($form, &$form_state) { } // Now import strings into the language - if ($ret = _locale_import_po($file, $langcode, $form_state['values']['mode'], $form_state['values']['group']) == FALSE) { + if ($return = _locale_import_po($file, $langcode, $form_state['values']['mode'], $form_state['values']['group']) == FALSE) { $variables = array('%filename' => $file->filename); drupal_set_message(t('The translation import of %filename failed.', $variables), 'error'); watchdog('locale', 'The translation import of %filename failed.', $variables, WATCHDOG_ERROR); @@ -816,7 +812,7 @@ function locale_translate_export_screen() { * @param $names * An associate array with localized language names */ -function locale_translate_export_po_form(&$form_state, $names) { +function locale_translate_export_po_form($form, &$form_state, $names) { $form['export'] = array('#type' => 'fieldset', '#title' => t('Export translation'), '#collapsible' => TRUE, @@ -881,7 +877,7 @@ function locale_translate_export_po_form_submit($form, &$form_state) { /** * User interface for string editing. */ -function locale_translate_edit_form(&$form_state, $lid) { +function locale_translate_edit_form($form, &$form_state, $lid) { // Fetch source string, if possible. $source = db_query('SELECT source, context, textgroup, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject(); if (!$source) { @@ -1051,7 +1047,7 @@ function locale_translate_delete_page($lid) { /** * User interface for the string deletion confirmation screen. */ -function locale_translate_delete_form(&$form_state, $source) { +function locale_translate_delete_form($form, &$form_state, $source) { $form['lid'] = array('#type' => 'value', '#value' => $source->lid); return confirm_form($form, t('Are you sure you want to delete the string "%source"?', array('%source' => $source->source)), 'admin/config/regional/translate/translate', t('Deleting the string will remove all translations of this string in all languages. This action cannot be undone.'), t('Delete'), t('Cancel')); } @@ -2179,7 +2175,7 @@ function _locale_export_string($str) { */ function _locale_export_wrap($str, $len) { $words = explode(' ', $str); - $ret = array(); + $return = array(); $cur = ""; $nstr = 1; @@ -2190,16 +2186,16 @@ function _locale_export_wrap($str, $len) { $nstr = 0; } elseif (strlen("$cur $word") > $len) { - $ret[] = $cur . " "; + $return[] = $cur . " "; $cur = $word; } else { $cur = "$cur $word"; } } - $ret[] = $cur; + $return[] = $cur; - return implode("\n", $ret); + return implode("\n", $return); } /** @@ -2429,7 +2425,7 @@ function _locale_rebuild_js($langcode = NULL) { $data .= "'pluralFormula': function (\$n) { return Number({$language->formula}); }, "; } - $data .= "'strings': " . drupal_to_js($translations) . " };"; + $data .= "'strings': " . drupal_json_encode($translations) . " };"; $data_hash = md5($data); } diff --git a/includes/menu.inc b/includes/menu.inc index 9a30658..d3aa799 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1,5 +1,5 @@ $data) { - $extra_class = array(); + $class = array(); if ($i == 0) { - $extra_class[] = 'first'; + $class[] = 'first'; } if ($i == $num_items - 1) { - $extra_class[] = 'last'; + $class[] = 'last'; } - $extra_class = implode(' ', $extra_class); - $link = theme('menu_item_link', $data['link']); + // Set a class if the link has children. if ($data['below']) { - $output .= theme('menu_item', $link, $data['link']['has_children'], menu_tree_output($data['below']), $data['link']['in_active_trail'], $extra_class); + $class[] = 'expanded'; + } + elseif ($data['link']['has_children']) { + $class[] = 'collapsed'; } else { - $output .= theme('menu_item', $link, $data['link']['has_children'], '', $data['link']['in_active_trail'], $extra_class); + $class[] = 'leaf'; } + // Set a class if the link is in the active trail. + if ($data['link']['in_active_trail']) { + $class[] = 'active-trail'; + $data['localized_options']['attributes']['class'][] = 'active-trail'; + } + + $element['#theme'] = 'menu_link'; + $element['#attributes']['class'] = $class; + $element['#title'] = $data['link']['title']; + $element['#href'] = $data['link']['href']; + $element['#localized_options'] = !empty($data['localized_options']) ? $data['localized_options'] : array(); + $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below']; + $element['#original_link'] = $data['link']; + // Index using the link's unique mlid. + $build[$data['link']['mlid']] = $element; } - return $output ? theme('menu_tree', $output) : ''; + if ($build) { + // Make sure drupal_render() does not re-order the links. + $build['#sorted'] = TRUE; + // Add the theme wrapper for outer markup. + $build['#theme_wrappers'][] = 'menu_tree'; + } + + return $build; } /** @@ -899,6 +938,8 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) { 'title', 'title_callback', 'title_arguments', + 'theme_callback', + 'theme_arguments', 'type', 'description', )); @@ -1080,6 +1121,8 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { 'title', 'title_callback', 'title_arguments', + 'theme_callback', + 'theme_arguments', 'type', 'description', )); @@ -1225,48 +1268,45 @@ function menu_tree_data(array $links, array $parents = array(), $depth = 1) { * the next menu link. */ function _menu_tree_data(&$links, $parents, $depth) { - $done = FALSE; $tree = array(); - while (!$done && $item = array_pop($links)) { + while ($item = array_pop($links)) { // We need to determine if we're on the path to root so we can later build // the correct active trail and breadcrumb. $item['in_active_trail'] = in_array($item['mlid'], $parents); - // Look ahead to the next link, but leave it on the array so it's available - // to other recursive function calls if we return or build a sub-tree. - $next = end($links); // Add the current link to the tree. $tree[$item['mlid']] = array( 'link' => $item, 'below' => array(), ); + // Look ahead to the next link, but leave it on the array so it's available + // to other recursive function calls if we return or build a sub-tree. + $next = end($links); // Check whether the next link is the first in a new sub-tree. if ($next && $next['depth'] > $depth) { // Recursively call _menu_tree_data to build the sub-tree. $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']); + // Fetch next link after filling the sub-tree. + $next = end($links); } - else { - // Determine if we should exit the loop and return. - $done = (!$next || $next['depth'] < $depth); + // Determine if we should exit the loop and return. + if (!$next || $next['depth'] < $depth) { + break; } } return $tree; } /** - * Generate the HTML output for a single menu link. + * Preprocess the rendered tree for theme_menu_tree. * * @ingroup themeable */ -function theme_menu_item_link($link) { - if (empty($link['localized_options'])) { - $link['localized_options'] = array(); - } - - return l($link['title'], $link['href'], $link['localized_options']); +function template_preprocess_menu_tree(&$variables) { + $variables['tree'] = $variables['tree']['#children']; } /** - * Generate the HTML output for a menu tree + * Theme wrapper for the HTML output for a menu sub-tree. * * @ingroup themeable */ @@ -1275,56 +1315,47 @@ function theme_menu_tree($tree) { } /** - * Generate the HTML output for a menu item and submenu. + * Generate the HTML output for a menu link and submenu. * - * The menu item's LI element is given one of the following classes: - * - expanded: The menu item is showing its submenu. - * - collapsed: The menu item has a submenu which is not shown. - * - leaf: The menu item has no submenu. + * @param $element + * Structured array data for a menu link. * * @ingroup themeable - * - * @param $link - * The fully-formatted link for this menu item. - * @param $has_children - * Boolean value indicating if this menu item has children. - * @param $menu - * Contains a fully-formatted submenu, if one exists for this menu item. - * Defaults to NULL. - * @param $in_active_trail - * Boolean determining if the current page is below the menu item in the - * menu system. Defaults to FALSE. - * @param $extra_class - * Extra classes that should be added to the class of the list item. - * Defaults to NULL. */ -function theme_menu_item($link, $has_children, $menu = '', $in_active_trail = FALSE, $extra_class = NULL) { - $class = ($menu ? 'expanded' : ($has_children ? 'collapsed' : 'leaf')); - if (!empty($extra_class)) { - $class .= ' ' . $extra_class; - } - if ($in_active_trail) { - $class .= ' active-trail'; +function theme_menu_link(array $element) { + $sub_menu = ''; + + if ($element['#below']) { + $sub_menu = drupal_render($element['#below']); } - return '
  • ' . $link . $menu . "
  • \n"; + $output = l($element['#title'], $element['#href'], $element['#localized_options']); + return '' . $output . $sub_menu . "\n"; } /** * Generate the HTML output for a single local task link. * + * @param $link + * A menu link array with 'title', 'href', and 'localized_options' keys. + * @param $active + * A boolean indicating whether the local task is active. + * * @ingroup themeable */ function theme_menu_local_task($link, $active = FALSE) { - return '
  • ' . $link . "
  • \n"; + return '
  • ' . l($link['title'], $link['href'], $link['localized_options']) . "
  • \n"; } /** * Generate the HTML output for a single local action link. * + * @param $link + * A menu link array with 'title', 'href', and 'localized_options' keys. + * * @ingroup themeable */ function theme_menu_local_action($link) { - return '
  • ' . $link . "
  • \n"; + return '
  • ' . l($link['title'], $link['href'], $link['localized_options']) . "
  • \n"; } /** @@ -1364,6 +1395,38 @@ function menu_get_active_help() { return $output; } +/** + * Gets the custom theme for the current page, if there is one. + * + * @param $initialize + * This parameter should only be used internally; it is set to TRUE in order + * to force the custom theme to be initialized from the menu router item for + * the current page. + * @return + * The machine-readable name of the custom theme, if there is one. + * + * @see menu_set_custom_theme() + */ +function menu_get_custom_theme($initialize = FALSE) { + $custom_theme = &drupal_static(__FUNCTION__); + // Skip this if the site is offline or being installed or updated, since the + // menu system may not be correctly initialized then. + if ($initialize && !_menu_site_is_offline(TRUE) && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install'))) { + $router_item = menu_get_item(); + if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) { + $custom_theme = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']); + } + } + return $custom_theme; +} + +/** + * Sets a custom theme for the current page, if there is one. + */ +function menu_set_custom_theme() { + menu_get_custom_theme(TRUE); +} + /** * Build a list of named menus. */ @@ -1373,7 +1436,7 @@ function menu_get_names() { if (empty($names)) { $names = db_select('menu_links') ->distinct() - ->fields('menu_links', 'menu_name') + ->fields('menu_links', array('menu_name')) ->orderBy('menu_name') ->execute()->fetchCol(); } @@ -1527,17 +1590,18 @@ function menu_local_tasks($level = 0) { $action_count = 0; foreach ($children[$path] as $item) { if ($item['access']) { + $link = $item; // The default task is always active. if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) { // Find the first parent which is not a default local task or action. for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']); - $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item); + // Use the path of the parent instead. + $link['href'] = $tasks[$p]['href']; $tabs_current .= theme('menu_local_task', $link, TRUE); $next_path = $item['path']; $tab_count++; } else { - $link = theme('menu_item_link', $item); if ($item['type'] == MENU_LOCAL_TASK) { $tabs_current .= theme('menu_local_task', $link); $tab_count++; @@ -1573,17 +1637,16 @@ function menu_local_tasks($level = 0) { } if ($item['access']) { $count++; + $link = $item; if ($item['type'] == MENU_DEFAULT_LOCAL_TASK) { // Find the first parent which is not a default local task. for ($p = $item['tab_parent']; $tasks[$p]['type'] == MENU_DEFAULT_LOCAL_TASK; $p = $tasks[$p]['tab_parent']); - $link = theme('menu_item_link', array('href' => $tasks[$p]['href']) + $item); + // Use the path of the parent instead. + $link['href'] = $tasks[$p]['href']; if ($item['path'] == $router_item['path']) { $root_path = $tasks[$p]['path']; } } - else { - $link = theme('menu_item_link', $item); - } // We check for the active tab. if ($item['path'] == $path) { $tabs_current .= theme('menu_local_task', $link, TRUE); @@ -1709,7 +1772,25 @@ function menu_set_active_item($path) { } /** - * Set (or get) the active trail for the current page - the path to root in the menu tree. + * Sets or gets the active trail (path to root menu root) of the current page. + * + * @param $new_trail + * Menu trail to set, or NULL to use previously-set or calculated trail. If + * supplying a trail, use the same format as the return value (see below). + * @return + * Path to menu root of the current page, as an array of menu link items, + * starting with the site's home page. Each link item is an associative array + * with the following components: + * - 'title': Title of the item. + * - 'href': Drupal path of the item. + * - 'localized_options': Options for passing into the l() function. + * - 'type': A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to + * indicate it's not really in the menu (used for the home page item). + * If $new_trail is supplied, the value is saved in a static variable and + * returned. If $new_trail is not supplied, and there is a saved value from + * a previous call, the saved value is returned. If $new_trail is not supplied + * and there is no saved value, the path to the current page is calculated, + * saved as the static value, and returned. */ function menu_set_active_trail($new_trail = NULL) { $trail = &drupal_static(__FUNCTION__); @@ -1789,7 +1870,9 @@ function menu_set_active_trail($new_trail = NULL) { } /** - * Get the active trail for the current page - the path to root in the menu tree. + * Gets the active trail (path to root menu root) of the current page. + * + * See menu_set_active_trail() for details of return value. */ function menu_get_active_trail() { return menu_set_active_trail(); @@ -2139,6 +2222,9 @@ function _menu_delete_item($item, $force = FALSE) { } db_delete('menu_links')->condition('mlid', $item['mlid'])->execute(); + // Notify modules we have deleted the item. + module_invoke_all('menu_link_delete', $item); + // Update the has_children status of the parent. _menu_update_parental_status($item); menu_cache_clear($item['menu_name']); @@ -2333,7 +2419,13 @@ function menu_link_save(&$item) { if ($existing_item && $menu_name != $existing_item['menu_name']) { menu_cache_clear($existing_item['menu_name']); } - + // Notify modules we have acted on a menu item. + $hook = 'menu_link_insert'; + if ($existing_item) { + $hook = 'menu_link_update'; + } + module_invoke_all($hook, $item); + // Now clear the cache. _menu_clear_page_cache(); } return $item['mlid']; @@ -2436,21 +2528,11 @@ function menu_link_maintain($module, $op, $link_path, $link_title) { return menu_link_save($menu_link); break; case 'update': - db_update('menu_links') - ->fields(array('link_title' => $link_title)) - ->condition('link_path', $link_path) - ->condition('customized', 0) - ->condition('module', $module) - ->execute(); - $result = db_select('menu_links') - ->fields('menu_links', array('menu_name')) - ->condition('link_path', $link_path) - ->condition('customized', 0) - ->condition('module', $module) - ->groupBy('menu_name') - ->execute()->fetchCol(); - foreach ($result as $menu_name) { - menu_cache_clear($menu_name); + $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path AND module = :module AND customized = 0", array(':link_path' => $link_path, ':module' => $module))->fetchAll(PDO::FETCH_ASSOC); + foreach ($result as $link) { + $link['link_title'] = $link_title; + $link['options'] = unserialize($link['options']); + menu_link_save($link); } break; case 'delete': @@ -2704,6 +2786,13 @@ function _menu_router_build($callbacks) { $item['file path'] = $parent['file path']; } } + // Same for theme callbacks. + if (!isset($item['theme callback']) && isset($parent['theme callback'])) { + $item['theme callback'] = $parent['theme callback']; + if (!isset($item['theme arguments']) && isset($parent['theme arguments'])) { + $item['theme arguments'] = $parent['theme arguments']; + } + } } } if (!isset($item['access callback']) && isset($item['access arguments'])) { @@ -2725,6 +2814,8 @@ function _menu_router_build($callbacks) { 'block callback' => '', 'title arguments' => array(), 'title callback' => 't', + 'theme arguments' => array(), + 'theme callback' => '', 'description' => '', 'position' => '', 'tab_parent' => '', @@ -2774,6 +2865,8 @@ function _menu_router_save($menu, $masks) { 'title', 'title_callback', 'title_arguments', + 'theme_callback', + 'theme_arguments', 'type', 'block_callback', 'description', @@ -2799,6 +2892,8 @@ function _menu_router_save($menu, $masks) { 'title' => $item['title'], 'title_callback' => $item['title callback'], 'title_arguments' => ($item['title arguments'] ? serialize($item['title arguments']) : ''), + 'theme_callback' => $item['theme callback'], + 'theme_arguments' => serialize($item['theme arguments']), 'type' => $item['type'], 'block_callback' => $item['block callback'], 'description' => $item['description'], @@ -2829,20 +2924,24 @@ function menu_path_is_external($path) { * This function will log the current user out and redirect to front page * if the current user has no 'access site in maintenance mode' permission. * + * @param $check_only + * If this is set to TRUE, the function will perform the access checks and + * return the site offline status, but not log the user out or display any + * messages. * @return * FALSE if the site is not in maintenance mode, the user login page is * displayed, or the user has the 'access site in maintenance mode' * permission. TRUE for anonymous users not being on the login page when the * site is in maintenance mode. */ -function _menu_site_is_offline() { +function _menu_site_is_offline($check_only = FALSE) { // Check if site is in maintenance mode. if (variable_get('maintenance_mode', 0)) { if (user_access('access site in maintenance mode')) { // Ensure that the maintenance mode message is displayed only once // (allowing for page redirects) and specifically suppress its display on // the maintenance mode settings page. - if ($_GET['q'] != 'admin/config/development/maintenance') { + if (!$check_only && $_GET['q'] != 'admin/config/development/maintenance') { if (user_access('administer site configuration')) { drupal_set_message(t('Operating in maintenance mode. Go online.', array('@url' => url('admin/config/development/maintenance'))), 'status', FALSE); } @@ -2857,8 +2956,10 @@ function _menu_site_is_offline() { return ($_GET['q'] != 'user' && $_GET['q'] != 'user/login'); } // Logged in users are unprivileged here, so they are logged out. - require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'user') . '/user.pages.inc'; - user_logout(); + if (!$check_only) { + require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'user') . '/user.pages.inc'; + user_logout(); + } } } return FALSE; diff --git a/includes/module.inc b/includes/module.inc index 1b6c57d..7841b73 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -1,5 +1,5 @@ data; + } + } + if (!isset($implementations[$hook])) { $implementations[$hook] = array(); $list = module_list(FALSE, FALSE, $sort); foreach ($list as $module) { if (module_hook($module, $hook)) { - $implementations[$hook][] = $module; + $implementations[$hook][$module] = $module; + // We added something to the cache, so write it when we are done. + $implementations['#write_cache'] = TRUE; + } + } + } + else { + foreach ($implementations[$hook] as $module) { + // It is possible that a module removed a hook implementation without the + // implementations cache being rebuilt yet, so we check module_hook() on + // each request to avoid undefined function errors. + if (!module_hook($module, $hook)) { + // Clear out the stale implementation from the cache and force a cache + // refresh to forget about no longer existing hook implementations. + unset($implementations[$hook][$module]); + $implementations['#write_cache'] = TRUE; } } } @@ -370,6 +409,22 @@ function module_implements($hook, $sort = FALSE, $refresh = FALSE) { return (array)$implementations[$hook]; } +/** + * Writes the hook implementation cache. + * + * @see module_implements() + */ +function module_implements_write_cache() { + $implementations = &drupal_static('module_implements'); + // Check whether we need to write the cache. We do not want to cache hooks + // which are only invoked on HTTP POST requests since these do not need to be + // optimized as tightly, and not doing so keeps the cache entry smaller. + if (isset($implementations['#write_cache']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')) { + unset($implementations['#write_cache']); + cache_set('module_implements', $implementations); + } +} + /** * Invoke a hook in a particular module. * diff --git a/includes/pager.inc b/includes/pager.inc index 3f3c9ca..29c76a8 100644 --- a/includes/pager.inc +++ b/includes/pager.inc @@ -1,5 +1,5 @@ fetchField(); - $pager_total[$element] = ceil($pager_total_items[$element] / $limit); - $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1)); - $pager_limits[$element] = $limit; - return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit); -} - -/** - * Compose a query string to append to pager requests. + * Compose a URL query parameter array for pager links. * * @return - * A query string that consists of all components of the current page request - * except for those pertaining to paging. + * A URL query parameter array that consists of all components of the current + * page request except for those pertaining to paging. */ -function pager_get_querystring() { - static $string = NULL; - if (!isset($string)) { - $string = drupal_query_string_encode($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE))); +function pager_get_query_parameters() { + $query = &drupal_static(__FUNCTION__); + if (!isset($query)) { + $query = drupal_get_query_parameters($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE))); } - return $string; + return $query; } /** @@ -535,11 +465,10 @@ function theme_pager_link($text, $page_new, $element, $parameters = array(), $at $query = array(); if (count($parameters)) { - $query[] = drupal_query_string_encode($parameters, array()); + $query = drupal_get_query_parameters($parameters, array()); } - $querystring = pager_get_querystring(); - if ($querystring != '') { - $query[] = $querystring; + if ($query_pager = pager_get_query_parameters()) { + $query = array_merge($query, $query_pager); } // Set each pager link title @@ -561,7 +490,7 @@ function theme_pager_link($text, $page_new, $element, $parameters = array(), $at } } - return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => count($query) ? implode('&', $query) : NULL)); + return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => $query)); } /** diff --git a/includes/session.inc b/includes/session.inc index e54a842..00ea240 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -1,5 +1,5 @@ order(); $ts['sort'] = $this->getSort(); - $ts['query_string'] = $this->getQueryString(); + $ts['query'] = $this->getQueryParameters(); return $ts; } @@ -87,14 +87,16 @@ class TableSort extends SelectQueryExtender { } /** - * Compose a query string to append to table sorting requests. + * Compose a URL query parameter array to append to table sorting requests. * * @return - * A query string that consists of all components of the current page request - * except for those pertaining to table sorting. + * A URL query parameter array that consists of all components of the current + * page request except for those pertaining to table sorting. + * + * @see tablesort_get_query_parameters() */ - protected function getQueryString() { - return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE))); + protected function getQueryParameters() { + return tablesort_get_query_parameters(); } /** @@ -141,41 +143,10 @@ class TableSort extends SelectQueryExtender { function tablesort_init($header) { $ts = tablesort_get_order($header); $ts['sort'] = tablesort_get_sort($header); - $ts['query_string'] = tablesort_get_querystring(); + $ts['query'] = tablesort_get_query_parameters(); return $ts; } -/** - * Create an SQL sort clause. - * - * This function produces the ORDER BY clause to insert in your SQL queries, - * assuring that the returned database table rows match the sort order chosen - * by the user. - * - * @param $header - * An array of column headers in the format described in theme_table(). - * @param $before - * An SQL string to insert after ORDER BY and before the table sorting code. - * Useful for sorting by important attributes like "sticky" first. - * @return - * An SQL string to append to the end of a query. - * - * @ingroup database - */ -function tablesort_sql($header, $before = '') { - $ts = tablesort_init($header); - if ($ts['sql']) { - // Based on code from db_escape_table(), but this can also contain a dot. - $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']); - - // Sort order can only be ASC or DESC. - $sort = drupal_strtoupper($ts['sort']); - $sort = in_array($sort, array('ASC', 'DESC')) ? $sort : ''; - - return " ORDER BY $before $field $sort"; - } -} - /** * Format a column header. * @@ -205,11 +176,7 @@ function tablesort_header($cell, $header, $ts) { $ts['sort'] = 'asc'; $image = ''; } - - if (!empty($ts['query_string'])) { - $ts['query_string'] = '&' . $ts['query_string']; - } - $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => 'sort=' . $ts['sort'] . '&order=' . urlencode($cell['data']) . $ts['query_string'], 'html' => TRUE)); + $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); unset($cell['field'], $cell['sort']); } @@ -245,14 +212,14 @@ function tablesort_cell($cell, $header, $ts, $i) { } /** - * Compose a query string to append to table sorting requests. + * Compose a URL query parameter array for table sorting links. * * @return - * A query string that consists of all components of the current page request - * except for those pertaining to table sorting. + * A URL query parameter array that consists of all components of the current + * page request except for those pertaining to table sorting. */ -function tablesort_get_querystring() { - return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE))); +function tablesort_get_query_parameters() { + return drupal_get_query_parameters($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE))); } /** diff --git a/includes/theme.inc b/includes/theme.inc index f139e46..88a7f11 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1,5 +1,5 @@ status) || ($admin_theme && $theme->name == $admin_theme); +} + /** * Initialize the theme system by loading the theme. */ function drupal_theme_initialize() { - global $theme, $user, $custom_theme, $theme_key; + global $theme, $user, $theme_key; // If $theme is already set, assume the others are set, too, and do nothing if (isset($theme)) { @@ -52,12 +66,13 @@ function drupal_theme_initialize() { $themes = list_themes(); // Only select the user selected theme if it is available in the - // list of enabled themes. - $theme = !empty($user->theme) && !empty($themes[$user->theme]->status) ? $user->theme : variable_get('theme_default', 'garland'); + // list of themes that can be accessed. + $theme = !empty($user->theme) && isset($themes[$user->theme]) && drupal_theme_access($themes[$user->theme]) ? $user->theme : variable_get('theme_default', 'garland'); // Allow modules to override the present theme... only select custom theme - // if it is available in the list of installed themes. - $theme = $custom_theme && $themes[$custom_theme] ? $custom_theme : $theme; + // if it is available in the list of themes that can be accessed. + $custom_theme = menu_get_custom_theme(); + $theme = $custom_theme && isset($themes[$custom_theme]) && drupal_theme_access($themes[$custom_theme]) ? $custom_theme : $theme; // Store the identifier for retrieving theme settings with. $theme_key = $theme; @@ -1509,7 +1524,12 @@ function theme_image($path, $alt = '', $title = '', $attributes = array(), $gets */ function theme_breadcrumb($breadcrumb) { if (!empty($breadcrumb)) { - return ''; + // Provide a navigational heading to give context for breadcrumb links to + // screen-reader users. Make the heading invisible with .element-invisible. + $output = '

    ' . t('You are here') . '

    '; + + $output .= ''; + return $output; } } @@ -1835,10 +1855,11 @@ function theme_more_help_link($url) { * The url of the feed. * @param $title * A descriptive title of the feed. - */ + */ function theme_feed_icon($url, $title) { - if ($image = theme('image', 'misc/feed.png', t('Subscribe to %feed-title', array('%feed-title' => $title)))) { - return '' . $image . ''; + $text = t('Subscribe to @feed-title', array('@feed-title' => $title)); + if ($image = theme('image', 'misc/feed.png', $text)) { + return '' . $image . ''; } } @@ -1854,53 +1875,105 @@ function theme_more_link($url, $title) { return ''; } +/** + * Preprocess variables for theme_username(). + * + * Modules that make any changes to the $variables['object'] properties like + * 'name' or 'extra' must insure that the final string is safe to include + * directly in the ouput by using check_plain() or filter_xss(). + * + * @see theme_username(). + */ +function template_preprocess_username(&$variables) { + $account = $variables['object']; + // Create a new empty object to populate with standardized data. + $variables['object'] = new stdClass; + // Keep a reference to the original data. + $variables['object']->account = $account; + $variables['object']->extra = ''; + if (empty($account->uid)) { + $variables['object']->uid = 0; + if (theme_get_setting('toggle_comment_user_verification')) { + $variables['object']->extra = ' (' . t('not verified') . ')'; + } + } + else { + $variables['object']->uid = (int)$account->uid; + } + if (empty($account->name)) { + $variables['object']->name = variable_get('anonymous', t('Anonymous')); + } + else { + $variables['object']->name = $account->name; + } + + $variables['object']->profile_access = user_access('access user profiles'); + $variables['object']->link_attributes = array(); + // Populate link path and attributes if appropriate. + if ($variables['object']->uid && $variables['object']->profile_access) { + // We are linking to a local user. + $variables['object']->link_attributes = array('title' => t('View user profile.')); + $variables['object']->link_path = 'user/' . $variables['object']->uid; + } + elseif (!empty($account->homepage)) { + $variables['object']->link_attributes = array('rel' => 'nofollow'); + $variables['object']->link_path = $account->homepage; + $variables['object']->homepage = $account->homepage; + } + // We do not want the l() function to check_plain() a second time. + $variables['object']->link_options['html'] = TRUE; + // Set a default class. + $variables['object']->attributes = array('class' => array('username')); + // Shorten the name when it is too long or it will break many tables. + if (drupal_strlen($variables['object']->name) > 20) { + $variables['object']->name = drupal_substr($variables['object']->name, 0, 15) . '...'; + } + // Make sure name is safe for use in the theme function. + $variables['object']->name = check_plain($variables['object']->name); +} + +/** + * Process variables for theme_username(). + * + * @see theme_username(). + */ +function template_process_username(&$variables) { + // Finalize the link_options array for passing to the l() function. + // This is done in the process phase so that attributes may be added by + // modules or the theme during the preprocess phase. + if (isset($variables['object']->link_path)) { + $variables['object']->link_options['attributes'] = $variables['object']->link_attributes + $variables['object']->attributes; + } +} + /** * Format a username. * * @param $object - * The user object to format, usually returned from user_load(). + * The user object to format, which has been processed to provide safe and + * standarized elements. The object keys 'name', and 'extra' are safe strings + * that can be used directly. + * * @return * A string containing an HTML link to the user's page if the passed object * suggests that this is a site user. Otherwise, only the username is returned. + * + * @see template_preprocess_username() + * @see template_process_username() */ function theme_username($object) { - - if ($object->uid && $object->name) { - // Shorten the name when it is too long or it will break many tables. - if (drupal_strlen($object->name) > 20) { - $name = drupal_substr($object->name, 0, 15) . '...'; - } - else { - $name = $object->name; - } - - if (user_access('access user profiles')) { - $output = l($name, 'user/' . $object->uid, array('attributes' => array('title' => t('View user profile.')))); - } - else { - $output = check_plain($name); - } - } - elseif ($object->name) { - // Sometimes modules display content composed by people who are - // not registered members of the site (e.g. mailing list or news - // aggregator modules). This clause enables modules to display - // the true author of the content. - if (!empty($object->homepage)) { - $output = l($object->name, $object->homepage, array('attributes' => array('rel' => 'nofollow'))); - } - else { - $output = check_plain($object->name); - } - - if (theme_get_setting('toggle_comment_user_verification')) { - $output .= ' (' . t('not verified') . ')'; - } + if (isset($object->link_path)) { + // We have a link path, so we should generate a link using l(). + // Additional classes may be added as array elements like + // $object->link_options['attributes']['class'][] = 'myclass'; + $output = l($object->name . $object->extra, $object->link_path, $object->link_options); } else { - $output = check_plain(variable_get('anonymous', t('Anonymous'))); + // Modules may have added important attributes so they must be included + // in the output. Additional classes may be added as array elements like + // $object->attributes['class'][] = 'myclass'; + $output = 'attributes) . '>' . $object->name . $object->extra . ''; } - return $output; } @@ -1989,6 +2062,10 @@ function template_preprocess(&$variables, $hook) { // Initialize html class attribute for the current hook. $variables['classes_array'] = array($hook); + // Initialize attributes for the top-level template entity and its title. + $variables['attributes_array'] = array(); + $variables['title_attributes_array'] = array(); + // Set default variables that depend on the database. $variables['is_admin'] = FALSE; $variables['is_front'] = FALSE; @@ -2013,6 +2090,90 @@ function template_preprocess(&$variables, $hook) { function template_process(&$variables, $hook) { // Flatten out classes. $variables['classes'] = implode(' ', $variables['classes_array']); + + // Flatten out attributes and title_attributes. + $variables['attributes'] = drupal_attributes($variables['attributes_array']); + $variables['title_attributes'] = drupal_attributes($variables['title_attributes_array']); +} + +/** + * Preprocess variables for html.tpl.php + * + * @see system_elements() + * @see html.tpl.php + */ +function template_preprocess_html(&$variables) { + // Compile a list of classes that are going to be applied to the body element. + // This allows advanced theming based on context (home page, node of certain type, etc.). + // Add a class that tells us whether we're on the front page or not. + $variables['classes_array'][] = $variables['is_front'] ? 'front' : 'not-front'; + // Add a class that tells us whether the page is viewed by an authenticated user or not. + $variables['classes_array'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; + + // Add information about the number of sidebars. + if (!empty($variables['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) { + $variables['classes_array'][] = 'two-sidebars'; + } + elseif (!empty($variables['page']['sidebar_first'])) { + $variables['classes_array'][] = 'one-sidebar sidebar-first'; + } + elseif (!empty($variables['page']['sidebar_second'])) { + $variables['classes_array'][] = 'one-sidebar sidebar-second'; + } + else { + $variables['classes_array'][] = 'no-sidebars'; + } + + // Populate the body classes. + if ($suggestions = template_page_suggestions(arg(), 'page')) { + foreach ($suggestions as $suggestion) { + if ($suggestion != 'page-front') { + // Add current suggestion to page classes to make it possible to theme the page + // depending on the current page type (e.g. node, admin, user, etc.) as well as + // more specific data like node-12 or node-edit. To avoid illegal characters in + // the class, we're removing everything disallowed. We are not using 'a-z' as + // that might leave in certain international characters (e.g. German umlauts). + $variables['classes_array'][] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', form_clean_id(drupal_strtolower($suggestion))); + } + } + } + + if ($node = menu_get_object()) { + $variables['classes_array'][] = 'node-type-' . form_clean_id($node->type); + } + + // RDFa allows annotation of XHTML pages with RDF data, while GRDDL provides + // mechanisms for extraction of this RDF content via XSLT transformation + // using an associated GRDDL profile. + $variables['rdf_namespaces'] = drupal_get_rdf_namespaces(); + $variables['grddl_profile'] = 'http://ns.inria.fr/grddl/rdfa/'; + $variables['language'] = $GLOBALS['language']; + $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; + + + // Add favicon. + if (theme_get_setting('toggle_favicon')) { + $favicon = theme_get_setting('favicon'); + $type = theme_get_setting('favicon_mimetype'); + drupal_add_html_head(''); + } + + // Construct page title. + if (drupal_get_title()) { + $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal')); + } + else { + $head_title = array(variable_get('site_name', 'Drupal')); + if (variable_get('site_slogan', '')) { + $head_title[] = variable_get('site_slogan', ''); + } + } + $variables['head_title'] = implode(' | ', $head_title); + + // Populate the page template suggestions. + if ($suggestions = template_page_suggestions(arg(), 'html')) { + $variables['template_files'] = $suggestions; + } } /** @@ -2036,33 +2197,21 @@ function template_preprocess_page(&$variables) { // Move some variables to the top level for themer convenience and template cleanliness. $variables['show_messages'] = $variables['page']['#show_messages']; - // Add favicon. - if (theme_get_setting('toggle_favicon')) { - $favicon = theme_get_setting('favicon'); - $type = theme_get_setting('favicon_mimetype'); - drupal_add_html_head(''); - } - // Set up layout variable. $variables['layout'] = 'none'; if (!empty($variables['page']['sidebar_first'])) { $variables['layout'] = 'first'; } + else { + $variables['page']['sidebar_first'] = array(); + } if (!empty($variables['page']['sidebar_second'])) { $variables['layout'] = ($variables['layout'] == 'first') ? 'both' : 'second'; } - - // Construct page title - if (drupal_get_title()) { - $head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal')); - } else { - $head_title = array(variable_get('site_name', 'Drupal')); - if (variable_get('site_slogan', '')) { - $head_title[] = variable_get('site_slogan', ''); - } + $variables['page']['sidebar_second'] = array(); } - $variables['head_title'] = implode(' | ', $head_title); + $variables['base_path'] = base_path(); $variables['front_page'] = url(); $variables['breadcrumb'] = theme('breadcrumb', drupal_get_breadcrumb()); @@ -2074,74 +2223,36 @@ function template_preprocess_page(&$variables) { $variables['main_menu'] = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array(); $variables['secondary_menu'] = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array(); $variables['action_links'] = menu_local_actions(); - $variables['search_box'] = (theme_get_setting('toggle_search') ? drupal_render(drupal_get_form('search_theme_form')) : ''); $variables['site_name'] = (theme_get_setting('toggle_name') ? filter_xss_admin(variable_get('site_name', 'Drupal')) : ''); $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? filter_xss_admin(variable_get('site_slogan', '')) : ''); $variables['tabs'] = theme('menu_local_tasks'); $variables['title'] = drupal_get_title(); - // RDFa allows annotation of XHTML pages with RDF data, while GRDDL provides - // mechanisms for extraction of this RDF content via XSLT transformation - // using an associated GRDDL profile. - $variables['rdf_namespaces'] = drupal_get_rdf_namespaces(); - $variables['grddl_profile'] = 'http://ns.inria.fr/grddl/rdfa/'; if ($node = menu_get_object()) { $variables['node'] = $node; } - // Compile a list of classes that are going to be applied to the body element. - // This allows advanced theming based on context (home page, node of certain type, etc.). - // Add a class that tells us whether we're on the front page or not. - $variables['classes_array'][] = $variables['is_front'] ? 'front' : 'not-front'; - // Add a class that tells us whether the page is viewed by an authenticated user or not. - $variables['classes_array'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; - // Populate the page template suggestions. - if ($suggestions = template_page_suggestions(arg())) { + if ($suggestions = template_page_suggestions(arg(), 'page')) { $variables['template_files'] = $suggestions; - foreach ($suggestions as $suggestion) { - if ($suggestion != 'page-front') { - // Add current suggestion to page classes to make it possible to theme the page - // depending on the current page type (e.g. node, admin, user, etc.) as well as - // more specific data like node-12 or node-edit. To avoid illegal characters in - // the class, we're removing everything disallowed. We are not using 'a-z' as - // that might leave in certain international characters (e.g. German umlauts). - $variables['classes_array'][] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', form_clean_id(drupal_strtolower($suggestion))); - } - } - } - - // If on an individual node page, add the node type to body classes. - if (isset($variables['node']) && $variables['node']->type) { - $variables['classes_array'][] = 'node-type-' . form_clean_id($variables['node']->type); - } - // Add information about the number of sidebars. - if ($variables['layout'] == 'both') { - $variables['classes_array'][] = 'two-sidebars'; - } - elseif ($variables['layout'] == 'none') { - $variables['classes_array'][] = 'no-sidebars'; - } - else { - $variables['classes_array'][] = 'one-sidebar sidebar-' . $variables['layout']; } } /** - * Process variables for page.tpl.php + * Process variables for html.tpl.php * * Perform final addition and modification of variables before passing into * the template. To customize these variables, call drupal_render() on elements * in $variables['page'] during THEME_preprocess_page(). * - * @see template_preprocess_page() - * @see page.tpl.php + * @see template_preprocess_html() + * @see html.tpl.php */ -function template_process_page(&$variables) { - // Render each region into top level variables. - foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) { - $variables[$region_key] = drupal_render($variables['page'][$region_key]); - } - // Append javascript to $page_bottom +function template_process_html(&$variables) { + // Render page_top and page_bottom into top level variables. + $variables['page_top'] = drupal_render($variables['page']['page_top']); + $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']); + // Place the rendered HTML for the page body into a top level variable. + $variables['page'] = $variables['page']['#children']; $variables['page_bottom'] .= drupal_get_js('footer'); $variables['head'] = drupal_get_html_head(); @@ -2159,7 +2270,7 @@ function template_process_page(&$variables) { * @return * An array of suggested template files. */ -function template_page_suggestions($args) { +function template_page_suggestions($args, $suggestion) { // Build a list of suggested template files and body classes in order of // specificity. One suggestion is made for every element of the current path, @@ -2172,7 +2283,6 @@ function template_page_suggestions($args) { // page-node.tpl.php page-node // page.tpl.php - $suggestion = 'page'; $suggestions = array(); foreach ($args as $arg) { // Remove slashes or null per SA-CORE-2009-003. @@ -2188,7 +2298,7 @@ function template_page_suggestions($args) { } } if (drupal_is_front_page()) { - $suggestions[] = 'page-front'; + $suggestions[] = $suggestion . '-front'; } return $suggestions; @@ -2261,7 +2371,6 @@ function template_preprocess_maintenance_page(&$variables) { $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; $variables['main_menu'] = array(); $variables['secondary_menu'] = array(); - $variables['search_box'] = ''; $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : ''); $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : ''); $variables['css'] = drupal_add_css(); diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc index f054b33..4f9d19a 100644 --- a/includes/theme.maintenance.inc +++ b/includes/theme.maintenance.inc @@ -1,5 +1,5 @@ '; + $output = '

    Installation tasks

    '; + $output .= '
      '; + foreach ($items as $k => $item) { if ($active == $k) { $class = 'active'; + $status = '(' . t('active') . ')'; $done = FALSE; } else { $class = $done ? 'done' : ''; + $status = $done ? '(' . t('done') . ')' : ''; } - $output .= '
    1. ' . $item . '
    2. '; + $output .= ''; + $output .= $item; + $output .= ($status ? '' . $status . '' : ''); + $output .= ''; } $output .= '
    '; return $output; @@ -111,7 +119,7 @@ function theme_task_list($items, $active = NULL) { * The page content to show. */ function theme_install_page($content) { - drupal_set_header('Content-Type', 'text/html; charset=utf-8'); + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); // Assign content. $variables['content'] = $content; @@ -168,7 +176,7 @@ function theme_install_page($content) { */ function theme_update_page($content, $show_messages = TRUE) { // Set required headers. - drupal_set_header('Content-Type', 'text/html; charset=utf-8'); + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); // Assign content and show message flag. $variables['content'] = $content; diff --git a/includes/unicode.inc b/includes/unicode.inc index 4d1a21e..7041cca 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -1,5 +1,5 @@ = 0xC0)) { return substr($string, 0, $len); } - // Scan backwards to beginning of the byte sequence. + // Scan backwards to beginning of the byte sequence. while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0); return substr($string, 0, $len); @@ -393,6 +395,8 @@ function _decode_entities($prefix, $codepoint, $original, &$html_entities, &$exc /** * Count the amount of characters in a UTF-8 string. This is less than or * equal to the byte count. + * + * @ingroup php_wrappers */ function drupal_strlen($text) { global $multibyte; @@ -407,6 +411,8 @@ function drupal_strlen($text) { /** * Uppercase a UTF-8 string. + * + * @ingroup php_wrappers */ function drupal_strtoupper($text) { global $multibyte; @@ -424,6 +430,8 @@ function drupal_strtoupper($text) { /** * Lowercase a UTF-8 string. + * + * @ingroup php_wrappers */ function drupal_strtolower($text) { global $multibyte; @@ -449,6 +457,8 @@ function _unicode_caseflip($matches) { /** * Capitalize the first letter of a UTF-8 string. + * + * @ingroup php_wrappers */ function drupal_ucfirst($text) { // Note: no mbstring equivalent! @@ -462,6 +472,8 @@ function drupal_ucfirst($text) { * 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. + * + * @ingroup php_wrappers */ function drupal_substr($text, $start, $length = NULL) { global $multibyte; @@ -545,5 +557,3 @@ function drupal_substr($text, $start, $length = NULL) { return substr($text, $istart, max(0, $iend - $istart + 1)); } } - - diff --git a/includes/update.inc b/includes/update.inc index ba57d28..0361c97 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -1,5 +1,5 @@ "''" - * in the $attributes array. If NOT NULL and DEFAULT are set the PostgreSQL - * version will set values of the added column in old rows to the - * DEFAULT value. - * - * @param $ret - * Reference to an array to which results will be added. - * @param $table - * Name of the table, without {} - * @param $column - * Name of the column - * @param $type - * Type of column - * @param $attributes - * Additional optional attributes. Recognized attributes: - * not null => TRUE|FALSE - * default => NULL|FALSE|value (the value must be enclosed in '' marks) - * @return - * nothing, but modifies $ret parameter. - */ -function db_add_column(&$ret, $table, $column, $type, $attributes = array()) { - if (array_key_exists('not null', $attributes) and $attributes['not null']) { - $not_null = 'NOT NULL'; - } - if (array_key_exists('default', $attributes)) { - if (is_null($attributes['default'])) { - $default_val = 'NULL'; - $default = 'default NULL'; - } - elseif ($attributes['default'] === FALSE) { - $default = ''; - } - else { - $default_val = "$attributes[default]"; - $default = "default $attributes[default]"; - } - } - - $ret[] = update_sql("ALTER TABLE {" . $table . "} ADD $column $type"); - if (!empty($default)) { - $ret[] = update_sql("ALTER TABLE {" . $table . "} ALTER $column SET $default"); - } - if (!empty($not_null)) { - if (!empty($default)) { - $ret[] = update_sql("UPDATE {" . $table . "} SET $column = $default_val"); - } - $ret[] = update_sql("ALTER TABLE {" . $table . "} ALTER $column SET NOT NULL"); - } -} - -/** - * Change a column definition using syntax appropriate for PostgreSQL. - * Save result of SQL commands in $ret array. - * - * Remember that changing a column definition involves adding a new column - * and dropping an old one. This means that any indices, primary keys and - * sequences from serial-type columns are dropped and might need to be - * recreated. - * - * @param $ret - * Array to which results will be added. - * @param $table - * Name of the table, without {} - * @param $column - * Name of the column to change - * @param $column_new - * New name for the column (set to the same as $column if you don't want to change the name) - * @param $type - * Type of column - * @param $attributes - * Additional optional attributes. Recognized attributes: - * not null => TRUE|FALSE - * default => NULL|FALSE|value (with or without '', it won't be added) - * @return - * nothing, but modifies $ret parameter. - */ -function db_change_column(&$ret, $table, $column, $column_new, $type, $attributes = array()) { - if (array_key_exists('not null', $attributes) and $attributes['not null']) { - $not_null = 'NOT NULL'; - } - if (array_key_exists('default', $attributes)) { - if (is_null($attributes['default'])) { - $default_val = 'NULL'; - $default = 'default NULL'; - } - elseif ($attributes['default'] === FALSE) { - $default = ''; - } - else { - $default_val = "$attributes[default]"; - $default = "default $attributes[default]"; - } - } - - $ret[] = update_sql("ALTER TABLE {" . $table . "} RENAME $column TO " . $column . "_old"); - $ret[] = update_sql("ALTER TABLE {" . $table . "} ADD $column_new $type"); - $ret[] = update_sql("UPDATE {" . $table . "} SET $column_new = " . $column . "_old"); - if ($default) { - $ret[] = update_sql("ALTER TABLE {" . $table . "} ALTER $column_new SET $default"); - } - if ($not_null) { - $ret[] = update_sql("ALTER TABLE {" . $table . "} ALTER $column_new SET NOT NULL"); - } - $ret[] = update_sql("ALTER TABLE {" . $table . "} DROP " . $column . "_old"); -} - /** * Disable anything in the {system} table that is not compatible with the * current version of Drupal core. */ function update_fix_compatibility() { - $ret = array(); $incompatible = array(); - $query = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')"); - while ($result = db_fetch_object($query)) { - if (update_check_incompatibility($result->name, $result->type)) { - $incompatible[] = $result->name; + $result = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')"); + foreach ($result as $row) { + if (update_check_incompatibility($row->name, $row->type)) { + $incompatible[] = $row->name; } } if (!empty($incompatible)) { - $ret[] = update_sql("UPDATE {system} SET status = 0 WHERE name IN ('" . implode("','", $incompatible) . "')"); + db_update('system') + ->fields(array('status' => 0)) + ->condition('name', $incompatible, 'IN') + ->execute(); } - return $ret; } /** @@ -202,24 +88,19 @@ function update_prepare_d7_bootstrap() { ); update_extra_requirements($requirements); } + + // The new {blocked_ips} table is used in Drupal 7 to store a list of + // banned IP addresses. If this table doesn't exist then we are still + // running on a Drupal 6 database, so we suppress the unavoidable errors + // that occur by creating a static list. + $GLOBALS['conf']['blocked_ips'] = array(); + // Allow the database system to work even if the registry has not been // created yet. drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); drupal_install_initialize_database(); spl_autoload_unregister('drupal_autoload_class'); spl_autoload_unregister('drupal_autoload_interface'); - // The new {blocked_ips} table is used in Drupal 7 to store a list of - // banned IP addresses. If this table doesn't exist then we are still - // running on a Drupal 6 database, so suppress the unavoidable errors - // that occur. - try { - drupal_bootstrap(DRUPAL_BOOTSTRAP_ACCESS); - } - catch (Exception $e) { - if (db_table_exists('blocked_ips')) { - throw $e; - } - } } /** @@ -233,7 +114,6 @@ function update_prepare_d7_bootstrap() { */ function update_fix_d7_requirements() { global $conf; - $ret = array(); // Rewrite the settings.php file if necessary. // @see update_prepare_d7_bootstrap(). @@ -246,11 +126,11 @@ function update_fix_d7_requirements() { // Add the cache_path table. $schema['cache_path'] = drupal_get_schema_unprocessed('system', 'cache'); $schema['cache_path']['description'] = 'Cache table used for path alias lookups.'; - db_create_table($ret, 'cache_path', $schema['cache_path']); + db_create_table('cache_path', $schema['cache_path']); // Add column for locale context. if (db_table_exists('locales_source')) { - db_add_field($ret, 'locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The context this string applies to.')); + db_add_field('locales_source', 'context', array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => '', 'description' => 'The context this string applies to.')); } // Rename 'site_offline_message' variable to 'maintenance_mode_message'. @@ -264,8 +144,6 @@ function update_fix_d7_requirements() { } update_fix_d7_install_profile(); - - return $ret; } /** @@ -273,7 +151,7 @@ function update_fix_d7_requirements() { * * Install profiles are now treated as modules by Drupal, and have an upgrade path * based on their schema version in the system table. - * + * * The install profile will be set to schema_version 0, as it has already been * installed. Any other hook_update_N functions provided by the install profile * will be run by update.php. @@ -281,7 +159,6 @@ function update_fix_d7_requirements() { function update_fix_d7_install_profile() { $profile = drupal_get_profile(); - $results = db_select('system', 's') ->fields('s', array('name', 'schema_version')) ->condition('name', $profile) @@ -365,13 +242,31 @@ function update_parse_db_url($db_url) { * Perform one update and store the results which will later be displayed on * the finished page. * - * An update function can force the current and all later updates for this - * module to abort by returning a $ret array with an element like: - * $ret['#abort'] = array('success' => FALSE, 'query' => 'What went wrong'); - * The schema version will not be updated in this case, and all the - * aborted updates will continue to appear on update.php as updates that + * If an update function completes successfully, it should return a message + * as a string indicating success, for example: + * @code + * return t('New index added successfully.'); + * @endcode + * + * Alternatively, it may return nothing. In that case, no message + * will be displayed at all. + * + * If it fails for whatever reason, it should throw an instance of + * DrupalUpdateException with an appropriate error message, for example: + * @code + * throw new DrupalUpdateException(t('Description of what went wrong')); + * @endcode + * + * If an exception is thrown, the current and all later updates for this module + * will be aborted. The schema version will not be updated in this case, and all + * the aborted updates will continue to appear on update.php as updates that * have not yet been run. * + * If an update function needs to be re-run as part of a batch process, it + * should accept the $sandbox array by reference as its first parameter + * and set the #finished property to the percentage completed that it is, as a + * fraction of 1. + * * @param $module * The module whose update will be run. * @param $number @@ -386,16 +281,49 @@ function update_do_one($module, $number, &$context) { return; } + if (!isset($context['log'])) { + $context['log'] = variable_get('update_log_queries', 0); + } + + $ret = array(); $function = $module . '_update_' . $number; if (function_exists($function)) { - $ret = $function($context['sandbox']); + try { + if ($context['log']) { + Database::startLog($function); + } + $ret['results']['query'] = $function($context['sandbox']); + $ret['results']['success'] = TRUE; + + // @TODO Remove this block after all updates have been converted to + // return only strings. + if (is_array($ret['results']['query'])) { + $ret = $ret['results']['query']; + } + } + // @TODO We may want to do different error handling for different exception + // types, but for now we'll just print the message. + catch (Exception $e) { + $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); + } + + if ($context['log']) { + $ret['queries'] = Database::getLog($function); + } } + // @TODO Remove this block after all updates have been converted to return + // only strings. if (isset($ret['#finished'])) { $context['finished'] = $ret['#finished']; unset($ret['#finished']); } + if (isset($context['sandbox']['#finished'])) { + $context['finished'] = $context['sandbox']['#finished']; + unset($context['sandbox']['#finished']); + } + if (!isset($context['results'][$module])) { $context['results'][$module] = array(); } @@ -407,16 +335,20 @@ function update_do_one($module, $number, &$context) { if (!empty($ret['#abort'])) { $context['results'][$module]['#abort'] = TRUE; } + // Record the schema update if it was completed successfully. if ($context['finished'] == 1 && empty($context['results'][$module]['#abort'])) { drupal_set_installed_schema_version($module, $number); - // Conserve memory and avoid errors by resetting all static variables. - drupal_static_reset(); } $context['message'] = 'Updating ' . check_plain($module) . ' module'; } +/** + * @class Exception class used to throw error if a module update fails. + */ +class DrupalUpdateException extends Exception { } + /** * Start the database update batch process. * @@ -518,7 +450,7 @@ function update_finished($success, $results, $operations) { function update_get_update_list() { // Make sure that the system module is first in the list of updates. $ret = array('system' => array()); - + $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE); foreach ($modules as $module => $schema_version) { $pending = array(); @@ -532,7 +464,7 @@ function update_get_update_list() { $ret[$module]['warning'] = '' . $module . ' module can not be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update ' . $module . ' module, you will first need to upgrade to the last version in which these updates were available.'; continue; } - + $updates = drupal_map_assoc($updates); foreach (array_keys($updates) as $update) { if ($update > $schema_version) { @@ -550,7 +482,7 @@ function update_get_update_list() { } } } - + if (empty($ret['system'])) { unset($ret['system']); } diff --git a/includes/xmlrpcs.inc b/includes/xmlrpcs.inc index 7e54ae3..3bdbd5e 100644 --- a/includes/xmlrpcs.inc +++ b/includes/xmlrpcs.inc @@ -1,5 +1,5 @@ ' . "\n" . $xml; - drupal_set_header('Content-Length', strlen($xml)); - drupal_set_header('Content-Type', 'text/xml'); + drupal_add_http_header('Content-Length', strlen($xml)); + drupal_add_http_header('Content-Type', 'text/xml'); echo $xml; exit; } diff --git a/install.php b/install.php index 17043a2..afa3d38 100644 --- a/install.php +++ b/install.php @@ -1,5 +1,5 @@ try again.', array('!url' => request_uri())); + $status_report .= st('Check the error messages and proceed with the installation.', array('!url' => request_uri())); return $status_report; } else { @@ -757,8 +756,8 @@ function install_system_module(&$install_state) { */ function install_verify_completed_task() { try { - if ($result = db_query("SELECT value FROM {variable} WHERE name = '%s'", 'install_task')) { - $task = unserialize(db_result($result)); + if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) { + $task = unserialize($result->fetchField()); } } // Do not trigger an error if the database query fails, since the database @@ -802,7 +801,7 @@ function install_verify_settings() { * @return * The form API definition for the database configuration form. */ -function install_settings_form(&$form_state, &$install_state) { +function install_settings_form($form, &$form_state, &$install_state) { global $databases, $db_prefix; $profile = $install_state['parameters']['profile']; $install_locale = $install_state['parameters']['locale']; @@ -1001,8 +1000,7 @@ function install_find_profiles() { } /** - * Installation task; allow the site administrator to select which profile to - * install. + * Installation task; select which profile to install. * * @param $install_state * An array of information about the current installation state. The chosen @@ -1071,7 +1069,7 @@ function _install_select_profile($profiles) { * @param $profile_files * Array of .profile files, as returned from file_scan_directory(). */ -function install_select_profile_form(&$form_state, $profile_files) { +function install_select_profile_form($form, &$form_state, $profile_files) { $profiles = array(); $names = array(); @@ -1118,8 +1116,7 @@ function install_find_locales($profilename) { } /** - * Installation task; allow the site administrator to select which locale to - * use for the current profile. + * Installation task; select which locale to use for the current profile. * * @param $install_state * An array of information about the current installation state. The chosen @@ -1208,7 +1205,7 @@ function install_select_locale(&$install_state) { /** * Form API array definition for language selection. */ -function install_select_locale_form(&$form_state, $locales) { +function install_select_locale_form($form, &$form_state, $locales) { include_once DRUPAL_ROOT . '/includes/iso.inc'; $languages = _locale_get_predefined_list(); foreach ($locales as $locale) { @@ -1340,7 +1337,7 @@ function install_import_locales(&$install_state) { * @return * The form API definition for the site configuration form. */ -function install_configure_form(&$form_state, &$install_state) { +function install_configure_form($form, &$form_state, &$install_state) { if (variable_get('site_name', FALSE) || variable_get('site_mail', FALSE)) { // Site already configured: This should never happen, means re-running the // installer, possibly by an attacker after the 'install_task' variable got @@ -1354,7 +1351,7 @@ function install_configure_form(&$form_state, &$install_state) { $settings_dir = './' . conf_path(); $settings_file = $settings_dir . '/settings.php'; if (!drupal_verify_install_file($settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file($settings_dir, FILE_NOT_WRITABLE, 'dir')) { - drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, please consult the online handbook.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'error'); + drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, consult the online handbook.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'error'); } else { drupal_set_message(st('All necessary changes to %dir and %file have been made. They have been set to read-only for security.', array('%dir' => $settings_dir, '%file' => $settings_file))); @@ -1385,7 +1382,7 @@ function install_configure_form(&$form_state, &$install_state) { drupal_get_schema(NULL, TRUE); // Return the form. - return _install_configure_form($form_state, $install_state); + return _install_configure_form($form, $form_state, $install_state); } /** @@ -1400,6 +1397,7 @@ function install_import_locales_remaining(&$install_state) { include_once DRUPAL_ROOT . '/includes/locale.inc'; // Collect files to import for this language. Skip components already covered // in the initial batch set. + $install_locale = $install_state['parameters']['locale']; $batch = locale_batch_by_language($install_locale, NULL, variable_get('install_locale_batch_components', array())); // Remove temporary variable. variable_del('install_locale_batch_components'); @@ -1415,18 +1413,23 @@ function install_import_locales_remaining(&$install_state) { * A message informing the user that the installation is complete. */ function install_finished(&$install_state) { - drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name()))); + drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_name())), PASS_THROUGH); $messages = drupal_set_message(); $output = '

    ' . st('Congratulations, @drupal has been successfully installed.', array('@drupal' => drupal_install_profile_name())) . '

    '; - $output .= '

    ' . (isset($messages['error']) ? st('Please review the messages above before continuing on to your new site.', array('@url' => url(''))) : st('You may now visit your new site.', array('@url' => url('')))) . '

    '; + $output .= '

    ' . (isset($messages['error']) ? st('Review the messages above before continuing on to your new site.', array('@url' => url(''))) : st('You may now visit your new site.', array('@url' => url('')))) . '

    '; if (module_exists('help')) { - $output .= '

    ' . st('For more information on configuring Drupal, please refer to the help section.', array('@help' => url('admin/help'))) . '

    '; + $output .= '

    ' . st('For more information on configuring Drupal, refer to the help section.', array('@help' => url('admin/help'))) . '

    '; } // Rebuild menu and registry to get content type links registered by the // profile, and possibly any other menu items created through the tasks. menu_rebuild(); + // Rebuild the database cache of node types, so that any node types added + // by newly installed modules are registered correctly and initialized with + // the necessary fields. + node_types_rebuild(); + // Register actions declared by any modules. actions_synchronize(); @@ -1515,7 +1518,7 @@ function install_check_requirements($install_state) { 'title' => st('Settings file'), 'value' => st('The settings file is not writable.'), 'severity' => REQUIREMENT_ERROR, - 'description' => st('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, please consult the online handbook.', array('@drupal' => drupal_install_profile_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), + 'description' => st('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, consult the online handbook.', array('@drupal' => drupal_install_profile_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), ); } else { @@ -1532,7 +1535,7 @@ function install_check_requirements($install_state) { /** * Form API array definition for site configuration. */ -function _install_configure_form(&$form_state, &$install_state) { +function _install_configure_form($form, &$form_state, &$install_state) { include_once DRUPAL_ROOT . '/includes/locale.inc'; $form['site_information'] = array( @@ -1556,7 +1559,7 @@ function _install_configure_form(&$form_state, &$install_state) { ); $form['admin_account'] = array( '#type' => 'fieldset', - '#title' => st('Administrator account'), + '#title' => st('Site maintenance account'), '#collapsible' => FALSE, ); @@ -1678,8 +1681,8 @@ function install_configure_form_submit($form, &$form_state) { if ($form_state['values']['update_status_module'][1]) { drupal_install_modules(array('update')); - // Add the administrator's email address to the list of addresses to be - // notified when updates are available, if selected. + // Add the site maintenance account's email address to the list of + // addresses to be notified when updates are available, if selected. if ($form_state['values']['update_status_module'][2]) { variable_set('update_notify_emails', array($form_state['values']['account']['mail'])); } diff --git a/misc/CVS/Entries b/misc/CVS/Entries index 771477e..2d25cf0 100644 --- a/misc/CVS/Entries +++ b/misc/CVS/Entries @@ -1,10 +1,8 @@ D/farbtastic//// D/ui//// -/ajax.js/1.1/Thu Aug 27 22:12:14 2009// /arrow-asc.png/1.1/Thu Aug 27 22:12:14 2009/-kb/ /arrow-desc.png/1.2/Thu Aug 27 22:12:14 2009/-kb/ /batch.js/1.10/Tue Sep 1 10:21:22 2009// -/collapse.js/1.24/Tue Sep 1 10:21:22 2009// /draggable.png/1.2/Thu Aug 27 22:12:15 2009/-kb/ /drupal.js/1.58/Tue Sep 1 10:21:22 2009// /druplicon.png/1.5/Thu Aug 27 22:12:15 2009/-kb/ @@ -40,8 +38,6 @@ D/ui//// /print.css/1.6/Thu Aug 27 22:12:15 2009// /progress.gif/1.3/Thu Aug 27 22:12:15 2009/-kb/ /progress.js/1.25/Thu Aug 27 22:12:15 2009// -/tabledrag.js/1.30/Tue Sep 1 10:21:24 2009// -/tableheader.js/1.25/Tue Sep 1 10:21:24 2009// /tableselect.js/1.13/Tue Sep 1 10:21:24 2009// /textarea.js/1.30/Tue Sep 1 10:21:24 2009// /throbber.gif/1.1/Thu Aug 27 22:12:15 2009/-kb/ @@ -55,3 +51,7 @@ D/ui//// /watchdog-ok.png/1.2/Thu Aug 27 22:12:16 2009/-kb/ /watchdog-warning.png/1.2/Thu Aug 27 22:12:16 2009/-kb/ /autocomplete.js/1.34/Sat Sep 5 13:40:15 2009// +/ajax.js/1.3/Fri Oct 2 19:50:13 2009// +/collapse.js/1.25/Fri Oct 2 19:50:13 2009// +/tabledrag.js/1.31/Fri Oct 2 19:50:13 2009// +/tableheader.js/1.27/Fri Oct 2 19:50:13 2009// diff --git a/misc/ajax.js b/misc/ajax.js index 8a74467..2073ad7 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -1,4 +1,4 @@ -// $Id: ajax.js,v 1.1 2009/08/17 07:12:15 webchick Exp $ +// $Id: ajax.js,v 1.3 2009/10/02 14:55:40 dries Exp $ (function ($) { /** @@ -96,7 +96,7 @@ Drupal.ajax = function (base, element, element_settings) { type: 'bar', message: 'Please wait...' }, - button: {}, + button: {} }; $.extend(this, defaults, element_settings); @@ -183,6 +183,10 @@ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { // Disable the element that received the change. $(this.element).addClass('progress-disabled').attr('disabled', true); + // Server-side code needs to know what element triggered the call, so it can + // find the #ajax binding. + form_values.push({ name: 'ajax_triggering_element', value: this.formPath }); + // Insert progressbar or throbber. if (this.progress.type == 'bar') { var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback)); diff --git a/misc/collapse.js b/misc/collapse.js index 3d137f7..0d058fe 100644 --- a/misc/collapse.js +++ b/misc/collapse.js @@ -1,8 +1,8 @@ -// $Id: collapse.js,v 1.24 2009/08/31 05:51:07 dries Exp $ +// $Id: collapse.js,v 1.25 2009/09/11 02:01:26 webchick Exp $ (function ($) { /** - * Toggle the visibility of a fieldset using smooth animations + * Toggle the visibility of a fieldset using smooth animations. */ Drupal.toggleFieldset = function (fieldset) { if ($(fieldset).is('.collapsed')) { @@ -20,7 +20,7 @@ Drupal.toggleFieldset = function (fieldset) { $('div.action', fieldset).show(); }, step: function () { - // Scroll the fieldset into view + // Scroll the fieldset into view. Drupal.collapseScrollIntoView(this.parentNode); } }); @@ -45,7 +45,8 @@ Drupal.collapseScrollIntoView = function (node) { if (posY + node.offsetHeight + fudge > h + offset) { if (node.offsetHeight > h) { window.scrollTo(0, posY); - } else { + } + else { window.scrollTo(0, posY + node.offsetHeight - h + fudge); } } @@ -55,7 +56,7 @@ Drupal.behaviors.collapse = { attach: function (context, settings) { $('fieldset.collapsible > legend', context).once('collapse', function () { var fieldset = $(this.parentNode); - // Expand if there are errors inside + // Expand if there are errors inside. if ($('input.error, textarea.error, select.error', fieldset).size() > 0) { fieldset.removeClass('collapsed'); } @@ -68,12 +69,12 @@ Drupal.behaviors.collapse = { }) .trigger('summaryUpdated'); - // Turn the legend into a clickable link and wrap the contents of the fieldset - // in a div for easier animation + // Turn the legend into a clickable link and wrap the contents of the + // fieldset in a div for easier animation. var text = this.innerHTML; $(this).empty().append($('' + text + '').click(function () { var fieldset = $(this).parents('fieldset:first')[0]; - // Don't animate multiple times + // Don't animate multiple times. if (!fieldset.animating) { fieldset.animating = true; Drupal.toggleFieldset(fieldset); diff --git a/misc/tabledrag.js b/misc/tabledrag.js index 18d4763..ca67eeb 100644 --- a/misc/tabledrag.js +++ b/misc/tabledrag.js @@ -1,4 +1,4 @@ -// $Id: tabledrag.js,v 1.30 2009/08/31 05:51:08 dries Exp $ +// $Id: tabledrag.js,v 1.31 2009/09/20 19:14:40 dries Exp $ (function ($) { /** @@ -293,7 +293,7 @@ Drupal.tableDrag.prototype.makeDraggable = function (item) { self.rowObject.swap('before', previousRow); self.rowObject.interval = null; self.rowObject.indent(0); - window.scrollBy(0, -parseInt(item.offsetHeight)); + window.scrollBy(0, -parseInt(item.offsetHeight, 10)); } handle.get(0).focus(); // Regain focus after the DOM manipulation. } @@ -325,7 +325,7 @@ Drupal.tableDrag.prototype.makeDraggable = function (item) { nextGroupRow = $(nextGroup.group).filter(':last').get(0); self.rowObject.swap('after', nextGroupRow); // No need to check for indentation, 0 is the only valid one. - window.scrollBy(0, parseInt(groupHeight)); + window.scrollBy(0, parseInt(groupHeight, 10)); } } else { @@ -333,7 +333,7 @@ Drupal.tableDrag.prototype.makeDraggable = function (item) { self.rowObject.swap('after', nextRow); self.rowObject.interval = null; self.rowObject.indent(0); - window.scrollBy(0, parseInt(item.offsetHeight)); + window.scrollBy(0, parseInt(item.offsetHeight, 10)); } handle.get(0).focus(); // Regain focus after the DOM manipulation. } @@ -524,11 +524,11 @@ Drupal.tableDrag.prototype.findDropTargetRow = function (x, y) { // table cells, grab the firstChild of the row and use that instead. // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari. if (row.offsetHeight == 0) { - var rowHeight = parseInt(row.firstChild.offsetHeight) / 2; + var rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2; } // Other browsers. else { - var rowHeight = parseInt(row.offsetHeight) / 2; + var rowHeight = parseInt(row.offsetHeight, 10) / 2; } // Because we always insert before, we need to offset the height a bit. @@ -697,7 +697,7 @@ Drupal.tableDrag.prototype.updateField = function (changedRow, group) { } else { // Assume a numeric input field. - var weight = parseInt($(targetClass, siblings[0]).val()) || 0; + var weight = parseInt($(targetClass, siblings[0]).val(), 10) || 0; $(targetClass, siblings).each(function () { this.value = weight; weight++; diff --git a/misc/tableheader.js b/misc/tableheader.js index 576dc39..e59af36 100644 --- a/misc/tableheader.js +++ b/misc/tableheader.js @@ -1,4 +1,4 @@ -// $Id: tableheader.js,v 1.25 2009/08/31 05:51:08 dries Exp $ +// $Id: tableheader.js,v 1.27 2009/09/20 19:14:40 dries Exp $ (function ($) { Drupal.tableHeaderDoScroll = function () { @@ -10,7 +10,7 @@ Drupal.tableHeaderDoScroll = function () { Drupal.behaviors.tableHeader = { attach: function (context, settings) { // This breaks in anything less than IE 7. Prevent it from running. - if ($.browser.msie && parseInt($.browser.version) < 7) { + if ($.browser.msie && parseInt($.browser.version, 10) < 7) { return; } @@ -41,11 +41,14 @@ Drupal.behaviors.tableHeader = { // Track positioning and visibility. function tracker(e) { + // Reset top position of sticky table headers to the current top offset. + var topOffset = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0; + $('.sticky-header').css('top', topOffset + 'px'); // Save positioning data. var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; if (e.viewHeight != viewHeight) { e.viewHeight = viewHeight; - e.vPosition = $(e.table).offset().top - 4; + e.vPosition = $(e.table).offset().top - 4 - topOffset; e.hPosition = $(e.table).offset().left; e.vLength = e.table.clientHeight - 100; // Resize header and its cell widths. diff --git a/modules/CVS/Entries b/modules/CVS/Entries index e0282a7..946abe3 100644 --- a/modules/CVS/Entries +++ b/modules/CVS/Entries @@ -35,3 +35,4 @@ D/image//// D/field_ui//// D/file//// /README.txt/1.2/Thu Sep 3 08:50:15 2009// +D/overlay//// diff --git a/modules/aggregator/CVS/Entries b/modules/aggregator/CVS/Entries index f2c9df1..929dd85 100644 --- a/modules/aggregator/CVS/Entries +++ b/modules/aggregator/CVS/Entries @@ -5,14 +5,14 @@ D/tests//// /aggregator-summary-item.tpl.php/1.2/Thu Sep 3 08:50:15 2009// /aggregator-summary-items.tpl.php/1.3/Thu Sep 3 08:50:15 2009// /aggregator-wrapper.tpl.php/1.3/Thu Sep 3 08:50:15 2009// -/aggregator.admin.inc/1.42/Thu Sep 3 08:50:15 2009// /aggregator.api.php/1.5/Thu Sep 3 08:50:15 2009// /aggregator.css/1.2/Thu Sep 3 08:50:15 2009// -/aggregator.fetcher.inc/1.8/Thu Sep 3 08:50:15 2009// /aggregator.info/1.12/Thu Sep 3 08:50:15 2009// -/aggregator.install/1.25/Thu Sep 3 08:50:15 2009// -/aggregator.module/1.422/Thu Sep 3 08:50:15 2009// -/aggregator.pages.inc/1.32/Thu Sep 3 08:50:15 2009// /aggregator.parser.inc/1.4/Thu Sep 3 08:50:16 2009// /aggregator.processor.inc/1.10/Thu Sep 3 08:50:16 2009// -/aggregator.test/1.31/Thu Sep 3 08:50:16 2009// +/aggregator.admin.inc/1.43/Fri Oct 2 19:50:13 2009// +/aggregator.fetcher.inc/1.9/Fri Oct 2 19:50:13 2009// +/aggregator.install/1.27/Fri Oct 2 19:50:13 2009// +/aggregator.module/1.424/Fri Oct 2 19:50:13 2009// +/aggregator.pages.inc/1.36/Fri Oct 2 19:50:13 2009// +/aggregator.test/1.34/Fri Oct 2 19:50:13 2009// diff --git a/modules/aggregator/aggregator.admin.inc b/modules/aggregator/aggregator.admin.inc index e1c928c..d13db44 100644 --- a/modules/aggregator/aggregator.admin.inc +++ b/modules/aggregator/aggregator.admin.inc @@ -1,5 +1,5 @@ array( @@ -230,7 +230,7 @@ function aggregator_admin_remove_feed_submit($form, &$form_state) { * @see aggregator_form_opml_validate() * @see aggregator_form_opml_submit() */ -function aggregator_form_opml(&$form_state) { +function aggregator_form_opml($form, &$form_state) { $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval'); $form['upload'] = array( @@ -390,7 +390,7 @@ function aggregator_admin_refresh_feed($feed) { * * @ingroup forms */ -function aggregator_admin_form($form_state) { +function aggregator_admin_form($form, $form_state) { // Make sure configuration is sane. aggregator_sanitize_configuration(); @@ -496,7 +496,7 @@ function aggregator_admin_form_submit($form, &$form_state) { * @see aggregator_form_category_validate() * @see aggregator_form_category_submit() */ -function aggregator_form_category(&$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) { +function aggregator_form_category($form, &$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) { $form['title'] = array('#type' => 'textfield', '#title' => t('Title'), '#default_value' => $edit['title'], diff --git a/modules/aggregator/aggregator.fetcher.inc b/modules/aggregator/aggregator.fetcher.inc index c5298ea..38a77ad 100644 --- a/modules/aggregator/aggregator.fetcher.inc +++ b/modules/aggregator/aggregator.fetcher.inc @@ -1,5 +1,5 @@ etag; } if ($feed->modified) { - $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed->modified) . ' GMT'; + $headers['If-Modified-Since'] = gmdate(DATE_RFC1123, $feed->modified); } // Request feed. diff --git a/modules/aggregator/aggregator.install b/modules/aggregator/aggregator.install index 002a06f..96cf93d 100644 --- a/modules/aggregator/aggregator.install +++ b/modules/aggregator/aggregator.install @@ -1,26 +1,15 @@ 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '')); - return $ret; + db_add_field('aggregator_feed', 'hash', array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => '')); } + /** * Add aggregator teaser length to settings from old global default teaser length */ diff --git a/modules/aggregator/aggregator.module b/modules/aggregator/aggregator.module index 8bc4660..3472825 100644 --- a/modules/aggregator/aggregator.module +++ b/modules/aggregator/aggregator.module @@ -1,5 +1,5 @@ REQUEST_TIME, ':never' => AGGREGATOR_CLEAR_NEVER )); + $queue = DrupalQueue::get('aggregator_feeds'); foreach ($result as $feed) { - aggregator_refresh($feed); + $queue->createItem($feed); } } +/** + * Implement hook_cron_queue_info(). + */ +function aggregator_cron_queue_info() { + $queues['aggregator_feeds'] = array( + 'worker callback' => 'aggregator_refresh', + 'time' => 60, + ); + return $queues; +} + /** * Implement hook_block_info(). */ @@ -377,7 +389,7 @@ function aggregator_block_view($delta = '') { case 'feed': if ($feed = db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE block <> 0 AND fid = :fid', array(':fid' => $id))->fetchObject()) { $block['subject'] = check_plain($feed->title); - $result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", array(':fid' => $id), 0, $feed->block); + $result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", 0, $feed->block, array(':fid' => $id)); $read_more = theme('more_link', url('aggregator/sources/' . $feed->fid), t("View this feed's recent news.")); } break; @@ -385,7 +397,7 @@ function aggregator_block_view($delta = '') { case 'category': if ($category = db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchObject()) { $block['subject'] = check_plain($category->title); - $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = :cid ORDER BY i.timestamp DESC, i.iid DESC', array(':cid' => $category->cid), 0, $category->block); + $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = :cid ORDER BY i.timestamp DESC, i.iid DESC', 0, $category->block, array(':cid' => $category->cid)); $read_more = theme('more_link', url('aggregator/categories/' . $category->cid), t("View this category's recent news.")); } break; diff --git a/modules/aggregator/aggregator.pages.inc b/modules/aggregator/aggregator.pages.inc index a8f16eb..69e24c6 100644 --- a/modules/aggregator/aggregator.pages.inc +++ b/modules/aggregator/aggregator.pages.inc @@ -1,5 +1,5 @@ $data->fid), 0, $range_limit); + $result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC', 0, $range_limit, array(':fid' => $data->fid)); break; case 'category': - $result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', array(':cid' => $data['cid']), 0, $range_limit); + $result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', 0, $range_limit, array(':cid' => $data['cid'])); break; } @@ -304,7 +304,7 @@ function aggregator_page_sources() { // Most recent items: $summary_items = array(); if (variable_get('aggregator_summary_items', 3)) { - $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = :fid ORDER BY i.timestamp DESC', array(':fid' => $feed->fid), 0, variable_get('aggregator_summary_items', 3)); + $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = :fid ORDER BY i.timestamp DESC', 0, variable_get('aggregator_summary_items', 3), array(':fid' => $feed->fid)); foreach ($items as $item) { $summary_items[] = theme('aggregator_summary_item', $item); } @@ -327,7 +327,7 @@ function aggregator_page_categories() { foreach ($result as $category) { if (variable_get('aggregator_summary_items', 3)) { $summary_items = array(); - $items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = :cid ORDER BY i.timestamp DESC', array(':cid' => $category->cid), 0, variable_get('aggregator_summary_items', 3)); + $items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = :cid ORDER BY i.timestamp DESC', 0, variable_get('aggregator_summary_items', 3), array(':cid' => $category->cid)); foreach ($items as $item) { $summary_items[] = theme('aggregator_summary_item', $item); } @@ -347,7 +347,7 @@ function aggregator_page_rss() { // arg(2) is the passed cid, only select for that category. if (arg(2)) { $category = db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = :cid', array(':cid' => arg(2)))->fetchObject(); - $result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', array(':cid' => $category->cid), 0, variable_get('feed_default_items', 10)); + $result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', 0, variable_get('feed_default_items', 10), array(':cid' => $category->cid)); } // Or, get the default aggregator items. else { @@ -369,10 +369,10 @@ function aggregator_page_rss() { * @ingroup themeable */ function theme_aggregator_page_rss($feeds, $category = NULL) { - drupal_set_header('Content-Type', 'application/rss+xml; charset=utf-8'); + drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8'); $items = ''; - $feed_length = variable_get('feed_item_length', 'teaser'); + $feed_length = variable_get('feed_item_length', 'fulltext'); foreach ($feeds as $feed) { switch ($feed_length) { case 'teaser': @@ -429,13 +429,13 @@ function aggregator_page_opml($cid = NULL) { * @ingroup themeable */ function theme_aggregator_page_opml($feeds) { - drupal_set_header('Content-Type', 'text/xml; charset=utf-8'); + drupal_add_http_header('Content-Type', 'text/xml; charset=utf-8'); $output = "\n"; $output .= "\n"; $output .= "\n"; $output .= '' . check_plain(variable_get('site_name', 'Drupal')) . "\n"; - $output .= '' . gmdate('r') . "\n"; + $output .= '' . gmdate(DATE_RFC2822, REQUEST_TIME) . "\n"; $output .= "\n"; $output .= "\n"; foreach ($feeds as $feed) { diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test index 8f9f570..320ea5a 100644 --- a/modules/aggregator/aggregator.test +++ b/modules/aggregator/aggregator.test @@ -1,5 +1,5 @@ randomName(10); if (!$feed_url) { $feed_url = url('rss.xml', array( - 'query' => 'feed=' . $feed_name, + 'query' => array('feed' => $feed_name), 'absolute' => TRUE, )); } @@ -128,7 +128,8 @@ class AggregatorTestCase extends DrupalWebTestCase { */ function updateAndRemove($feed, $expected_count) { $this->updateFeedItems($feed, $expected_count); - $this->assertText('There is new syndicated content from'); + $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $this->assertTrue($count); $this->removeFeedItems($feed); $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); $this->assertTrue($count == 0); @@ -551,20 +552,20 @@ class ImportOPMLTestCase extends AggregatorTestCase { function validateImportFormFields() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); - $form = array(); - $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); + $edit = array(); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); $this->assertRaw(t('You must either upload a file or enter a URL.'), t('Error if no fields are filled.')); $path = $this->getEmptyOpml(); - $form = array( + $edit = array( 'files[upload]' => $path, 'remote' => file_create_url($path), ); - $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); $this->assertRaw(t('You must either upload a file or enter a URL.'), t('Error if both fields are filled.')); - $form = array('remote' => 'invalidUrl://empty'); - $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); + $edit = array('remote' => 'invalidUrl://empty'); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); $this->assertText(t('This URL is not valid.'), t('Error if the URL is invalid.')); $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -581,8 +582,8 @@ class ImportOPMLTestCase extends AggregatorTestCase { $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); $this->assertText(t('No new feed has been added.'), t('Attempting to upload invalid XML.')); - $form = array('remote' => file_create_url($this->getEmptyOpml())); - $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); + $edit = array('remote' => file_create_url($this->getEmptyOpml())); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); $this->assertText(t('No new feed has been added.'), t('Attempting to load empty OPML from remote URL.')); $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); @@ -604,12 +605,12 @@ class ImportOPMLTestCase extends AggregatorTestCase { $feeds[0] = $this->getFeedEditArray(); $feeds[1] = $this->getFeedEditArray(); $feeds[2] = $this->getFeedEditArray(); - $form = array( + $edit = array( 'files[upload]' => $this->getValidOpml($feeds), 'refresh' => '900', 'category[1]' => $category, ); - $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); $this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url'])), t('Verifying that a duplicate URL was identified')); $this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title'])), t('Verifying that a duplicate title was identified')); diff --git a/modules/aggregator/tests/CVS/Entries b/modules/aggregator/tests/CVS/Entries index f0f45b1..e259ca1 100644 --- a/modules/aggregator/tests/CVS/Entries +++ b/modules/aggregator/tests/CVS/Entries @@ -1,4 +1,4 @@ /aggregator_test.info/1.1/Thu Sep 3 08:50:16 2009// -/aggregator_test.module/1.3/Thu Sep 3 08:50:16 2009// /aggregator_test_rss091.xml/1.1/Thu Sep 3 08:50:16 2009// +/aggregator_test.module/1.4/Fri Oct 2 19:50:13 2009// D diff --git a/modules/aggregator/tests/aggregator_test.module b/modules/aggregator/tests/aggregator_test.module index cdcbdab..c546f6e 100644 --- a/modules/aggregator/tests/aggregator_test.module +++ b/modules/aggregator/tests/aggregator_test.module @@ -1,5 +1,5 @@ 'checkbox', - '#title' => t('Cache blocks'), - '#default_value' => variable_get('block_cache', FALSE), - '#disabled' => $disabled, - '#description' => $disabled ? t('Block caching is inactive because you have enabled modules defining content access restrictions.') : NULL, - '#weight' => -1, - ); - - // Check if the "Who's online" block is enabled. - $online_block_enabled = db_query_range("SELECT 1 FROM {block} b WHERE module = 'user' AND delta = 'online' AND status = 1", array(), 0, 1)->fetchField(); - - // If the "Who's online" block is enabled, append some descriptive text to - // the end of the form description. - if ($online_block_enabled) { - $form['page_cache']['cache']['#description'] .= '

    ' . t('When caching is enabled, anonymous user sessions are only saved to the database when needed, so the "Who\'s online" block does not display the number of anonymous users.') . '

    '; - } -} - /** * Menu callback for admin/structure/block. */ function block_admin_display($theme = NULL) { - global $custom_theme; - - // If non-default theme configuration has been selected, set the custom theme. - $custom_theme = isset($theme) ? $theme : variable_get('theme_default', 'garland'); - // Fetch and sort blocks. $blocks = _block_rehash(); usort($blocks, '_block_compare'); @@ -49,15 +20,11 @@ function block_admin_display($theme = NULL) { /** * Generate main blocks administration form. */ -function block_admin_display_form(&$form_state, $blocks, $theme = NULL) { - global $theme_key, $custom_theme; +function block_admin_display_form($form, &$form_state, $blocks, $theme = NULL) { + global $theme_key; drupal_add_css(drupal_get_path('module', 'block') . '/block.css', array('preprocess' => FALSE)); - // If non-default theme configuration has been selected, set the custom theme. - $custom_theme = isset($theme) ? $theme : variable_get('theme_default', 'garland'); - drupal_theme_initialize(); - $block_regions = system_region_list($theme_key, REGIONS_VISIBLE) + array(BLOCK_REGION_NONE => '<' . t('none') . '>'); // Weights range from -delta to +delta, so delta should be at least half @@ -66,10 +33,8 @@ function block_admin_display_form(&$form_state, $blocks, $theme = NULL) { $weight_delta = round(count($blocks) / 2); // Build the form tree. - $form = array( - '#action' => arg(4) ? url('admin/structure/block/list/' . $theme_key) : url('admin/structure/block'), - '#tree' => TRUE, - ); + $form['#action'] = arg(4) ? url('admin/structure/block/list/' . $theme_key) : url('admin/structure/block'); + $form['#tree'] = TRUE; foreach ($blocks as $i => $block) { $key = $block['module'] . '_' . $block['delta']; @@ -179,7 +144,7 @@ function _block_compare($a, $b) { /** * Menu callback; displays the block configuration form. */ -function block_admin_configure(&$form_state, $module = NULL, $delta = 0) { +function block_admin_configure($form, &$form_state, $module = NULL, $delta = 0) { $form['module'] = array( '#type' => 'value', '#value' => $module, @@ -357,10 +322,10 @@ function block_admin_configure(&$form_state, $module = NULL, $delta = 0) { function block_admin_configure_validate($form, &$form_state) { if ($form_state['values']['module'] == 'block') { - $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', array( + $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array( ':bid' => $form_state['values']['delta'], ':info' => $form_state['values']['info'], - ), 0, 1)->fetchField(); + ))->fetchField(); if (empty($form_state['values']['info']) || $custom_block_exists) { form_set_error('info', t('Please ensure that each block description is unique.')); } @@ -430,12 +395,12 @@ function block_admin_configure_submit($form, &$form_state) { /** * Menu callback: display the custom block addition form. */ -function block_add_block_form(&$form_state) { - return block_admin_configure($form_state, 'block', NULL); +function block_add_block_form($form, &$form_state) { + return block_admin_configure($form, $form_state, 'block', NULL); } function block_add_block_form_validate($form, &$form_state) { - $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE info = :info', array(':info' => $form_state['values']['info']), 0, 1)->fetchField(); + $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE info = :info', 0, 1, array(':info' => $form_state['values']['info']))->fetchField(); if (empty($form_state['values']['info']) || $custom_block_exists) { form_set_error('info', t('Please ensure that each block description is unique.')); @@ -513,7 +478,7 @@ function block_add_block_form_submit($form, &$form_state) { /** * Menu callback; confirm deletion of custom blocks. */ -function block_custom_block_delete(&$form_state, $bid = 0) { +function block_custom_block_delete($form, &$form_state, $bid = 0) { $custom_block = block_custom_block_get($bid); $form['info'] = array('#type' => 'hidden', '#value' => $custom_block['info'] ? $custom_block['info'] : $custom_block['title']); $form['bid'] = array('#type' => 'hidden', '#value' => $bid); diff --git a/modules/block/block.install b/modules/block/block.install index 7311d40..5565e05 100644 --- a/modules/block/block.install +++ b/modules/block/block.install @@ -1,5 +1,5 @@ execute(); } -/** - * Implement hook_uninstall(). - */ -function block_uninstall() { - drupal_uninstall_schema('block'); -} - /** * Set system.weight to a low value for block module. * @@ -231,9 +223,10 @@ function block_uninstall() { * so before block module runs, there will not be much to alter. */ function block_update_7000() { - $ret = array(); - $ret[] = update_sql("UPDATE {system} SET weight = -5 WHERE name = 'block'"); - return $ret; + db_update('system') + ->fields(array('weight', '-5')) + ->condition('name', 'block') + ->execute(); } @@ -241,8 +234,6 @@ function block_update_7000() { * Add the block_node_type table. */ function block_update_7001() { - $ret = array(); - $schema['block_node_type'] = array( 'description' => 'Sets up display criteria for blocks based on content types', 'fields' => array( @@ -271,6 +262,5 @@ function block_update_7001() { ), ); - db_create_table($ret, 'block_node_type', $schema['block_node_type']); - return $ret; + db_create_table('block_node_type', $schema['block_node_type']); } diff --git a/modules/block/block.module b/modules/block/block.module index 3ba83ce..a4d9bb6 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -1,5 +1,5 @@ ' . t('Blocks are boxes of content rendered into an area, or region, of a web page. The default theme Garland, for example, implements the regions "left sidebar", "right sidebar", "content", "header", and "footer", and a block may appear in any one of these areas. The blocks administration page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions.', array('@blocks' => url('admin/structure/block'))) . '

    '; - $output .= '

    ' . t('Although blocks are usually generated automatically by modules (like the User login block, for example), administrators can also define custom blocks. Custom blocks have a title, description, and body. The body of the block can be as long as necessary, and can contain content supported by any available text format.', array('@text-format' => url('admin/settings/filter'))) . '

    '; + $output .= '

    ' . t('Although blocks are usually generated automatically by modules (like the User login block, for example), administrators can also define custom blocks. Custom blocks have a title, description, and body. The body of the block can be as long as necessary, and can contain content supported by any available text format.', array('@text-format' => url('admin/config/content/formats'))) . '

    '; $output .= '

    ' . t('When working with blocks, remember that:') . '

    '; $output .= '
    • ' . t('since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis.') . '
    • '; $output .= '
    • ' . t('disabled blocks, or blocks not in a region, are never shown.') . '
    • '; @@ -76,6 +76,7 @@ function block_menu() { 'description' => 'Configure what block content appears in your site\'s sidebars and other regions.', 'page callback' => 'block_admin_display', 'access arguments' => array('administer blocks'), + 'theme callback' => '_block_custom_theme', 'file' => 'block.admin.inc', ); $items['admin/structure/block/list'] = array( @@ -123,6 +124,8 @@ function block_menu() { 'weight' => $key == $default ? -10 : 0, 'access callback' => '_block_themes_access', 'access arguments' => array($theme), + 'theme callback' => '_block_custom_theme', + 'theme arguments' => array($key), 'file' => 'block.admin.inc', ); } @@ -133,8 +136,23 @@ function block_menu() { * Menu item access callback - only admin or enabled themes can be accessed. */ function _block_themes_access($theme) { - $admin_theme = variable_get('admin_theme'); - return user_access('administer blocks') && ($theme->status || ($admin_theme && ($theme->name == $admin_theme))); + return user_access('administer blocks') && drupal_theme_access($theme); +} + +/** + * Theme callback for the block configuration pages. + * + * @param $theme + * The theme whose blocks are being configured. If not set, the default theme + * is assumed. + * @return + * The theme that should be used for the block configuration page, or NULL + * to indicate that the default theme should be used. + */ +function _block_custom_theme($theme = NULL) { + // We return exactly what was passed in, to guarantee that the page will + // always be displayed using the theme whose blocks are being configured. + return $theme; } /** @@ -156,7 +174,7 @@ function block_block_info() { * Implement hook_block_configure(). */ function block_block_configure($delta = 0) { - $custom_block = array('format' => FILTER_FORMAT_DEFAULT); + $custom_block = array('format' => filter_default_format()); if ($delta) { $custom_block = block_custom_block_get($delta); } @@ -197,6 +215,7 @@ function block_page_build(&$page) { // Load all region content assigned via blocks. foreach (array_keys($all_regions) as $region) { + $page[$region] = array(); // Assign blocks to region. if ($blocks = block_get_blocks_by_region($region)) { $page[$region] = $blocks; @@ -349,12 +368,12 @@ function block_custom_block_form($edit = array()) { '#type' => 'textarea', '#title' => t('Block body'), '#default_value' => $edit['body'], - '#text_format' => isset($edit['format']) ? $edit['format'] : FILTER_FORMAT_DEFAULT, + '#text_format' => isset($edit['format']) ? $edit['format'] : filter_default_format(), '#rows' => 15, '#description' => t('The content of the block as shown to the user.'), '#required' => TRUE, '#weight' => -17, - '#access' => filter_access($edit['format']), + '#access' => filter_access(filter_format_load($edit['format'])), ); return $form; @@ -373,10 +392,11 @@ function block_custom_block_save($edit, $delta) { } /** - * Implement hook_user_form(). + * Implement hook_form_FORM_ID_alter(). */ -function block_user_form(&$edit, $account, $category) { - if ($category == 'account') { +function block_form_user_profile_form_alter(&$form, &$form_state) { + if ($form['#user_category'] == 'account') { + $account = $form['#user']; $rids = array_keys($account->roles); $result = db_query("SELECT DISTINCT b.* FROM {block} b LEFT JOIN {block_role} r ON b.module = r.module AND b.delta = r.delta WHERE b.status = 1 AND b.custom <> 0 AND (r.rid IN (:rids) OR r.rid IS NULL) ORDER BY b.weight, b.module", array(':rids' => $rids)); $form['block'] = array( @@ -399,8 +419,8 @@ function block_user_form(&$edit, $account, $category) { } } - if (!empty($return)) { - return $form; + if (!isset($return)) { + $form['block']['#access'] = FALSE; } } } @@ -439,7 +459,7 @@ function block_system_themes_form_submit(&$form, &$form_state) { } if ($form_state['values']['admin_theme'] && $form_state['values']['admin_theme'] !== variable_get('admin_theme', 0)) { // If we're changing themes, make sure the theme has its blocks initialized. - $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', array(':theme' => $form_state['values']['admin_theme']), 0, 1)->fetchField(); + $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', 0, 1, array(':theme' => $form_state['values']['admin_theme']))->fetchField(); if (!$has_blocks) { block_theme_initialize($form_state['values']['admin_theme']); } @@ -460,7 +480,7 @@ function block_system_themes_form_submit(&$form, &$form_state) { */ function block_theme_initialize($theme) { // Initialize theme's blocks if none already registered. - $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', array(':theme' => $theme), 0, 1)->fetchField(); + $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', 0, 1, array(':theme' => $theme))->fetchField(); if (!$has_blocks) { $default_theme = variable_get('theme_default', 'garland'); $regions = system_region_list($theme); @@ -798,10 +818,33 @@ function block_user_role_delete($role) { /** * Implement hook_filter_format_delete(). */ -function block_filter_format_delete($format, $default) { +function block_filter_format_delete($format, $fallback) { db_update('block_custom') - ->fields(array('format' => $default->format)) + ->fields(array('format' => $fallback->format)) ->condition('format', $format->format) ->execute(); } +/** + * Implement hook_form_FORM_ID_alter(). + */ +function block_form_system_performance_settings_alter(&$form, &$form_state) { + $disabled = count(module_implements('node_grants')); + $form['caching']['block_cache'] = array( + '#type' => 'checkbox', + '#title' => t('Cache blocks'), + '#default_value' => variable_get('block_cache', FALSE), + '#disabled' => $disabled, + '#description' => $disabled ? t('Block caching is inactive because you have enabled modules defining content access restrictions.') : NULL, + '#weight' => -1, + ); + + // Check if the "Who's online" block is enabled. + $online_block_enabled = db_query_range("SELECT 1 FROM {block} b WHERE module = 'user' AND delta = 'online' AND status = 1", 0, 1)->fetchField(); + + // If the "Who's online" block is enabled, append some descriptive text to + // the end of the form description. + if ($online_block_enabled) { + $form['page_cache']['cache']['#description'] .= '

      ' . t('When caching is enabled, anonymous user sessions are only saved to the database when needed, so the "Who\'s online" block does not display the number of anonymous users.') . '

      '; + } +} diff --git a/modules/block/block.tpl.php b/modules/block/block.tpl.php index 73389a4..7805005 100644 --- a/modules/block/block.tpl.php +++ b/modules/block/block.tpl.php @@ -1,5 +1,5 @@ -
      +
      > subject): ?> -

      subject ?>

      + >subject ?>
      diff --git a/modules/book/CVS/Entries b/modules/book/CVS/Entries index e4677ed..930dae5 100644 --- a/modules/book/CVS/Entries +++ b/modules/book/CVS/Entries @@ -1,14 +1,14 @@ -/book-all-books-block.tpl.php/1.3/Thu Sep 3 08:50:17 2009// /book-export-html.tpl.php/1.2/Thu Sep 3 08:50:17 2009// /book-navigation.tpl.php/1.3/Thu Sep 3 08:50:17 2009// /book-node-export-html.tpl.php/1.2/Thu Sep 3 08:50:17 2009// /book-rtl.css/1.2/Thu Sep 3 08:50:17 2009// -/book.admin.inc/1.21/Thu Sep 3 08:50:17 2009// /book.css/1.12/Thu Sep 3 08:50:17 2009// /book.info/1.12/Thu Sep 3 08:50:17 2009// -/book.install/1.32/Thu Sep 3 08:50:17 2009// /book.js/1.6/Thu Sep 3 08:50:17 2009// -/book.pages.inc/1.15/Thu Sep 3 08:50:18 2009// /book.test/1.16/Thu Sep 3 08:50:18 2009// -/book.module/1.512/Sat Sep 5 15:25:49 2009// +/book-all-books-block.tpl.php/1.4/Fri Oct 2 19:50:13 2009// +/book.admin.inc/1.22/Fri Oct 2 19:50:13 2009// +/book.install/1.33/Fri Oct 2 19:50:13 2009// +/book.module/1.516/Fri Oct 2 19:50:13 2009// +/book.pages.inc/1.16/Fri Oct 2 19:50:13 2009// D diff --git a/modules/book/book-all-books-block.tpl.php b/modules/book/book-all-books-block.tpl.php index 5bf4aa1..c0bea14 100644 --- a/modules/book/book-all-books-block.tpl.php +++ b/modules/book/book-all-books-block.tpl.php @@ -1,5 +1,5 @@ $menu) : ?>
      - +
      diff --git a/modules/book/book.admin.inc b/modules/book/book.admin.inc index 5b76d9d..72871dc 100644 --- a/modules/book/book.admin.inc +++ b/modules/book/book.admin.inc @@ -1,5 +1,5 @@ title); - $form = array(); $form['#node'] = $node; _book_admin_table($node, $form); $form['save'] = array( diff --git a/modules/book/book.install b/modules/book/book.install index 4ef9060..07490a8 100644 --- a/modules/book/book.install +++ b/modules/book/book.install @@ -1,5 +1,5 @@ t('Add child page'), 'href' => 'node/add/' . str_replace('_', '-', $child_type), - 'query' => 'parent=' . $node->book['mlid'], + 'query' => array('parent' => $node->book['mlid']), ); } @@ -102,6 +102,7 @@ function book_node_view_link($node, $build_mode) { function book_menu() { $items['admin/content/book'] = array( 'title' => 'Books', + 'description' => "Manage your site's book outlines.", 'page callback' => 'book_admin_overview', 'access arguments' => array('administer book outlines'), 'type' => MENU_LOCAL_TASK, @@ -248,7 +249,8 @@ function book_block_view($delta = '') { $book_menus[$book_id] = menu_tree_output($pseudo_tree); } } - $block['content'] = theme('book_all_books_block', $book_menus); + $book_menus['#theme'] = 'book_all_books_block'; + $block['content'] = $book_menus; } elseif ($current_bid) { // Only display this block when the user is browsing a book. @@ -705,7 +707,7 @@ function book_children($book_link) { } } - return $children ? menu_tree_output($children) : ''; + return $children ? drupal_render(menu_tree_output($children)) : ''; } /** @@ -891,6 +893,27 @@ function _book_link_defaults($nid) { return array('original_bid' => 0, 'menu_name' => '', 'nid' => $nid, 'bid' => 0, 'router_path' => 'node/%', 'plid' => 0, 'mlid' => 0, 'has_children' => 0, 'weight' => 0, 'module' => 'book', 'options' => array()); } +/** + * Process variables for book-all-books-block.tpl.php. + * + * The $variables array contains the following arguments: + * - $book_menus + * + * All non-renderable elements are removed so that the template has full + * access to the structured data but can also simply iterate over all + * elements and render them (as in the default template). + * + * @see book-navigation.tpl.php + */ +function template_preprocess_book_all_books_block(&$variables) { + // Remove all non-renderable elements. + $elements = $variables['book_menus']; + $variables['book_menus'] = array(); + foreach (element_children($elements) as $index) { + $variables['book_menus'][$index] = $elements[$index]; + } +} + /** * Process variables for book-navigation.tpl.php. * @@ -1061,7 +1084,7 @@ function book_export_traverse($tree, $visit_func) { * The HTML generated for the given node. */ function book_node_export($node, $children = '') { - $node = node_build_content($node, 'print'); + node_build_content($node, 'print'); $node->rendered = drupal_render($node->content); return theme('book_node_export_html', $node, $children); diff --git a/modules/book/book.pages.inc b/modules/book/book.pages.inc index 5b63be7..d36a22b 100644 --- a/modules/book/book.pages.inc +++ b/modules/book/book.pages.inc @@ -1,5 +1,5 @@ book)) { // The node is not part of any book yet - set default options. $node->book = _book_link_defaults($node->nid); @@ -186,7 +186,7 @@ function book_outline_form_submit($form, &$form_state) { * * @ingroup forms */ -function book_remove_form(&$form_state, $node) { +function book_remove_form($form, &$form_state, $node) { $form['#node'] = $node; $title = array('%title' => $node->title); diff --git a/modules/color/CVS/Entries b/modules/color/CVS/Entries index ee581bb..fe38a6d 100644 --- a/modules/color/CVS/Entries +++ b/modules/color/CVS/Entries @@ -3,6 +3,6 @@ D/images//// /color.css/1.4/Thu Sep 3 08:50:18 2009// /color.info/1.10/Thu Sep 3 08:52:47 2009// /color.install/1.5/Thu Sep 3 08:50:18 2009// -/color.js/1.14/Thu Sep 3 08:50:18 2009// /color.test/1.1/Tue Sep 1 20:40:40 2009// -/color.module/1.70/Sat Sep 5 15:25:49 2009// +/color.js/1.15/Fri Oct 2 19:50:13 2009// +/color.module/1.72/Fri Oct 2 19:50:13 2009// diff --git a/modules/color/color.js b/modules/color/color.js index 1e793d4..1968e18 100644 --- a/modules/color/color.js +++ b/modules/color/color.js @@ -1,4 +1,4 @@ -// $Id: color.js,v 1.14 2009/08/31 05:51:08 dries Exp $ +// $Id: color.js,v 1.15 2009/09/20 19:14:40 dries Exp $ (function ($) { Drupal.behaviors.color = { @@ -26,7 +26,7 @@ Drupal.behaviors.color = { // Build a preview. $('#preview').once('color').append('
      '); var gradient = $('#preview #gradient'); - var h = parseInt(gradient.css('height')) / 10; + var h = parseInt(gradient.css('height'), 10) / 10; for (i = 0; i < h; ++i) { gradient.append('
      '); } diff --git a/modules/color/color.module b/modules/color/color.module index 9a2c946..a10c48b 100644 --- a/modules/color/color.module +++ b/modules/color/color.module @@ -1,5 +1,5 @@ 'fieldset', '#title' => t('Color scheme'), @@ -39,7 +39,7 @@ function color_form_system_theme_settings_alter(&$form, &$form_state) { '#attributes' => array('id' => 'color_scheme_form'), '#theme' => 'color_scheme_form', ); - $form['color'] += color_scheme_form($form_state, arg(3)); + $form['color'] += color_scheme_form($form, $form_state, $theme); $form['#submit'][] = 'color_scheme_form_submit'; } } @@ -69,7 +69,7 @@ function _color_theme_select_form_alter(&$form, &$form_state) { /** * Callback for the theme to alter the resources used. */ -function _color_page_alter(&$vars) { +function _color_html_alter(&$vars) { global $language, $theme_key; $themes = list_themes(); @@ -93,6 +93,13 @@ function _color_page_alter(&$vars) { $vars['styles'] = drupal_get_css($vars['css']); } +} + +/** + * Callback for the theme to alter the resources used. + */ +function _color_page_alter(&$vars) { + global $language, $theme_key; // Override logo. $logo = variable_get('color_' . $theme_key . '_logo'); @@ -132,7 +139,8 @@ function color_get_palette($theme, $default = FALSE) { /** * Form callback. Returns the configuration form. */ -function color_scheme_form(&$form_state, $theme) { +function color_scheme_form($form, &$form_state, $theme) { + $form = array(); $base = drupal_get_path('module', 'color'); $info = color_get_info($theme); @@ -188,7 +196,7 @@ function color_scheme_form(&$form_state, $theme) { '#size' => 8, ); } - $form['theme'] = array('#type' => 'value', '#value' => arg(3)); + $form['theme'] = array('#type' => 'value', '#value' => $theme); $form['info'] = array('#type' => 'value', '#value' => $info); return $form; diff --git a/modules/comment/CVS/Entries b/modules/comment/CVS/Entries index 3272c6a..3e225fe 100644 --- a/modules/comment/CVS/Entries +++ b/modules/comment/CVS/Entries @@ -1,15 +1,15 @@ /comment-node-form.js/1.3/Thu Sep 3 08:50:18 2009// /comment-rtl.css/1.2/Thu Sep 3 08:50:18 2009// -/comment-wrapper.tpl.php/1.7/Thu Sep 3 08:50:18 2009// -/comment.admin.inc/1.31/Thu Sep 3 08:50:18 2009// /comment.api.php/1.11/Thu Sep 3 08:50:18 2009// /comment.css/1.5/Thu Sep 3 08:50:18 2009// /comment.info/1.11/Thu Sep 3 08:50:18 2009// -/comment.install/1.44/Thu Sep 3 08:50:18 2009// /comment.js/1.12/Thu Sep 3 08:50:19 2009// -/comment.pages.inc/1.24/Thu Sep 3 08:50:19 2009// -/comment.test/1.44/Thu Sep 3 08:50:19 2009// -/comment.tokens.inc/1.1/Thu Sep 3 08:50:19 2009// -/comment.tpl.php/1.11/Thu Sep 3 08:50:19 2009// -/comment.module/1.767/Sat Sep 5 15:25:49 2009// +/comment-wrapper.tpl.php/1.8/Fri Oct 2 19:50:13 2009// +/comment.admin.inc/1.32/Fri Oct 2 19:50:13 2009// +/comment.install/1.47/Fri Oct 2 19:50:13 2009// +/comment.module/1.775/Fri Oct 2 19:50:13 2009// +/comment.pages.inc/1.25/Fri Oct 2 19:50:13 2009// +/comment.test/1.46/Fri Oct 2 19:50:13 2009// +/comment.tokens.inc/1.2/Fri Oct 2 19:50:13 2009// +/comment.tpl.php/1.12/Fri Oct 2 19:50:13 2009// D diff --git a/modules/comment/comment-wrapper.tpl.php b/modules/comment/comment-wrapper.tpl.php index 4e09d31..6064059 100644 --- a/modules/comment/comment-wrapper.tpl.php +++ b/modules/comment/comment-wrapper.tpl.php @@ -1,5 +1,5 @@ -
      +
      > type != 'forum'): ?>

      diff --git a/modules/comment/comment.admin.inc b/modules/comment/comment.admin.inc index 6cd2333..d3658d9 100644 --- a/modules/comment/comment.admin.inc +++ b/modules/comment/comment.admin.inc @@ -1,5 +1,5 @@ 'fieldset', @@ -159,7 +159,7 @@ function comment_admin_overview_submit($form, &$form_state) { * @ingroup forms * @see comment_multiple_delete_confirm_submit() */ -function comment_multiple_delete_confirm(&$form_state) { +function comment_multiple_delete_confirm($form, &$form_state) { $edit = $form_state['input']; $form['comments'] = array( @@ -232,8 +232,7 @@ function comment_delete_page($cid = NULL) { * @ingroup forms * @see comment_confirm_delete_submit() */ -function comment_confirm_delete(&$form_state, $comment) { - $form = array(); +function comment_confirm_delete($form, &$form_state, $comment) { $form['#comment'] = $comment; return confirm_form( $form, diff --git a/modules/comment/comment.install b/modules/comment/comment.install index f283476..d5ae646 100644 --- a/modules/comment/comment.install +++ b/modules/comment/comment.install @@ -1,26 +1,15 @@ execute(); } -/** - * Changed node_comment_statistics to use node->changed to avoid future timestamps. - */ -function comment_update_1() { - // Change any future last comment timestamps to current time. - db_query('UPDATE {node_comment_statistics} SET last_comment_timestamp = %d WHERE last_comment_timestamp > %d', REQUEST_TIME, REQUEST_TIME); - - // Unstuck node indexing timestamp if needed. - if (($last = variable_get('node_cron_last', FALSE)) !== FALSE) { - variable_set('node_cron_last', min(REQUEST_TIME, $last)); - } - - return array(); -} - /** * @defgroup updates-6.x-to-7.x Comment updates from 6.x to 7.x * @{ @@ -84,40 +58,42 @@ function comment_update_7000() { foreach ($types as $type => $object) { variable_del('comment_default_order' . $type); } - return array(array('success' => TRUE, 'query' => 'Comment order settings removed.')); + return t('Comment order settings removed.'); } /** * Change comment status from published being 0 to being 1 */ function comment_update_7001() { - $ret = array(); - $ret[] = update_sql("UPDATE {comments} SET status = 3 WHERE status = 0"); - $ret[] = update_sql("UPDATE {comments} SET status = 0 WHERE status = 1"); - $ret[] = update_sql("UPDATE {comments} SET status = 1 WHERE status = 3"); + $changes = array( + 3 => 0, + 0 => 1, + 1 => 3, + ); - return $ret; + foreach ($changes as $old => $new) { + db_update('comments') + ->fields(array('status', $new)) + ->condition('status', $old) + ->execute(); + } } /** * Rename {comments} table to {comment}. */ function comment_update_7002() { - $ret = array(); - db_rename_table($ret, 'comments', 'comment'); - return $ret; + db_rename_table('comments', 'comment'); } /** * Improve indexes on the comment table. */ function comment_update_7003() { - $ret = array(); - db_drop_index($ret, 'comment', 'status'); - db_drop_index($ret, 'comment', 'pid'); - db_add_index($ret, 'comment', 'comment_pid_status', array('pid', 'status')); - db_add_index($ret, 'comment', 'comment_num_new', array('nid', 'timestamp', 'status')); - return $ret; + db_drop_index('comment', 'status'); + db_drop_index('comment', 'pid'); + db_add_index('comment', 'comment_pid_status', array('pid', 'status')); + db_add_index('comment', 'comment_num_new', array('nid', 'timestamp', 'status')); } /** @@ -134,29 +110,23 @@ function comment_update_7004() { variable_set('comment_default_mode_' . $type, 0); } } - return array(); } /** * Create comment Field API bundles. */ function comment_update_7005() { - $ret = array(); - foreach (node_type_get_types() as $info) { field_attach_create_bundle('comment_node_' . $info->type); } - return $ret; } /** * Create user related indexes. */ function comment_update_7006() { - $ret = array(); - db_add_index($ret, 'comment', 'comment_uid', array('uid')); - db_add_index($ret, 'node_comment_statistics', 'last_comment_uid', array('last_comment_uid')); - return $ret; + db_add_index('comment', 'comment_uid', array('uid')); + db_add_index('node_comment_statistics', 'last_comment_uid', array('last_comment_uid')); } /** diff --git a/modules/comment/comment.module b/modules/comment/comment.module index cbce909..1d39658 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -1,5 +1,5 @@ $node->nid), 0, $new_replies)->fetchField(); + ORDER BY SUBSTRING(thread, 1, (LENGTH(thread) - 1))', 0, $new_replies, array(':nid' => $node->nid))->fetchField(); $thread = substr($result, 0, -1); $count = db_query('SELECT COUNT(*) FROM {comment} WHERE nid = :nid AND status = 0 AND SUBSTRING(thread, 1, (LENGTH(thread) - 1)) < :thread', array( ':nid' => $node->nid, @@ -445,7 +445,7 @@ function comment_new_page_count($num_comments, $new_replies, $node) { } if ($pageno >= 1) { - $pagenum = "page=" . intval($pageno); + $pagenum = array('page' => intval($pageno)); } return $pagenum; @@ -589,7 +589,7 @@ function comment_node_page_additions($node) { if ($cids = comment_get_thread($node)) { $comments = comment_load_multiple($cids); comment_prepare_thread($comments); - $build = comment_build_multiple($comments); + $build = comment_build_multiple($comments, $node); $build['#attached']['css'][] = drupal_get_path('module', 'comment') . '/comment.css'; $build['pager']['#theme'] = 'pager'; $additions['comments'] = $build; @@ -759,21 +759,26 @@ function comment_prepare_thread(&$comments) { * * @param $comment * A comment object. + * @param $node + * The node the comment is attached to. * @param $build_mode * Build mode, e.g. 'full', 'teaser'... * * @return * An array as expected by drupal_render(). */ -function comment_build($comment, $build_mode = 'full') { - $node = node_load($comment->nid); - $comment = comment_build_content($comment, $build_mode); +function comment_build($comment, $node, $build_mode = 'full') { + // Populate $comment->content with a render() array. + comment_build_content($comment, $node, $build_mode); $build = $comment->content; + // We don't need duplicate rendering info in comment->content. + unset($comment->content); $build += array( '#theme' => 'comment', '#comment' => $comment, + '#node' => $node, '#build_mode' => $build_mode, ); @@ -810,13 +815,12 @@ function comment_build($comment, $build_mode = 'full') { * * @param $comment * A comment object. + * @param $node + * The node the comment is attached to. * @param $build_mode * Build mode, e.g. 'full', 'teaser'... - * @return - * A structured array containing the individual elements - * of the comment's content. */ -function comment_build_content($comment, $build_mode = 'full') { +function comment_build_content($comment, $node, $build_mode = 'full') { if (empty($comment->content)) { $comment->content = array(); } @@ -831,7 +835,7 @@ function comment_build_content($comment, $build_mode = 'full') { if (empty($comment->in_preview)) { $comment->content['links']['comment'] = array( '#theme' => 'links', - '#links' => comment_links($comment), + '#links' => comment_links($comment, $node), '#attributes' => array('class' => array('links', 'inline')), ); } @@ -841,8 +845,6 @@ function comment_build_content($comment, $build_mode = 'full') { // Allow modules to modify the structured comment. drupal_alter('comment_build', $comment, $build_mode); - - return $comment; } /** @@ -852,12 +854,13 @@ function comment_build_content($comment, $build_mode = 'full') { * * @param $comment * The comment object. + * @param $node + * The node the comment is attached to. * @return * A structured array of links. */ -function comment_links($comment) { +function comment_links($comment, $node) { $links = array(); - $node = node_load($comment->nid); if ($node->comment == COMMENT_NODE_OPEN) { if (user_access('administer comments') && user_access('post comments')) { $links['comment_delete'] = array( @@ -910,6 +913,8 @@ function comment_links($comment) { * * @param $comments * An array of comments as returned by comment_load_multiple(). + * @param $node + * The node the comments are attached to. * @param $build_mode * Build mode, e.g. 'full', 'teaser'... * @param $weight @@ -917,12 +922,12 @@ function comment_links($comment) { * @return * An array in the format expected by drupal_render(). */ -function comment_build_multiple($comments, $build_mode = 'full', $weight = 0) { +function comment_build_multiple($comments, $node, $build_mode = 'full', $weight = 0) { $build = array( '#sorted' => TRUE, ); foreach ($comments as $comment) { - $build[$comment->cid] = comment_build($comment, $build_mode); + $build[$comment->cid] = comment_build($comment, $node, $build_mode); $build[$comment->cid]['#weight'] = $weight; $weight++; } @@ -1611,7 +1616,7 @@ function comment_get_display_page($cid, $node_type) { * @see comment_form_validate() * @see comment_form_submit() */ -function comment_form(&$form_state, $comment) { +function comment_form($form, &$form_state, $comment) { global $user; $op = isset($_POST['op']) ? $_POST['op'] : ''; @@ -1629,7 +1634,6 @@ function comment_form(&$form_state, $comment) { $comment += array('name' => '', 'mail' => '', 'homepage' => ''); $comment = (object) $comment; - $form = array(); if (isset($form_state['comment_preview'])) { $form += $form_state['comment_preview']; } @@ -1810,7 +1814,7 @@ function comment_form(&$form_state, $comment) { '#title' => t('Comment'), '#rows' => 15, '#default_value' => $default, - '#text_format' => isset($comment->format) ? $comment->format : FILTER_FORMAT_DEFAULT, + '#text_format' => isset($comment->format) ? $comment->format : filter_default_format(), '#required' => TRUE, ); @@ -1904,7 +1908,7 @@ function comment_preview($comment) { $comment->timestamp = !empty($comment->timestamp) ? $comment->timestamp : REQUEST_TIME; $comment->in_preview = TRUE; - $comment_build = comment_build($comment); + $comment_build = comment_build($comment, $node); $comment_build += array( '#weight' => -100, '#prefix' => '
      ', @@ -1918,7 +1922,7 @@ function comment_preview($comment) { $build = array(); if ($comments = comment_load_multiple(array($comment->pid), array('status' => COMMENT_PUBLISHED))) { $parent_comment = $comments[$comment->pid]; - $build = comment_build($parent_comment); + $build = comment_build($parent_comment, $node); } } else { @@ -2060,16 +2064,21 @@ function comment_form_submit($form, &$form_state) { else { drupal_set_message(t('Your comment has been posted.')); } - $redirect = array('comment/' . $comment->cid, array(), 'comment-' . $comment->cid); + $query = array(); + // Find the current display page for this comment. + $page = comment_get_display_page($comment->cid, $node->type); + if ($page > 0) { + $query['page'] = $page; + } + // Redirect to the newly posted comment. + $redirect = array('node/' . $node->nid, $query, 'comment-' . $comment->cid); } else { watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject), WATCHDOG_WARNING); drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject)), 'error'); - $page = comment_new_page_count($node->comment_count, 1, $node); - $redirect = array('node/' . $node->nid, $page); + // Redirect the user to the node they are commenting on. + $redirect = 'node/' . $node->nid; } - - // Redirect the user to the node they're commenting on. unset($form_state['rebuild']); $form_state['redirect'] = $redirect; } @@ -2081,16 +2090,22 @@ function comment_form_submit($form, &$form_state) { */ function template_preprocess_comment(&$variables) { $comment = $variables['elements']['#comment']; + $node = $variables['elements']['#node']; $variables['comment'] = $comment; - $variables['node'] = node_load($comment->nid); + $variables['node'] = $node; $variables['author'] = theme('username', $comment); - $variables['content'] = $comment->content; $variables['date'] = format_date($comment->timestamp); $variables['new'] = !empty($comment->new) ? t('new') : ''; $variables['picture'] = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', $comment) : ''; $variables['signature'] = $comment->signature; $variables['title'] = l($comment->subject, 'comment/' . $comment->cid, array('fragment' => "comment-$comment->cid")); $variables['template_files'][] = 'comment-' . $variables['node']->type; + + // Helpful $content variable for templates. + foreach (element_children($variables['elements']) as $key) { + $variables['content'][$key] = $variables['elements'][$key]; + } + // Set status to a string representation of comment->status. if (isset($comment->in_preview)) { $variables['status'] = 'comment-preview'; @@ -2138,10 +2153,10 @@ function theme_comment_post_forbidden($node) { // We cannot use drupal_get_destination() because these links // sometimes appear on /node and taxonomy listing pages. if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_SEPARATE_PAGE) { - $destination = 'destination=' . rawurlencode("comment/reply/$node->nid#comment-form"); + $destination = array('destination' => "comment/reply/$node->nid#comment-form"); } else { - $destination = 'destination=' . rawurlencode("node/$node->nid#comment-form"); + $destination = array('destination' => "node/$node->nid#comment-form"); } if (variable_get('user_register', 1)) { @@ -2229,10 +2244,10 @@ function _comment_update_node_statistics($nid) { if ($count > 0) { // Comments exist. - $last_reply = db_query_range('SELECT cid, name, timestamp, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', array( + $last_reply = db_query_range('SELECT cid, name, timestamp, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array( ':nid' => $nid, ':status' => COMMENT_PUBLISHED, - ), 0, 1)->fetchObject(); + ))->fetchObject(); db_update('node_comment_statistics') ->fields( array( 'comment_count' => $count, @@ -2285,50 +2300,22 @@ function vancode2int($c = '00') { return base_convert(substr($c, 1), 36, 10); } -/** - * Implement hook_hook_info(). - */ -function comment_hook_info() { - return array( - 'comment' => array( - 'comment' => array( - 'insert' => array( - 'runs when' => t('After saving a new comment'), - ), - 'update' => array( - 'runs when' => t('After saving an updated comment'), - ), - 'delete' => array( - 'runs when' => t('After deleting a comment') - ), - 'view' => array( - 'runs when' => t('When a comment is being viewed by an authenticated user') - ), - ), - ), - ); -} - /** * Implement hook_action_info(). */ function comment_action_info() { return array( 'comment_unpublish_action' => array( - 'description' => t('Unpublish comment'), + 'label' => t('Unpublish comment'), 'type' => 'comment', 'configurable' => FALSE, - 'hooks' => array( - 'comment' => array('insert', 'update'), - ) + 'triggers' => array('comment_insert', 'comment_update'), ), 'comment_unpublish_by_keyword_action' => array( - 'description' => t('Unpublish comment containing keyword(s)'), + 'label' => t('Unpublish comment containing keyword(s)'), 'type' => 'comment', 'configurable' => TRUE, - 'hooks' => array( - 'comment' => array('insert', 'update'), - ) + 'triggers' => array('comment_insert', 'comment_update'), ) ); } @@ -2436,9 +2423,9 @@ function comment_menu_alter(&$items) { /** * Implement hook_filter_format_delete(). */ -function comment_filter_format_delete($format, $default) { +function comment_filter_format_delete($format, $fallback) { db_update('comment') - ->fields(array('format' => $default->format)) + ->fields(array('format' => $fallback->format)) ->condition('format', $format->format) ->execute(); } diff --git a/modules/comment/comment.pages.inc b/modules/comment/comment.pages.inc index 2233289..e38ed69 100644 --- a/modules/comment/comment.pages.inc +++ b/modules/comment/comment.pages.inc @@ -1,5 +1,5 @@ node_type = 'comment_node_' . $node->type; field_attach_load('comment', array($comment->cid => $comment)); $comment->name = $comment->uid ? $comment->registered_name : $comment->name; - $build['comment_parent'] = comment_build($comment); + $build['comment_parent'] = comment_build($comment, $node); } else { drupal_set_message(t('The comment you are replying to does not exist.'), 'error'); diff --git a/modules/comment/comment.test b/modules/comment/comment.test index 663dd9d..78b334b 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -1,5 +1,5 @@ drupalPost(NULL, $edit, t('Save')); $match = array(); // Get comment ID - preg_match('/#comment-([^"]+)/', $this->getURL(), $match); + preg_match('/#comment-([0-9]+)/', $this->getURL(), $match); // Get comment. if ($contact !== TRUE) { // If true then attempting to find error message. @@ -308,7 +308,7 @@ class CommentInterfaceTest extends CommentHelperCase { $this->setCommentsPerPage(2); $comment_new_page = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); $this->assertTrue($this->commentExists($comment_new_page), t('Page one exists. %s')); - $this->drupalGet('node/' . $this->node->nid, array('query' => 'page=1')); + $this->drupalGet('node/' . $this->node->nid, array('query' => array('page' => 1))); $this->assertTrue($this->commentExists($reply, TRUE), t('Page two exists. %s')); $this->setCommentsPerPage(50); @@ -588,13 +588,13 @@ class CommentPagerTest extends CommentHelperCase { $this->assertFalse($this->commentExists($comments[2]), t('Comment 3 does not appear on page 1.')); // Check the second page. - $this->drupalGet('node/' . $node->nid, array('query' => 'page=1')); + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 1))); $this->assertTrue($this->commentExists($comments[1]), t('Comment 2 appears on page 2.')); $this->assertFalse($this->commentExists($comments[0]), t('Comment 1 does not appear on page 2.')); $this->assertFalse($this->commentExists($comments[2]), t('Comment 3 does not appear on page 2.')); // Check the third page. - $this->drupalGet('node/' . $node->nid, array('query' => 'page=2')); + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 2))); $this->assertTrue($this->commentExists($comments[2]), t('Comment 3 appears on page 3.')); $this->assertFalse($this->commentExists($comments[0]), t('Comment 1 does not appear on page 3.')); $this->assertFalse($this->commentExists($comments[1]), t('Comment 2 does not appear on page 3.')); @@ -608,21 +608,21 @@ class CommentPagerTest extends CommentHelperCase { $this->setCommentsPerPage(2); // We are still in flat view - the replies should not be on the first page, // even though they are replies to the oldest comment. - $this->drupalGet('node/' . $node->nid, array('query' => 'page=0')); + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); $this->assertFalse($this->commentExists($reply, TRUE), t('In flat mode, reply does not appear on page 1.')); // If we switch to threaded mode, the replies on the oldest comment // should be bumped to the first page and comment 6 should be bumped // to the second page. $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Switched to threaded mode.')); - $this->drupalGet('node/' . $node->nid, array('query' => 'page=0')); + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); $this->assertTrue($this->commentExists($reply, TRUE), t('In threaded mode, reply appears on page 1.')); $this->assertFalse($this->commentExists($comments[1]), t('In threaded mode, comment 2 has been bumped off of page 1.')); // If (# replies > # comments per page) in threaded expanded view, // the overage should be bumped. $reply2 = $this->postComment(NULL, $this->randomName(), $this->randomName(), FALSE, TRUE); - $this->drupalGet('node/' . $node->nid, array('query' => 'page=0')); + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); $this->assertFalse($this->commentExists($reply2, TRUE), t('In threaded mode where # replies > # comments per page, the newest reply does not appear on page 1.')); $this->drupalLogout(); diff --git a/modules/comment/comment.tokens.inc b/modules/comment/comment.tokens.inc index 100bd0c..4a7068b 100644 --- a/modules/comment/comment.tokens.inc +++ b/modules/comment/comment.tokens.inc @@ -1,5 +1,5 @@ TRUE); if (isset($options['language'])) { - $url_options['language'] = $language; - $language_code = $language->language; + $url_options['language'] = $options['language']; + $language_code = $options['language']->language; } else { $language_code = NULL; diff --git a/modules/comment/comment.tpl.php b/modules/comment/comment.tpl.php index 4ac565d..1ec809f 100644 --- a/modules/comment/comment.tpl.php +++ b/modules/comment/comment.tpl.php @@ -1,5 +1,5 @@ -
      +
      > -

      + >