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 '
\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 '
\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 '
\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 '
' . implode(' » ', $breadcrumb) . '
';
+ // 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 .= '
' . implode(' » ', $breadcrumb) . '
';
+ 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 = '
';
}
$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/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 @@
-
+
>
-
+
>
'value',
@@ -162,18 +162,3 @@ function contact_admin_delete_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/structure/contact';
return;
}
-
-function contact_admin_settings() {
- $form['contact_hourly_threshold'] = array('#type' => 'select',
- '#title' => t('Hourly threshold'),
- '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50)),
- '#default_value' => 3,
- '#description' => t('The maximum number of contact form submissions a user can perform per hour.'),
- );
- $form['contact_default_status'] = array(
- '#type' => 'checkbox',
- '#title' => t('Enable the personal contact form by default for new users'),
- '#default_value' => 1,
- );
- return system_settings_form($form, TRUE);
-}
diff --git a/modules/contact/contact.install b/modules/contact/contact.install
index bcb88e0..d7dfe6a 100644
--- a/modules/contact/contact.install
+++ b/modules/contact/contact.install
@@ -1,29 +1,18 @@
' . t('This page lets you set up your site-wide contact form. To do so, add one or more categories. You can associate different recipients with each category to route e-mails to different people. For example, you can route website feedback to the webmaster and direct product information requests to the sales department. On the settings page, you can customize the information shown above the contact form. This can be useful to provide additional contact information such as your postal address and telephone number.', array('@settings' => url('admin/structure/contact/settings'), '@form' => url('contact'))) . '';
if (!module_exists('menu')) {
- $menu_note = t('The menu item can be customized and configured only once the menu module has been enabled.', array('@modules-page' => url('admin/settings/modules')));
+ $menu_note = t('The menu item can be customized and configured only once the menu module has been enabled.', array('@modules-page' => url('admin/config/modules')));
}
else {
$menu_note = '';
@@ -59,12 +59,6 @@ function contact_menu() {
'access arguments' => array('administer site-wide contact form'),
'file' => 'contact.admin.inc',
);
- $items['admin/structure/contact/list'] = array(
- 'title' => 'List',
- 'page callback' => 'contact_admin_categories',
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- 'file' => 'contact.admin.inc',
- );
$items['admin/structure/contact/add'] = array(
'title' => 'Add category',
'page callback' => 'drupal_get_form',
@@ -90,15 +84,6 @@ function contact_menu() {
'type' => MENU_CALLBACK,
'file' => 'contact.admin.inc',
);
- $items['admin/structure/contact/settings'] = array(
- 'title' => 'Settings',
- 'page callback' => 'drupal_get_form',
- 'page arguments' => array('contact_admin_settings'),
- 'access arguments' => array('administer site-wide contact form'),
- 'file' => 'contact.admin.inc',
- 'type' => MENU_LOCAL_TASK,
- 'weight' => 1,
- );
$items['contact'] = array(
'title' => 'Contact',
'page callback' => 'contact_site_page',
@@ -145,10 +130,11 @@ function contact_load($cid) {
}
/**
- * Implement hook_user_form().
+ * Implement hook_form_FORM_ID_alter().
*/
-function contact_user_form(&$edit, $account, $category) {
- if ($category == 'account') {
+function contact_form_user_profile_form_alter(&$form, &$form_state) {
+ if ($form['#user_category'] == 'account') {
+ $account = $form['#user'];
$form['contact'] = array('#type' => 'fieldset',
'#title' => t('Contact settings'),
'#weight' => 5,
@@ -156,10 +142,9 @@ function contact_user_form(&$edit, $account, $category) {
);
$form['contact']['contact'] = array('#type' => 'checkbox',
'#title' => t('Personal contact form'),
- '#default_value' => !empty($edit['contact']) ? $edit['contact'] : FALSE,
+ '#default_value' => !empty($account->contact) ? $account->contact : FALSE,
'#description' => t('Allow other users to contact you via a personal contact form which keeps your e-mail address hidden. Note that some privileged users such as site administrators are still able to contact you even if you choose to disable this feature.', array('@url' => url("user/$account->uid/contact"))),
);
- return $form;
}
}
@@ -201,3 +186,19 @@ function contact_mail($key, &$message, $params) {
break;
}
}
+
+/**
+ * Implement of hook_form_FORM_ID_alter().
+ */
+function contact_form_user_admin_settings_alter(&$form, $form_state) {
+ $form['contact'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Contact forms'),
+ '#weight' => 0,
+ );
+ $form['contact']['contact_default_status'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Enable the personal contact form by default for new users.'),
+ '#default_value' => 1,
+ );
+}
diff --git a/modules/contact/contact.pages.inc b/modules/contact/contact.pages.inc
index 193f283..875f783 100644
--- a/modules/contact/contact.pages.inc
+++ b/modules/contact/contact.pages.inc
@@ -1,5 +1,5 @@
variable_get('contact_hourly_threshold', 3)));
+ if (!flood_is_allowed('contact', variable_get('contact_threshold_limit', 5), variable_get('contact_threshold_window', 3600)) && !user_access('administer site-wide contact form')) {
+ $output = t("You cannot send more than %number messages in @interval. Please try again later.", array('%number' => variable_get('contact_threshold_limit', 3), '@interval' => format_interval(variable_get('contact_threshold_window', 3600))));
}
elseif (!db_query("SELECT COUNT(cid) FROM {contact}")->fetchField()) {
if (user_access('administer site-wide contact form')) {
@@ -155,10 +155,10 @@ function contact_personal_page($account) {
global $user;
if (!valid_email_address($user->mail)) {
- $output = t('You need to provide a valid e-mail address to contact other users. Please update your user information and try again.', array('@url' => url("user/$user->uid/edit", array('query' => 'destination=' . drupal_get_destination()))));
+ $output = t('You need to provide a valid e-mail address to contact other users. Please update your user information and try again.', array('@url' => url("user/$user->uid/edit", array('query' => drupal_get_destination()))));
}
- elseif (!flood_is_allowed('contact', variable_get('contact_hourly_threshold', 3)) && !user_access('administer site-wide contact form')) {
- $output = t("You cannot send more than %number messages per hour. Please try again later.", array('%number' => variable_get('contact_hourly_threshold', 3)));
+ elseif (!flood_is_allowed('contact', variable_get('contact_threshold_limit', 5), variable_get('contact_threshold_window', 3600)) && !user_access('administer site-wide contact form')) {
+ $output = t("You cannot send more than %number messages in @interval. Please try again later.", array('%number' => variable_get('contact_threshold_limit', 3), '@interval' => format_interval(variable_get('contact_threshold_window', 3600))));
}
else {
drupal_set_title($account->name);
@@ -171,7 +171,7 @@ function contact_personal_page($account) {
/**
* Form builder; the personal contact form.
*/
-function contact_personal_form(&$form_state, $recipient) {
+function contact_personal_form($form, &$form_state, $recipient) {
global $user;
$form['#token'] = $user->name . $user->mail;
$form['recipient'] = array('#type' => 'value', '#value' => $recipient);
diff --git a/modules/contact/contact.test b/modules/contact/contact.test
index 23383c5..ba0e2de 100644
--- a/modules/contact/contact.test
+++ b/modules/contact/contact.test
@@ -1,5 +1,5 @@
drupalCreateUser(array('access site-wide contact form', 'administer site-wide contact form', 'administer permissions'));
+ $admin_user = $this->drupalCreateUser(array('access site-wide contact form', 'administer site-wide contact form', 'administer permissions', 'administer users'));
$this->drupalLogin($admin_user);
+
+ $flood_limit = 3;
+ variable_set('contact_threshold_limit', $flood_limit);
+ variable_set('contact_threshold_window', 600);
// Set settings.
$edit = array();
- $edit['contact_hourly_threshold'] = 3;
$edit['contact_default_status'] = TRUE;
- $this->drupalPost('admin/structure/contact/settings', $edit, t('Save configuration'));
+ $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.'));
// Delete old categories to ensure that new categories are used.
@@ -139,13 +142,13 @@ class ContactSitewideTestCase extends DrupalWebTestCase {
$this->assertText(t('You must select a valid category.'), t('Valid category required.'));
// Submit contact form with correct values and check flood interval.
- for ($i = 0; $i < $edit['contact_hourly_threshold']; $i++) {
+ for ($i = 0; $i < $flood_limit; $i++) {
$this->submitContact($this->randomName(16), $recipients[0], $this->randomName(16), $categories[0], $this->randomName(64));
$this->assertText(t('Your message has been sent.'), t('Message sent.'));
}
// Submit contact form one over limit.
$this->drupalGet('contact');
- $this->assertRaw(t('You cannot send more than %number messages per hour. Please try again later.', array('%number' => $edit['contact_hourly_threshold'])), t('Message threshold reached.'));
+ $this->assertRaw(t('You cannot send more than %number messages in @interval. Please try again later.', array('%number' => variable_get('contact_threshold_limit', 3), '@interval' => format_interval(600))), t('Message threshold reached.'));
// Delete created categories.
$this->drupalLogin($admin_user);
@@ -157,7 +160,7 @@ class ContactSitewideTestCase extends DrupalWebTestCase {
*/
function testAutoReply() {
// Create and login administrative user.
- $admin_user = $this->drupalCreateUser(array('access site-wide contact form', 'administer site-wide contact form', 'administer permissions'));
+ $admin_user = $this->drupalCreateUser(array('access site-wide contact form', 'administer site-wide contact form', 'administer permissions', 'administer users'));
$this->drupalLogin($admin_user);
// Set up three categories, 2 with an auto-reply and one without.
@@ -314,15 +317,16 @@ class ContactPersonalTestCase extends DrupalWebTestCase {
* Test personal contact form.
*/
function testPersonalContact() {
- $admin_user = $this->drupalCreateUser(array('administer site-wide contact form'));
+ $admin_user = $this->drupalCreateUser(array('administer site-wide contact form', 'administer users'));
$this->drupalLogin($admin_user);
+
+ $flood_limit = 3;
+ variable_set('contact_threshold_limit', $flood_limit);
// Enable the personal contact form.
- $flood_control = 3;
$edit = array();
$edit['contact_default_status'] = TRUE;
- $edit['contact_hourly_threshold'] = $flood_control;
- $this->drupalPost('admin/structure/contact/settings', $edit, t('Save configuration'));
+ $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.'));
// Reload variables.
@@ -349,7 +353,7 @@ class ContactPersonalTestCase extends DrupalWebTestCase {
$this->assertIdentical($num_records_flood, '0', t('Flood table emptied.'));
// Submit contact form with correct values and check flood interval.
- for ($i = 0; $i < $flood_control; $i++) {
+ for ($i = 0; $i < $flood_limit; $i++) {
$this->drupalGet('user/' . $web_user2->uid . '/contact');
$this->drupalPost(NULL, $edit, t('Send message'));
$this->assertText(t('Your message has been sent.'), t('Message sent.'));
@@ -357,7 +361,7 @@ class ContactPersonalTestCase extends DrupalWebTestCase {
// Submit contact form one over limit.
$this->drupalGet('user/' . $web_user2->uid . '/contact');
- $this->assertRaw(t('You cannot send more than %number messages per hour. Please try again later.', array('%number' => $flood_control)), t('Message threshold reached.'));
+ $this->assertRaw(t('You cannot send more than %number messages in @interval. Please try again later.', array('%number' => $flood_limit, '@interval' => format_interval(variable_get('contact_threshold_window', 3600)))), t('Message threshold reached.'));
$this->drupalLogout();
@@ -366,7 +370,7 @@ class ContactPersonalTestCase extends DrupalWebTestCase {
// Disable the personal contact form.
$edit = array();
$edit['contact_default_status'] = FALSE;
- $this->drupalPost('admin/structure/contact/settings', $edit, t('Save configuration'));
+ $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.'));
// Reload variables.
diff --git a/modules/dblog/CVS/Entries b/modules/dblog/CVS/Entries
index dee089f..f0632a8 100644
--- a/modules/dblog/CVS/Entries
+++ b/modules/dblog/CVS/Entries
@@ -1,8 +1,8 @@
/dblog-rtl.css/1.5/Thu Sep 3 08:50:20 2009//
-/dblog.admin.inc/1.29/Thu Sep 3 08:50:20 2009//
/dblog.css/1.7/Thu Sep 3 08:50:20 2009//
/dblog.info/1.7/Thu Sep 3 08:50:20 2009//
-/dblog.install/1.16/Thu Sep 3 08:50:20 2009//
/dblog.module/1.42/Thu Sep 3 08:50:20 2009//
/dblog.test/1.28/Thu Sep 3 08:50:20 2009//
+/dblog.admin.inc/1.30/Fri Oct 2 19:50:13 2009//
+/dblog.install/1.18/Fri Oct 2 19:50:13 2009//
D
diff --git a/modules/dblog/dblog.admin.inc b/modules/dblog/dblog.admin.inc
index d23ea60..3b94836 100644
--- a/modules/dblog/dblog.admin.inc
+++ b/modules/dblog/dblog.admin.inc
@@ -1,5 +1,5 @@
'fieldset',
'#title' => t('Clear log messages'),
@@ -367,7 +367,7 @@ function dblog_clear_log_form() {
/**
* Submit callback: clear database with log messages.
*/
-function dblog_clear_log_submit(&$form_state, $form) {
+function dblog_clear_log_submit() {
db_delete('watchdog')->execute();
drupal_set_message(t('Database log cleared.'));
}
diff --git a/modules/dblog/dblog.install b/modules/dblog/dblog.install
index 89aefbc..59afeb1 100644
--- a/modules/dblog/dblog.install
+++ b/modules/dblog/dblog.install
@@ -1,27 +1,11 @@
'varchar', 'length' => 255, 'not null' => FALSE, 'default' => ''));
- db_change_field($ret, 'watchdog', 'referer', 'referer', array('type' => 'text', 'not null' => FALSE));
- return $ret;
+ db_change_field('watchdog', 'link', 'link', array('type' => 'varchar', 'length' => 255, 'not null' => FALSE, 'default' => ''));
+ db_change_field('watchdog', 'referer', 'referer', array('type' => 'text', 'not null' => FALSE));
}
/**
* Add index on uid.
*/
function dblog_update_7002() {
- $ret = array();
- db_add_index($ret, 'watchdog', 'uid', array('uid'));
- return $ret;
+ db_add_index('watchdog', 'uid', array('uid'));
}
/**
* Allow longer type values.
*/
function dblog_update_7003() {
- $ret = array();
- db_change_field($ret, 'watchdog', 'type', 'type', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''));
- return $ret;
+ db_change_field('watchdog', 'type', 'type', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''));
}
diff --git a/modules/field/CVS/Entries b/modules/field/CVS/Entries
index 7a70a7d..98ad1bd 100644
--- a/modules/field/CVS/Entries
+++ b/modules/field/CVS/Entries
@@ -1,13 +1,13 @@
D/modules////
D/theme////
-/field.api.php/1.31/Thu Sep 3 08:50:20 2009//
-/field.crud.inc/1.27/Thu Sep 3 08:50:20 2009//
-/field.default.inc/1.17/Thu Sep 3 08:50:21 2009//
-/field.form.inc/1.23/Thu Sep 3 08:50:21 2009//
/field.info/1.5/Thu Sep 3 08:50:21 2009//
-/field.install/1.12/Thu Sep 3 08:50:21 2009//
-/field.module/1.30/Thu Sep 3 08:50:21 2009//
/field.multilingual.inc/1.1/Thu Sep 3 08:50:21 2009//
-/field.test/1.47/Thu Sep 3 08:50:21 2009//
-/field.info.inc/1.17/Sat Sep 5 13:40:16 2009//
-/field.attach.inc/1.45/Sat Sep 5 15:25:49 2009//
+/field.api.php/1.37/Fri Oct 2 19:50:13 2009//
+/field.attach.inc/1.51/Fri Oct 2 19:50:13 2009//
+/field.crud.inc/1.38/Fri Oct 2 19:50:13 2009//
+/field.default.inc/1.18/Fri Oct 2 19:50:13 2009//
+/field.form.inc/1.26/Fri Oct 2 19:50:13 2009//
+/field.info.inc/1.19/Fri Oct 2 19:50:13 2009//
+/field.install/1.15/Fri Oct 2 19:50:13 2009//
+/field.module/1.37/Fri Oct 2 19:50:13 2009//
+/field.test/1.58/Fri Oct 2 19:50:13 2009//
diff --git a/modules/field/field.api.php b/modules/field/field.api.php
index 438dc78..cd7ffc1 100644
--- a/modules/field/field.api.php
+++ b/modules/field/field.api.php
@@ -1,5 +1,5 @@
array(
+ * 'arguments' => array('element' => NULL),
+ * )
+ * @code
+ *
+ * If a formatter requires a different theme hook definition,
+ * implement hook_theme_registry_alter().
*
* @see hook_field_formatter_info().
* @see hook_field_formatter_info_alter().
* @see theme_field_formatter_FORMATTER_NAME().
+ * @see hook_theme().
+ * @see hook_theme_registry_alter().
*
* @return
* An array describing the formatter types implemented by the module.
@@ -1085,6 +1099,45 @@ function hook_field_attach_delete_bundle($bundle, $instances) {
* @{
*/
+/**
+ * Expose Field API storage backends.
+ *
+ * @return
+ * An array describing the storage backends implemented by the module.
+ * The keys are storage backend names. To avoid name clashes, storage backend
+ * names should be prefixed with the name of the module that exposes them.
+ * The values are arrays describing the storage backend, with the following
+ * key/value pairs:
+ * - label: The human-readable name of the storage backend.
+ * - description: A short description for the storage backend.
+ * - settings: An array whose keys are the names of the settings available
+ * for the storage backend, and whose values are the default values for
+ * those settings.
+ */
+function hook_field_storage_info() {
+ return array(
+ 'field_sql_storage' => array(
+ 'label' => t('Default SQL storage'),
+ 'description' => t('Stores fields in the local SQL database, using per-field tables.'),
+ 'settings' => array(),
+ ),
+ );
+}
+
+/**
+ * Perform alterations on Field API storage types.
+ *
+ * @param $info
+ * Array of informations on storage types exposed by
+ * hook_field_field_storage_info() implementations.
+ */
+function hook_field_storage_info_alter(&$info) {
+ // Add a setting to a storage type.
+ $info['field_sql_storage']['settings'] += array(
+ 'mymodule_additional_setting' => 'default value',
+ );
+}
+
/**
* Load field data for a set of objects.
*
@@ -1096,15 +1149,15 @@ function hook_field_attach_delete_bundle($bundle, $instances) {
* FIELD_LOAD_CURRENT to load the most recent revision for all
* fields, or FIELD_LOAD_REVISION to load the version indicated by
* each object.
- * @param $skip_fields
- * An array keyed by field ids whose data has already been loaded and
- * therefore should not be loaded again. The values associated to these keys
- * are not specified.
+ * @param $fields
+ * An array listing the fields to be loaded. The keys of the array are field
+ * ids, the values of the array are the object ids (or revision ids,
+ * depending on the $age parameter) to be loaded for each field.
* @return
* Loaded field values are added to $objects. Fields with no values should be
* set as an empty array.
*/
-function hook_field_storage_load($obj_type, $objects, $age, $skip_fields) {
+function hook_field_storage_load($obj_type, $objects, $age, $fields) {
}
/**
@@ -1117,12 +1170,11 @@ function hook_field_storage_load($obj_type, $objects, $age, $skip_fields) {
* @param $op
* FIELD_STORAGE_UPDATE when updating an existing object,
* FIELD_STORAGE_INSERT when inserting a new object.
- * @param $skip_fields
- * An array keyed by field ids whose data has already been written and
- * therefore should not be written again. The values associated to these keys
- * are not specified.
+ * @param $fields
+ * An array listing the fields to be written. The keys and values of the
+ * array are field ids.
*/
-function hook_field_storage_write($obj_type, $object, $op, $skip_fields) {
+function hook_field_storage_write($obj_type, $object, $op, $fields) {
}
/**
@@ -1132,8 +1184,11 @@ function hook_field_storage_write($obj_type, $object, $op, $skip_fields) {
* The entity type of object, such as 'node' or 'user'.
* @param $object
* The object on which to operate.
+ * @param $fields
+ * An array listing the fields to delete. The keys and values of the
+ * array are field ids.
*/
-function hook_field_storage_delete($obj_type, $object) {
+function hook_field_storage_delete($obj_type, $object, $fields) {
}
/**
@@ -1148,8 +1203,11 @@ function hook_field_storage_delete($obj_type, $object) {
* The object on which to operate. The revision to delete is
* indicated by the object's revision id property, as identified by
* hook_fieldable_info() for $obj_type.
+ * @param $fields
+ * An array listing the fields to delete. The keys and values of the
+ * array are field ids.
*/
-function hook_field_storage_delete_revision($obj_type, $object) {
+function hook_field_storage_delete_revision($obj_type, $object, $fields) {
}
/**
@@ -1174,26 +1232,6 @@ function hook_field_storage_delete_revision($obj_type, $object) {
function hook_field_storage_query($field_name, $conditions, $count, &$cursor = NULL, $age) {
}
-/**
- * Act on creation of a new bundle.
- *
- * @param $bundle
- * The name of the bundle being created.
- */
-function hook_field_storage_create_bundle($bundle) {
-}
-
-/**
- * Act on a bundle being renamed.
- *
- * @param $bundle_old
- * The old name of the bundle.
- * @param $bundle_new
- * The new name of the bundle.
- */
-function hook_field_storage_rename_bundle($bundle_old, $bundle_new) {
-}
-
/**
* Act on creation of a new field.
*
@@ -1206,21 +1244,19 @@ function hook_field_storage_create_field($field) {
/**
* Act on deletion of a field.
*
- * @param $field_name
- * The name of the field being deleted.
+ * @param $field
+ * The field being deleted.
*/
-function hook_field_storage_delete_field($field_name) {
+function hook_field_storage_delete_field($field) {
}
/**
* Act on deletion of a field instance.
*
- * @param $field_name
- * The name of the field in the new instance.
- * @param $bundle
- * The name of the bundle in the new instance.
+ * @param $instance
+ * The instance being deleted.
*/
-function hook_field_storage_delete_instance($field_name, $bundle) {
+function hook_field_storage_delete_instance($instance) {
}
/**
@@ -1263,18 +1299,65 @@ function hook_field_create_instance($instance) {
}
/**
- * Act on a field being deleted.
+ * Forbid a field update from occurring.
*
- * This hook is invoked just before the field is deleted.
+ * Any module may forbid any update for any reason. For example, the
+ * field's storage module might forbid an update if it would change
+ * the storage schema while data for the field exists. A field type
+ * module might forbid an update if it would change existing data's
+ * semantics, or if there are external dependencies on field settings
+ * that cannot be updated.
*
- * TODO: Not implemented.
+ * @param $field
+ * The field as it will be post-update.
+ * @param $prior_field
+ * The field as it is pre-update.
+ * @param $has_data
+ * Whether any data already exists for this field.
+ * @return
+ * Throws a FieldUpdateForbiddenException to prevent the update from occuring.
+ */
+function hook_field_update_field_forbid($field, $prior_field, $has_data) {
+ // A 'list' field stores integer keys mapped to display values. If
+ // the new field will have fewer values, and any data exists for the
+ // abandonded keys, the field will have no way to display them. So,
+ // forbid such an update.
+ if ($has_data && count($field['settings']['allowed_values']) < count($prior_field['settings']['allowed_values'])) {
+ // Identify the keys that will be lost.
+ $lost_keys = array_diff(array_keys($field['settings']['allowed_values']), array_keys($prior_field['settings']['allowed_values']));
+ // If any data exist for those keys, forbid the update.
+ $count = field_attach_query($prior_field['id'], array('value', $lost_keys, 'IN'), 1);
+ if ($count > 0) {
+ throw new FieldUpdateForbiddenException("Cannot update a list field not to include keys with existing data");
+ }
+ }
+}
+
+/**
+ * Act on a field being updated.
+ *
+ * This hook is invoked just after field is updated.
*
* @param $field
- * The field being deleted.
+ * The field as it is post-update.
+ * @param $prior_field
+ * The field as it was pre-update.
+ * @param $has_data
+ * Whether any data already exists for this field.
*/
-function hook_field_delete_field($field) {
+function hook_field_update_field($field, $prior_field, $has_data) {
}
+/**
+ * Act on a field being deleted.
+ *
+ * This hook is invoked just after field is deleted.
+ *
+ * @param $field
+ * The field just deleted.
+ */
+function hook_field_delete_field($field) {
+}
/**
* Act on a field instance being updated.
@@ -1293,12 +1376,10 @@ function hook_field_update_instance($instance) {
/**
* Act on a field instance being deleted.
*
- * This hook is invoked just before the instance is deleted.
- *
- * TODO: Not implemented.
+ * This hook is invoked just after the instance is deleted.
*
* @param $instance
- * The instance just updated.
+ * The instance just deleted.
*/
function hook_field_delete_instance($instance) {
}
diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc
index c253edb..38dbc0c 100644
--- a/modules/field/field.attach.inc
+++ b/modules/field/field.attach.inc
@@ -1,5 +1,5 @@
$bundle), array('include_deleted' => $options['deleted']));
@@ -299,7 +298,7 @@ function _field_invoke_multiple($op, $obj_type, $objects, &$a = NULL, &$b = NULL
// is deleted, so we reference field data via the
// $object->$field_name property.
foreach ($objects as $object) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
if ($options['deleted']) {
$instances = field_read_field(array('bundle' => $bundle, array('include_deleted' => $options['deleted'])));
@@ -453,7 +452,7 @@ function _field_invoke_multiple_default($op, $obj_type, $objects, &$a = NULL, &$
*
* // Only for 'single' widgets:
* '#theme' => 'field_multiple_value_form',
- * '#multiple' => the field cardinality,
+ * '#cardinality' => the field cardinality,
* // One sub-array per copy of the widget, keyed by delta.
* 0 => array(
* '#title' => the title to be displayed by the widget,
@@ -489,7 +488,7 @@ function field_attach_form($obj_type, $object, &$form, &$form_state, $langcode =
$form += (array) _field_invoke_default('form', $obj_type, $object, $form, $form_state, $options);
// Add custom weight handling.
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$form['#attached']['css'][] = drupal_get_path('module', 'field') . '/theme/field.css';
$form['#pre_render'][] = '_field_extra_weights_pre_render';
$form['#extra_fields'] = field_extra_fields($bundle);
@@ -525,9 +524,8 @@ function field_attach_form($obj_type, $object, &$form, &$form_state, $langcode =
* - 'deleted': If TRUE, the function will operate on deleted fields
* as well as non-deleted fields. If unset or FALSE, only
* non-deleted fields are operated on.
- * @returns
- * Loaded field values are added to $objects. Fields with no values should be
- * set as an empty array.
+ * @return
+ * Loaded field values are added to $objects.
*/
function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT, $options = array()) {
$load_current = $age == FIELD_LOAD_CURRENT;
@@ -578,7 +576,7 @@ function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT, $opti
if ($queried_objects) {
// The invoke order is:
// - hook_field_attach_pre_load()
- // - storage engine's hook_field_storage_load()
+ // - storage backend's hook_field_storage_load()
// - field-type module's hook_field_load()
// - hook_field_attach_load()
@@ -590,9 +588,39 @@ function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT, $opti
$function($obj_type, $queried_objects, $age, $skip_fields, $options);
}
- // Invoke the storage engine's hook_field_storage_load(): the field storage
- // engine loads the rest.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_load', $obj_type, $queried_objects, $age, $skip_fields, $options);
+ // Collect the storage backends used by the remaining fields in the objects.
+ $storages = array();
+ foreach ($queried_objects as $obj) {
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $obj);
+ if ($options['deleted']) {
+ $instances = field_read_instances(array('bundle' => $bundle), array('include_deleted' => $options['deleted']));
+ }
+ else {
+ $instances = field_info_instances($bundle);
+ }
+
+ foreach ($instances as $instance) {
+ if (!isset($options['field_id']) || $options['field_id'] == $instance['field_id']) {
+ $field_name = $instance['field_name'];
+ $field_id = $instance['field_id'];
+ // Make sure all fields are present at least as empty arrays.
+ if (!isset($queried_objects[$id]->{$field_name})) {
+ $queried_objects[$id]->{$field_name} = array();
+ }
+ // Collect the storage backend if the field has not been loaded yet.
+ if (!isset($skip_fields[$field_id])) {
+ $field = field_info_field_by_id($field_id);
+ $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid;
+ }
+ }
+ }
+ }
+
+ // Invoke hook_field_storage_load() on the relevant storage backends.
+ foreach ($storages as $storage => $fields) {
+ $storage_info = field_info_storage_types($storage);
+ module_invoke($storage_info['module'], 'field_storage_load', $obj_type, $queried_objects, $age, $fields, $options);
+ }
// Invoke field-type module's hook_field_load().
_field_invoke_multiple('load', $obj_type, $queried_objects, $age, $options);
@@ -608,7 +636,7 @@ function field_attach_load($obj_type, $objects, $age = FIELD_LOAD_CURRENT, $opti
if ($cache_write) {
foreach ($queried_objects as $id => $object) {
$data = array();
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$instances = field_info_instances($bundle);
foreach ($instances as $instance) {
$data[$instance['field_name']] = $queried_objects[$id]->{$instance['field_name']};
@@ -791,6 +819,8 @@ function field_attach_insert($obj_type, $object) {
_field_invoke_default('insert', $obj_type, $object);
_field_invoke('insert', $obj_type, $object);
+ list($id, $vid, $bundle, $cacheable) = field_extract_ids($obj_type, $object);
+
// Let other modules act on inserting the object, accumulating saved
// fields along the way.
$skip_fields = array();
@@ -799,10 +829,26 @@ function field_attach_insert($obj_type, $object) {
$function($obj_type, $object, $skip_fields);
}
- // Field storage module saves any remaining unsaved fields.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_INSERT, $skip_fields);
+ // Collect the storage backends used by the remaining fields in the objects.
+ $storages = array();
+ foreach (field_info_instances($bundle) as $instance) {
+ $field = field_info_field_by_id($instance['field_id']);
+ $field_id = $field['id'];
+ $field_name = $field['field_name'];
+ if (!empty($object->$field_name)) {
+ // Collect the storage backend if the field has not been written yet.
+ if (!isset($skip_fields[$field_id])) {
+ $storages[$field['storage']['type']][$field_id] = $field_id;
+ }
+ }
+ }
+
+ // Field storage backends save any remaining unsaved fields.
+ foreach ($storages as $storage => $fields) {
+ $storage_info = field_info_storage_types($storage);
+ module_invoke($storage_info['module'], 'field_storage_write', $obj_type, $object, FIELD_STORAGE_INSERT, $fields);
+ }
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
if ($cacheable) {
cache_clear_all("field:$obj_type:$id", 'cache_field');
}
@@ -819,6 +865,8 @@ function field_attach_insert($obj_type, $object) {
function field_attach_update($obj_type, $object) {
_field_invoke('update', $obj_type, $object);
+ list($id, $vid, $bundle, $cacheable) = field_extract_ids($obj_type, $object);
+
// Let other modules act on updating the object, accumulating saved
// fields along the way.
$skip_fields = array();
@@ -827,10 +875,30 @@ function field_attach_update($obj_type, $object) {
$function($obj_type, $object, $skip_fields);
}
- // Field storage module saves any remaining unsaved fields.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_write', $obj_type, $object, FIELD_STORAGE_UPDATE, $skip_fields);
+ // Collect the storage backends used by the remaining fields in the objects.
+ $storages = array();
+ foreach (field_info_instances($bundle) as $instance) {
+ $field = field_info_field_by_id($instance['field_id']);
+ $field_id = $field['id'];
+ $field_name = $field['field_name'];
+ // Leave the field untouched if $object comes with no $field_name property,
+ // but empty the field if it comes as a NULL value or an empty array.
+ // Function property_exists() is slower, so we catch the more frequent
+ // cases where it's an empty array with the faster isset().
+ if (isset($object->$field_name) || property_exists($object, $field_name)) {
+ // Collect the storage backend if the field has not been written yet.
+ if (!isset($skip_fields[$field_id])) {
+ $storages[$field['storage']['type']][$field_id] = $field_id;
+ }
+ }
+ }
+
+ // Field storage backends save any remaining unsaved fields.
+ foreach ($storages as $storage => $fields) {
+ $storage_info = field_info_storage_types($storage);
+ module_invoke($storage_info['module'], 'field_storage_write', $obj_type, $object, FIELD_STORAGE_UPDATE, $fields);
+ }
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
if ($cacheable) {
cache_clear_all("field:$obj_type:$id", 'cache_field');
}
@@ -847,7 +915,22 @@ function field_attach_update($obj_type, $object) {
*/
function field_attach_delete($obj_type, $object) {
_field_invoke('delete', $obj_type, $object);
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete', $obj_type, $object);
+
+ list($id, $vid, $bundle, $cacheable) = field_extract_ids($obj_type, $object);
+
+ // Collect the storage backends used by the fields in the objects.
+ $storages = array();
+ foreach (field_info_instances($bundle) as $instance) {
+ $field = field_info_field_by_id($instance['field_id']);
+ $field_id = $field['id'];
+ $storages[$field['storage']['type']][$field_id] = $field_id;
+ }
+
+ // Field storage backends delete their data.
+ foreach ($storages as $storage => $fields) {
+ $storage_info = field_info_storage_types($storage);
+ module_invoke($storage_info['module'], 'field_storage_delete', $obj_type, $object, $fields);
+ }
// Let other modules act on deleting the object.
foreach (module_implements('field_attach_delete') as $module) {
@@ -855,7 +938,6 @@ function field_attach_delete($obj_type, $object) {
$function($obj_type, $object);
}
- list($id, $vid, $bundle, $cacheable) = field_attach_extract_ids($obj_type, $object);
if ($cacheable) {
cache_clear_all("field:$obj_type:$id", 'cache_field');
}
@@ -872,7 +954,22 @@ function field_attach_delete($obj_type, $object) {
*/
function field_attach_delete_revision($obj_type, $object) {
_field_invoke('delete_revision', $obj_type, $object);
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_revision', $obj_type, $object);
+
+ list($id, $vid, $bundle, $cacheable) = field_extract_ids($obj_type, $object);
+
+ // Collect the storage backends used by the fields in the objects.
+ $storages = array();
+ foreach (field_info_instances($bundle) as $instance) {
+ $field = field_info_field_by_id($instance['field_id']);
+ $field_id = $field['id'];
+ $storages[$field['storage']['type']][$field_id] = $field_id;
+ }
+
+ // Field storage backends delete their data.
+ foreach ($storages as $storage => $fields) {
+ $storage_info = field_info_storage_types($storage);
+ module_invoke($storage_info['module'], 'field_storage_delete_revision', $obj_type, $object, $fields);
+ }
// Let other modules act on deleting the revision.
foreach (module_implements('field_attach_delete_revision') as $module) {
@@ -977,7 +1074,8 @@ function field_attach_query($field_id, $conditions, $count, &$cursor = NULL, $ag
}
// If the request hasn't been handled, let the storage engine handle it.
if (!$skip_field) {
- $function = variable_get('field_storage_module', 'field_sql_storage') . '_field_storage_query';
+ $field = field_info_field_by_id($field_id);
+ $function = $field['storage']['module'] . '_field_storage_query';
$results = $function($field_id, $conditions, $count, $cursor, $age);
}
@@ -1089,7 +1187,7 @@ function field_attach_view($obj_type, $object, $build_mode = 'full', $langcode =
$output = _field_invoke_default('view', $obj_type, $object, $build_mode, $null, $options);
// Add custom weight handling.
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$output['#attached']['css'][] = drupal_get_path('module', 'field') . '/theme/field.css';
$output['#pre_render'][] = '_field_extra_weights_pre_render';
$output['#extra_fields'] = field_extra_fields($bundle);
@@ -1119,7 +1217,7 @@ function field_attach_view($obj_type, $object, $build_mode = 'full', $langcode =
* values.
*/
function field_attach_preprocess($obj_type, $object, $element, &$variables) {
- list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+ list(, , $bundle) = field_extract_ids($obj_type, $object);
foreach (field_info_instances($bundle) as $instance) {
$field_name = $instance['field_name'];
@@ -1181,8 +1279,6 @@ function field_attach_prepare_translation($node) {
* The name of the newly created bundle.
*/
function field_attach_create_bundle($bundle) {
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_create_bundle', $bundle);
-
// Clear the cache.
field_cache_clear();
@@ -1201,7 +1297,6 @@ function field_attach_create_bundle($bundle) {
* The new name of the bundle.
*/
function field_attach_rename_bundle($bundle_old, $bundle_new) {
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_rename_bundle', $bundle_old, $bundle_new);
db_update('field_config_instance')
->fields(array('bundle' => $bundle_new))
->condition('bundle', $bundle_old)
@@ -1230,12 +1325,15 @@ function field_attach_rename_bundle($bundle_old, $bundle_new) {
* The bundle to delete.
*/
function field_attach_delete_bundle($bundle) {
- // Delete the instances themseves
+ // First, delete the instances themseves.
$instances = field_info_instances($bundle);
foreach ($instances as $instance) {
- field_delete_instance($instance['field_name'], $bundle);
+ field_delete_instance($instance);
}
+ // Clear the cache.
+ field_cache_clear();
+
// Let other modules act on deleting the bundle.
foreach (module_implements('field_attach_delete_bundle') as $module) {
$function = $module . '_field_attach_delete_bundle';
@@ -1243,85 +1341,6 @@ function field_attach_delete_bundle($bundle) {
}
}
-/**
- * Helper function to extract id, vid, and bundle name from an object.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $object
- * The object from which to extract values.
- * @return
- * A numerically indexed array (not a hash table) containing these
- * elements:
- *
- * 0: primary id of the object
- * 1: revision id of the object, or NULL if $obj_type is not versioned
- * 2: bundle name of the object
- * 3: whether $obj_type's fields should be cached (TRUE/FALSE)
- */
-function field_attach_extract_ids($obj_type, $object) {
- // TODO D7 : prevent against broken 3rd party $node without 'type'.
- $info = field_info_fieldable_types($obj_type);
- // Objects being created might not have id/vid yet.
- $id = isset($object->{$info['object keys']['id']}) ? $object->{$info['object keys']['id']} : NULL;
- $vid = ($info['object keys']['revision'] && isset($object->{$info['object keys']['revision']})) ? $object->{$info['object keys']['revision']} : NULL;
- // If no bundle key provided, then we assume a single bundle, named after the
- // type of the object.
- $bundle = $info['object keys']['bundle'] ? $object->{$info['object keys']['bundle']} : $obj_type;
- $cacheable = $info['cacheable'];
- return array($id, $vid, $bundle, $cacheable);
-}
-
-/**
- * Helper function to extract id, vid, and bundle name from an object.
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $bundle
- * The bundle object (or string if bundles for this object type do not exist
- * as standalone objects).
- * @return
- * The bundle name.
- */
-function field_attach_extract_bundle($obj_type, $bundle) {
- if (is_string($bundle)) {
- return $bundle;
- }
-
- $info = field_info_fieldable_types($obj_type);
- if (is_object($bundle) && isset($info['bundle keys']['bundle']) && isset($bundle->{$info['bundle keys']['bundle']})) {
- return $bundle->{$info['bundle keys']['bundle']};
- }
-}
-
-/**
- * Helper function to assemble an object structure with initial ids.
- *
- * This function can be seen as reciprocal to field_attach_extract_ids().
- *
- * @param $obj_type
- * The type of $object; e.g. 'node' or 'user'.
- * @param $ids
- * A numerically indexed array, as returned by field_attach_extract_ids(),
- * containing these elements:
- * 0: primary id of the object
- * 1: revision id of the object, or NULL if $obj_type is not versioned
- * 2: bundle name of the object
- * @return
- * An $object structure, initialized with the ids provided.
- */
-function field_attach_create_stub_object($obj_type, $ids) {
- $object = new stdClass();
- $info = field_info_fieldable_types($obj_type);
- $object->{$info['object keys']['id']} = $ids[0];
- if (isset($info['object keys']['revision']) && !is_null($ids[1])) {
- $object->{$info['object keys']['revision']} = $ids[1];
- }
- if ($info['object keys']['bundle']) {
- $object->{$info['object keys']['bundle']} = $ids[2];
- }
- return $object;
-}
/**
* @} End of "defgroup field_attach"
diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc
index 020ca0f..9ff636c 100644
--- a/modules/field/field.crud.inc
+++ b/modules/field/field.crud.inc
@@ -1,5 +1,5 @@
$field['field_name'])));
}
- // Check that the field type is known.
- $field_type = field_info_field_types($field['type']);
- if (!$field_type) {
- throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type'])));
- }
-
// Ensure the field name is unique over active and disabled fields.
// We do not care about deleted fields.
- // TODO : do we want specific messages when clashing with a disabled or inactive field ?
$prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE));
if (!empty($prior_field)) {
- throw new FieldException(t('Attempt to create field name %name which already exists.', array('%name' => $field['field_name'])));
+ $message = $prior_field['active']?
+ t('Attempt to create field name %name which already exists and is active.', array('%name' => $field['field_name'])):
+ t('Attempt to create field name %name which already exists, although it is inactive.', array('%name' => $field['field_name']));
+ throw new FieldException($message);
+ }
+
+ // Disallow reserved field names. This can't prevent all field name
+ // collisions with existing object properties, but some is better
+ // than none.
+ foreach (field_info_fieldable_types() as $type => $info) {
+ if (in_array($field['field_name'], $info['object keys'])) {
+ throw new FieldException(t('Attempt to create field name %name which is reserved by entity type %type.', array('%name' => $field['field_name'], '%type' => $type)));
+ }
}
$field += array(
@@ -242,22 +263,40 @@ function field_create_field($field) {
'translatable' => FALSE,
'locked' => FALSE,
'settings' => array(),
+ 'storage' => array(),
+ 'deleted' => 0,
);
+ // Check that the field type is known.
+ $field_type = field_info_field_types($field['type']);
+ if (!$field_type) {
+ throw new FieldException(t('Attempt to create a field of unknown type %type.', array('%type' => $field['type'])));
+ }
// Create all per-field-type properties (needed here as long as we have
// settings that impact column definitions).
$field['settings'] += field_info_field_settings($field['type']);
$field['module'] = $field_type['module'];
$field['active'] = 1;
- $field['deleted'] = 0;
+ // Provide default storage.
+ $field['storage'] += array(
+ 'type' => variable_get('field_storage_default', 'field_sql_storage'),
+ 'settings' => array(),
+ );
+ // Check that the storage type is known.
+ $storage_type = field_info_storage_types($field['storage']['type']);
+ if (!$storage_type) {
+ throw new FieldException(t('Attempt to create a field with unknown storage type %type.', array('%type' => $field['storage']['type'])));
+ }
+ // Provide default storage settings.
+ $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
+ $field['storage']['module'] = $storage_type['module'];
+ $field['storage']['active'] = 1;
// Collect storage information.
$schema = (array) module_invoke($field['module'], 'field_schema', $field);
$schema += array('columns' => array(), 'indexes' => array());
-
// 'columns' are hardcoded in the field type.
$field['columns'] = $schema['columns'];
-
// 'indexes' can be both hardcoded in the field type, and specified in the
// incoming $field definition.
$field += array(
@@ -269,18 +308,42 @@ function field_create_field($field) {
// have its own column and is not automatically populated when the field is
// read.
$data = $field;
- unset($data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']);
- $field['data'] = $data;
+ unset($data['columns'], $data['field_name'], $data['type'], $data['active'], $data['module'], $data['storage_type'], $data['storage_active'], $data['storage_module'], $data['locked'], $data['cardinality'], $data['deleted']);
- // Store the field and create the id.
- drupal_write_record('field_config', $field);
+ $record = array(
+ 'field_name' => $field['field_name'],
+ 'type' => $field['type'],
+ 'module' => $field['module'],
+ 'active' => $field['active'],
+ 'storage_type' => $field['storage']['type'],
+ 'storage_module' => $field['storage']['module'],
+ 'storage_active' => $field['storage']['active'],
+ 'locked' => $field['locked'],
+ 'data' => $data,
+ 'cardinality' => $field['cardinality'],
+ 'translatable' => $field['translatable'],
+ 'deleted' => $field['deleted'],
+ );
- // The 'data' property is not part of the public field record.
- unset($field['data']);
+ // Store the field and get the id back.
+ drupal_write_record('field_config', $record);
+ $field['id'] = $record['id'];
// Invoke hook_field_storage_create_field after the field is
// complete (e.g. it has its id).
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_create_field', $field);
+ try {
+ // Invoke hook_field_storage_create_field after
+ // drupal_write_record() sets the field id.
+ module_invoke($storage_type['module'], 'field_storage_create_field', $field);
+ }
+ catch (Exception $e) {
+ // If storage creation failed, remove the field_config record before
+ // rethrowing the exception.
+ db_delete('field_config')
+ ->condition('id', $field['id'])
+ ->execute();
+ throw $e;
+ }
// Clear caches
field_cache_clear(TRUE);
@@ -291,6 +354,92 @@ function field_create_field($field) {
return $field;
}
+/*
+ * Update a field.
+ *
+ * Any module may forbid any update for any reason. For example, the
+ * field's storage module might forbid an update if it would change
+ * the storage schema while data for the field exists. A field type
+ * module might forbid an update if it would change existing data's
+ * semantics, or if there are external dependencies on field settings
+ * that cannot be updated.
+ *
+ * @param $field
+ * A field structure. $field['field_name'] must provided; it
+ * identifies the field that will be updated to match this
+ * structure. Any other properties of the field that are not
+ * specified in $field will be left unchanged, so it is not
+ * necessary to pass in a fully populated $field structure.
+ * @return
+ * Throws a FieldException if the update cannot be performed.
+ * @see field_create_field()
+ */
+function field_update_field($field) {
+ // Check that the specified field exists.
+ $prior_field = field_read_field($field['field_name']);
+ if (empty($prior_field)) {
+ throw new FieldException('Attempt to update a non-existent field.');
+ }
+
+ // Use the prior field values for anything not specifically set by the new
+ // field to be sure that all values are set.
+ $field += $prior_field;
+ $field['settings'] += $prior_field['settings'];
+
+ // Some updates are always disallowed.
+ if ($field['type'] != $prior_field['type']) {
+ throw new FieldException("Cannot change an existing field's type.");
+ }
+ if ($field['storage']['type'] != $prior_field['storage']['type']) {
+ throw new FieldException("Cannot change an existing field's storage type.");
+ }
+
+ // Collect the new storage information, since what is in
+ // $prior_field may no longer be right.
+ $schema = (array) module_invoke($field['module'], 'field_schema', $field);
+ $schema += array('columns' => array(), 'indexes' => array());
+ // 'columns' are hardcoded in the field type.
+ $field['columns'] = $schema['columns'];
+ // 'indexes' can be both hardcoded in the field type, and specified in the
+ // incoming $field definition.
+ $field += array(
+ 'indexes' => array(),
+ );
+ $field['indexes'] += $schema['indexes'];
+
+ $has_data = field_has_data($field);
+
+ // See if any module forbids the update by throwing an exception.
+ foreach (module_implements('field_update_forbid') as $module) {
+ $function = $module . '_field_update_forbid';
+ $function($field, $prior_field, $has_data);
+ }
+
+ // Tell the storage engine to update the field. Do this before
+ // saving the new definition since it still might fail.
+ module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_update_field', $field, $prior_field, $has_data);
+
+ // Save the new field definition. @todo: refactor with
+ // field_create_field.
+
+ // The serialized 'data' column contains everything from $field that does not
+ // have its own column and is not automatically populated when the field is
+ // read.
+ $data = $field;
+ unset($data['columns'], $data['field_name'], $data['type'], $data['locked'], $data['module'], $data['cardinality'], $data['active'], $data['deleted']);
+ $field['data'] = $data;
+
+ // Store the field and create the id.
+ $primary_key = array('id');
+ drupal_write_record('field_config', $field, $primary_key);
+
+ // Clear caches
+ field_cache_clear(TRUE);
+
+ // Invoke external hooks after the cache is cleared for API consistency.
+ module_invoke_all('field_update_field', $field, $prior_field, $has_data);
+}
+
/**
* Read a single field record directly from the database. Generally,
* you should use the field_info_field() instead.
@@ -338,7 +487,9 @@ function field_read_fields($params = array(), $include_additional = array()) {
$query->condition($key, $value);
}
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
- $query->condition('fc.active', 1);
+ $query
+ ->condition('fc.active', 1)
+ ->condition('fc.storage_active', 1);
}
$include_deleted = (isset($include_additional['include_deleted']) && $include_additional['include_deleted']);
if (!$include_deleted) {
@@ -347,11 +498,20 @@ function field_read_fields($params = array(), $include_additional = array()) {
$fields = array();
$results = $query->execute();
- foreach ($results as $field) {
- // Extract serialized data.
- $data = unserialize($field['data']);
- unset($field['data']);
- $field += $data;
+ foreach ($results as $record) {
+ $field = unserialize($record['data']);
+ $field['id'] = $record['id'];
+ $field['field_name'] = $record['field_name'];
+ $field['type'] = $record['type'];
+ $field['module'] = $record['module'];
+ $field['active'] = $record['active'];
+ $field['storage']['type'] = $record['storage_type'];
+ $field['storage']['module'] = $record['storage_module'];
+ $field['storage']['active'] = $record['storage_active'];
+ $field['locked'] = $record['locked'];
+ $field['cardinality'] = $record['cardinality'];
+ $field['translatable'] = $record['translatable'];
+ $field['deleted'] = $record['deleted'];
module_invoke_all('field_read_field', $field);
@@ -377,14 +537,17 @@ function field_read_fields($params = array(), $include_additional = array()) {
* The field name to delete.
*/
function field_delete_field($field_name) {
- // Mark field storage for deletion.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_field', $field_name);
+ // Delete all non-deleted instances.
+ $field = field_info_field($field_name);
+ if (isset($field['bundles'])) {
+ foreach ($field['bundles'] as $bundle) {
+ $instance = field_info_instance($field_name, $bundle);
+ field_delete_instance($instance);
+ }
+ }
- // Mark any instances of the field for deletion.
- db_update('field_config_instance')
- ->fields(array('deleted' => 1))
- ->condition('field_name', $field_name)
- ->execute();
+ // Mark field data for deletion.
+ module_invoke($field['storage']['module'], 'field_storage_delete_field', $field);
// Mark the field for deletion.
db_update('field_config')
@@ -394,6 +557,8 @@ function field_delete_field($field_name) {
// Clear the cache.
field_cache_clear(TRUE);
+
+ module_invoke_all('field_delete_field', $field);
}
/**
@@ -430,7 +595,7 @@ function field_create_instance($instance) {
// Check that the specified field exists.
$field = field_read_field($instance['field_name']);
if (empty($field)) {
- throw new FieldException("Attempt to create an instance of a field that doesn't exist.");
+ throw new FieldException("Attempt to create an instance of a field that doesn't exist or is currently inactive.");
}
// Set the field id.
@@ -446,11 +611,13 @@ function field_create_instance($instance) {
// Those checks should probably happen in _field_write_instance() ?
// Problem : this would mean that a UI module cannot update an instance with a disabled formatter.
- // Ensure the field instance is unique.
- // TODO : do we want specific messages when clashing with a disabled or inactive instance ?
- $prior_instance = field_read_instance($instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE));
+ // Ensure the field instance is unique within the bundle.
+ // We only check for instances of active fields, since adding an instance of
+ // a disabled field is not supported.
+ $prior_instance = field_read_instance($instance['field_name'], $instance['bundle']);
if (!empty($prior_instance)) {
- throw new FieldException(t('Attempt to create a field instance %field_name,%bundle which already exists.', array('%field_name' => $instance['field_name'], '%bundle' => $instance['bundle'])));
+ $message = t('Attempt to create a field instance that already exists.');
+ throw new FieldException($message);
}
_field_write_instance($instance);
@@ -538,11 +705,8 @@ function _field_write_instance($instance, $update = FALSE) {
);
// Check widget module.
$widget_type = field_info_widget_types($instance['widget']['type']);
- $widget_module = $widget_type['module'];
- $widget_active = module_exists($widget_module);
+ $instance['widget']['module'] = $widget_type['module'];
$instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']);
- $instance['widget']['module'] = $widget_module;
- $instance['widget']['active'] = $widget_active;
// Make sure there is at least display info for the 'full' build mode.
$instance['display'] += array(
@@ -567,15 +731,12 @@ function _field_write_instance($instance, $update = FALSE) {
// not have its own column and is not automatically populated when the
// instance is read.
$data = $instance;
- unset($data['id'], $data['field_id'], $data['field_name'], $data['bundle'], $data['widget']['type'], $data['deleted']);
+ unset($data['id'], $data['field_id'], $data['field_name'], $data['bundle'], $data['deleted']);
$record = array(
'field_id' => $instance['field_id'],
'field_name' => $instance['field_name'],
'bundle' => $instance['bundle'],
- 'widget_type' => $instance['widget']['type'],
- 'widget_module' => $widget_module,
- 'widget_active' => $widget_active,
'data' => $data,
'deleted' => $instance['deleted'],
);
@@ -604,8 +765,9 @@ function _field_write_instance($instance, $update = FALSE) {
* The bundle to which the field is bound.
* @param array $include_additional
* The default behavior of this function is to not return an instance that
- * is inactive. Setting
- * $include_additional['include_inactive'] to TRUE will override this
+ * has been deleted, or whose field is inactive. Setting
+ * $include_additional['include_inactive'] or
+ * $include_additional['include_deleted'] to TRUE will override this
* behavior.
* @return
* An instance structure, or FALSE.
@@ -624,8 +786,8 @@ function field_read_instance($field_name, $bundle, $include_additional = array()
* field_config_instance table. If NULL, all instances will be returned.
* @param $include_additional
* The default behavior of this function is to not return field
- * instances that are inactive or have been marked deleted. Setting
- * $include_additional['include_inactive'] or
+ * instances that have been marked deleted, or whose field is inactive.
+ * Setting $include_additional['include_inactive'] or
* $include_additional['include_deleted'] to TRUE will override this
* behavior.
* @return
@@ -641,8 +803,9 @@ function field_read_instances($params = array(), $include_additional = array())
$query->condition('fci.' . $key, $value);
}
if (!isset($include_additional['include_inactive']) || !$include_additional['include_inactive']) {
- $query->condition('fc.active', 1);
- $query->condition('fci.widget_active', 1);
+ $query
+ ->condition('fc.active', 1)
+ ->condition('fc.storage_active', 1);
}
if (!isset($include_additional['include_deleted']) || !$include_additional['include_deleted']) {
$query->condition('fc.deleted', 0);
@@ -659,9 +822,6 @@ function field_read_instances($params = array(), $include_additional = array())
$instance['field_name'] = $record['field_name'];
$instance['bundle'] = $record['bundle'];
$instance['deleted'] = $record['deleted'];
- $instance['widget']['type'] = $record['widget_type'];
- $instance['widget']['module'] = $record['widget_module'];
- $instance['widget']['active'] = $record['widget_active'];
module_invoke_all('field_read_instance', $instance);
$instances[] = $instance;
@@ -673,23 +833,25 @@ function field_read_instances($params = array(), $include_additional = array())
* Mark a field instance for deletion, including all data associated with
* it.
*
- * @param $field_name
- * The name of the field whose instance will be deleted.
- * @param $bundle
- * The bundle for the instance which will be deleted.
+ * @param $instance
+ * An instance structure.
*/
-function field_delete_instance($field_name, $bundle) {
+function field_delete_instance($instance) {
// Mark the field instance for deletion.
db_update('field_config_instance')
->fields(array('deleted' => 1))
- ->condition('field_name', $field_name)
- ->condition('bundle', $bundle)
+ ->condition('field_name', $instance['field_name'])
+ ->condition('bundle', $instance['bundle'])
->execute();
- // Mark all data associated with the field for deletion.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_delete_instance', $field_name, $bundle);
+ // Mark instance data for deletion.
+ $field = field_info_field($instance['field_name']);
+ module_invoke($field['storage']['module'], 'field_storage_delete_instance', $instance);
+
// Clear the cache.
field_cache_clear();
+
+ module_invoke_all('field_delete_instance', $instance);
}
/**
@@ -843,7 +1005,7 @@ function field_purge_data($obj_type, $object, $field, $instance) {
_field_invoke('delete', $obj_type, $object, $dummy, $dummy, $options);
// Tell the field storage system to purge the data.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_purge', $obj_type, $object, $field, $instance);
+ module_invoke($field['storage']['module'], 'field_storage_purge', $obj_type, $object, $field, $instance);
// Let other modules act on purging the data.
foreach (module_implements('field_attach_purge') as $module) {
@@ -867,10 +1029,11 @@ function field_purge_instance($instance) {
->execute();
// Notify the storage engine.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_purge_instance', $instance);
+ $field = field_info_field_by_id($instance['field_id']);
+ module_invoke($field['storage']['module'], 'field_storage_purge_instance', $instance);
// Clear the cache.
- _field_info_cache_clear();
+ field_info_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_purge_instance', $instance);
@@ -896,10 +1059,10 @@ function field_purge_field($field) {
->execute();
// Notify the storage engine.
- module_invoke(variable_get('field_storage_module', 'field_sql_storage'), 'field_storage_purge_field', $field);
+ module_invoke($field['storage']['module'], 'field_storage_purge_field', $field);
// Clear the cache.
- _field_info_cache_clear();
+ field_info_cache_clear();
// Invoke external hooks after the cache is cleared for API consistency.
module_invoke_all('field_purge_field', $field);
diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc
index 1f7dc4f..9b87e7a 100644
--- a/modules/field/field.default.inc
+++ b/modules/field/field.default.inc
@@ -1,5 +1,5 @@
'field_multiple_value_form',
- '#multiple' => $field['cardinality'],
+ '#cardinality' => $field['cardinality'],
'#title' => $title,
'#required' => $instance['required'],
'#description' => $description,
@@ -224,7 +224,7 @@ function field_multiple_value_form($field, $instance, $langcode, $items, &$form,
function theme_field_multiple_value_form($element) {
$output = '';
- if ($element['#multiple'] > 1 || $element['#multiple'] == FIELD_CARDINALITY_UNLIMITED) {
+ if ($element['#cardinality'] > 1 || $element['#cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
$table_id = $element['#field_name'] . '_values';
$order_class = $element['#field_name'] . '-delta-order';
$required = !empty($element['#required']) ? '*' : '';
@@ -362,9 +362,5 @@ function field_add_more_js($form, $form_state) {
$field_form[$langcode][$delta]['#prefix'] = '
';
- $output = theme('status_messages') . drupal_render($field_form);
-
- $commands = array();
- $commands[] = ajax_command_replace(NULL, $output);
- ajax_render($commands);
+ return drupal_render($field_form);
}
diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc
index bc32bb1..6c5a7ef 100644
--- a/modules/field/field.info.inc
+++ b/modules/field/field.info.inc
@@ -1,5 +1,5 @@
array(),
'widget types' => array(),
'formatter types' => array(),
+ 'storage types' => array(),
'fieldable types' => array(),
);
@@ -110,7 +111,7 @@ function _field_info_collate_types($reset = FALSE) {
}
drupal_alter('field_widget_info', $info['widget types']);
- // Populate formatters.
+ // Populate formatter types.
foreach (module_implements('field_formatter_info') as $module) {
$formatter_types = (array) module_invoke($module, 'field_formatter_info');
foreach ($formatter_types as $name => $formatter_info) {
@@ -124,6 +125,20 @@ function _field_info_collate_types($reset = FALSE) {
}
drupal_alter('field_formatter_info', $info['formatter types']);
+ // Populate storage types.
+ foreach (module_implements('field_storage_info') as $module) {
+ $storage_types = (array) module_invoke($module, 'field_storage_info');
+ foreach ($storage_types as $name => $storage_info) {
+ // Provide defaults.
+ $storage_info += array(
+ 'settings' => array(),
+ );
+ $info['storage types'][$name] = $storage_info;
+ $info['storage types'][$name]['module'] = $module;
+ }
+ }
+ drupal_alter('field_storage_info', $info['storage types']);
+
// Populate information about 'fieldable' entities.
foreach (module_implements('entity_info') as $module) {
$entities = (array) module_invoke($module, 'entity_info');
@@ -246,6 +261,7 @@ function _field_info_collate_fields($reset = FALSE) {
function _field_info_prepare_field($field) {
// Make sure all expected field settings are present.
$field['settings'] += field_info_field_settings($field['type']);
+ $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
return $field;
}
@@ -371,8 +387,8 @@ function field_info_field_types($field_type = NULL) {
* returned.
* @return
* Either a widget type description, as provided by
- * hook_field_widget_info(), or an array of all existing widget
- * types, keyed by widget type name.
+ * hook_field_widget_info(), or an array of all existing widget types, keyed
+ * by widget type name.
*/
function field_info_widget_types($widget_type = NULL) {
$info = _field_info_collate_types();
@@ -394,8 +410,9 @@ function field_info_widget_types($widget_type = NULL) {
* (optional) A formatter type name. If ommitted, all formatter types will be
* returned.
* @return
- * Either a formatter type description, as provided by hook_field_formatter_info(),
- * or an array of all existing widget types, keyed by widget type name.
+ * Either a formatter type description, as provided by
+ * hook_field_formatter_info(), or an array of all existing formatter types,
+ * keyed by formatter type name.
*/
function field_info_formatter_types($formatter_type = NULL) {
$info = _field_info_collate_types();
@@ -410,6 +427,30 @@ function field_info_formatter_types($formatter_type = NULL) {
}
}
+/**
+ * Return hook_field_storage_info() data.
+ *
+ * @param $storage_type
+ * (optional) A storage type name. If ommitted, all storage types will be
+ * returned.
+ * @return
+ * Either a storage type description, as provided by
+ * hook_field_storage_info(), or an array of all existing storage types,
+ * keyed by storage type name.
+ */
+function field_info_storage_types($storage_type = NULL) {
+ $info = _field_info_collate_types();
+ $storage_types = $info['storage types'];
+ if ($storage_type) {
+ if (isset($storage_types[$storage_type])) {
+ return $storage_types[$storage_type];
+ }
+ }
+ else {
+ return $storage_types;
+ }
+}
+
/**
* Return hook_fieldable_info() data.
*
@@ -574,8 +615,8 @@ function field_info_instance_settings($type) {
* @param $type
* A widget type name.
* @return
- * The field type's default settings, as provided by hook_field_info(), or an
- * empty array.
+ * The widget type's default settings, as provided by
+ * hook_field_widget_info(), or an empty array.
*/
function field_info_widget_settings($type) {
$info = field_info_widget_types($type);
@@ -588,14 +629,28 @@ function field_info_widget_settings($type) {
* @param $type
* A field formatter type name.
* @return
- * The field formatter's default settings, as provided by
- * hook_field_info(), or an empty array.
+ * The formatter type's default settings, as provided by
+ * hook_field_formatter_info(), or an empty array.
*/
function field_info_formatter_settings($type) {
$info = field_info_formatter_types($type);
return isset($info['settings']) ? $info['settings'] : array();
}
+/**
+ * Return a field formatter's default settings.
+ *
+ * @param $type
+ * A field storage type name.
+ * @return
+ * The storage type's default settings, as provided by
+ * hook_field_storage_info(), or an empty array.
+ */
+function field_info_storage_settings($type) {
+ $info = field_info_storage_types($type);
+ return isset($info['settings']) ? $info['settings'] : array();
+}
+
/**
* @} End of "defgroup field_info"
*/
diff --git a/modules/field/field.install b/modules/field/field.install
index 66869d1..2b13cf7 100644
--- a/modules/field/field.install
+++ b/modules/field/field.install
@@ -1,18 +1,11 @@
'varchar',
'length' => 128,
'not null' => TRUE,
- 'description' => 'The type of this field, coming from a field module',
+ 'description' => 'The type of this field.',
),
- 'locked' => array(
+ 'module' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The module that implements the field type.',
+ ),
+ 'active' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
- 'description' => '@TODO',
+ 'description' => 'Boolean indicating whether the module that implements the field type is enabled.',
),
- 'data' => array(
- 'type' => 'text',
- 'size' => 'medium',
+ 'storage_type' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
'not null' => TRUE,
- 'serialize' => TRUE,
- 'description' => 'Field specific settings, for example maximum length',
+ 'description' => 'The storage backend for the field.',
),
- 'module' => array(
+ 'storage_module' => array(
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
+ 'description' => 'The module that implements the storage backend.',
),
- 'cardinality' => array(
+ 'storage_active' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
+ 'description' => 'Boolean indicating whether the module that implements the storage backend is enabled.',
),
- 'translatable' => array(
+ 'locked' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
'default' => 0,
+ 'description' => '@TODO',
),
- 'active' => array(
+ 'data' => array(
+ 'type' => 'text',
+ 'size' => 'medium',
+ 'not null' => TRUE,
+ 'serialize' => TRUE,
+ 'description' => 'Serialized data containing the field properties that do not warrant a dedicated column.',
+ ),
+ 'cardinality' => array(
+ 'type' => 'int',
+ 'size' => 'tiny',
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'translatable' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE,
@@ -84,14 +99,17 @@ function field_schema() {
),
'primary key' => array('id'),
'indexes' => array(
- // used by field_delete_field() among others
'field_name' => array('field_name'),
- // used by field_read_fields()
- 'active_deleted' => array('active', 'deleted'),
- // used by field_modules_disabled()
+ // Used by field_read_fields().
+ 'active' => array('active'),
+ 'storage_active' => array('storage_active'),
+ 'deleted' => array('deleted'),
+ // Used by field_modules_disabled().
'module' => array('module'),
- // used by field_associate_fields()
+ 'storage_module' => array('storage_module'),
+ // Used by field_associate_fields().
'type' => array('type'),
+ 'storage_type' => array('storage_type'),
),
);
$schema['field_config_instance'] = array(
@@ -106,15 +124,17 @@ function field_schema() {
'not null' => TRUE,
'description' => 'The identifier of the field attached by this instance',
),
- 'field_name' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
- 'bundle' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
- 'widget_type' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
- 'widget_module' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
- 'widget_active' => array(
- 'type' => 'int',
- 'size' => 'tiny',
+ 'field_name' => array(
+ 'type' => 'varchar',
+ 'length' => 32,
'not null' => TRUE,
- 'default' => 0,
+ 'default' => ''
+ ),
+ 'bundle' => array(
+ 'type' => 'varchar',
+ 'length' => 128,
+ 'not null' => TRUE,
+ 'default' => ''
),
'data' => array(
'type' => 'text',
@@ -131,14 +151,10 @@ function field_schema() {
),
'primary key' => array('id'),
'indexes' => array(
- // used by field_delete_instance()
+ // Used by field_delete_instance().
'field_name_bundle' => array('field_name', 'bundle'),
- // used by field_read_instances()
- 'widget_active_deleted' => array('widget_active', 'deleted'),
- // used by field_modules_disabled()
- 'widget_module' => array('widget_module'),
- // used by field_associate_fields()
- 'widget_type' => array('widget_type'),
+ // Used by field_read_instances().
+ 'deleted' => array('deleted'),
),
);
$schema['cache_field'] = drupal_get_schema_unprocessed('system', 'cache');
diff --git a/modules/field/field.module b/modules/field/field.module
index d30b2e2..e2919ec 100644
--- a/modules/field/field.module
+++ b/modules/field/field.module
@@ -1,5 +1,5 @@
array(
'template' => 'field',
'arguments' => array('element' => NULL),
@@ -164,6 +168,13 @@ function field_theme() {
'arguments' => array('element' => NULL),
),
);
+ $field_formatters = field_info_formatter_types(NULL);
+ foreach ($field_formatters as $key => $field_formatter) {
+ $items["field_formatter_$key"] = array(
+ 'arguments' => array('element' => NULL),
+ );
+ }
+ return $items;
}
/**
@@ -213,9 +224,9 @@ function field_modules_disabled($modules) {
->fields(array('active' => 0))
->condition('module', $module)
->execute();
- db_update('field_config_instance')
- ->fields(array('widget_active' => 0))
- ->condition('widget_module', $module)
+ db_update('field_config')
+ ->fields(array('storage_active' => 0))
+ ->condition('storage_module', $module)
->execute();
field_cache_clear(TRUE);
}
@@ -228,25 +239,23 @@ function field_modules_disabled($modules) {
* The name of the module to update on.
*/
function field_associate_fields($module) {
- $module_fields = module_invoke($module, 'field_info');
- if ($module_fields) {
- foreach ($module_fields as $name => $field_info) {
- watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
- db_update('field_config')
- ->fields(array('module' => $module, 'active' => 1))
- ->condition('type', $name)
- ->execute();
- }
+ // Associate field types.
+ $field_types =(array) module_invoke($module, 'field_info');
+ foreach ($field_types as $name => $field_info) {
+ watchdog('field', 'Updating field type %type with module %module.', array('%type' => $name, '%module' => $module));
+ db_update('field_config')
+ ->fields(array('module' => $module, 'active' => 1))
+ ->condition('type', $name)
+ ->execute();
}
- $module_widgets = module_invoke($module, 'widget_info');
- if ($module_widgets) {
- foreach ($module_widgets as $name => $widget_info) {
- watchdog('field', 'Updating widget type %type with module %module.', array('%type' => $name, '%module' => $module));
- db_update('field_config_instance')
- ->fields(array('widget_module' => $module, 'widget_active' => 1))
- ->condition('widget_type', $name)
- ->execute();
- }
+ // Associate storage backends.
+ $storage_types = (array) module_invoke($module, 'field_storage_info');
+ foreach ($storage_types as $name => $storage_info) {
+ watchdog('field', 'Updating field storage %type with module %module.', array('%type' => $name, '%module' => $module));
+ db_update('field_config')
+ ->fields(array('storage_module' => $module, 'storage_active' => 1))
+ ->condition('storage_type', $name)
+ ->execute();
}
}
@@ -421,7 +430,7 @@ function field_cache_clear($rebuild_schema = FALSE) {
cache_clear_all('*', 'cache_field', TRUE);
module_load_include('inc', 'field', 'field.info');
- _field_info_cache_clear();
+ field_info_cache_clear();
// Refresh the schema to pick up new information.
// TODO : if db storage gets abstracted out, we'll need to revisit how and when
@@ -494,7 +503,7 @@ function field_format($obj_type, $object, $field, $item, $formatter_type = NULL,
$field_type = field_info_field_types($field['type']);
// We need $field, $instance, $obj_type, $object to be able to display a value...
- list(, , $bundle) = field_attach_extract_ids($obj_type, $object);
+ list(, , $bundle) = field_extract_ids($obj_type, $object);
$instance = field_info_instance($field['field_name'], $bundle);
$display = array(
@@ -593,6 +602,19 @@ function field_view_field($obj_type, $object, $field, $instance, $build_mode = '
return $output;
}
+/**
+ * Determine whether a field has any data.
+ *
+ * @param $field
+ * A field structure.
+ * @return
+ * TRUE if the field has data for any object; FALSE otherwise.
+ */
+function field_has_data($field) {
+ $results = field_attach_query($field['id'], array(), 1);
+ return !empty($results);
+}
+
/**
* Determine whether the user has access to a given field.
*
@@ -624,6 +646,86 @@ function field_access($op, $field, $account = NULL) {
return TRUE;
}
+/**
+ * Helper function to extract id, vid, and bundle name from an object.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $object
+ * The object from which to extract values.
+ * @return
+ * A numerically indexed array (not a hash table) containing these
+ * elements:
+ *
+ * 0: primary id of the object
+ * 1: revision id of the object, or NULL if $obj_type is not versioned
+ * 2: bundle name of the object
+ * 3: whether $obj_type's fields should be cached (TRUE/FALSE)
+ */
+function field_extract_ids($obj_type, $object) {
+ // TODO D7 : prevent against broken 3rd party $node without 'type'.
+ $info = field_info_fieldable_types($obj_type);
+ // Objects being created might not have id/vid yet.
+ $id = isset($object->{$info['object keys']['id']}) ? $object->{$info['object keys']['id']} : NULL;
+ $vid = ($info['object keys']['revision'] && isset($object->{$info['object keys']['revision']})) ? $object->{$info['object keys']['revision']} : NULL;
+ // If no bundle key provided, then we assume a single bundle, named after the
+ // type of the object.
+ $bundle = $info['object keys']['bundle'] ? $object->{$info['object keys']['bundle']} : $obj_type;
+ $cacheable = $info['cacheable'];
+ return array($id, $vid, $bundle, $cacheable);
+}
+
+/**
+ * Helper function to extract id, vid, and bundle name from an object.
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $bundle
+ * The bundle object (or string if bundles for this object type do not exist
+ * as standalone objects).
+ * @return
+ * The bundle name.
+ */
+function field_extract_bundle($obj_type, $bundle) {
+ if (is_string($bundle)) {
+ return $bundle;
+ }
+
+ $info = field_info_fieldable_types($obj_type);
+ if (is_object($bundle) && isset($info['bundle keys']['bundle']) && isset($bundle->{$info['bundle keys']['bundle']})) {
+ return $bundle->{$info['bundle keys']['bundle']};
+ }
+}
+
+/**
+ * Helper function to assemble an object structure with initial ids.
+ *
+ * This function can be seen as reciprocal to field_extract_ids().
+ *
+ * @param $obj_type
+ * The type of $object; e.g. 'node' or 'user'.
+ * @param $ids
+ * A numerically indexed array, as returned by field_extract_ids(),
+ * containing these elements:
+ * 0: primary id of the object
+ * 1: revision id of the object, or NULL if $obj_type is not versioned
+ * 2: bundle name of the object
+ * @return
+ * An $object structure, initialized with the ids provided.
+ */
+function field_create_stub_entity($obj_type, $ids) {
+ $object = new stdClass();
+ $info = field_info_fieldable_types($obj_type);
+ $object->{$info['object keys']['id']} = $ids[0];
+ if (isset($info['object keys']['revision']) && !is_null($ids[1])) {
+ $object->{$info['object keys']['revision']} = $ids[1];
+ }
+ if ($info['object keys']['bundle']) {
+ $object->{$info['object keys']['bundle']} = $ids[2];
+ }
+ return $object;
+}
+
/**
* Theme preprocess function for field.tpl.php.
*
@@ -631,7 +733,7 @@ function field_access($op, $field, $account = NULL) {
*/
function template_preprocess_field(&$variables) {
$element = $variables['element'];
- list(, , $bundle) = field_attach_extract_ids($element['#object_type'], $element['#object']);
+ list(, , $bundle) = field_extract_ids($element['#object_type'], $element['#object']);
$instance = field_info_instance($element['#field_name'], $bundle);
$field = field_info_field($element['#field_name']);
@@ -670,8 +772,24 @@ function template_preprocess_field(&$variables) {
),
);
$variables = array_merge($variables, $additions);
+
+ // Initialize attributes for each item.
+ foreach ($variables['items'] as $delta => $item) {
+ $variables['item_attributes_array'][$delta] = array();
+ }
}
+/**
+ * Theme process function for field.tpl.php.
+ *
+ * @see field.tpl.php
+ */
+function template_process_field(&$variables) {
+ // Flatten out attributes for each item.
+ foreach ($variables['items'] as $delta => $item) {
+ $variables['item_attributes'][$delta] = drupal_attributes($variables['item_attributes_array'][$delta]);
+ }
+}
/**
* @} End of "defgroup field"
*/
diff --git a/modules/field/field.test b/modules/field/field.test
index 96a1ead..9bd8eac 100644
--- a/modules/field/field.test
+++ b/modules/field/field.test
@@ -1,5 +1,5 @@
default_storage);
+ }
/**
* Generate random values for a field_test field.
@@ -211,6 +223,56 @@ class FieldAttachStorageTestCase extends FieldAttachTestCase {
$this->assert(!isset($entity->{$field_names[3]}), t('Entity %index: field %field_name is not loaded.', array('%index' => 3, '%field_name' => $field_names[3])));
}
+ /**
+ * Test saving and loading fields using different storage backends.
+ */
+ function testFieldAttachSaveLoadDifferentStorage() {
+ $entity_type = 'test_entity';
+ $langcode = FIELD_LANGUAGE_NONE;
+
+ // Create two fields using different storage backends, and their instances.
+ $fields = array(
+ array(
+ 'field_name' => 'field_1',
+ 'type' => 'test_field',
+ 'cardinality' => 4,
+ 'storage' => array('type' => 'field_sql_storage')
+ ),
+ array(
+ 'field_name' => 'field_2',
+ 'type' => 'test_field',
+ 'cardinality' => 4,
+ 'storage' => array('type' => 'field_test_storage')
+ ),
+ );
+ foreach ($fields as $field) {
+ field_create_field($field);
+ $instance = array(
+ 'field_name' => $field['field_name'],
+ 'bundle' => 'test_bundle',
+ );
+ field_create_instance($instance);
+ }
+
+ $entity_init = field_test_create_stub_entity();
+
+ // Create entity and insert random values.
+ $entity = clone($entity_init);
+ $values = array();
+ foreach ($fields as $field) {
+ $values[$field['field_name']] = $this->_generateTestFieldValues($this->field['cardinality']);
+ $entity->{$field['field_name']}[$langcode] = $values[$field['field_name']];
+ }
+ field_attach_insert($entity_type, $entity);
+
+ // Check that values are loaded as expected.
+ $entity = clone($entity_init);
+ field_attach_load($entity_type, array($entity->ftid => $entity));
+ foreach ($fields as $field) {
+ $this->assertEqual($values[$field['field_name']], $entity->{$field['field_name']}[$langcode], t('%storage storage: expected values were found.', array('%storage' => $field['storage']['type'])));
+ }
+ }
+
/**
* Tests insert and update with missing or NULL fields.
*/
@@ -225,17 +287,17 @@ class FieldAttachStorageTestCase extends FieldAttachTestCase {
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: missing field results in no value saved'));
+ $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: missing field results in no value saved'));
// Insert: Field is NULL.
field_cache_clear();
$entity = clone($entity_init);
- $entity->{$this->field_name}[$langcode] = NULL;
+ $entity->{$this->field_name} = NULL;
field_attach_insert($entity_type, $entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Insert: NULL field results in no value saved'));
+ $this->assertTrue(empty($entity->{$this->field_name}), t('Insert: NULL field results in no value saved'));
// Add some real data.
field_cache_clear();
@@ -260,12 +322,33 @@ class FieldAttachStorageTestCase extends FieldAttachTestCase {
// Update: Field is NULL. Data should be wiped.
field_cache_clear();
$entity = clone($entity_init);
- $entity->{$this->field_name}[$langcode] = NULL;
+ $entity->{$this->field_name} = NULL;
field_attach_update($entity_type, $entity);
$entity = clone($entity_init);
field_attach_load($entity_type, array($entity->ftid => $entity));
- $this->assertTrue(empty($entity->{$this->field_name}[$langcode]), t('Update: NULL field removes existing values'));
+ $this->assertTrue(empty($entity->{$this->field_name}), t('Update: NULL field removes existing values'));
+
+ // Re-add some data.
+ field_cache_clear();
+ $entity = clone($entity_init);
+ $values = $this->_generateTestFieldValues(1);
+ $entity->{$this->field_name}[$langcode] = $values;
+ field_attach_update($entity_type, $entity);
+
+ $entity = clone($entity_init);
+ field_attach_load($entity_type, array($entity->ftid => $entity));
+ $this->assertEqual($entity->{$this->field_name}[$langcode], $values, t('Field data saved'));
+
+ // Update: Field is empty array. Data should be wiped.
+ field_cache_clear();
+ $entity = clone($entity_init);
+ $entity->{$this->field_name} = array();
+ field_attach_update($entity_type, $entity);
+
+ $entity = clone($entity_init);
+ field_attach_load($entity_type, array($entity->ftid => $entity));
+ $this->assertTrue(empty($entity->{$this->field_name}), t('Update: empty array removes existing values'));
}
/**
@@ -391,7 +474,7 @@ class FieldAttachStorageTestCase extends FieldAttachTestCase {
$this->assertIdentical($this->instance['bundle'], $new_bundle, "Bundle name has been updated in the instance.");
// Verify the field data is present on load.
- $entity = field_test_create_stub_entity(0, 0, $this->instance['bundle']);
+ $entity = field_test_create_stub_entity(0, 0, $new_bundle);
field_attach_load($entity_type, array(0 => $entity));
$this->assertEqual(count($entity->{$this->field_name}[$langcode]), $this->field['cardinality'], "Bundle name has been updated in the field storage");
}
@@ -956,6 +1039,7 @@ class FieldInfoTestCase extends FieldTestCase {
$field_test_info = field_test_field_info();
$formatter_info = field_test_field_formatter_info();
$widget_info = field_test_field_widget_info();
+ $storage_info = field_test_field_storage_info();
$info = field_info_field_types();
foreach ($field_test_info as $t_key => $field_type) {
@@ -981,6 +1065,14 @@ class FieldInfoTestCase extends FieldTestCase {
$this->assertEqual($info[$w_key]['module'], 'field_test', t("Widget type field_test module appears"));
}
+ $info = field_info_storage_types();
+ foreach ($storage_info as $s_key => $storage) {
+ foreach ($storage as $key => $val) {
+ $this->assertEqual($info[$s_key][$key], $val, t("Storage type $s_key key $key is $val"));
+ }
+ $this->assertEqual($info[$s_key]['module'], 'field_test', t("Storage type field_test module appears"));
+ }
+
// Verify that no unexpected instances exist.
$core_fields = field_info_fields();
$instances = field_info_instances(FIELD_TEST_BUNDLE);
@@ -1036,7 +1128,7 @@ class FieldInfoTestCase extends FieldTestCase {
// Simulate a stored field definition missing a field setting (e.g. a
// third-party module adding a new field setting has been enabled, and
// existing fields do not know the setting yet).
- $data = db_result(db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name'])));
+ $data = db_query('SELECT data FROM {field_config} WHERE field_name = :field_name', array(':field_name' => $field_definition['field_name']))->fetchField();
$data = unserialize($data);
$data['settings'] = array();
db_update('field_config')
@@ -1072,7 +1164,7 @@ class FieldInfoTestCase extends FieldTestCase {
// Simulate a stored instance definition missing various settings (e.g. a
// third-party module adding instance, widget or display settings has been
// enabled, but existing instances do not know the new settings).
- $data = db_result(db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle'])));
+ $data = db_query('SELECT data FROM {field_config_instance} WHERE field_name = :field_name AND bundle = :bundle', array(':field_name' => $instance_definition['field_name'], ':bundle' => $instance_definition['bundle']))->fetchField();
$data = unserialize($data);
$data['settings'] = array();
$data['widget']['settings'] = 'unavailable_widget';
@@ -1405,7 +1497,7 @@ class FieldFormTestCase extends FieldTestCase {
$this->drupalPost(NULL, $edit, $submit);
unset($this->additionalCurlOptions[CURLOPT_URL]);
- // The response is drupal_json, so we need to undo some escaping.
+ // The response is drupal_json_output, so we need to undo some escaping.
$commands = json_decode(str_replace(array('\x3c', '\x3e', '\x26'), array("<", ">", "&"), $this->drupalGetContent()));
// The JSON response will be two AJAX commands. The first is a settings
@@ -1429,13 +1521,14 @@ class FieldCrudTestCase extends FieldTestCase {
public static function getInfo() {
return array(
'name' => 'Field CRUD tests',
- 'description' => 'Create / read /update fields.',
+ 'description' => 'Test field create, read, update, and delete.',
'group' => 'Field',
);
}
function setUp() {
- parent::setUp('field_test');
+ // field_update_field() tests use number.module
+ parent::setUp('field_test', 'number');
}
// TODO : test creation with
@@ -1472,6 +1565,9 @@ class FieldCrudTestCase extends FieldTestCase {
$field_type = field_info_field_types($field_definition['type']);
$this->assertIdentical($record['data']['settings'], $field_type['settings'], t('Default field settings have been written.'));
+ // Ensure that default storage was set.
+ $this->assertEqual($record['storage_type'], variable_get('field_storage_default'), t('The field type is properly saved.'));
+
// Guarantee that the name is unique.
try {
field_create_field($field_definition);
@@ -1543,8 +1639,48 @@ class FieldCrudTestCase extends FieldTestCase {
catch (FieldException $e) {
$this->pass(t('Cannot create a field with a name longer than 32 characters.'));
}
+
+ // Check that field name can not be an object key.
+ // "ftvid" is known as an object key from the "test_entity" type.
+ try {
+ $field_definition = array(
+ 'type' => 'test_field',
+ 'field_name' => 'ftvid',
+ );
+ $field = field_create_field($field_definition);
+ $this->fail(t('Cannot create a field bearing the name of an object key.'));
+ }
+ catch (FieldException $e) {
+ $this->pass(t('Cannot create a field bearing the name of an object key.'));
+ }
}
+ /**
+ * Test failure to create a field.
+ */
+ function testCreateFieldFail() {
+ $field_name = 'duplicate';
+ $field_definition = array('field_name' => $field_name, 'type' => 'test_field', 'storage' => array('type' => 'field_test_storage_failure'));
+ $query = db_select('field_config')->condition('field_name', $field_name)->countQuery();
+
+ // The field does not appear in field_config.
+ $count = $query->execute()->fetchField();
+ $this->assertEqual($count, 0, 'A field_config row for the field does not exist.');
+
+ // Try to create the field.
+ try {
+ $field = field_create_field($field_definition);
+ $this->assertTrue(FALSE, 'Field creation (correctly) fails.');
+ }
+ catch (Exception $e) {
+ $this->assertTrue(TRUE, 'Field creation (correctly) fails.');
+ }
+
+ // The field does not appear in field_config.
+ $count = $query->execute()->fetchField();
+ $this->assertEqual($count, 0, 'A field_config row for the field does not exist.');
+ }
+
/**
* Test reading back a field definition.
*/
@@ -1635,8 +1771,7 @@ class FieldCrudTestCase extends FieldTestCase {
// Make sure that the field is marked as deleted when it is specifically
// loaded.
- $fields = field_read_fields(array(), array('include_deleted' => TRUE));
- $field = current($field);
+ $field = field_read_field($this->field['field_name'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($field['deleted']), t('A deleted field is marked for deletion.'));
// Make sure that this field's instance is marked as deleted when it is
@@ -1683,6 +1818,147 @@ class FieldCrudTestCase extends FieldTestCase {
$this->assertEqual($entity->{$field['field_name']}[$langcode][$delta]['value'], $values[$delta]['value'], "Data in previously deleted field saves and loads correctly");
}
}
+
+ function testUpdateNonExistentField() {
+ $test_field = array('field_name' => 'does_not_exist', 'type' => 'number_decimal');
+ try {
+ field_update_field($test_field);
+ $this->fail(t('Cannot update a field that does not exist.'));
+ }
+ catch (FieldException $e) {
+ $this->pass(t('Cannot update a field that does not exist.'));
+ }
+ }
+
+ function testUpdateFieldType() {
+ $field = array('field_name' => 'field_type', 'type' => 'number_decimal');
+ $field = field_create_field($field);
+
+ $test_field = array('field_name' => 'field_type', 'type' => 'number_integer');
+ try {
+ field_update_field($test_field);
+ $this->fail(t('Cannot update a field to a different type.'));
+ }
+ catch (FieldException $e) {
+ $this->pass(t('Cannot update a field to a different type.'));
+ }
+ }
+
+ /**
+ * Test updating a field.
+ */
+ function testUpdateField() {
+ // Create a decimal 5.2 field.
+ $field = array('field_name' => 'decimal53', 'type' => 'number_decimal', 'cardinality' => 3, 'settings' => array('precision' => 5, 'scale' => 2));
+ $field = field_create_field($field);
+ $instance = array('field_name' => 'decimal53', 'bundle' => FIELD_TEST_BUNDLE);
+ $instance = field_create_instance($instance);
+
+ // Update it to a deciaml 5.3 field.
+ $field['settings']['scale'] = 3;
+ field_update_field($field);
+
+ // Save values with 2, 3, and 4 decimal places.
+ $entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
+ $entity->decimal53[FIELD_LANGUAGE_NONE][0]['value'] = '1.23';
+ $entity->decimal53[FIELD_LANGUAGE_NONE][1]['value'] = '1.235';
+ $entity->decimal53[FIELD_LANGUAGE_NONE][2]['value'] = '1.2355';
+ field_attach_insert('test_entity', $entity);
+ $entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
+
+ // Verify that the updated 5.3 field rounds to 3 decimal places.
+ field_attach_load('test_entity', array(0 => $entity));
+ $this->assertEqual($entity->decimal53[FIELD_LANGUAGE_NONE][0]['value'], '1.23', t('2 decimal places are left alone'));
+ $this->assertEqual($entity->decimal53[FIELD_LANGUAGE_NONE][1]['value'], '1.235', t('3 decimal places are left alone'));
+ $this->assertEqual($entity->decimal53[FIELD_LANGUAGE_NONE][2]['value'], '1.236', t('4 decimal places are rounded to 3'));
+ }
+
+ /**
+ * Test field type modules forbidding an update.
+ */
+ function testUpdateFieldForbid() {
+ $field = array('field_name' => 'forbidden', 'type' => 'test_field', 'settings' => array('changeable' => 0, 'unchangeable' => 0));
+ $field = field_create_field($field);
+ $field['settings']['changeable']++;
+ try {
+ field_update_field($field);
+ $this->pass(t("A changeable setting can be updated."));
+ }
+ catch (FieldException $e) {
+ $this->fail(t("An unchangeable setting cannot be updated."));
+ }
+ $field['settings']['unchangeable']++;
+ try {
+ field_update_field($field);
+ $this->fail(t("An unchangeable setting can be updated."));
+ }
+ catch (FieldException $e) {
+ $this->pass(t("An unchangeable setting cannot be updated."));
+ }
+ }
+
+ /**
+ * Test that fields are properly marked active or inactive.
+ */
+ function testActive() {
+ $field_definition = array(
+ 'field_name' => 'field_1',
+ 'type' => 'test_field',
+ // For this test, we need a storage backend provided by a different
+ // module than field_test.module.
+ 'storage' => array(
+ 'type' => 'field_sql_storage',
+ ),
+ );
+ field_create_field($field_definition);
+
+ // Test disabling and enabling:
+ // - the field type module,
+ // - the storage module,
+ // - both.
+ $this->_testActiveHelper($field_definition, array('field_test'));
+ $this->_testActiveHelper($field_definition, array('field_sql_storage'));
+ $this->_testActiveHelper($field_definition, array('field_test', 'field_sql_storage'));
+ }
+
+ /**
+ * Helper function for testActive().
+ *
+ * Test dependency between a field and a set of modules.
+ *
+ * @param $field_definition
+ * A field definition.
+ * @param $modules
+ * An aray of module names. The field will be tested to be inactive as long
+ * as any of those modules is disabled.
+ */
+ function _testActiveHelper($field_definition, $modules) {
+ $field_name = $field_definition['field_name'];
+
+ // Read the field.
+ $field = field_read_field($field_name);
+ $this->assertTrue($field_definition <= $field, t('The field was properly read.'));
+
+ module_disable($modules);
+
+ $fields = field_read_fields(array('field_name' => $field_name), array('include_inactive' => TRUE));
+ $this->assertTrue(isset($fields[$field_name]) && $field_definition < $field, t('The field is properly read when explicitly fetching inactive fields.'));
+
+ // Re-enable modules one by one, and check that the field is still inactive
+ // while some modules remain disabled.
+ while ($modules) {
+ $field = field_read_field($field_name);
+ $this->assertTrue(empty($field), t('%modules disabled. The field is marked inactive.', array('%modules' => implode(', ', $modules))));
+
+ $module = array_shift($modules);
+ module_enable(array($module));
+ }
+
+ // Check that the field is active again after all modules have been
+ // enabled.
+ $field = field_read_field($field_name);
+ $this->assertTrue($field_definition <= $field, t('The field was was marked active.'));
+ }
}
class FieldInstanceCrudTestCase extends FieldTestCase {
@@ -1698,6 +1974,7 @@ class FieldInstanceCrudTestCase extends FieldTestCase {
function setUp() {
parent::setUp('field_test');
+
$this->field = array(
'field_name' => drupal_strtolower($this->randomName()),
'type' => 'test_field',
@@ -1735,7 +2012,7 @@ class FieldInstanceCrudTestCase extends FieldTestCase {
$this->assertIdentical($record['data']['required'], FALSE, t('Required defaults to false.'));
$this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.'));
$this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.'));
- $this->assertIdentical($record['widget_type'], $field_type['default_widget'], t('Default widget has been written.'));
+ $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], t('Default widget has been written.'));
$this->assertTrue(isset($record['data']['display']['full']), t('Display for "full" build_mode has been written.'));
$this->assertIdentical($record['data']['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" build_mode has been written.'));
@@ -1848,12 +2125,12 @@ class FieldInstanceCrudTestCase extends FieldTestCase {
field_create_instance($this->instance_definition);
$this->another_instance_definition = $this->instance_definition;
$this->another_instance_definition['bundle'] .= '_another_bundle';
- field_create_instance($this->another_instance_definition);
+ $instance = field_create_instance($this->another_instance_definition);
// Test that the first instance is not deleted, and then delete it.
$instance = field_read_instance($this->instance_definition['field_name'], $this->instance_definition['bundle'], array('include_deleted' => TRUE));
$this->assertTrue(!empty($instance) && empty($instance['deleted']), t('A new field instance is not marked for deletion.'));
- field_delete_instance($this->instance_definition['field_name'], $this->instance_definition['bundle']);
+ field_delete_instance($instance);
// Make sure the instance is marked as deleted when the instance is
// specifically loaded.
@@ -1893,7 +2170,7 @@ class FieldTranslationsTestCase extends FieldTestCase {
$this->obj_type = 'test_entity';
- $this->field = array(
+ $field = array(
'field_name' => $this->field_name,
'type' => 'test_field',
'cardinality' => 4,
@@ -1902,9 +2179,10 @@ class FieldTranslationsTestCase extends FieldTestCase {
'test_hook_in' => FALSE,
),
);
- field_create_field($this->field);
+ field_create_field($field);
+ $this->field = field_read_field($this->field_name);
- $this->instance = array(
+ $instance = array(
'field_name' => $this->field_name,
'bundle' => 'test_bundle',
'label' => $this->randomName() . '_label',
@@ -1921,7 +2199,8 @@ class FieldTranslationsTestCase extends FieldTestCase {
),
),
);
- field_create_instance($this->instance);
+ field_create_instance($instance);
+ $this->instance = field_read_instance($this->field_name, 'test_bundle');
for ($i = 0; $i < 3; ++$i) {
locale_inc_callback('locale_add_language', 'l' . $i, $this->randomString(), $this->randomString());
@@ -2079,7 +2358,9 @@ class FieldTranslationsTestCase extends FieldTestCase {
$obj_type = 'test_entity';
$object = field_test_create_stub_entity($eid, $evid, $this->instance['bundle']);
$field_translations = array();
- foreach (field_multilingual_available_languages($obj_type, $this->field) as $langcode) {
+ $available_languages = field_multilingual_available_languages($obj_type, $this->field);
+ $this->assertTrue(count($available_languages) > 1, t('Field is translatable.'));
+ foreach ($available_languages as $langcode) {
$field_translations[$langcode] = $this->_generateTestFieldValues($this->field['cardinality']);
}
@@ -2135,7 +2416,7 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
function _generateStubObjects($obj_type, $objects, $field_name = NULL) {
$stubs = array();
foreach ($objects as $obj) {
- $stub = field_attach_create_stub_object($obj_type, field_attach_extract_ids($obj_type, $obj));
+ $stub = field_create_stub_entity($obj_type, field_extract_ids($obj_type, $obj));
if (isset($field_name)) {
$stub->{$field_name} = $obj->{$field_name};
}
@@ -2209,7 +2490,8 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
$this->assertEqual(count($found['test_entity']), 10, 'Correct number of objects found before deleting');
// Delete the instance.
- field_delete_instance($field['field_name'], $bundle);
+ $instance = field_info_instance($field['field_name'], $bundle);
+ field_delete_instance($instance);
// The instance still exists, deleted.
$instances = field_read_instances(array('field_id' => $field['id'], 'deleted' => 1), array('include_deleted' => 1, 'include_inactive' => 1));
@@ -2241,7 +2523,8 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
$field = reset($this->fields);
// Delete the instance.
- field_delete_instance($field['field_name'], $bundle);
+ $instance = field_info_instance($field['field_name'], $bundle);
+ field_delete_instance($instance);
// No field hooks were called.
$mem = field_test_memorize();
@@ -2284,7 +2567,7 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
// The field still exists, not deleted, because it has a second instance.
$fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
- $this->assertEqual($field, $fields[$field['id']], 'The field exists and is not deleted');
+ $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted');
}
/**
@@ -2296,7 +2579,8 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
foreach ($this->bundles as $bundle) {
// Delete the instance.
- field_delete_instance($field['field_name'], $bundle);
+ $instance = field_info_instance($field['field_name'], $bundle);
+ field_delete_instance($instance);
// Purge the data.
field_purge_batch(10);
@@ -2306,7 +2590,7 @@ class FieldBulkDeleteTestCase extends FieldTestCase {
// The field still exists, not deleted, because it was never deleted.
$fields = field_read_fields(array('id' => $field['id']), array('include_deleted' => 1, 'include_inactive' => 1));
- $this->assertEqual($field, $fields[$field['id']], 'The field exists and is not deleted');
+ $this->assertTrue(isset($fields[$field['id']]), 'The field exists and is not deleted');
}
// Delete the field.
diff --git a/modules/field/modules/field_sql_storage/CVS/Entries b/modules/field/modules/field_sql_storage/CVS/Entries
index 868de90..9caf4aa 100644
--- a/modules/field/modules/field_sql_storage/CVS/Entries
+++ b/modules/field/modules/field_sql_storage/CVS/Entries
@@ -1,5 +1,5 @@
/field_sql_storage.info/1.3/Thu Sep 3 08:50:21 2009//
-/field_sql_storage.install/1.5/Thu Sep 3 08:50:21 2009//
-/field_sql_storage.module/1.20/Thu Sep 3 08:50:21 2009//
-/field_sql_storage.test/1.7/Thu Sep 3 08:50:21 2009//
+/field_sql_storage.install/1.7/Fri Oct 2 19:50:13 2009//
+/field_sql_storage.module/1.26/Fri Oct 2 19:50:13 2009//
+/field_sql_storage.test/1.10/Fri Oct 2 19:50:13 2009//
D
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.install b/modules/field/modules/field_sql_storage/field_sql_storage.install
index 6c20082..8b3b4ef 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.install
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.install
@@ -1,25 +1,11 @@
TRUE, 'include_inactive' => TRUE));
drupal_load('module', 'field_sql_storage');
foreach ($fields as $field) {
- $schema += _field_sql_storage_schema($field);
+ if ($field['storage']['type'] == 'field_sql_storage') {
+ $schema += _field_sql_storage_schema($field);
+ }
}
}
return $schema;
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.module b/modules/field/modules/field_sql_storage/field_sql_storage.module
index 9b47f41..9587e75 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.module
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.module
@@ -1,5 +1,5 @@
' . t('The Field SQL Storage module stores Field API data in the database. It is the default field storage module, but other field storage modules may be available in the contributions repository.') . '';
+ $output = '
' . t('The Field SQL Storage module stores Field API data in the database. It is the default field storage module, but other field storage modules may be available in the contributions repository.') . '
';
return $output;
}
}
+/**
+ * Implement hook_field_storage_info().
+ */
+function field_sql_storage_field_storage_info() {
+ return array(
+ 'field_sql_storage' => array(
+ 'label' => t('Default SQL storage'),
+ 'description' => t('Stores fields in the local SQL database, using per-field tables.'),
+ ),
+ );
+}
+
/**
* Generate a table name for a field data table.
*
@@ -26,7 +38,7 @@ function field_sql_storage_help($path, $arg) {
* A string containing the generated name for the database table
*/
function _field_sql_storage_tablename($field) {
- return "field_data_{$field['field_name']}_{$field['id']}";
+ return "field_data_{$field['field_name']}" . ($field['deleted'] ? "_{$field['id']}" : '');
}
/**
@@ -38,7 +50,7 @@ function _field_sql_storage_tablename($field) {
* A string containing the generated name for the database table
*/
function _field_sql_storage_revision_tablename($field) {
- return "field_revision_{$field['field_name']}_{$field['id']}";
+ return "field_revision_{$field['field_name']}" . ($field['deleted'] ? "_{$field['id']}" : '');
}
/**
@@ -199,53 +211,95 @@ function _field_sql_storage_schema($field) {
function field_sql_storage_field_storage_create_field($field) {
$schema = _field_sql_storage_schema($field);
foreach ($schema as $name => $table) {
- db_create_table($ret, $name, $table);
+ db_create_table($name, $table);
+ }
+}
+
+/**
+ * Implement hook_field_update_field_forbid().
+ *
+ * Forbid any field update that changes column definitions if there is
+ * any data.
+ */
+function field_sql_storage_field_update_forbid($field, $prior_field, $has_data) {
+ if ($has_data && $field['columns'] != $prior_field['columns']) {
+ throw new FieldUpdateForbiddenException("field_sql_storage cannot change the schema for an existing field with data.");
+ }
+}
+
+/**
+ * Implement hook_field_storage_update_field().
+ */
+function field_sql_storage_field_storage_update_field($field, $prior_field, $has_data) {
+ if (! $has_data) {
+ // There is no data. Re-create the tables completely.
+ $prior_schema = _field_sql_storage_schema($prior_field);
+ foreach ($prior_schema as $name => $table) {
+ db_drop_table($name, $table);
+ }
+ $schema = _field_sql_storage_schema($field);
+ foreach ($schema as $name => $table) {
+ db_create_table($name, $table);
+ }
+ }
+ else {
+ // There is data, so there are no column changes. Drop all the
+ // prior indexes and create all the new ones, except for all the
+ // priors that exist unchanged.
+ $table = _field_sql_storage_tablename($prior_field);
+ $revision_table = _field_sql_storage_revision_tablename($prior_field);
+ foreach ($prior_field['indexes'] as $name => $columns) {
+ if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) {
+ $real_name = _field_sql_storage_indexname($field['field_name'], $name);
+ db_drop_index($table, $real_name);
+ db_drop_index($revision_table, $real_name);
+ }
+ }
+ $table = _field_sql_storage_tablename($field);
+ $revision_table = _field_sql_storage_revision_tablename($field);
+ foreach ($field['indexes'] as $name => $columns) {
+ if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) {
+ $real_name = _field_sql_storage_indexname($field['field_name'], $name);
+ $real_columns = array();
+ foreach ($columns as $column_name) {
+ $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name);
+ }
+ db_add_index($table, $real_name, $real_columns);
+ db_add_index($revision_table, $real_name, $real_columns);
+ }
+ }
}
}
/**
* Implement hook_field_storage_delete_field().
*/
-function field_sql_storage_field_storage_delete_field($field_name) {
+function field_sql_storage_field_storage_delete_field($field) {
// Mark all data associated with the field for deletion.
- $field = field_info_field($field_name);
+ $field['deleted'] = 0;
$table = _field_sql_storage_tablename($field);
+ $revision_table = _field_sql_storage_revision_tablename($field);
db_update($table)
->fields(array('deleted' => 1))
->execute();
+
+ // Move the table to a unique name while the table contents are being deleted.
+ $field['deleted'] = 1;
+ $new_table = _field_sql_storage_tablename($field);
+ $revision_new_table = _field_sql_storage_revision_tablename($field);
+ db_rename_table($table, $new_table);
+ db_rename_table($revision_table, $revision_new_table);
+ drupal_get_schema(NULL, TRUE);
}
/**
* Implement hook_field_storage_load().
*/
-function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_fields, $options) {
+function field_sql_storage_field_storage_load($obj_type, $objects, $age, $fields, $options) {
$etid = _field_sql_storage_etid($obj_type);
$load_current = $age == FIELD_LOAD_CURRENT;
- // Gather ids needed for each field.
- $field_ids = array();
- $delta_count = array();
- foreach ($objects as $obj) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $obj);
-
- if ($options['deleted']) {
- $instances = field_read_instances(array('bundle' => $bundle), array('include_deleted' => $options['deleted']));
- }
- else {
- $instances = field_info_instances($bundle);
- }
-
- foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- if (!isset($skip_fields[$instance['field_id']]) && (!isset($options['field_id']) || $options['field_id'] == $instance['field_id'])) {
- $objects[$id]->{$field_name} = array();
- $field_ids[$instance['field_id']][] = $load_current ? $id : $vid;
- $delta_count[$id][$field_name] = array();
- }
- }
- }
-
- foreach ($field_ids as $field_id => $ids) {
+ foreach ($fields as $field_id => $ids) {
$field = field_info_field_by_id($field_id);
$field_name = $field['field_name'];
$table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
@@ -263,12 +317,13 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
$results = $query->execute();
+ $delta_count = array();
foreach ($results as $row) {
- if (!isset($delta_count[$row->entity_id][$field_name][$row->language])) {
- $delta_count[$row->entity_id][$field_name][$row->language] = 0;
+ if (!isset($delta_count[$row->entity_id][$row->language])) {
+ $delta_count[$row->entity_id][$row->language] = 0;
}
- if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name][$row->language] < $field['cardinality']) {
+ if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->language] < $field['cardinality']) {
$item = array();
// For each column declared by the field, populate the item
// from the prefixed database column.
@@ -279,7 +334,7 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
// Add the item to the field values for the entity.
$objects[$row->entity_id]->{$field_name}[$row->language][] = $item;
- $delta_count[$row->entity_id][$field_name][$row->language]++;
+ $delta_count[$row->entity_id][$row->language]++;
}
}
}
@@ -288,41 +343,30 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f
/**
* Implement hook_field_storage_write().
*/
-function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fields) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+function field_sql_storage_field_storage_write($obj_type, $object, $op, $fields) {
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$etid = _field_sql_storage_etid($obj_type);
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- if (isset($skip_fields[$instance['field_id']])) {
- continue;
- }
-
- $field = field_info_field($field_name);
+ foreach ($fields as $field_id) {
+ $field = field_info_field_by_id($field_id);
+ $field_name = $field['field_name'];
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
- // Leave the field untouched if $object comes with no $field_name property.
- // Empty the field if $object->$field_name is NULL or an empty array.
-
- // Function property_exists() is slower, so we catch the more frequent cases
- // where it's an empty array with the faster isset().
- if (isset($object->$field_name) || property_exists($object, $field_name)) {
- $available_languages = field_multilingual_available_languages($obj_type, $field);
- $available_translations = is_array($object->$field_name) ? array_intersect($available_languages, array_keys($object->$field_name)) : FALSE;
-
- // Delete and insert, rather than update, in case a value was added.
- // If no translation is available, empty the field for all the available languages.
- if ($op == FIELD_STORAGE_UPDATE && count($available_translations)) {
- $languages = empty($object->$field_name) ? $available_languages : $available_translations;
+ $all_languages = field_multilingual_available_languages($obj_type, $field);
+ $field_languages = array_intersect($all_languages, array_keys((array) $object->$field_name));
+ // Delete and insert, rather than update, in case a value was added.
+ if ($op == FIELD_STORAGE_UPDATE) {
+ // Delete languages present in the incoming $object->$field_name.
+ // Delete all languages if $object->$field_name is empty.
+ $languages = !empty($object->$field_name) ? $field_languages : $all_languages;
+ if ($languages) {
db_delete($table_name)
->condition('etid', $etid)
->condition('entity_id', $id)
->condition('language', $languages, 'IN')
->execute();
-
if (isset($vid)) {
db_delete($revision_name)
->condition('etid', $etid)
@@ -332,46 +376,48 @@ function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fi
->execute();
}
}
+ }
+
+ // Prepare the multi-insert query.
+ $do_insert = FALSE;
+ $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language');
+ foreach ($field['columns'] as $column => $attributes) {
+ $columns[] = _field_sql_storage_columnname($field_name, $column);
+ }
+ $query = db_insert($table_name)->fields($columns);
+ if (isset($vid)) {
+ $revision_query = db_insert($revision_name)->fields($columns);
+ }
- if (!empty($available_translations)) {
- // Prepare the multi-insert query.
- $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language');
+ foreach ($field_languages as $langcode) {
+ $items = (array) $object->{$field_name}[$langcode];
+ $delta_count = 0;
+ foreach ($items as $delta => $item) {
+ // We now know we have someting to insert.
+ $do_insert = TRUE;
+ $record = array(
+ 'etid' => $etid,
+ 'entity_id' => $id,
+ 'revision_id' => $vid,
+ 'bundle' => $bundle,
+ 'delta' => $delta,
+ 'language' => $langcode,
+ );
foreach ($field['columns'] as $column => $attributes) {
- $columns[] = _field_sql_storage_columnname($field_name, $column);
+ $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
}
- $query = db_insert($table_name)->fields($columns);
+ $query->values($record);
if (isset($vid)) {
- $revision_query = db_insert($revision_name)->fields($columns);
+ $revision_query->values($record);
}
- foreach ($available_translations as $langcode) {
- if ($items = $object->{$field_name}[$langcode]) {
- $delta_count = 0;
- foreach ($items as $delta => $item) {
- $record = array(
- 'etid' => $etid,
- 'entity_id' => $id,
- 'revision_id' => $vid,
- 'bundle' => $bundle,
- 'delta' => $delta,
- 'language' => $langcode,
- );
- foreach ($field['columns'] as $column => $attributes) {
- $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
- }
- $query->values($record);
- if (isset($vid)) {
- $revision_query->values($record);
- }
-
- if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
- break;
- }
- }
- }
+ if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
+ break;
}
+ }
- // Execute the insert.
+ // Execute the query if we have values to insert.
+ if ($do_insert) {
$query->execute();
if (isset($vid)) {
$revision_query->execute();
@@ -386,14 +432,15 @@ function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fi
*
* This function deletes data for all fields for an object from the database.
*/
-function field_sql_storage_field_storage_delete($obj_type, $object) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+function field_sql_storage_field_storage_delete($obj_type, $object, $fields) {
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$etid = _field_sql_storage_etid($obj_type);
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- $field = field_info_field($instance['field_name']);
- field_sql_storage_field_storage_purge($obj_type, $object, $field, $instance);
+ foreach (field_info_instances($bundle) as $instance) {
+ if (isset($fields[$instance['field_id']])) {
+ $field = field_info_field_by_id($instance['field_id']);
+ field_sql_storage_field_storage_purge($obj_type, $object, $field, $instance);
+ }
}
}
@@ -404,10 +451,9 @@ function field_sql_storage_field_storage_delete($obj_type, $object) {
* an object.
*/
function field_sql_storage_field_storage_purge($obj_type, $object, $field, $instance) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$etid = _field_sql_storage_etid($obj_type);
- $field = field_info_field_by_id($field['id']);
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_delete($table_name)
@@ -483,16 +529,16 @@ function field_sql_storage_field_storage_query($field_id, $conditions, $count, &
$value = _field_sql_storage_etid($value);
}
}
-
- $query->condition($column, $value, $operator);
-
+ // Track condition on 'deleted'.
if ($column == 'deleted') {
- $deleted = $value;
+ $condition_deleted = TRUE;
}
+
+ $query->condition($column, $value, $operator);
}
// Exclude deleted data unless we have a condition on it.
- if (!isset($deleted)) {
+ if (!isset($condition_deleted)) {
$query->condition('deleted', 0);
}
@@ -520,7 +566,7 @@ function field_sql_storage_field_storage_query($field_id, $conditions, $count, &
$id = ($load_current || empty($entity_type['object keys']['revision'])) ? $row->entity_id : $row->revision_id;
if (!isset($return[$row->type][$id])) {
- $return[$row->type][$id] = field_attach_create_stub_object($row->type, array($row->entity_id, $row->revision_id, $row->bundle));
+ $return[$row->type][$id] = field_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle));
$obj_count++;
}
}
@@ -540,15 +586,13 @@ function field_sql_storage_field_storage_query($field_id, $conditions, $count, &
*
* This function actually deletes the data from the database.
*/
-function field_sql_storage_field_storage_delete_revision($obj_type, $object) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+function field_sql_storage_field_storage_delete_revision($obj_type, $object, $fields) {
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$etid = _field_sql_storage_etid($obj_type);
if (isset($vid)) {
- $instances = field_info_instances($bundle);
- foreach ($instances as $instance) {
- $field_name = $instance['field_name'];
- $field = field_read_field($field_name);
+ foreach ($fields as $field_id) {
+ $field = field_info_field_by_id($field_id);
$revision_name = _field_sql_storage_revision_tablename($field);
db_delete($revision_name)
->condition('etid', $etid)
@@ -564,37 +608,40 @@ function field_sql_storage_field_storage_delete_revision($obj_type, $object) {
*
* This function simply marks for deletion all data associated with the field.
*/
-function field_sql_storage_field_storage_delete_instance($field_name, $bundle) {
- $field = field_read_field($field_name);
+function field_sql_storage_field_storage_delete_instance($instance) {
+ $field = field_info_field($instance['field_name']);
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
db_update($table_name)
->fields(array('deleted' => 1))
- ->condition('bundle', $bundle)
+ ->condition('bundle', $instance['bundle'])
->execute();
db_update($revision_name)
->fields(array('deleted' => 1))
- ->condition('bundle', $bundle)
+ ->condition('bundle', $instance['bundle'])
->execute();
}
/**
- * Implement hook_field_storage_rename_bundle().
+ * Implement hook_field_attach_rename_bundle().
*/
-function field_sql_storage_field_storage_rename_bundle($bundle_old, $bundle_new) {
- $instances = field_info_instances($bundle_old);
+function field_sql_storage_field_attach_rename_bundle($bundle_old, $bundle_new) {
+ // We need to account for deleted or inactive fields and instances.
+ $instances = field_read_instances(array('bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE));
foreach ($instances as $instance) {
- $field = field_read_field($instance['field_name']);
- $table_name = _field_sql_storage_tablename($field);
- $revision_name = _field_sql_storage_revision_tablename($field);
- db_update($table_name)
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle_old)
- ->execute();
- db_update($revision_name)
- ->fields(array('bundle' => $bundle_new))
- ->condition('bundle', $bundle_old)
- ->execute();
+ $field = field_info_field_by_id($instance['field_id']);
+ if ($field['storage']['type'] == 'field_sql_storage') {
+ $table_name = _field_sql_storage_tablename($field);
+ $revision_name = _field_sql_storage_revision_tablename($field);
+ db_update($table_name)
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle_old)
+ ->execute();
+ db_update($revision_name)
+ ->fields(array('bundle' => $bundle_new))
+ ->condition('bundle', $bundle_old)
+ ->execute();
+ }
}
}
@@ -605,10 +652,9 @@ function field_sql_storage_field_storage_rename_bundle($bundle_old, $bundle_new)
* that is left is to delete the table.
*/
function field_sql_storage_field_storage_purge_field($field) {
- $ret = array();
$table_name = _field_sql_storage_tablename($field);
$revision_name = _field_sql_storage_revision_tablename($field);
- db_drop_table($ret, $table_name);
- db_drop_table($ret, $revision_name);
+ db_drop_table($table_name);
+ db_drop_table($revision_name);
}
diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test
index 4458136..fe8bb93 100644
--- a/modules/field/modules/field_sql_storage/field_sql_storage.test
+++ b/modules/field/modules/field_sql_storage/field_sql_storage.test
@@ -1,5 +1,5 @@
field_name = drupal_strtolower($this->randomName() . '_field_name');
$this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4);
$this->field = field_create_field($this->field);
@@ -183,8 +183,8 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
$this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}");
// Check that update leaves the field data untouched if
- // $object->{$field_name} has no language key.
- unset($entity->{$this->field_name}[$langcode]);
+ // $object->{$field_name} is absent.
+ unset($entity->{$this->field_name});
field_attach_update($entity_type, $entity);
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
foreach ($values as $delta => $value) {
@@ -194,7 +194,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
}
// Check that update with an empty $object->$field_name empties the field.
- $entity->{$this->field_name}[$langcode] = NULL;
+ $entity->{$this->field_name} = NULL;
field_attach_update($entity_type, $entity);
$rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC);
$this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field."));
@@ -217,7 +217,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
$this->assertEqual($count, 0, 'Missing field results in no inserts');
// Insert: Field is NULL
- $entity->{$this->field_name}[$langcode] = NULL;
+ $entity->{$this->field_name} = NULL;
field_attach_insert($entity_type, $entity);
$count = db_select($this->table)
->countQuery()
@@ -294,4 +294,88 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase {
->fetchField();
$this->assertEqual($count, 1, 'NULL field translation is wiped.');
}
+
+ /**
+ * Test trying to update a field with data.
+ */
+ function testUpdateFieldSchemaWithData() {
+ // Create a decimal 5.2 field and add some data.
+ $field = array('field_name' => 'decimal52', 'type' => 'number_decimal', 'settings' => array('precision' => 5, 'scale' => 2));
+ $field = field_create_field($field);
+ $instance = array('field_name' => 'decimal52', 'bundle' => FIELD_TEST_BUNDLE);
+ $instance = field_create_instance($instance);
+ $entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
+ $entity->decimal52[FIELD_LANGUAGE_NONE][0]['value'] = '1.235';
+ field_attach_insert('test_entity', $entity);
+
+ // Attempt to update the field in a way that would work without data.
+ $field['settings']['scale'] = 3;
+ try {
+ field_update_field($field);
+ $this->fail(t('Cannot update field schema with data.'));
+ }
+ catch (FieldException $e) {
+ $this->pass(t('Cannot update field schema with data.'));
+ }
+ }
+
+ /**
+ * Test adding and removing indexes while data is present.
+ */
+ function testFieldUpdateIndexesWithData() {
+ // We do not have a db-agnostic inspection system in core yet, so
+ // for now we can only test this on mysql.
+ if (Database::getConnection()->databaseType() == 'mysql') {
+ // Create a decimal field.
+ $field_name = 'testfield';
+ $field = array('field_name' => $field_name, 'type' => 'text');
+ $field = field_create_field($field);
+ $instance = array('field_name' => $field_name, 'bundle' => FIELD_TEST_BUNDLE);
+ $instance = field_create_instance($instance);
+ $tables = array(_field_sql_storage_tablename($field), _field_sql_storage_revision_tablename($field));
+
+ // Verify the indexes we will create do not exist yet.
+ foreach ($tables as $table) {
+ $indexes = $this->getIndexes($table);
+ $this->assertTrue(empty($indexes['value']), t("No index named value exists in $table"));
+ $this->assertTrue(empty($indexes['value_format']), t("No index named value_format exists in $table"));
+ }
+
+ // Add data so the table cannot be dropped.
+ $entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
+ $entity->{$field_name}[FIELD_LANGUAGE_NONE][0]['value'] = 'field data';
+ field_attach_insert('test_entity', $entity);
+
+ // Add an index
+ $field = array('field_name' => $field_name, 'indexes' => array('value' => array('value')));
+ field_update_field($field);
+ foreach ($tables as $table) {
+ $indexes = $this->getIndexes($table);
+ $this->assertTrue($indexes["{$field_name}_value"] == array(1 => "{$field_name}_value"), t("Index on value created in $table"));
+ }
+
+ // Add a different index, removing the existing custom one.
+ $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array('value', 'format')));
+ field_update_field($field);
+ foreach ($tables as $table) {
+ $indexes = $this->getIndexes($table);
+ $this->assertTrue($indexes["{$field_name}_value_format"] == array(1 => "{$field_name}_value", 2 => "{$field_name}_format"), t("Index on value_format created in $table"));
+ $this->assertTrue(empty($indexes["{$field_name}_value"]), t("Index on value removed in $table"));
+ }
+
+ // Verify that the tables were not dropped.
+ $entity = field_test_create_stub_entity(0, 0, $instance['bundle']);
+ field_attach_load('test_entity', array(0 => $entity));
+ $this->assertEqual($entity->{$field_name}[FIELD_LANGUAGE_NONE][0]['value'], 'field data', t("Index changes performed without dropping the tables"));
+ }
+ }
+
+ function getIndexes($table) {
+ $indexes = array();
+ $result = db_query("SHOW INDEXES FROM {" . $table . "}");
+ foreach ($result as $row) {
+ $indexes[$row->key_name][$row->seq_in_index] = $row->column_name;
+ }
+ return $indexes;
+ }
}
diff --git a/modules/field/modules/list/CVS/Entries b/modules/field/modules/list/CVS/Entries
index 091c401..8aacd4a 100644
--- a/modules/field/modules/list/CVS/Entries
+++ b/modules/field/modules/list/CVS/Entries
@@ -1,3 +1,3 @@
/list.info/1.4/Thu Sep 3 08:50:21 2009//
-/list.module/1.13/Thu Sep 3 08:50:21 2009//
+/list.module/1.15/Fri Oct 2 19:50:13 2009//
D
diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module
index f1c4b67..894bc23 100644
--- a/modules/field/modules/list/list.module
+++ b/modules/field/modules/list/list.module
@@ -1,25 +1,11 @@
array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_list_key' => array(
- 'arguments' => array('element' => NULL),
- ),
- );
-}
-
/**
* Implement hook_field_info().
*/
@@ -99,8 +85,14 @@ function list_field_schema($field) {
/**
* Implement hook_field_settings_form().
+ *
+ * @todo: If $has_data, add a form validate function to verify that the
+ * new allowed values do not exclude any keys for which data already
+ * exists in the databae (use field_attach_query()) to find out.
+ * Implement the validate function via hook_field_update_forbid() so
+ * list.module does not depend on form submission.
*/
-function list_field_settings_form($field, $instance) {
+function list_field_settings_form($field, $instance, $has_data) {
$settings = $field['settings'];
$form['allowed_values'] = array(
diff --git a/modules/field/modules/number/CVS/Entries b/modules/field/modules/number/CVS/Entries
index 0fb9165..86c8884 100644
--- a/modules/field/modules/number/CVS/Entries
+++ b/modules/field/modules/number/CVS/Entries
@@ -1,3 +1,3 @@
/number.info/1.4/Thu Sep 3 08:50:21 2009//
-/number.module/1.15/Thu Sep 3 08:50:21 2009//
+/number.module/1.20/Fri Oct 2 19:50:13 2009//
D
diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module
index 18d3f89..22a4547 100644
--- a/modules/field/modules/number/number.module
+++ b/modules/field/modules/number/number.module
@@ -1,5 +1,5 @@
array('arguments' => array('element' => NULL)),
- 'field_formatter_number_integer' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
- 'field_formatter_number_decimal' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'),
- 'field_formatter_number_unformatted' => array('arguments' => array('element' => NULL)),
);
}
+/**
+ * Implement hook_theme_alter().
+ */
+function number_theme_registry_alter(&$theme_registry) {
+ // The number_integer and number_decimal formatters use the same function.
+ $theme_registry['field_formatter_number_default']['function'] = 'theme_field_formatter_number';
+ $theme_registry['field_formatter_number_decimal']['function'] = 'theme_field_formatter_number';
+}
+
/**
* Implement hook_field_info().
*/
@@ -28,22 +34,22 @@ function number_field_info() {
'description' => t('This field stores a number in the database as an integer.'),
'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
'default_widget' => 'number',
- 'default_formatter' => 'number_integer',
+ 'default_formatter' => 'number_default',
),
'number_decimal' => array(
'label' => t('Decimal'),
'description' => t('This field stores a number in the database in a fixed decimal format.'),
- 'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => ' .'),
+ 'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => '.'),
'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
'default_widget' => 'number',
- 'default_formatter' => 'number_integer',
+ 'default_formatter' => 'number_decimal',
),
'number_float' => array(
'label' => t('Float'),
'description' => t('This field stores a number in the database in a floating point format.'),
'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''),
'default_widget' => 'number',
- 'default_formatter' => 'number_integer',
+ 'default_formatter' => 'number_decimal',
),
);
}
@@ -90,7 +96,7 @@ function number_field_schema($field) {
/**
* Implement hook_field_settings_form().
*/
-function number_field_settings_form($field, $instance) {
+function number_field_settings_form($field, $instance, $has_data) {
$settings = $field['settings'];
$form = array();
@@ -101,6 +107,7 @@ function number_field_settings_form($field, $instance) {
'#options' => drupal_map_assoc(range(10, 32)),
'#default_value' => $settings['precision'],
'#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'),
+ '#disabled' => $has_data,
);
$form['scale'] = array(
'#type' => 'select',
@@ -108,6 +115,7 @@ function number_field_settings_form($field, $instance) {
'#options' => drupal_map_assoc(range(0, 10)),
'#default_value' => $settings['scale'],
'#description' => t('The number of digits to the right of the decimal.'),
+ '#disabled' => $has_data,
);
$form['decimal'] = array(
'#type' => 'select',
@@ -204,7 +212,7 @@ function number_field_is_empty($item, $field) {
*/
function number_field_formatter_info() {
return array(
- 'number_integer' => array(
+ 'number_default' => array(
'label' => t('default'),
'field types' => array('number_integer'),
'settings' => array(
@@ -286,32 +294,26 @@ function number_field_widget_info() {
}
/**
- * Implement FAPI hook_elements().
- *
- * Any FAPI callbacks needed for individual widgets can be declared here,
- * and the element will be passed to those callbacks for processing.
- *
- * Drupal will automatically theme the element using a theme with
- * the same name as the hook_elements key.
+ * Implement hook_element_info().
*
* Includes a regex to check for valid values as an additional parameter
* the validator can use. The regex can be overridden if necessary.
*/
-function number_elements() {
- return array(
- 'number' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('number_elements_process'),
- ),
+function number_element_info() {
+ $types['number'] = array(
+ '#input' => TRUE,
+ '#columns' => array('value'),
+ '#delta' => 0,
+ '#process' => array('number_elements_process'),
);
+ return $types;
}
/**
* Implement hook_field_widget().
*
* Attach a single form element to the form. It will be built out and
- * validated in the callback(s) listed in hook_elements. We build it
+ * validated in the callback(s) listed in hook_element_info(). We build it
* out in the callbacks rather than here in hook_widget so it can be
* plugged into any module that can provide it with valid
* $field information.
@@ -480,7 +482,7 @@ function number_decimal_validate($element, &$form_state) {
form_set_error($error_field, t('Only numbers and the decimal character (%decimal) are allowed in %field.', array('%decimal' => $field['settings']['decimal'], '%field' => t($instance['label']))));
}
else {
- $value = str_replace($field['settings']['decimal'], ' .', $value);
+ $value = str_replace($field['settings']['decimal'], '.', $value);
$value = round($value, $field['settings']['scale']);
form_set_value($element[$field_key], $value, $form_state);
}
diff --git a/modules/field/modules/options/CVS/Entries b/modules/field/modules/options/CVS/Entries
index 8c560e0..8ea6006 100644
--- a/modules/field/modules/options/CVS/Entries
+++ b/modules/field/modules/options/CVS/Entries
@@ -1,3 +1,3 @@
/options.info/1.3/Thu Sep 3 08:50:21 2009//
-/options.module/1.9/Thu Sep 3 08:50:21 2009//
+/options.module/1.10/Fri Oct 2 19:50:13 2009//
D
diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module
index a37f6eb..c621f48 100644
--- a/modules/field/modules/options/options.module
+++ b/modules/field/modules/options/options.module
@@ -1,5 +1,5 @@
array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('options_select_elements_process'),
- ),
- 'options_buttons' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('options_buttons_elements_process'),
- ),
- 'options_onoff' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('options_onoff_elements_process'),
- ),
- );
+function options_element_info() {
+ $types['options_select'] = array(
+ '#input' => TRUE,
+ '#columns' => array('value'),
+ '#delta' => 0,
+ '#process' => array('options_select_elements_process'),
+ );
+ $types['options_buttons'] = array(
+ '#input' => TRUE,
+ '#columns' => array('value'),
+ '#delta' => 0,
+ '#process' => array('options_buttons_elements_process'),
+ );
+ $types['options_onoff'] = array(
+ '#input' => TRUE,
+ '#columns' => array('value'),
+ '#delta' => 0,
+ '#process' => array('options_onoff_elements_process'),
+ );
+ return $types;
}
/**
diff --git a/modules/field/modules/text/CVS/Entries b/modules/field/modules/text/CVS/Entries
index 302aae8..211d3dd 100644
--- a/modules/field/modules/text/CVS/Entries
+++ b/modules/field/modules/text/CVS/Entries
@@ -1,4 +1,5 @@
/text.info/1.5/Thu Sep 3 08:50:21 2009//
-/text.module/1.25/Thu Sep 3 08:50:21 2009//
-/text.test/1.10/Thu Sep 3 08:50:21 2009//
+/text.js/1.1/Fri Sep 11 13:30:49 2009//
+/text.module/1.30/Fri Oct 2 19:50:13 2009//
+/text.test/1.12/Fri Oct 2 19:50:13 2009//
D
diff --git a/modules/field/modules/text/text.js b/modules/field/modules/text/text.js
new file mode 100644
index 0000000..7397a8d
--- /dev/null
+++ b/modules/field/modules/text/text.js
@@ -0,0 +1,40 @@
+// $Id: text.js,v 1.1 2009/09/11 13:30:49 webchick Exp $
+
+(function ($) {
+
+/**
+ * Auto-hide summary textarea if empty and show hide and unhide links.
+ */
+Drupal.behaviors.textTextareaSummary = {
+ attach: function (context, settings) {
+ $('textarea.text-textarea-summary:not(.text-textarea-summary-processed)', context).addClass('text-textarea-summary-processed').each(function () {
+ var $fieldset = $(this).closest('#body-wrapper');
+ var $summary = $fieldset.find('div.text-summary-wrapper');
+ var $summaryLabel = $summary.find('div.form-type-textarea label');
+ var $full = $fieldset.find('div.text-full-wrapper');
+ var $fullLabel = $full.find('div.form-type-textarea label');
+
+ // Setup the edit/hide summary link.
+ var $link = $('(' + Drupal.t('Hide summary') + ')').toggle(
+ function () {
+ $summary.hide();
+ $(this).find('a').html(Drupal.t('Edit summary')).end().appendTo($fullLabel);
+ return false;
+ },
+ function () {
+ $summary.show();
+ $(this).find('a').html(Drupal.t('Hide summary')).end().appendTo($summaryLabel);
+ return false;
+ }
+ ).appendTo($summaryLabel);
+
+ // If no summary is set, hide the summary field.
+ if ($(this).val() == '') {
+ $link.click();
+ }
+ return;
+ });
+ }
+};
+
+})(jQuery);
diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module
index 2148d57..eae1c1b 100644
--- a/modules/field/modules/text/text.module
+++ b/modules/field/modules/text/text.module
@@ -1,5 +1,5 @@
array(
'arguments' => array('element' => NULL),
),
- 'field_formatter_text_default' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_text_plain' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_text_trimmed' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_text_summary_or_trimmed' => array(
- 'arguments' => array('element' => NULL),
- ),
);
}
@@ -128,7 +116,7 @@ function text_field_schema($field) {
/**
* Implement hook_field_settings_form().
*/
-function text_field_settings_form($field, $instance) {
+function text_field_settings_form($field, $instance, $has_data) {
$settings = $field['settings'];
$form['max_length'] = array(
@@ -138,6 +126,9 @@ function text_field_settings_form($field, $instance) {
'#required' => FALSE,
'#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'),
'#element_validate' => array('_element_validate_integer_positive'),
+ // @todo: If $has_data, add a validate handler that only allows
+ // max_length to increase.
+ '#disabled' => $has_data,
);
return $form;
@@ -330,7 +321,7 @@ function theme_field_formatter_text_trimmed($element) {
/**
* Theme function for 'summary or trimmed' field formatter for
- * text_with_summary fields. This formatter returns the summary
+ * text_with_summary fields. This formatter returns the summary
* element of the field or, if the summary is empty, the trimmed
* version of the full element of the field.
*/
@@ -533,48 +524,44 @@ function text_field_widget_settings_form($field, $instance) {
}
/**
- * Implement FAPI hook_elements().
- *
- * Any FAPI callbacks needed for individual widgets can be declared here,
- * and the element will be passed to those callbacks for processing.
- *
- * Drupal will automatically theme the element using a theme with
- * the same name as the hook_elements key.
+ * Implement hook_element_info().
*
* Autocomplete_path is not used by text_field_widget but other
* widgets can use it (see nodereference and userreference).
*/
-function text_elements() {
- return array(
- 'text_textfield' => array(
- '#input' => TRUE,
- '#columns' => array('value'), '#delta' => 0,
- '#process' => array('text_textfield_elements_process'),
- '#theme_wrappers' => array('text_textfield'),
- '#autocomplete_path' => FALSE,
- ),
- 'text_textarea' => array(
- '#input' => TRUE,
- '#columns' => array('value', 'format'), '#delta' => 0,
- '#process' => array('text_textarea_elements_process'),
- '#theme_wrappers' => array('text_textarea'),
- '#filter_value' => FILTER_FORMAT_DEFAULT,
- ),
- 'text_textarea_with_summary' => array(
- '#input' => TRUE,
- '#columns' => array('value', 'format', 'summary'), '#delta' => 0,
- '#process' => array('text_textarea_with_summary_process'),
- '#theme_wrappers' => array('text_textarea'),
- '#filter_value' => FILTER_FORMAT_DEFAULT,
- ),
+function text_element_info() {
+ $types['text_textfield'] = array(
+ '#input' => TRUE,
+ '#columns' => array('value'),
+ '#delta' => 0,
+ '#process' => array('text_textfield_elements_process'),
+ '#theme_wrappers' => array('text_textfield'),
+ '#autocomplete_path' => FALSE,
+ );
+ $types['text_textarea'] = array(
+ '#input' => TRUE,
+ '#columns' => array('value', 'format'),
+ '#delta' => 0,
+ '#process' => array('text_textarea_elements_process'),
+ '#theme_wrappers' => array('text_textarea'),
+ '#filter_value' => filter_default_format(),
+ );
+ $types['text_textarea_with_summary'] = array(
+ '#input' => TRUE,
+ '#columns' => array('value', 'format', 'summary'),
+ '#delta' => 0,
+ '#process' => array('text_textarea_with_summary_process'),
+ '#theme_wrappers' => array('text_textarea'),
+ '#filter_value' => filter_default_format(),
);
+ return $types;
}
/**
* Implement hook_field_widget().
*
* Attach a single form element to the form. It will be built out and
- * validated in the callback(s) listed in hook_elements. We build it
+ * validated in the callback(s) listed in hook_element_info(). We build it
* out in the callbacks rather than here in hook_field_widget so it can be
* plugged into any module that can provide it with valid
* $field information.
@@ -667,7 +654,7 @@ function text_textfield_elements_process($element, $form_state, $form) {
if (!empty($instance['settings']['text_processing'])) {
$filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
- $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+ $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format();
$element[$field_key]['#text_format'] = $format;
}
@@ -700,7 +687,7 @@ function text_textarea_elements_process($element, $form_state, $form) {
if (!empty($instance['settings']['text_processing'])) {
$filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
- $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+ $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format();
$element[$field_key]['#text_format'] = $format;
}
@@ -732,6 +719,10 @@ function text_textarea_with_summary_process($element, $form_state, $form) {
'#description' => t('Leave blank to use trimmed value of full text as the summary.'),
'#required' => $element['#required'],
'#display' => $display,
+ '#attached' => array('js' => array(drupal_get_path('module', 'text') . '/text.js')),
+ '#attributes' => array('class' => array('text-textarea-summary')),
+ '#prefix' => '
',
);
if (!empty($instance['settings']['text_processing'])) {
$filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format';
- $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT;
+ $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format();
$element[$field_key]['#text_format'] = $format;
}
@@ -769,7 +763,7 @@ function text_field_widget_formatted_text_value($form, $edit = FALSE) {
// The format selector uses #access = FALSE if only one format is
// available. In this case, we don't receive its value, and need to
// manually set it.
- $edit['format'] = !empty($edit[$default_key]) ? $edit[$default_key] : filter_resolve_format(FILTER_FORMAT_DEFAULT);
+ $edit['format'] = !empty($edit[$default_key]) ? $edit[$default_key] : filter_default_format();
unset($edit[$default_key]);
return $edit;
}
diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test
index 98ead18..072676d 100644
--- a/modules/field/modules/text/text.test
+++ b/modules/field/modules/text/text.test
@@ -1,8 +1,10 @@
drupalCreateUser(array('access field_test content', 'administer field_test content'));
- $this->drupalLogin($web_user);
+ $this->admin_user = $this->drupalCreateUser(array('administer filters'));
+ $this->web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content'));
+ $this->drupalLogin($this->web_user);
}
// Test fields.
@@ -147,15 +150,23 @@ class TextFieldTestCase extends DrupalWebTestCase {
field_create_instance($this->instance);
$langcode = FIELD_LANGUAGE_NONE;
- // Display creation form.
- // By default, the user only has access to 'Filtered HTML', and no format
- // selector is displayed
+ // Delete all text formats besides the plain text fallback format.
+ $this->drupalLogin($this->admin_user);
+ foreach (filter_formats() as $format) {
+ if ($format->format != filter_fallback_format()) {
+ $this->drupalPost('admin/config/content/formats/' . $format->format . '/delete', array(), t('Delete'));
+ }
+ }
+ $this->drupalLogin($this->web_user);
+
+ // Display the creation form. Since the user only has access to one format,
+ // no format selector will be displayed.
$this->drupalGet('test-entity/add/test-bundle');
$this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed'));
- $this->assertNoFieldByName("{$this->field_name}[$langcode][0][value_format]", '1', t('Format selector is not displayed'));
+ $this->assertNoFieldByName("{$this->field_name}[$langcode][0][value_format]", '', t('Format selector is not displayed'));
// Submit with data that should be filtered.
- $value = $this->randomName() . ' ' . $this->randomName();
+ $value = '' . $this->randomName() . '';
$edit = array(
"{$this->field_name}[$langcode][0][value]" => $value,
);
@@ -168,21 +179,31 @@ class TextFieldTestCase extends DrupalWebTestCase {
$entity = field_test_entity_load($id);
$entity->content = field_attach_view($entity_type, $entity);
$this->content = drupal_render($entity->content);
- $this->assertNoRaw($value, 'Filtered tags are not displayed');
- $this->assertRaw(str_replace(' ', '', $value), t('Filtered value is displayed correctly'));
+ $this->assertNoRaw($value, t('HTML tags are not displayed.'));
+ $this->assertRaw(check_plain($value), t('Escaped HTML is displayed correctly.'));
- // Allow the user to use the 'Full HTML' format.
- db_update('filter_format')->fields(array('roles' => ',2,'))->condition('format', 2)->execute();
+ // Create a new text format that does not escape HTML, and grant the user
+ // access to it.
+ $this->drupalLogin($this->admin_user);
+ $edit = array('name' => $this->randomName());
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ filter_formats_reset();
+ $this->checkPermissions(array(), TRUE);
+ $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField();
+ $permission = filter_permission_name(filter_format_load($format_id));
+ $rid = max(array_keys($this->web_user->roles));
+ user_role_grant_permissions($rid, array($permission), TRUE);
+ $this->drupalLogin($this->web_user);
// Display edition form.
// We should now have a 'text format' selector.
$this->drupalGet('test-entity/' . $id . '/edit');
$this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed'));
- $this->assertFieldByName("{$this->field_name}[$langcode][0][value_format]", '1', t('Format selector is displayed'));
+ $this->assertFieldByName("{$this->field_name}[$langcode][0][value_format]", '', t('Format selector is displayed'));
- // Edit and change the format to 'Full HTML'.
+ // Edit and change the text format to the new one that was created.
$edit = array(
- "{$this->field_name}[$langcode][0][value_format]" => 2,
+ "{$this->field_name}[$langcode][0][value_format]" => $format_id,
);
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated'));
@@ -193,11 +214,6 @@ class TextFieldTestCase extends DrupalWebTestCase {
$this->content = drupal_render($entity->content);
$this->assertRaw($value, t('Value is displayed unfiltered'));
}
-
- // Test formatters.
- /**
- *
- */
}
class TextSummaryTestCase extends DrupalWebTestCase {
diff --git a/modules/field/theme/CVS/Entries b/modules/field/theme/CVS/Entries
index bf69097..e6325a2 100644
--- a/modules/field/theme/CVS/Entries
+++ b/modules/field/theme/CVS/Entries
@@ -1,4 +1,4 @@
/field-rtl.css/1.1/Thu Sep 3 08:50:22 2009//
/field.css/1.6/Thu Sep 3 08:50:22 2009//
-/field.tpl.php/1.5/Thu Sep 3 08:50:22 2009//
+/field.tpl.php/1.6/Fri Oct 2 19:50:13 2009//
D
diff --git a/modules/field/theme/field.tpl.php b/modules/field/theme/field.tpl.php
index b8df04d..c3a59e7 100644
--- a/modules/field/theme/field.tpl.php
+++ b/modules/field/theme/field.tpl.php
@@ -1,5 +1,5 @@
-
+
>
-
:
+
>:
$item) : ?>
-
+
>
diff --git a/modules/field_ui/CVS/Entries b/modules/field_ui/CVS/Entries
index fd71b3b..7207656 100644
--- a/modules/field_ui/CVS/Entries
+++ b/modules/field_ui/CVS/Entries
@@ -1,10 +1,10 @@
/field_ui-display-overview-form.tpl.php/1.1/Thu Sep 3 08:50:22 2009//
/field_ui-field-overview-form.tpl.php/1.1/Thu Sep 3 08:50:22 2009//
/field_ui-rtl.css/1.1/Thu Sep 3 08:50:22 2009//
-/field_ui.api.php/1.1/Thu Sep 3 08:50:22 2009//
/field_ui.css/1.1/Thu Sep 3 08:50:23 2009//
/field_ui.info/1.1/Thu Sep 3 08:50:23 2009//
/field_ui.js/1.1/Thu Sep 3 08:50:23 2009//
-/field_ui.module/1.7/Thu Sep 3 08:50:23 2009//
-/field_ui.admin.inc/1.11/Sat Sep 5 13:40:16 2009//
+/field_ui.admin.inc/1.21/Fri Oct 2 19:50:14 2009//
+/field_ui.api.php/1.3/Fri Oct 2 19:50:14 2009//
+/field_ui.module/1.8/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc
index 66b4932..802d6f3 100644
--- a/modules/field_ui/field_ui.admin.inc
+++ b/modules/field_ui/field_ui.admin.inc
@@ -1,5 +1,5 @@
TRUE,
'#bundle' => $bundle,
'#fields' => array_keys($instances),
@@ -480,6 +480,7 @@ function field_ui_field_overview_form_submit($form, &$form_state) {
$field = array(
'field_name' => $values['field_name'],
'type' => $values['type'],
+ 'translatable' => TRUE,
);
$instance = array(
'field_name' => $field['field_name'],
@@ -538,12 +539,11 @@ function field_ui_field_overview_form_submit($form, &$form_state) {
}
if ($destinations) {
- $destinations[] = urldecode(substr(drupal_get_destination(), 12));
- unset($_REQUEST['destination']);
+ $destination = drupal_get_destination();
+ $destinations[] = $destination['destination'];
+ unset($_GET['destination']);
$form_state['redirect'] = field_ui_get_destinations($destinations);
}
-
- field_cache_clear();
}
/**
@@ -552,8 +552,8 @@ function field_ui_field_overview_form_submit($form, &$form_state) {
* This form includes form widgets to select which fields appear in teaser and
* full build modes, and how the field labels should be rendered.
*/
-function field_ui_display_overview_form(&$form_state, $obj_type, $bundle, $build_modes_selector = 'basic') {
- $bundle = field_attach_extract_bundle($obj_type, $bundle);
+function field_ui_display_overview_form($form, &$form_state, $obj_type, $bundle, $build_modes_selector = 'basic') {
+ $bundle = field_extract_bundle($obj_type, $bundle);
field_ui_inactive_message($bundle);
$admin_path = _field_ui_bundle_admin_path($bundle);
@@ -564,7 +564,7 @@ function field_ui_display_overview_form(&$form_state, $obj_type, $bundle, $build
$field_types = field_info_field_types();
$build_modes = field_ui_build_modes_tabs($entity, $build_modes_selector);
- $form = array(
+ $form += array(
'#tree' => TRUE,
'#bundle' => $bundle,
'#fields' => array_keys($instances),
@@ -781,19 +781,11 @@ function field_ui_existing_field_options($bundle) {
return $options;
}
-/**
- * Helper function to determine if a field has data in the database.
- */
-function field_ui_field_has_data($field) {
- $results = field_attach_query($field['id'], array(), 1);
- return !empty($results);
-}
-
/**
* Menu callback; presents the field settings edit page.
*/
-function field_ui_field_settings_form(&$form_state, $obj_type, $bundle, $instance) {
- $bundle = field_attach_extract_bundle($obj_type, $bundle);
+function field_ui_field_settings_form($form, &$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_extract_bundle($obj_type, $bundle);
$field = field_info_field($instance['field_name']);
// When a field is first created, we have to get data from the db.
@@ -822,7 +814,7 @@ function field_ui_field_settings_form(&$form_state, $obj_type, $bundle, $instanc
// See if data already exists for this field.
// If so, prevent changes to the field settings.
- $has_data = field_ui_field_has_data($field);
+ $has_data = field_has_data($field);
if ($has_data) {
$form['field']['#description'] = '
' . t('There is data for this field in the database. The field settings can no longer be changed.' . '
') . $form['field']['#description'];
}
@@ -833,27 +825,28 @@ function field_ui_field_settings_form(&$form_state, $obj_type, $bundle, $instanc
$form['field']['module'] = array('#type' => 'value', '#value' => $field['module']);
$form['field']['active'] = array('#type' => 'value', '#value' => $field['active']);
- // Add settings provided by the field module.
+ // Set translatability.
+ $form['field']['translatable'] = array(
+ '#type' => 'radios',
+ '#title' => t('Multilingual settings'),
+ '#options' => array(TRUE => t('Translatable field'), FALSE => t('Language neutral field')),
+ '#default_value' => $field['translatable'],
+ '#description' => t("Translatable fields can have a different value for each available language. An example of a translatable field is an article's body. Language neutral fields will retain the same value across all translations. An example of a language neutral field is a user profile's first name."),
+ );
+
+ // Add settings provided by the field module. The field module is
+ // responsible for not returning settings that cannot be changed if
+ // the field already has data.
$form['field']['settings'] = array();
- $additions = module_invoke($field_type['module'], 'field_settings_form', $field, $instance);
+ $additions = module_invoke($field_type['module'], 'field_settings_form', $field, $instance, $has_data);
if (is_array($additions)) {
$form['field']['settings'] = $additions;
- // @todo Filter this so only the settings that cannot be changed are shown
- // in this form. For now, treating all settings as changeable, which means
- // they show up here and in edit form.
}
if (empty($form['field']['settings'])) {
$form['field']['settings'] = array(
'#markup' => t('%field has no field settings.', array('%field' => $instance['label'])),
);
}
- else {
- foreach ($form['field']['settings'] as $key => $setting) {
- if (substr($key, 0, 1) != '#') {
- $form['field']['settings'][$key]['#disabled'] = $has_data;
- }
- }
- }
$form['#bundle'] = $bundle;
$form['submit'] = array('#type' => 'submit', '#value' => t('Save field settings'));
@@ -870,27 +863,29 @@ function field_ui_field_settings_form_submit($form, &$form_state) {
// Merge incoming form values into the existing field.
$field = field_info_field($field_values['field_name']);
- // Do not allow changes to fields with data.
- if (field_ui_field_has_data($field)) {
- return;
- }
-
$bundle = $form['#bundle'];
$instance = field_info_instance($field['field_name'], $bundle);
// Update the field.
$field = array_merge($field, $field_values);
- field_ui_update_field($field);
- drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label'])));
- $form_state['redirect'] = field_ui_next_destination($bundle);
+ try {
+ field_update_field($field);
+ drupal_set_message(t('Updated field %label field settings.', array('%label' => $instance['label'])));
+ $form_state['redirect'] = field_ui_next_destination($bundle);
+ }
+ catch (FieldException $e) {
+ drupal_set_message(t('Attempt to update field %label failed: %message.', array('%label' => $instance['label'], '%message' => $e->getMessage())), 'error');
+ // TODO: Where do we go from here?
+ $form_state['redirect'] = field_ui_next_destination($bundle);
+ }
}
/**
* Menu callback; select a widget for the field.
*/
-function field_ui_widget_type_form(&$form_state, $obj_type, $bundle, $instance) {
- $bundle = field_attach_extract_bundle($obj_type, $bundle);
+function field_ui_widget_type_form($form, &$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_extract_bundle($obj_type, $bundle);
$field = field_read_field($instance['field_name']);
$field_type = field_info_field_types($field['type']);
@@ -948,8 +943,8 @@ function field_ui_widget_type_form_submit($form, &$form_state) {
/**
* Menu callback; present a form for removing a field from a content type.
*/
-function field_ui_field_delete_form(&$form_state, $obj_type, $bundle, $instance) {
- $bundle = field_attach_extract_bundle($obj_type, $bundle);
+function field_ui_field_delete_form($form, &$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_extract_bundle($obj_type, $bundle);
$field = field_info_field($instance['field_name']);
$admin_path = _field_ui_bundle_admin_path($bundle);
@@ -984,7 +979,7 @@ function field_ui_field_delete_form_submit($form, &$form_state) {
$bundle_label = $bundles[$bundle]['label'];
if (!empty($bundle) && $field && !$field['locked'] && $form_values['confirm']) {
- field_delete_instance($field['field_name'], $bundle);
+ field_delete_instance($instance);
// Delete the field if that was the last instance.
if (count($field['bundles'] == 1)) {
field_delete_field($field['field_name']);
@@ -1002,8 +997,8 @@ function field_ui_field_delete_form_submit($form, &$form_state) {
/**
* Menu callback; presents the field instance edit page.
*/
-function field_ui_field_edit_form(&$form_state, $obj_type, $bundle, $instance) {
- $bundle = field_attach_extract_bundle($obj_type, $bundle);
+function field_ui_field_edit_form($form, &$form_state, $obj_type, $bundle, $instance) {
+ $bundle = field_extract_bundle($obj_type, $bundle);
$field = field_info_field($instance['field_name']);
$form['#field'] = $field;
@@ -1120,7 +1115,13 @@ function field_ui_field_edit_form(&$form_state, $obj_type, $bundle, $instance) {
$description .= $info['description'] . '';
$form['#prefix'] = '
' . $description . '
';
- $description = '
' . t('These settings apply to the %field field everywhere it is used.', array('%field' => $instance['label'])) . '
' . t('These settings apply to the %field field everywhere it is used. Because the field already has data, some settings can no longer be changed.', array('%field' => $instance['label'])) . '
';
+ }
+ else {
+ $description = '
' . t('These settings apply to the %field field everywhere it is used.', array('%field' => $instance['label'])) . '
';
+ }
// Create a form structure for the field values.
$form['field'] = array(
@@ -1143,10 +1144,11 @@ function field_ui_field_edit_form(&$form_state, $obj_type, $bundle, $instance) {
'#description' => $description,
);
- // Add additional field settings from the field module.
- $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance);
+ // Add additional field type settings. The field type module is
+ // responsible for not returning settings that cannot be changed if
+ // the field already has data.
+ $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance, $has_data);
if (is_array($additions)) {
- // @todo Filter additional settings by whether they can be changed.
$form['field']['settings'] = $additions;
}
@@ -1281,7 +1283,7 @@ function field_ui_field_edit_form_submit($form, &$form_state) {
// Update any field settings that have changed.
$field = field_info_field($instance_values['field_name']);
$field = array_merge($field, $field_values);
- field_ui_update_field($field);
+ field_update_field($field);
// Move the default value from the sample widget to the default value field.
if (isset($instance_values['default_value_widget'])) {
diff --git a/modules/field_ui/field_ui.api.php b/modules/field_ui/field_ui.api.php
index 48a62a2..9e1865a 100644
--- a/modules/field_ui/field_ui.api.php
+++ b/modules/field_ui/field_ui.api.php
@@ -1,5 +1,5 @@
'textfield',
@@ -85,7 +98,6 @@ function hook_field_instance_settings_form($field, $instance) {
function hook_field_widget_settings_form($field, $instance) {
$widget = $instance['widget'];
$settings = $widget['settings'];
- $form = array();
if ($widget['type'] == 'text_textfield') {
$form['size'] = array(
diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module
index 7fcfaa4..b9596d8 100644
--- a/modules/field_ui/field_ui.module
+++ b/modules/field_ui/field_ui.module
@@ -1,5 +1,5 @@
$scheme_options,
'#default_value' => $settings['uri_scheme'],
'#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'),
+ '#disabled' => $has_data,
);
$form['default_file'] = array(
@@ -292,7 +293,7 @@ function file_field_update($obj_type, $object, $field, $instance, $langcode, &$i
}
// Delete items from original object.
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
$load_function = $obj_type . '_load';
$original = $load_function($id);
@@ -312,7 +313,7 @@ function file_field_update($obj_type, $object, $field, $instance, $langcode, &$i
* Implement hook_field_delete().
*/
function file_field_delete($obj_type, $object, $field, $instance, $langcode, &$items) {
- list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object);
+ list($id, $vid, $bundle) = field_extract_ids($obj_type, $object);
foreach ($items as $delta => $item) {
// For hook_file_references(), remember that this is being deleted.
$item['file_field_name'] = $field['field_name'];
diff --git a/modules/file/file.module b/modules/file/file.module
index 722e065..b85178b 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -1,5 +1,5 @@
TRUE,
'#process' => array('file_managed_file_process'),
'#value_callback' => 'file_managed_file_value',
@@ -55,8 +53,7 @@ function file_elements() {
'js' => array($file_path . '/file.js'),
),
);
-
- return $elements;
+ return $types;
}
/**
@@ -85,15 +82,6 @@ function file_theme() {
'file_upload_help' => array(
'arguments' => array('upload_validators' => NULL),
),
- 'field_formatter_file_default' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_file_table' => array(
- 'arguments' => array('element' => NULL),
- ),
- 'field_formatter_file_url_plain' => array(
- 'arguments' => array('element' => NULL),
- ),
);
}
diff --git a/modules/file/tests/CVS/Entries b/modules/file/tests/CVS/Entries
index 111f91f..b27419f 100644
--- a/modules/file/tests/CVS/Entries
+++ b/modules/file/tests/CVS/Entries
@@ -1,4 +1,4 @@
-/file.test/1.1/Thu Sep 3 08:50:25 2009//
/file_module_test.info/1.1/Thu Sep 3 08:50:25 2009//
-/file_module_test.module/1.1/Thu Sep 3 08:50:25 2009//
+/file.test/1.2/Fri Oct 2 19:50:14 2009//
+/file_module_test.module/1.2/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test
index 6b87fac..969a983 100644
--- a/modules/file/tests/file.test
+++ b/modules/file/tests/file.test
@@ -1,5 +1,5 @@
t('File field revision test'),
- 'description' => t('Test creating and deleting revisions with files attached.'),
- 'group' => t('File'),
+ 'name' => 'File field revision test',
+ 'description' => 'Test creating and deleting revisions with files attached.',
+ 'group' => 'File',
);
}
@@ -269,11 +269,11 @@ class FileFieldRevisionTestCase extends FileFieldTestCase {
* Test class to check that formatters are working properly.
*/
class FileFieldDisplayTestCase extends FileFieldTestCase {
- public function getInfo() {
+ public static function getInfo() {
return array(
- 'name' => t('File field display tests'),
- 'description' => t('Test the display of file fields in node and views.'),
- 'group' => t('File'),
+ 'name' => 'File field display tests',
+ 'description' => 'Test the display of file fields in node and views.',
+ 'group' => 'File',
);
}
@@ -323,11 +323,11 @@ class FileFieldValidateTestCase extends FileFieldTestCase {
protected $field;
protected $node_type;
- public function getInfo() {
+ public static function getInfo() {
return array(
- 'name' => t('File field validation tests'),
- 'description' => t('Tests validation functions such as file type, max file size, max size per node, and required.'),
- 'group' => t('File'),
+ 'name' => 'File field validation tests',
+ 'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.',
+ 'group' => 'File',
);
}
@@ -478,11 +478,11 @@ class FileFieldValidateTestCase extends FileFieldTestCase {
* Test class to check that files are uploaded to proper locations.
*/
class FileFieldPathTestCase extends FileFieldTestCase {
- function getInfo() {
+ public static function getInfo() {
return array(
- 'name' => t('File field file path tests'),
- 'description' => t('Test that files are uploaded to the proper location with token support.'),
- 'group' => t('File'),
+ 'name' => 'File field file path tests',
+ 'description' => 'Test that files are uploaded to the proper location with token support.',
+ 'group' => 'File',
);
}
diff --git a/modules/file/tests/file_module_test.module b/modules/file/tests/file_module_test.module
index 4e2f541..535dd57 100644
--- a/modules/file/tests/file_module_test.module
+++ b/modules/file/tests/file_module_test.module
@@ -1,5 +1,5 @@
TRUE,
- );
+function file_module_test_form($form, $form_state) {
+ $form['#tree'] = TRUE;
$form['file'] = array(
'#type' => 'managed_file',
diff --git a/modules/filter/CVS/Entries b/modules/filter/CVS/Entries
index 9e1e422..443ab35 100644
--- a/modules/filter/CVS/Entries
+++ b/modules/filter/CVS/Entries
@@ -1,9 +1,9 @@
-/filter.api.php/1.13/Thu Sep 3 08:50:25 2009//
/filter.css/1.1/Thu Sep 3 08:50:25 2009//
/filter.info/1.12/Thu Sep 3 08:50:25 2009//
-/filter.install/1.19/Thu Sep 3 08:50:25 2009//
-/filter.module/1.287/Thu Sep 3 08:50:25 2009//
/filter.pages.inc/1.7/Thu Sep 3 08:50:25 2009//
-/filter.test/1.39/Thu Sep 3 08:50:25 2009//
-/filter.admin.inc/1.43/Sat Sep 5 13:40:16 2009//
+/filter.admin.inc/1.47/Fri Oct 2 19:50:14 2009//
+/filter.api.php/1.15/Fri Oct 2 19:50:14 2009//
+/filter.install/1.21/Fri Oct 2 19:50:14 2009//
+/filter.module/1.291/Fri Oct 2 19:50:14 2009//
+/filter.test/1.43/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/filter/filter.admin.inc b/modules/filter/filter.admin.inc
index 8e633bb..36b524d 100644
--- a/modules/filter/filter.admin.inc
+++ b/modules/filter/filter.admin.inc
@@ -1,5 +1,5 @@
TRUE);
+ $form['#tree'] = TRUE;
foreach ($formats as $id => $format) {
- $roles = array();
- foreach (user_roles() as $rid => $name) {
- // Prepare a roles array with roles that may access the filter.
- if (strpos($format->roles, ",$rid,") !== FALSE) {
- $roles[] = $name;
- }
+ // Check whether this is the fallback text format. This format is available
+ // to all roles and cannot be deleted via the admin interface.
+ $form['formats'][$id]['#is_fallback'] = ($id == $fallback_format);
+ if ($form['formats'][$id]['#is_fallback']) {
+ $form['formats'][$id]['name'] = array('#markup' => theme('placeholder', $format->name));
+ $roles_markup = theme('placeholder', t('All roles may use this format'));
+ }
+ else {
+ $form['formats'][$id]['name'] = array('#markup' => check_plain($format->name));
+ $roles = filter_get_roles_by_format($format);
+ $roles_markup = $roles ? implode(', ', $roles) : t('No roles may use this format');
}
- $default = ($id == variable_get('filter_default_format', 1));
- $options[$id] = '';
- $form[$id]['name'] = array('#markup' => $format->name);
- $form[$id]['roles'] = array('#markup' => $default ? t('All roles may use the default format') : ($roles ? implode(', ', $roles) : t('No roles may use this format')));
- $form[$id]['configure'] = array('#markup' => l(t('configure'), 'admin/config/content/formats/' . $id));
- $form[$id]['delete'] = array('#markup' => $default ? '' : l(t('delete'), 'admin/config/content/formats/delete/' . $id));
- $form[$id]['weight'] = array('#type' => 'weight', '#default_value' => $format->weight);
+ $form['formats'][$id]['roles'] = array('#markup' => $roles_markup);
+ $form['formats'][$id]['configure'] = array('#markup' => l(t('configure'), 'admin/config/content/formats/' . $id));
+ $form['formats'][$id]['delete'] = array('#markup' => $form['formats'][$id]['#is_fallback'] ? '' : l(t('delete'), 'admin/config/content/formats/' . $id . '/delete'));
+ $form['formats'][$id]['weight'] = array('#type' => 'weight', '#default_value' => $format->weight);
}
- $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => variable_get('filter_default_format', 1));
$form['submit'] = array('#type' => 'submit', '#value' => t('Save changes'));
return $form;
}
function filter_admin_overview_submit($form, &$form_state) {
- // Process form submission to set the default format.
- if (is_numeric($form_state['values']['default'])) {
- drupal_set_message(t('Default format updated.'));
- variable_set('filter_default_format', $form_state['values']['default']);
- }
- foreach ($form_state['values'] as $id => $data) {
+ foreach ($form_state['values']['formats'] as $id => $data) {
if (is_array($data) && isset($data['weight'])) {
// Only update if this is a form element with weight.
db_update('filter_format')
@@ -56,35 +50,31 @@ function filter_admin_overview_submit($form, &$form_state) {
->execute();
}
}
+ filter_formats_reset();
drupal_set_message(t('The text format ordering has been saved.'));
}
/**
- * Theme the admin overview form.
+ * Theme the text format administration overview form.
*
* @ingroup themeable
*/
function theme_filter_admin_overview($form) {
$rows = array();
- foreach (element_children($form) as $id) {
- $element = $form[$id];
- if (isset($element['roles']) && is_array($element['roles'])) {
- $element['weight']['#attributes']['class'] = array('text-format-order-weight');
- $rows[] = array(
- 'data' => array(
- check_plain($element['name']['#markup']),
- drupal_render($element['roles']),
- drupal_render($form['default'][$id]),
- drupal_render($element['weight']),
- drupal_render($element['configure']),
- drupal_render($element['delete']),
- ),
- 'class' => array('draggable'),
- );
- unset($form[$id]);
- }
+ foreach (element_children($form['formats']) as $id) {
+ $form['formats'][$id]['weight']['#attributes']['class'] = array('text-format-order-weight');
+ $rows[] = array(
+ 'data' => array(
+ drupal_render($form['formats'][$id]['name']),
+ drupal_render($form['formats'][$id]['roles']),
+ drupal_render($form['formats'][$id]['weight']),
+ drupal_render($form['formats'][$id]['configure']),
+ drupal_render($form['formats'][$id]['delete']),
+ ),
+ 'class' => array('draggable'),
+ );
}
- $header = array(t('Name'), t('Roles'), t('Default'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2));
+ $header = array(t('Name'), t('Roles'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2));
$output = theme('table', $header, $rows, array('id' => 'text-format-order'));
$output .= drupal_render_children($form);
@@ -99,7 +89,7 @@ function theme_filter_admin_overview($form) {
function filter_admin_format_page($format = NULL) {
if (!isset($format->name)) {
drupal_set_title(t('Add text format'), PASS_THROUGH);
- $format = (object)array('name' => '', 'roles' => '', 'format' => FILTER_FORMAT_DEFAULT);
+ $format = (object)array('name' => '', 'format' => 0);
}
return drupal_get_form('filter_admin_format_form', $format);
}
@@ -111,14 +101,14 @@ function filter_admin_format_page($format = NULL) {
* @see filter_admin_format_form_validate()
* @see filter_admin_format_form_submit()
*/
-function filter_admin_format_form(&$form_state, $format) {
- $default = ($format->format == variable_get('filter_default_format', 1));
- if ($default) {
- $help = t('All roles for the default format must be enabled and cannot be changed.');
- $form['default_format'] = array('#type' => 'hidden', '#value' => 1);
+function filter_admin_format_form($form, &$form_state, $format) {
+ $is_fallback = ($format->format == filter_fallback_format());
+ if ($is_fallback) {
+ $help = t('All roles for this text format must be enabled and cannot be changed.');
}
- $form['name'] = array('#type' => 'textfield',
+ $form['name'] = array(
+ '#type' => 'textfield',
'#title' => t('Name'),
'#default_value' => $format->name,
'#description' => t('Specify a unique name for this text format.'),
@@ -128,23 +118,22 @@ function filter_admin_format_form(&$form_state, $format) {
// Add a row of checkboxes for form group.
$form['roles'] = array('#type' => 'fieldset',
'#title' => t('Roles'),
- '#description' => $default ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'),
+ '#description' => $is_fallback ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'),
'#tree' => TRUE,
);
-
+ $checked = filter_get_roles_by_format($format);
foreach (user_roles() as $rid => $name) {
- $checked = strpos($format->roles, ",$rid,") !== FALSE;
$form['roles'][$rid] = array('#type' => 'checkbox',
'#title' => $name,
- '#default_value' => ($default || $checked),
+ '#default_value' => ($is_fallback || isset($checked[$rid])),
);
- if ($default) {
+ if ($is_fallback) {
$form['roles'][$rid]['#disabled'] = TRUE;
}
}
// Table with filters
$filter_info = filter_get_filters();
- $filters = filter_list_format($format->format);
+ $filters = filter_list_format($format->format, TRUE);
$form['filters'] = array('#type' => 'fieldset',
'#title' => t('Filters'),
@@ -152,10 +141,10 @@ function filter_admin_format_form(&$form_state, $format) {
'#tree' => TRUE,
);
foreach ($filter_info as $name => $filter) {
- $form['filters'][$name] = array(
+ $form['filters'][$name]['status'] = array(
'#type' => 'checkbox',
'#title' => $filter['title'],
- '#default_value' => isset($filters[$name]),
+ '#default_value' => !empty($filters[$name]->status),
'#description' => $filter['description'],
);
}
@@ -201,13 +190,19 @@ function filter_admin_format_form_submit($form, &$form_state) {
$format->format = isset($form_state['values']['format']) ? $form_state['values']['format'] : NULL;
$status = filter_format_save($format);
+ if ($permission = filter_permission_name($format)) {
+ foreach ($format->roles as $rid => $enabled) {
+ user_role_change_permissions($rid, array($permission => $enabled));
+ }
+ }
+
switch ($status) {
case SAVED_NEW:
drupal_set_message(t('Added text format %format.', array('%format' => $format->name)));
break;
case SAVED_UPDATED:
- drupal_set_message(t('The text format settings have been updated.'));
+ drupal_set_message(t('The text format %format has been updated.', array('%format' => $format->name)));
break;
}
}
@@ -218,27 +213,16 @@ function filter_admin_format_form_submit($form, &$form_state) {
* @ingroup forms
* @see filter_admin_delete_submit()
*/
-function filter_admin_delete(&$form_state, $format) {
- if ($format) {
- if ($format->format != variable_get('filter_default_format', 1)) {
- $form['#format'] = $format;
-
- return confirm_form($form,
- t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)),
- 'admin/config/content/formats',
- t('If you have any content left in this text format, it will be switched to the default text format. This action cannot be undone.'),
- t('Delete'),
- t('Cancel')
- );
- }
- else {
- drupal_set_message(t('The default format cannot be deleted.'));
- drupal_goto('admin/config/content/formats');
- }
- }
- else {
- drupal_not_found();
- }
+function filter_admin_delete($form, &$form_state, $format) {
+ $form['#format'] = $format;
+
+ return confirm_form($form,
+ t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)),
+ 'admin/config/content/formats',
+ t('If you have any content left in this text format, it will be switched to the %fallback text format. This action cannot be undone.', array('%fallback' => filter_fallback_format_title())),
+ t('Delete'),
+ t('Cancel')
+ );
}
/**
@@ -268,7 +252,7 @@ function filter_admin_configure_page($format) {
*
* @ingroup forms
*/
-function filter_admin_configure(&$form_state, $format) {
+function filter_admin_configure($form, &$form_state, $format) {
$filters = filter_list_format($format->format);
$filter_info = filter_get_filters();
@@ -278,7 +262,7 @@ function filter_admin_configure(&$form_state, $format) {
// Pass along stored filter settings and default settings, but also the
// format object and all filters to allow for complex implementations.
$defaults = (isset($filter_info[$name]['default settings']) ? $filter_info[$name]['default settings'] : array());
- $settings_form = $filter_info[$name]['settings callback']($form_state, $filters[$name], $defaults, $format, $filters);
+ $settings_form = $filter_info[$name]['settings callback']($form, $form_state, $filters[$name], $defaults, $format, $filters);
if (isset($settings_form) && is_array($settings_form)) {
$form['settings'][$name] = array(
'#type' => 'fieldset',
@@ -338,7 +322,7 @@ function filter_admin_order_page($format) {
* @see theme_filter_admin_order()
* @see filter_admin_order_submit()
*/
-function filter_admin_order(&$form_state, $format = NULL) {
+function filter_admin_order($form, &$form_state, $format = NULL) {
// Get list (with forced refresh).
$filters = filter_list_format($format->format);
diff --git a/modules/filter/filter.api.php b/modules/filter/filter.api.php
index 4c96f8b..8552f67 100644
--- a/modules/filter/filter.api.php
+++ b/modules/filter/filter.api.php
@@ -1,5 +1,5 @@
'textfield',
* '#title' => t('Maximum link text length'),
@@ -236,20 +236,21 @@ function hook_filter_format_update($format) {
*
* It is recommended for modules to implement this hook, when they store
* references to text formats to replace existing references to the deleted
- * text format with the default format.
+ * text format with the fallback format.
*
* @param $format
* The format object of the format being deleted.
- * @param $default
- * The format object of the site's default format.
+ * @param $fallback
+ * The format object of the site's fallback format, which is always available
+ * to all users.
*
* @see hook_filter_format_update().
* @see hook_filter_format_delete().
*/
-function hook_filter_format_delete($format, $default) {
- // Replace the deleted format with the default format.
+function hook_filter_format_delete($format, $fallback) {
+ // Replace the deleted format with the fallback format.
db_update('my_module_table')
- ->fields(array('format' => $default->format))
+ ->fields(array('format' => $fallback->format))
->condition('format', $format->format)
->execute();
}
diff --git a/modules/filter/filter.install b/modules/filter/filter.install
index 4a78830..82ec17a 100644
--- a/modules/filter/filter.install
+++ b/modules/filter/filter.install
@@ -1,5 +1,5 @@
'',
'description' => 'Name of the text format (Filtered HTML).',
),
- 'roles' => array(
- 'type' => 'varchar',
- 'length' => 255,
- 'not null' => TRUE,
- 'default' => '',
- 'description' => 'A comma-separated string of roles; references {role}.rid.', // This is bad since you can't use joins, nor index.
- ),
'cache' => array(
'type' => 'int',
'not null' => TRUE,
@@ -111,46 +104,53 @@ function filter_schema() {
return $schema;
}
+/**
+ * @defgroup updates-6.x-to-7.x Filter updates from 6.x to 7.x
+ * @{
+ */
+
/**
* Add a weight column to the filter formats table.
*/
function filter_update_7000() {
- $ret = array();
- db_add_field($ret, 'filter_formats', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'));
- return $ret;
+ db_add_field('filter_formats', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny'));
}
/**
* Break out "escape HTML filter" option to its own filter.
*/
function filter_update_7001() {
- $ret = array();
$result = db_query("SELECT format FROM {filter_formats}");
+ $insert = db_insert('filters')->fields(array('format', 'module', 'delta', 'weight'));
+
foreach ($result as $format) {
// Deprecated constants FILTER_HTML_STRIP = 1 and FILTER_HTML_ESCAPE = 2.
if (variable_get('filter_html_' . $format->format, 1) == 2) {
- $ret[] = update_sql("INSERT INTO {filters} (format, module, delta, weight) VALUES (" . $format->format . ", 'filter', 4, 0)");
+ $insert->values(array(
+ 'format' => $format->format,
+ 'filter' => 'filter',
+ 'delta' => 4,
+ 'weight' => 0,
+ ));
}
variable_del('filter_html_' . $format->format);
}
- return $ret;
+
+ $insert->execute();
}
/**
* Rename {filters} table to {filter} and {filter_formats} table to {filter_format}.
*/
function filter_update_7002() {
- $ret = array();
- db_rename_table($ret, 'filters', 'filter');
- db_rename_table($ret, 'filter_formats', 'filter_format');
- return $ret;
+ db_rename_table('filters', 'filter');
+ db_rename_table('filter_formats', 'filter_format');
}
/**
* Remove hardcoded numeric deltas from all filters in core.
*/
function filter_update_7003() {
- $ret = array();
// Get an array of the renamed filter deltas, organized by module.
$renamed_deltas = array(
'filter' => array(
@@ -166,9 +166,9 @@ function filter_update_7003() {
);
// Rename field 'delta' to 'name'.
- db_drop_unique_key($ret, 'filter', 'fmd');
- db_drop_index($ret, 'filter', 'list');
- db_change_field($ret, 'filter', 'delta', 'name',
+ db_drop_unique_key('filter', 'fmd');
+ db_drop_index('filter', 'list');
+ db_change_field('filter', 'delta', 'name',
array(
'type' => 'varchar',
'length' => 32,
@@ -189,10 +189,13 @@ function filter_update_7003() {
// Loop through each filter and make changes to the core filter table.
foreach ($renamed_deltas as $module => $deltas) {
foreach ($deltas as $old_delta => $new_delta) {
- $ret[] = update_sql("UPDATE {filter} SET name = '" . $new_delta . "' WHERE module = '" . $module . "' AND name = '" . $old_delta . "'");
+ db_update('filter')
+ ->fields(array('name', $new_delta))
+ ->condition('module', $module)
+ ->condition('name', $old_delta)
+ ->execute();
}
}
- return $ret;
}
/**
@@ -204,10 +207,9 @@ function filter_update_7003() {
* - Add {filter}.settings.
*/
function filter_update_7004() {
- $ret = array();
- db_drop_field($ret, 'filter', 'fid');
- db_add_primary_key($ret, 'filter', array('format', 'name'));
- db_add_field($ret, 'filter', 'status',
+ db_drop_field('filter', 'fid');
+ db_add_primary_key('filter', array('format', 'name'));
+ db_add_field('filter', 'status',
array(
'type' => 'int',
'not null' => TRUE,
@@ -215,7 +217,7 @@ function filter_update_7004() {
'description' => 'Filter enabled status. (1 = enabled, 0 = disabled)',
)
);
- db_add_field($ret, 'filter', 'settings',
+ db_add_field('filter', 'settings',
array(
'type' => 'text',
'not null' => FALSE,
@@ -226,7 +228,9 @@ function filter_update_7004() {
);
// Enable all existing filters ({filter} contained only enabled previously).
- $ret[] = update_sql("UPDATE {filter} SET status = 1");
+ db_update('filter')
+ ->fields('status', '1')
+ ->execute();
// Move filter settings from system variables into {filter}.settings.
$filters = db_query("SELECT * FROM {filter} WHERE module = :name", array(':name' => 'filter'));
@@ -253,10 +257,95 @@ function filter_update_7004() {
}
}
if (!empty($settings)) {
- $ret[] = update_sql("UPDATE {filter} SET settings = '" . serialize($settings) . "' WHERE format = {$filter->format} AND name = '{$filter->name}'");
+ db_upddate('filter')
+ ->fields(array('settings' => serialize($settings)))
+ ->condition('format', $filter->format)
+ ->condition('name', $filter->name)
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Integrate text formats with the user permissions system.
+ *
+ * This function converts text format role assignments to use the new text
+ * format permissions introduced in Drupal 7, creates a fallback (plain text)
+ * format that is available to all users, and explicitly sets the text format
+ * in cases that used to rely on a single site-wide default.
+ */
+function filter_update_7005() {
+
+ // Move role data from the filter system to the user permission system.
+ $all_roles = array_keys(user_roles());
+ $default_format = variable_get('filter_default_format', 1);
+ $result = db_query("SELECT * FROM {filter_format}");
+ foreach ($result as $format) {
+ // We need to assign the default format to all roles (regardless of what
+ // was stored in the database) to preserve the behavior of the site at the
+ // moment of the upgrade.
+ $format_roles = ($format->format == $default_format ? $all_roles : explode(',', $format->roles));
+ foreach ($format_roles as $format_role) {
+ if (in_array($format_role, $all_roles)) {
+ user_role_grant_permissions($format_role, array(filter_permission_name($format)));
+ }
}
}
- return $ret;
+ // Drop the roles field from the {filter_format} table.
+ db_drop_field('filter_format', 'roles');
+
+ // Add a fallback text format which outputs plain text and appears last on
+ // the list for all users. Generate a unique name for it, starting with
+ // "Plain text".
+ $start_name = 'Plain text';
+ $format_name = $start_name;
+ while ($format = db_query('SELECT format FROM {filter_format} WHERE name = :name', array(':name' => $format_name))->fetchField()) {
+ $id = empty($id) ? 1 : $id + 1;
+ $format_name = $start_name . ' ' . $id;
+ }
+ $fallback_format = new stdClass();
+ $fallback_format->name = $format_name;
+ $fallback_format->cache = 1;
+ $fallback_format->weight = 1;
+ // This format should output plain text, so we escape all HTML and apply the
+ // line break filter only.
+ $fallback_format->filters = array(
+ 'filter_html_escape' => array('status' => 1),
+ 'filter_autop' => array('status' => 1),
+ );
+ filter_format_save($fallback_format);
+ variable_set('filter_fallback_format', $fallback_format->format);
+ drupal_set_message('A new Plain text format has been created which will be available to all users. You can configure this text format on the text format configuration page.');
+
+ // Move the former site-wide default text format to the top of the list, so
+ // that it continues to be the default text format for all users.
+ db_update('filter_format')
+ ->fields(array('weight' => -1))
+ ->condition('format', $default_format)
+ ->execute();
+
+ // It was previously possible for a value of "0" to be stored in database
+ // tables to indicate that a particular piece of text should be filtered
+ // using the default text format. Therefore, we have to convert all such
+ // instances (in Drupal core) to explicitly use the appropriate format.
+ // Note that the update of the node body field is handled separately, in
+ // node_update_7006().
+ foreach (array('block_custom', 'comment') as $table) {
+ if (db_table_exists($table)) {
+ db_update($table)
+ ->fields(array('format' => $default_format))
+ ->condition('format', 0)
+ ->execute();
+ }
+ }
+
+ // We do not delete the 'filter_default_format' variable, since other modules
+ // may need it in their update functions.
+ // @todo This variable can be deleted in Drupal 8.
}
+/**
+ * @} End of "defgroup updates-6.x-to-7.x"
+ * The next series of updates should start at 8000.
+ */
diff --git a/modules/filter/filter.module b/modules/filter/filter.module
index f661652..e11dc18 100644
--- a/modules/filter/filter.module
+++ b/modules/filter/filter.module
@@ -1,19 +1,11 @@
' . t("The filter module allows administrators to configure text formats for use on your site. A text format defines the HTML tags, codes, and other input allowed in both content and comments, and is a key feature in guarding against potentially damaging input from malicious users. Two formats included by default are Filtered HTML (which allows only an administrator-approved subset of HTML tags) and Full HTML (which allows the full set of HTML tags). Additional formats may be created by an administrator.") . '';
$output .= '
' . t('Each text format uses filters to manipulate text, and most formats apply several different filters to text in a specific order. Each filter is designed for a specific purpose, and generally either adds, removes or transforms elements within user-entered text before it is displayed. A filter does not change the actual content of a post, but instead, modifies it temporarily before it is displayed. A filter may remove unapproved HTML tags, for instance, while another automatically adds HTML to make links referenced in text clickable.') . '
';
- $output .= '
' . t('Users with access to more than one text format can use the Text format fieldset to choose between available text formats when creating or editing multi-line content. Administrators determine the text formats available to each user role, select a default text format, and control the order of formats listed in the Text format fieldset.') . '
';
+ $output .= '
' . t('Users with access to more than one text format can use the Text format fieldset to choose between available text formats when creating or editing multi-line content. Administrators determine the text formats available to each user role and control the order of formats listed in the Text format fieldset.') . '
';
$output .= '
' . t('For more information, see the online handbook entry for Filter module.', array('@filter' => 'http://drupal.org/handbook/modules/filter/')) . '
';
return $output;
case 'admin/config/content/formats':
- $output = '
' . t('Use the list below to review the text formats available to each user role, to select a default text format, and to control the order of formats listed in the Text format fieldset. (The Text format fieldset is displayed below textareas when users with access to more than one text format create multi-line content.) The text format selected as Default is available to all users and, unless another format is selected, is applied to all content. All text formats are available to users in roles with the "administer filters" permission.') . '
';
- $output .= '
' . t('Since text formats, if available, are presented in the same order as the list below, it may be helpful to arrange the formats in descending order of your preference for their use. Remember that your changes will not be saved until you click the Save changes button at the bottom of the page.') . '
';
+ $output = '
' . t('Use the list below to review the text formats available to each user role and to control the order of formats listed in the Text format fieldset. (The Text format fieldset is displayed below textareas when users with access to more than one text format create multi-line content.) All text formats are available to users in roles with the "administer filters" permission, and the special %fallback format is available to all users. You can configure access to other text formats on the permissions page.', array('%fallback' => filter_fallback_format_title(), '@url' => url('admin/config/people/permissions', array('fragment' => 'module-filter')))) . '
';
+ $output .= '
' . t('Since text formats, if available, are presented in the same order as the list below, and the default format for each user is the first one on the list for which that user has access, it may be helpful to arrange the formats in descending order of your preference for their use. Remember that your changes will not be saved until you click the Save changes button at the bottom of the page.') . '
';
return $output;
case 'admin/config/content/formats/%':
return '
' . t('Every filter performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this format. If you notice some filters are causing conflicts in the output, you can rearrange them.', array('@rearrange' => url('admin/config/content/formats/' . $arg[4] . '/order'))) . '
';
@@ -70,6 +62,13 @@ function filter_theme() {
* Implement hook_menu().
*/
function filter_menu() {
+ $items['filter/tips'] = array(
+ 'title' => 'Compose tips',
+ 'page callback' => 'filter_tips_long',
+ 'access callback' => TRUE,
+ 'type' => MENU_SUGGESTED_ITEM,
+ 'file' => 'filter.pages.inc',
+ );
$items['admin/config/content/formats'] = array(
'title' => 'Text formats',
'description' => 'Configure how content input by users is filtered, including allowed HTML tags. Also allows enabling of module-provided filters.',
@@ -90,13 +89,6 @@ function filter_menu() {
'weight' => 1,
'file' => 'filter.admin.inc',
);
- $items['filter/tips'] = array(
- 'title' => 'Compose tips',
- 'page callback' => 'filter_tips_long',
- 'access callback' => TRUE,
- 'type' => MENU_SUGGESTED_ITEM,
- 'file' => 'filter.pages.inc',
- );
$items['admin/config/content/formats/%filter_format'] = array(
'type' => MENU_CALLBACK,
'title callback' => 'filter_admin_format_title',
@@ -133,13 +125,28 @@ function filter_menu() {
'title' => 'Delete text format',
'page callback' => 'drupal_get_form',
'page arguments' => array('filter_admin_delete', 4),
- 'access arguments' => array('administer filters'),
+ 'access callback' => '_filter_delete_format_access',
+ 'access arguments' => array(4),
'type' => MENU_CALLBACK,
'file' => 'filter.admin.inc',
);
return $items;
}
+/**
+ * Access callback for deleting text formats.
+ *
+ * @param $format
+ * A text format object.
+ * @return
+ * TRUE if the text format can be deleted by the current user, FALSE
+ * otherwise.
+ */
+function _filter_delete_format_access($format) {
+ // The fallback format can never be deleted.
+ return user_access('administer filters') && ($format->format != filter_fallback_format());
+}
+
/**
* Load a text format object from the database.
*
@@ -150,7 +157,8 @@ function filter_menu() {
* A fully-populated text format object.
*/
function filter_format_load($format) {
- return filter_formats($format);
+ $formats = filter_formats();
+ return isset($formats[$format]) ? $formats[$format] : FALSE;
}
/**
@@ -160,17 +168,6 @@ function filter_format_load($format) {
* A format object.
*/
function filter_format_save($format) {
- // We store the roles as a string for ease of use.
- // We should always set all roles to TRUE when saving the default format.
- // We use leading and trailing comma's to allow easy substring matching.
- $roles = array_filter($format->roles);
- if (!empty($format->format) && $format->format == variable_get('filter_default_format', 1)) {
- $roles = ',' . implode(',', array_keys(user_roles())) . ',';
- }
- else {
- $roles = ',' . implode(',', array_keys($roles)) . ',';
- }
- $format->roles = $roles;
$format->name = trim($format->name);
// Add a new text format.
@@ -185,15 +182,14 @@ function filter_format_save($format) {
// to the bottom.
$current = filter_list_format($format->format);
$filters = $format->filters;
-
- foreach ($filters as $name => $status) {
+ foreach ($filters as $name => $filter) {
$fields = array();
// Add new filters to the bottom.
$fields['weight'] = isset($current[$name]->weight) ? $current[$name]->weight : 10;
- $fields['status'] = $status;
+ $fields['status'] = $filter['status'];
// Only update settings if there are any.
- if (!empty($format->settings[$name])) {
- $fields['settings'] = serialize($format->settings[$name]);
+ if (!empty($filter['settings'])) {
+ $fields['settings'] = serialize($filter['settings']);
}
db_merge('filter')
->key(array(
@@ -209,9 +205,17 @@ function filter_format_save($format) {
}
else {
module_invoke_all('filter_format_update', $format);
+ // Explicitly indicate that the format was updated. We need to do this
+ // since if the filters were updated but the format object itself was not,
+ // the call to drupal_write_record() above would not return an indication
+ // that anything had changed.
+ $return = SAVED_UPDATED;
+
+ // Clear the filter cache whenever a text format is updated.
+ cache_clear_all($format->format . ':', 'cache_filter', TRUE);
}
- cache_clear_all($format->format . ':', 'cache_filter', TRUE);
+ filter_formats_reset();
return $return;
}
@@ -231,9 +235,10 @@ function filter_format_delete($format) {
->execute();
// Allow modules to react on text format deletion.
- $default = filter_format_load(variable_get('filter_default_format', 1));
- module_invoke_all('filter_format_delete', $format, $default);
+ $fallback = filter_format_load(filter_fallback_format());
+ module_invoke_all('filter_format_delete', $format, $fallback);
+ filter_formats_reset();
cache_clear_all($format->format . ':', 'cache_filter', TRUE);
}
@@ -248,12 +253,42 @@ function filter_admin_format_title($format) {
* Implement hook_permission().
*/
function filter_permission() {
- return array(
- 'administer filters' => array(
- 'title' => t('Administer filters'),
- 'description' => t('Manage text formats and filters, and select which roles may use them. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
- ),
+ $perms['administer filters'] = array(
+ 'title' => t('Administer filters'),
+ 'description' => t('Manage text formats and filters, and use any of them, without restriction, when entering or editing content. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))),
);
+
+ // Generate permissions for each text format. Warn the administrator that any
+ // of them are potentially unsafe.
+ foreach (filter_formats() as $format) {
+ $permission = filter_permission_name($format);
+ if (!empty($permission)) {
+ // Only link to the text format configuration page if the user who is
+ // viewing this will have access to that page.
+ $format_name_replacement = user_access('administer filters') ? l($format->name, 'admin/config/content/formats/' . $format->format) : theme('placeholder', $format->name);
+ $perms[$permission] = array(
+ 'title' => t("Use the %text_format text format", array('%text_format' => $format->name)),
+ 'description' => t('Use !text_format in forms when entering or editing content. %warning', array('!text_format' => $format_name_replacement, '%warning' => t('Warning: This permission may have security implications depending on how the text format is configured.'))),
+ );
+ }
+ }
+ return $perms;
+}
+
+/**
+ * Returns the machine-readable permission name for a provided text format.
+ *
+ * @param $format
+ * An object representing a text format.
+ * @return
+ * The machine-readable permission name, or FALSE if the provided text format
+ * is malformed or is the fallback format (which is available to all users).
+ */
+function filter_permission_name($format) {
+ if (isset($format->format) && $format->format != filter_fallback_format()) {
+ return 'use text format ' . $format->format;
+ }
+ return FALSE;
}
/**
@@ -386,44 +421,143 @@ function _filter_html_escape_tips($filter, $format, $long = FALSE) {
/**
* @} End of "Tips callback for filters".
*/
+
/**
- * Retrieve a list of text formats.
+ * Retrieve a list of text formats, ordered by weight.
+ *
+ * @param $account
+ * (optional) If provided, only those formats that are allowed for this user
+ * account will be returned. All formats will be returned otherwise.
+ * @return
+ * An array of text format objects, keyed by the format ID and ordered by
+ * weight.
+ *
+ * @see filter_formats_reset()
*/
-function filter_formats($index = NULL) {
- global $user;
- static $formats;
-
- // Administrators can always use all text formats.
- $all = user_access('administer filters');
-
- if (!isset($formats)) {
- $formats = array();
-
- $query = db_select('filter_format', 'f');
- $query->addField('f', 'format', 'format');
- $query->addField('f', 'name', 'name');
- $query->addField('f', 'roles', 'roles');
- $query->addField('f', 'cache', 'cache');
- $query->addField('f', 'weight', 'weight');
- $query->orderBy('weight');
-
- // Build query for selecting the format(s) based on the user's roles.
- if (!$all) {
- $or = db_or()->condition('format', variable_get('filter_default_format', 1));
- foreach ($user->roles as $rid => $role) {
- $or->condition('roles', '%' . (int)$rid . '%', 'LIKE');
+function filter_formats($account = NULL) {
+ $formats = &drupal_static(__FUNCTION__, array());
+
+ // Statically cache all existing formats upfront.
+ if (!isset($formats['all'])) {
+ $formats['all'] = db_query('SELECT * FROM {filter_format} ORDER BY weight')->fetchAllAssoc('format');
+ }
+
+ // Build a list of user-specific formats.
+ if (isset($account) && !isset($formats['user'][$account->uid])) {
+ $formats['user'][$account->uid] = array();
+ foreach ($formats['all'] as $format) {
+ if (filter_access($format, $account)) {
+ $formats['user'][$account->uid][$format->format] = $format;
}
- $query->condition($or);
}
+ }
+
+ return isset($account) ? $formats['user'][$account->uid] : $formats['all'];
+}
+
+/**
+ * Resets the static cache of all text formats.
+ *
+ * @see filter_formats()
+ */
+function filter_formats_reset() {
+ drupal_static_reset('filter_list_format');
+ drupal_static_reset('filter_formats');
+}
- $formats = $query->execute()->fetchAllAssoc('format');
+/**
+ * Retrieves a list of roles that are allowed to use a given text format.
+ *
+ * @param $format
+ * An object representing the text format.
+ * @return
+ * An array of role names, keyed by role ID.
+ */
+function filter_get_roles_by_format($format) {
+ // Handle the fallback format upfront (all roles have access to this format).
+ if ($format->format == filter_fallback_format()) {
+ return user_roles();
}
- if (isset($index)) {
- return isset($formats[$index]) ? $formats[$index] : FALSE;
+ // Do not list any roles if the permission does not exist.
+ $permission = filter_permission_name($format);
+ return !empty($permission) ? user_roles(FALSE, $permission) : array();
+}
+
+/**
+ * Retrieves a list of text formats that are allowed for a given role.
+ *
+ * @param $rid
+ * The user role ID to retrieve text formats for.
+ * @return
+ * An array of text format objects that are allowed for the role, keyed by
+ * the text format ID and ordered by weight.
+ */
+function filter_get_formats_by_role($rid) {
+ $formats = array();
+ foreach (filter_formats() as $format) {
+ $roles = filter_get_roles_by_format($format);
+ if (isset($roles[$rid])) {
+ $formats[$format->format] = $format;
+ }
}
return $formats;
}
+/**
+ * Returns the ID of the default text format for a particular user.
+ *
+ * The default text format is the first available format that the user is
+ * allowed to access, when the formats are ordered by weight. It should
+ * generally be used as a default choice when presenting the user with a list
+ * of possible text formats (for example, in a node creation form).
+ *
+ * Conversely, when existing content that does not have an assigned text format
+ * needs to be filtered for display, the default text format is the wrong
+ * choice, because it is not guaranteed to be consistent from user to user, and
+ * some trusted users may have an unsafe text format set by default, which
+ * should not be used on text of unknown origin. Instead, the fallback format
+ * returned by filter_fallback_format() should be used, since that is intended
+ * to be a safe, consistent format that is always available to all users.
+ *
+ * @param $account
+ * (optional) The user account to check. Defaults to the currently logged-in
+ * user.
+ * @return
+ * The ID of the user's default text format.
+ *
+ * @see filter_fallback_format()
+ */
+function filter_default_format($account = NULL) {
+ global $user;
+ if (!isset($account)) {
+ $account = $user;
+ }
+ // Get a list of formats for this user, ordered by weight. The first one
+ // available is the user's default format.
+ $format = array_shift(filter_formats($account));
+ return $format->format;
+}
+
+/**
+ * Returns the ID of the fallback text format that all users have access to.
+ */
+function filter_fallback_format() {
+ // This variable is automatically set in the database for all installations
+ // of Drupal. In the event that it gets deleted somehow, there is no safe
+ // default to return, since we do not want to risk making an existing (and
+ // potentially unsafe) text format on the site automatically available to all
+ // users. Returning NULL at least guarantees that this cannot happen.
+ return variable_get('filter_fallback_format');
+}
+
+/**
+ * Returns the title of the fallback text format.
+ */
+function filter_fallback_format_title() {
+ $fallback_format = filter_format_load(filter_fallback_format());
+ return filter_admin_format_title($fallback_format);
+}
+
/**
* Return a list of all filters provided by modules.
*/
@@ -453,18 +587,11 @@ function _filter_list_cmp($a, $b) {
return strcmp($a['title'], $b['title']);
}
-/**
- * Resolve a format id, including the default format.
- */
-function filter_resolve_format($format) {
- return $format == FILTER_FORMAT_DEFAULT ? variable_get('filter_default_format', 1) : $format;
-}
/**
* Check if text in a certain text format is allowed to be cached.
*/
function filter_format_allowcache($format) {
static $cache = array();
- $format = filter_resolve_format($format);
if (!isset($cache[$format])) {
$cache[$format] = db_query('SELECT cache FROM {filter_format} WHERE format = :format', array(':format' => $format))->fetchField();
}
@@ -472,47 +599,52 @@ function filter_format_allowcache($format) {
}
/**
- * Retrieve a list of filters for a certain format.
+ * Retrieve a list of filters for a given text format.
*
* @param $format
* The format ID.
+ * @param $include_disabled
+ * (optional) Boolean whether to retrieve all filters associated with the
+ * given format, including those that are disabled. Defaults to FALSE.
* @return
* An array of filter objects assosiated to the given format.
*/
-function filter_list_format($format) {
- static $filters = array();
+function filter_list_format($format, $include_disabled = FALSE) {
+ $filters = &drupal_static(__FUNCTION__, array());
$filter_info = filter_get_filters();
- if (!isset($filters[$format])) {
- $filters[$format] = array();
- $result = db_select('filter', 'filter')
+ if (!isset($filters[$format]) || $include_disabled) {
+ $format_filters = array();
+ $query = db_select('filter', 'filter')
->fields('filter')
->condition('format', $format)
- ->condition('status', 1)
->orderBy('weight')
->orderBy('module')
- ->orderBy('name')
- ->execute();
- foreach ($result as $filter) {
- if (isset($filter_info[$filter->name])) {
- $filter->title = $filter_info[$filter->name]['title'];
+ ->orderBy('name');
+ if (!$include_disabled) {
+ $query->condition('status', 1);
+ }
+ $result = $query->execute()->fetchAllAssoc('name');
+ foreach ($result as $name => $filter) {
+ if (isset($filter_info[$name])) {
+ $filter->title = $filter_info[$name]['title'];
// Unpack stored filter settings.
- if (isset($filter->settings)) {
- $filter->settings = unserialize($filter->settings);
- }
- else {
- $filter->settings = array();
- }
+ $filter->settings = (isset($filter->settings) ? unserialize($filter->settings) : array());
// Apply default filter settings.
- if (isset($filter_info[$filter->name]['default settings'])) {
- $filter->settings = array_merge($filter_info[$filter->name]['default settings'], $filter->settings);
+ if (isset($filter_info[$name]['default settings'])) {
+ $filter->settings = array_merge($filter_info[$name]['default settings'], $filter->settings);
}
- $filters[$format][$filter->name] = $filter;
+ $format_filters[$name] = $filter;
}
}
+ // Prevent statically caching of disabled filters.
+ if ($include_disabled) {
+ return $format_filters;
+ }
+ $filters[$format] = $format_filters;
}
- return $filters[$format];
+ return isset($filters[$format]) ? $filters[$format] : array();
}
/**
@@ -536,8 +668,8 @@ function filter_list_format($format) {
* @param $text
* The text to be filtered.
* @param $format
- * The format of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for
- * the default format.
+ * The format of the text to be filtered. If no format is assigned, the
+ * fallback format will be used.
* @param $langcode
* Optional: the language code of the text to be filtered, e.g. 'en' for
* English. This allows filters to be language aware so language specific
@@ -547,8 +679,10 @@ function filter_list_format($format) {
* The caller may set this to FALSE when the output is already cached
* elsewhere to avoid duplicate cache lookups and storage.
*/
-function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $langcode = '', $cache = TRUE) {
- $format = filter_resolve_format($format);
+function check_markup($text, $format = NULL, $langcode = '', $cache = TRUE) {
+ if (empty($format)) {
+ $format = filter_fallback_format();
+ }
// Check for a cached version of this piece of text.
$cache_id = $format . ':' . $langcode . ':' . md5($text);
@@ -599,9 +733,16 @@ function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $langcode = '', $c
* @return
* HTML for the form element.
*/
-function filter_form($selected_format = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) {
- $selected_format = filter_resolve_format($selected_format);
- $formats = filter_formats();
+function filter_form($selected_format = NULL, $weight = NULL, $parents = array('format')) {
+ global $user;
+
+ // Use the default format for this user if none was selected.
+ if (empty($selected_format)) {
+ $selected_format = filter_default_format($user);
+ }
+
+ // Get a list of formats that the current user has access to.
+ $formats = filter_formats($user);
drupal_add_js('misc/form.js');
drupal_add_css(drupal_get_path('module', 'filter') . '/filter.css');
@@ -644,17 +785,31 @@ function filter_form($selected_format = FILTER_FORMAT_DEFAULT, $weight = NULL, $
}
/**
- * Returns TRUE if the user is allowed to access this format.
+ * Checks if a user has access to a particular text format.
+ *
+ * @param $format
+ * An object representing the text format.
+ * @param $account
+ * (optional) The user account to check access for; if omitted, the currently
+ * logged-in user is used.
+ *
+ * @return
+ * Boolean TRUE if the user is allowed to access the given format.
*/
-function filter_access($format) {
- $format = filter_resolve_format($format);
- if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) {
- return TRUE;
+function filter_access($format, $account = NULL) {
+ global $user;
+ if (!isset($account)) {
+ $account = $user;
}
- else {
- $formats = filter_formats();
- return isset($formats[$format]);
+ // Handle special cases up front. All users have access to the fallback
+ // format, and administrators have access to all formats.
+ if (user_access('administer filters', $account) || $format->format == filter_fallback_format()) {
+ return TRUE;
}
+ // Check the permission if one exists; otherwise, we have a non-existent
+ // format so we return FALSE.
+ $permission = filter_permission_name($format);
+ return !empty($permission) && user_access($permission, $account);
}
/**
@@ -666,7 +821,9 @@ function filter_access($format) {
* Helper function for fetching filter tips.
*/
function _filter_tips($format, $long = FALSE) {
- $formats = filter_formats();
+ global $user;
+
+ $formats = filter_formats($user);
$filter_info = filter_get_filters();
$tips = array();
@@ -809,7 +966,7 @@ function filter_filter_info() {
/**
* Settings callback for the HTML filter.
*/
-function _filter_html_settings(&$form_state, $filter, $defaults) {
+function _filter_html_settings($form, &$form_state, $filter, $defaults) {
$form['allowed_html'] = array(
'#type' => 'textfield',
'#title' => t('Allowed HTML tags'),
@@ -855,7 +1012,7 @@ function _filter_html($text, $filter) {
/**
* Settings callback for URL filter.
*/
-function _filter_url_settings(&$form_state, $filter, $defaults) {
+function _filter_url_settings($form, &$form_state, $filter, $defaults) {
$form['filter_url_length'] = array(
'#type' => 'textfield',
'#title' => t('Maximum link text length'),
@@ -1002,15 +1159,3 @@ function _filter_html_escape($text) {
/**
* @} End of "Standard filters".
*/
-
-/**
- * Implement hook_user_role_delete().
- *
- * Remove deleted role from formats that use it.
- */
-function filter_user_role_delete($role) {
- db_update('filter_format')
- ->expression('roles', 'REPLACE(roles, :rid, :replacement)', array(':rid' => ',' . $role->rid, ':replacement' => ''))
- ->condition('roles', '%,' . $role->rid . '%', 'LIKE')
- ->execute();
-}
diff --git a/modules/filter/filter.test b/modules/filter/filter.test
index 34a289f..7e8360d 100644
--- a/modules/filter/filter.test
+++ b/modules/filter/filter.test
@@ -1,5 +1,5 @@
admin_user = $this->drupalCreateUser(array('administer filters'));
+ $this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content'));
+ $this->drupalLogin($this->admin_user);
+ }
+
+ function testFormatAdmin() {
+ // Add text format.
+ $this->drupalGet('admin/config/content/formats');
+ $this->clickLink('Add text format');
+ $edit = array(
+ 'name' => $this->randomName(),
+ );
+ $this->drupalPost(NULL, $edit, t('Save configuration'));
+
+ // Edit text format.
+ $format = $this->getFilter($edit['name']);
+ $this->drupalGet('admin/config/content/formats');
+ $this->assertRaw('admin/config/content/formats/' . $format->format);
+ $this->drupalGet('admin/config/content/formats/' . $format->format);
+ $this->drupalPost(NULL, array(), t('Save configuration'));
+
+ // Delete text format.
+ $this->drupalGet('admin/config/content/formats');
+ $this->assertRaw('admin/config/content/formats/' . $format->format . '/delete');
+ $this->drupalGet('admin/config/content/formats/' . $format->format . '/delete');
+ $this->drupalPost(NULL, array(), t('Delete'));
+
+ // Verify that deleted text format no longer exists.
+ $this->drupalGet('admin/config/content/formats/' . $format->format);
+ $this->assertResponse(404, t('Deleted text format no longer exists.'));
+ }
+
/**
* Test filter administration functionality.
*/
@@ -19,20 +55,18 @@ class FilterAdminTestCase extends DrupalWebTestCase {
// Line filter.
$second_filter = 'filter_autop';
- // Create users.
- $admin_user = $this->drupalCreateUser(array('administer filters'));
- $web_user = $this->drupalCreateUser(array('create page content'));
- $this->drupalLogin($admin_user);
-
- list($filtered, $full) = $this->checkFilterFormats();
+ list($filtered, $full, $plain) = $this->checkFilterFormats();
- // Change default filter.
- $edit = array();
- $edit['default'] = $full;
- $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
- $this->assertText(t('Default format updated.'), t('Default filter updated successfully.'));
+ // Check that the fallback format exists and cannot be deleted.
+ $this->assertTrue(!empty($plain) && $plain == filter_fallback_format(), t('The fallback format is set to plain text.'));
+ $this->drupalGet('admin/config/content/formats');
+ $this->assertNoRaw('admin/config/content/formats/' . $plain . '/delete', t('Delete link for the fallback format not found.'));
+ $this->drupalGet('admin/config/content/formats/' . $plain . '/delete');
+ $this->assertResponse(403, t('The fallback format cannot be deleted.'));
- $this->assertNoRaw('admin/config/content/formats/' . $full . '/delete', t('Delete link not found.'));
+ // Verify access permissions to Full HTML format.
+ $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.'));
+ $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.'));
// Add an additional tag.
$edit = array();
@@ -65,8 +99,8 @@ class FilterAdminTestCase extends DrupalWebTestCase {
$edit = array();
$edit['name'] = $this->randomName();
$edit['roles[2]'] = 1;
- $edit['filters[' . $second_filter . ']'] = TRUE;
- $edit['filters[' . $first_filter . ']'] = TRUE;
+ $edit['filters[' . $second_filter . '][status]'] = TRUE;
+ $edit['filters[' . $first_filter . '][status]'] = TRUE;
$this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
$this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.'));
@@ -75,44 +109,38 @@ class FilterAdminTestCase extends DrupalWebTestCase {
if ($format !== NULL) {
$this->assertFieldByName('roles[2]', '', t('Role found.'));
- $this->assertFieldByName('filters[' . $second_filter . ']', '', t('Line break filter found.'));
- $this->assertFieldByName('filters[' . $first_filter . ']', '', t('Url filter found.'));
+ $this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.'));
+ $this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.'));
// Delete new filter.
$this->drupalPost('admin/config/content/formats/' . $format->format . '/delete', array(), t('Delete'));
$this->assertRaw(t('Deleted text format %format.', array('%format' => $edit['name'])), t('Format successfully deleted.'));
}
- // Change default filter back.
- $edit = array();
- $edit['default'] = $filtered;
- $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
- $this->assertText(t('Default format updated.'), t('Default filter updated successfully.'));
-
- $this->assertNoRaw('admin/config/content/formats/' . $filtered . '/delete', t('Delete link not found.'));
-
// Allow authenticated users on full HTML.
+ $format = filter_format_load($full);
$edit = array();
$edit['roles[1]'] = 0;
$edit['roles[2]'] = 1;
$this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
- $this->assertText(t('The text format settings have been updated.'), t('Full HTML format successfully updated.'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.'));
// Switch user.
$this->drupalLogout();
- $this->drupalLogin($web_user);
+ $this->drupalLogin($this->web_user);
$this->drupalGet('node/add/page');
$this->assertRaw('', t('Full HTML filter accessible.'));
// Use filtered HTML and see if it removes tags that are not allowed.
- $body = $this->randomName();
+ $body = '' . $this->randomName() . '';
$extra_text = 'text';
+ $text = $body . '' . $extra_text . '';
$edit = array();
$edit['title'] = $this->randomName();
$langcode = FIELD_LANGUAGE_NONE;
- $edit["body[$langcode][0][value]"] = $body . '' . $extra_text . '';
+ $edit["body[$langcode][0][value]"] = $text;
$edit["body[$langcode][0][value_format]"] = $filtered;
$this->drupalPost('node/add/page', $edit, t('Save'));
$this->assertRaw(t('Page %title has been created.', array('%title' => $edit['title'])), t('Filtered node created.'));
@@ -121,11 +149,18 @@ class FilterAdminTestCase extends DrupalWebTestCase {
$this->assertTrue($node, t('Node found in database.'));
$this->drupalGet('node/' . $node->nid);
- $this->assertText($body . $extra_text, t('Filter removed invalid tag.'));
+ $this->assertRaw($body . $extra_text, t('Filter removed invalid tag.'));
+
+ // Use plain text and see if it escapes all tags, whether allowed or not.
+ $edit = array();
+ $edit["body[$langcode][0][value_format]"] = $plain;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.'));
// Switch user.
$this->drupalLogout();
- $this->drupalLogin($admin_user);
+ $this->drupalLogin($this->admin_user);
// Clean up.
// Allowed tags.
@@ -138,7 +173,7 @@ class FilterAdminTestCase extends DrupalWebTestCase {
$edit = array();
$edit['roles[2]'] = FALSE;
$this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration'));
- $this->assertText(t('The text format settings have been updated.'), t('Full HTML format successfully reverted.'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.'));
// Filter order.
$edit = array();
@@ -149,16 +184,17 @@ class FilterAdminTestCase extends DrupalWebTestCase {
}
/**
- * Query the database to get the two basic formats.
+ * Query the database to get the three basic formats.
*
* @return
- * An array containing filtered and full filter ids.
+ * An array containing filtered, full, and plain text format ids.
*/
function checkFilterFormats() {
$result = db_query('SELECT format, name FROM {filter_format}');
$filtered = -1;
$full = -1;
+ $plain = -1;
foreach ($result as $format) {
if ($format->name == 'Filtered HTML') {
$filtered = $format->format;
@@ -166,9 +202,12 @@ class FilterAdminTestCase extends DrupalWebTestCase {
elseif ($format->name == 'Full HTML') {
$full = $format->format;
}
+ elseif ($format->name == 'Plain text') {
+ $plain = $format->format;
+ }
}
- return array($filtered, $full);
+ return array($filtered, $full, $plain);
}
/**
@@ -184,6 +223,184 @@ class FilterAdminTestCase extends DrupalWebTestCase {
}
}
+class FilterAccessTestCase extends DrupalWebTestCase {
+ protected $admin_user;
+ protected $web_user;
+ protected $allowed_format;
+ protected $disallowed_format;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Filter access functionality',
+ 'description' => 'Test the filter access system.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+
+ // Create two text formats and grant a regular user access to one of them.
+ $this->admin_user = $this->drupalCreateUser(array('administer filters'));
+ $this->drupalLogin($this->admin_user);
+ $formats = array();
+ for ($i = 0; $i < 2; $i++) {
+ $edit = array('name' => $this->randomName());
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->resetFilterCaches();
+ $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField();
+ $formats[] = filter_format_load($format_id);
+ }
+ list($this->allowed_format, $this->disallowed_format) = $formats;
+ $this->web_user = $this->drupalCreateUser(array('create page content', filter_permission_name($this->allowed_format)));
+ }
+
+ function testFormatPermissions() {
+ // Make sure that a regular user only has access to the text format they
+ // were granted access to, as well to the fallback format.
+ $this->assertTrue(filter_access($this->allowed_format, $this->web_user), t('A regular user has access to a text format they were granted access to.'));
+ $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), t('A regular user does not have access to a text format they were not granted access to.'));
+ $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.'));
+
+ // Perform similar checks as above, but now against the entire list of
+ // available formats for this user.
+ $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), t('The allowed format appears in the list of available formats for a regular user.'));
+ $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), t('The disallowed format does not appear in the list of available formats for a regular user.'));
+ $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), t('The fallback format appears in the list of available formats for a regular user.'));
+
+ // Make sure that a regular user only has permission to use the format
+ // they were granted access to.
+ $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), t('A regular user has permission to use the allowed text format.'));
+ $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), t('A regular user does not have permission to use the disallowed text format.'));
+
+ // Make sure that the allowed format appears on the node form and that
+ // the disallowed format does not.
+ $this->drupalLogin($this->web_user);
+ $this->drupalGet('node/add/page');
+ $this->assertRaw($this->formatSelectorHTML($this->allowed_format), t('The allowed text format appears as an option when adding a new node.'));
+ $this->assertNoRaw($this->formatSelectorHTML($this->disallowed_format), t('The disallowed text format does not appear as an option when adding a new node.'));
+ $this->assertRaw($this->formatSelectorHTML(filter_format_load(filter_fallback_format())), t('The fallback format appears as an option when adding a new node.'));
+ }
+
+ function testFormatRoles() {
+ // Get the role ID assigned to the regular user; it must be the maximum.
+ $rid = max(array_keys($this->web_user->roles));
+
+ // Check that this role appears in the list of roles that have access to an
+ // allowed text format, but does not appear in the list of roles that have
+ // access to a disallowed text format.
+ $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), t('A role which has access to a text format appears in the list of roles that have access to that format.'));
+ $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), t('A role which does not have access to a text format does not appear in the list of roles that have access to that format.'));
+
+ // Check that the correct text format appears in the list of formats
+ // available to that role.
+ $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role has access to appears in the list of formats available to that role.'));
+ $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role does not have access to does not appear in the list of formats available to that role.'));
+
+ // Check that the fallback format is always allowed.
+ $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.'));
+ $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), t('The fallback format appears in the list of allowed formats for any role.'));
+ }
+
+ /**
+ * Returns the expected HTML for a particular text format selector.
+ *
+ * @param $format
+ * An object representing the text format for which to return HTML.
+ * @return
+ * The expected HTML for that text format's selector.
+ */
+ function formatSelectorHTML($format) {
+ return '';
+ }
+
+ /**
+ * Rebuild text format and permission caches in the thread running the tests.
+ */
+ protected function resetFilterCaches() {
+ filter_formats_reset();
+ $this->checkPermissions(array(), TRUE);
+ }
+}
+
+class FilterDefaultFormatTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Default text format functionality',
+ 'description' => 'Test the default text formats for different users.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function testDefaultTextFormats() {
+ // Create two text formats, and two users. The first user has access to
+ // both formats, but the second user only has access to the second one.
+ $admin_user = $this->drupalCreateUser(array('administer filters'));
+ $this->drupalLogin($admin_user);
+ $formats = array();
+ for ($i = 0; $i < 2; $i++) {
+ $edit = array('name' => $this->randomName());
+ $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration'));
+ $this->resetFilterCaches();
+ $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField();
+ $formats[] = filter_format_load($format_id);
+ }
+ list($first_format, $second_format) = $formats;
+ $first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format)));
+ $second_user = $this->drupalCreateUser(array(filter_permission_name($second_format)));
+
+ // Adjust the weights so that the first and second formats (in that order)
+ // are the two lowest weighted formats available to any user.
+ $minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField();
+ $edit = array();
+ $edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2;
+ $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1;
+ $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
+ $this->resetFilterCaches();
+
+ // Check that each user's default format is the lowest weighted format that
+ // the user has access to.
+ $this->assertEqual(filter_default_format($first_user), $first_format->format, t("The first user's default format is the lowest weighted format that the user has access to."));
+ $this->assertEqual(filter_default_format($second_user), $second_format->format, t("The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's."));
+
+ // Reorder the two formats, and check that both users now have the same
+ // default.
+ $edit = array();
+ $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3;
+ $this->drupalPost('admin/config/content/formats', $edit, t('Save changes'));
+ $this->resetFilterCaches();
+ $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), t('After the formats are reordered, both users have the same default format.'));
+ }
+
+ /**
+ * Rebuild text format and permission caches in the thread running the tests.
+ */
+ protected function resetFilterCaches() {
+ filter_formats_reset();
+ $this->checkPermissions(array(), TRUE);
+ }
+}
+
+class FilterNoFormatTestCase extends DrupalWebTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Unassigned text format functionality',
+ 'description' => 'Test the behavior of check_markup() when it is called without a text format.',
+ 'group' => 'Filter',
+ );
+ }
+
+ function testCheckMarkupNoFormat() {
+ // Create some text. Include some HTML and line breaks, so we get a good
+ // test of the filtering that is applied to it.
+ $text = "" . $this->randomName(32) . "\n\n
" . $this->randomName(32) . "
";
+
+ // Make sure that when this text is run through check_markup() with no text
+ // format, it is filtered as though it is in the fallback format.
+ $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), t('Text with no format is filtered the same as text in the fallback format.'));
+ }
+}
+
/**
* Unit tests for core filters.
*/
@@ -801,7 +1018,7 @@ class FilterHooksTestCase extends DrupalWebTestCase {
$edit = array();
$edit['roles[2]'] = 1;
$this->drupalPost('admin/config/content/formats/' . $format, $edit, t('Save configuration'));
- $this->assertRaw(t('The text format settings have been updated.'), t('Full HTML format successfully updated.'));
+ $this->assertRaw(t('The text format %format has been updated.', array('%format' => $name)), t('Format successfully updated.'));
$this->assertText('hook_filter_format_update invoked.', t('hook_filter_format_update() was invoked.'));
// Add a new custom block.
@@ -823,14 +1040,13 @@ class FilterHooksTestCase extends DrupalWebTestCase {
$this->assertRaw(t('Deleted text format %format.', array('%format' => $name)), t('Format successfully deleted.'));
$this->assertText('hook_filter_format_delete invoked.', t('hook_filter_format_delete() was invoked.'));
- // Verify that the deleted format was replaced with the default format.
+ // Verify that the deleted format was replaced with the fallback format.
$current_format = db_select('block_custom', 'b')
->fields('b', array('format'))
->condition('bid', $bid)
->execute()
->fetchField();
- $default = variable_get('filter_default_format', 1);
- $this->assertEqual($current_format, $default, t('Deleted text format replaced with the default format.'));
+ $this->assertEqual($current_format, filter_fallback_format(), t('Deleted text format replaced with the fallback format.'));
}
}
diff --git a/modules/forum/CVS/Entries b/modules/forum/CVS/Entries
index b3cdfbf..165b871 100644
--- a/modules/forum/CVS/Entries
+++ b/modules/forum/CVS/Entries
@@ -3,12 +3,12 @@
/forum-rtl.css/1.3/Thu Sep 3 08:50:25 2009//
/forum-submitted.tpl.php/1.5/Thu Sep 3 08:50:25 2009//
/forum-topic-list.tpl.php/1.7/Thu Sep 3 08:50:25 2009//
-/forum.admin.inc/1.24/Thu Sep 3 08:50:25 2009//
/forum.css/1.7/Thu Sep 3 08:50:25 2009//
/forum.info/1.12/Thu Sep 3 08:50:25 2009//
-/forum.install/1.33/Thu Sep 3 08:50:25 2009//
-/forum.module/1.516/Thu Sep 3 08:50:25 2009//
/forum.pages.inc/1.2/Thu Sep 3 08:50:25 2009//
-/forum.test/1.29/Thu Sep 3 08:50:25 2009//
/forums.tpl.php/1.5/Thu Sep 3 08:50:25 2009//
+/forum.admin.inc/1.25/Fri Oct 2 19:50:14 2009//
+/forum.install/1.35/Fri Oct 2 19:50:14 2009//
+/forum.module/1.519/Fri Oct 2 19:50:14 2009//
+/forum.test/1.32/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/forum/forum.admin.inc b/modules/forum/forum.admin.inc
index 573f25e..3f7042b 100644
--- a/modules/forum/forum.admin.inc
+++ b/modules/forum/forum.admin.inc
@@ -1,5 +1,5 @@
'',
'description' => '',
@@ -105,7 +105,7 @@ function forum_form_submit($form, &$form_state) {
* @ingroup forms
* @see forum_form_submit()
*/
-function forum_form_container(&$form_state, $edit = array()) {
+function forum_form_container($form, &$form_state, $edit = array()) {
$edit += array(
'name' => '',
'description' => '',
@@ -160,7 +160,7 @@ function forum_form_container(&$form_state, $edit = array()) {
*
* @param $tid ID of the term to be deleted
*/
-function forum_confirm_delete(&$form_state, $tid) {
+function forum_confirm_delete($form, &$form_state, $tid) {
$term = taxonomy_term_load($tid);
$form['tid'] = array('#type' => 'value', '#value' => $tid);
@@ -186,8 +186,7 @@ function forum_confirm_delete_submit($form, &$form_state) {
*
* @see system_settings_form()
*/
-function forum_admin_settings() {
- $form = array();
+function forum_admin_settings($form) {
$number = drupal_map_assoc(array(5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 80, 100, 150, 200, 250, 300, 350, 400, 500));
$form['forum_hot_topic'] = array('#type' => 'select',
'#title' => t('Hot topic threshold'),
@@ -215,12 +214,12 @@ function forum_admin_settings() {
/**
* Returns an overview list of existing forums and containers
*/
-function forum_overview(&$form_state) {
+function forum_overview($form, &$form_state) {
module_load_include('inc', 'taxonomy', 'taxonomy.admin');
$vid = variable_get('forum_nav_vocabulary', '');
$vocabulary = taxonomy_vocabulary_load($vid);
- $form = taxonomy_overview_terms($form_state, $vocabulary);
+ $form = taxonomy_overview_terms($form, $form_state, $vocabulary);
foreach (element_children($form) as $key) {
if (isset($form[$key]['#term'])) {
diff --git a/modules/forum/forum.install b/modules/forum/forum.install
index 51e6129..4604001 100644
--- a/modules/forum/forum.install
+++ b/modules/forum/forum.install
@@ -1,5 +1,5 @@
fields(array('weight' => 1))
@@ -61,7 +59,6 @@ function forum_uninstall() {
$vid = variable_get('forum_nav_vocabulary', 0);
taxonomy_vocabulary_delete($vid);
- drupal_uninstall_schema('forum');
variable_del('forum_containers');
variable_del('forum_nav_vocabulary');
variable_del('forum_hot_topic');
@@ -119,9 +116,6 @@ function forum_schema() {
* Add new index to forum table.
*/
function forum_update_7000() {
- $ret = array();
- db_drop_index($ret, 'forum', 'nid');
- db_add_index($ret, 'forum', 'forum_topic', array('nid', 'tid'));
-
- return $ret;
+ db_drop_index('forum', 'nid');
+ db_add_index('forum', 'forum_topic', array('nid', 'tid'));
}
diff --git a/modules/forum/forum.module b/modules/forum/forum.module
index 58bee9b..7c2653e 100644
--- a/modules/forum/forum.module
+++ b/modules/forum/forum.module
@@ -1,5 +1,5 @@
taxonomy as $term) {
- $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid', array(
+ $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid', 0, 1, array(
':tid' => $term,
':vid' => $vocabulary,
- ), 0, 1)->fetchField();
+ ))->fetchField();
if ($used && in_array($term, $containers)) {
$term = taxonomy_term_load($term);
form_set_error('taxonomy', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
@@ -287,7 +287,7 @@ function forum_node_presave($node) {
$node->tid = $term_id;
}
}
- $old_tid = db_query_range("SELECT f.tid FROM {forum} f INNER JOIN {node} n ON f.vid = n.vid WHERE n.nid = :nid ORDER BY f.vid DESC", array(':nid' => $node->nid), 0, 1)->fetchField();
+ $old_tid = db_query_range("SELECT f.tid FROM {forum} f INNER JOIN {node} n ON f.vid = n.vid WHERE n.nid = :nid ORDER BY f.vid DESC", 0, 1, array(':nid' => $node->nid))->fetchField();
if ($old_tid && isset($node->tid) && ($node->tid != $old_tid) && !empty($node->shadow)) {
// A shadow copy needs to be created. Retain new term and add old term.
$node->taxonomy[] = $old_tid;
@@ -538,14 +538,13 @@ function forum_block_view($delta = '') {
break;
}
- $cache_keys[] = 'forum';
- $cache_keys[] = $delta;
- // Cache based on the altered query. Enables us to cache with node access enabled.
- $query->preExecute();
- $cache_keys[] = md5(serialize(array((string) $query, $query->getArguments())));
+ $cache_keys = array_merge(array('forum', $delta), drupal_render_cid_parts());
+ // Cache based on the altered query. Enables us to cache with node access enabled.
+ $query->preExecute();
+ $cache_keys[] = md5(serialize(array((string) $query, $query->getArguments())));
- $block['subject'] = $title;
- $block['content'] = array(
+ $block['subject'] = $title;
+ $block['content'] = array(
'#access' => user_access('access content'),
'#pre_render' => array('forum_block_view_pre_render'),
'#cache' => array(
@@ -553,8 +552,8 @@ function forum_block_view($delta = '') {
'expire' => CACHE_TEMPORARY,
),
'#query' => $query,
- );
- return $block;
+ );
+ return $block;
}
/**
@@ -624,7 +623,7 @@ function forum_get_forums($tid = 0) {
if (count($_forums)) {
$query = db_select('node', 'n');
$query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
- $query->join('forum', 'f', 'f.vid = f.vid');
+ $query->join('forum', 'f', 'n.vid = f.vid');
$query->addExpression('COUNT(n.nid)', 'topic_count');
$query->addExpression('SUM(ncs.comment_count)', 'comment_count');
$counts = $query
diff --git a/modules/forum/forum.test b/modules/forum/forum.test
index 979e1ed..3481c19 100644
--- a/modules/forum/forum.test
+++ b/modules/forum/forum.test
@@ -1,8 +1,8 @@
big_user = $this->drupalCreateUser(array('administer blocks', 'administer forums', 'administer menu', 'administer taxonomy', 'create forum content')); // 'access administration pages'));
+ $this->admin_user = $this->drupalCreateUser(array('administer blocks', 'administer forums', 'administer menu', 'administer taxonomy', 'create forum content')); // 'access administration pages'));
$this->own_user = $this->drupalCreateUser(array('create forum content', 'edit own forum content', 'delete own forum content'));
$this->any_user = $this->drupalCreateUser(array('create forum content', 'edit any forum content', 'delete any forum content', 'access administration pages'));
$this->nid_user = $this->drupalCreateUser(array());
@@ -36,33 +36,38 @@ class ForumTestCase extends DrupalWebTestCase {
*/
function testForum() {
// Do the admin tests.
- $this->doAdminTests($this->big_user);
+ $this->doAdminTests($this->admin_user);
// Generate topics to populate the active forum block.
$this->generateForumTopics($this->forum);
- // Login the nid user to view the forum topics and generate an Active forum topics list (FAILS).
+ // Login the nid user to view the forum topics and generate an active forum
+ // topics list.
$this->drupalLogin($this->nid_user);
- // View the forum topics.
$this->viewForumTopics($this->nids);
// Do basic tests for the any forum user.
$this->doBasicTests($this->any_user, TRUE);
+
// Create another forum node for the any forum user.
-// $node = $this->drupalCreateNode(array('type' => 'forum', 'uid' => $this->any_user->uid));
$node = $this->createForumTopic($this->forum, FALSE);
// Do basic tests for the own forum user.
$this->doBasicTests($this->own_user, FALSE);
+
// Verify the own forum user only has access to the forum view node.
$this->verifyForums($this->any_user, $node, FALSE, 403);
// Create another forum node for the own forum user.
-// $node = $this->drupalCreateNode(array('type' => 'forum', 'uid' => $this->own_user->uid));
$node = $this->createForumTopic($this->forum, FALSE);
// Login the any forum user.
$this->drupalLogin($this->any_user);
// Verify the any forum user has access to all the forum nodes.
$this->verifyForums($this->own_user, $node, TRUE);
+
+ // Verify the topic and post counts on the forum page.
+ $this->drupalGet('forum');
+ $this->assertRaw("
\n 6
");
+ $this->assertRaw('
6
');
}
/**
@@ -79,7 +84,7 @@ class ForumTestCase extends DrupalWebTestCase {
$edit['forum_active[region]'] = 'sidebar_second';
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
$this->assertResponse(200);
- $this->assertText(t('The block settings have been updated.'), t('[Active forum topics] Forum block was enabled'));
+ $this->assertText(t('The block settings have been updated.'), t('Active forum topics forum block was enabled'));
// Enable the new forum block.
$edit = array();
@@ -93,7 +98,7 @@ class ForumTestCase extends DrupalWebTestCase {
// Add forum to navigation menu.
$edit = array();
- $this->drupalPost('admin/structure/menu-customize/navigation', $edit, t('Save configuration'));
+ $this->drupalPost('admin/structure/menu/manage/navigation', $edit, t('Save configuration'));
$this->assertResponse(200);
// Edit forum taxonomy.
@@ -153,9 +158,13 @@ class ForumTestCase extends DrupalWebTestCase {
/**
* Create a forum container or a forum.
*
- * @param string $type Forum type (forum container or forum).
- * @param integer $parent Forum parent (default = 0 = a root forum; >0 = a forum container or another forum).
- * @return object Taxonomy_term_data created.
+ * @param $type
+ * Forum type (forum container or forum).
+ * @param $parent
+ * Forum parent (default = 0 = a root forum; >0 = a forum container or
+ * another forum).
+ * @return
+ * taxonomy_term_data created.
*/
function createForum($type, $parent = 0) {
// Generate a random name/description.
@@ -190,10 +199,11 @@ class ForumTestCase extends DrupalWebTestCase {
/**
* Delete a forum.
*
- * @param integer $tid Forum id.
+ * @param $tid
+ * The forum ID.
*/
function deleteForum($tid) {
- // Delete the forum id.
+ // Delete the forum.
$this->drupalPost('admin/structure/forum/edit/forum/' . $tid, array(), t('Delete'));
$this->drupalPost(NULL, NULL, t('Delete'));
@@ -205,8 +215,10 @@ class ForumTestCase extends DrupalWebTestCase {
/**
* Run basic tests on the indicated user.
*
- * @param object $user The logged in user.
- * @param boolean $admin User has 'access administration pages' privilege.
+ * @param $user
+ * The logged in user.
+ * @param $admin
+ * User has 'access administration pages' privilege.
*/
private function doBasicTests($user, $admin) {
// Login the user.
@@ -230,7 +242,10 @@ class ForumTestCase extends DrupalWebTestCase {
// Generate a random subject/body.
$title = $this->randomName(20);
$body = $this->randomName(200);
- $tid = $forum['tid']; // Without this being set, post variable equals the first non-blank in select items list.
+
+ // Without this being set, post variable equals the first non-blank in
+ // select items list.
+ $tid = $forum['tid'];
$langcode = FIELD_LANGUAGE_NONE;
$edit = array(
@@ -239,12 +254,11 @@ class ForumTestCase extends DrupalWebTestCase {
'taxonomy[1]' => $tid
);
- // TODO The taxonomy select value is set by drupal code when the tid is part of the url.
- // However, unless a tid is passed in the edit array, when drupalPost runs, the select value is not preserved.
- // Instead, the post variables seem to pick up the first non-blank value in the select list.
-
+ // TODO The taxonomy select value is set by drupal code when the tid is part
+ // of the url. However, unless a tid is passed in the edit array, when
+ // drupalPost() runs, the select value is not preserved. Instead, the post
+ // variables seem to pick up the first non-blank value in the select list.
// Create forum topic.
-// $this->drupalPost('node/add/forum/' . $forum['tid'], $edit, t('Save'));
$this->drupalPost('node/add/forum/', $edit, t('Save'));
$type = t('Forum topic');
if ($container) {
@@ -270,12 +284,16 @@ class ForumTestCase extends DrupalWebTestCase {
}
/**
- * Verify the logged in user has the desired access to the various forum nodes.
+ * Verify the logged in user has access to a forum nodes.
*
- * @param object $node_user The user who creates the node.
- * @param object $node Node.
- * @param boolean $admin User has 'access administration pages' privilege.
- * @param integer $response HTTP response code.
+ * @param $node_user
+ * The user who creates the node.
+ * @param $node
+ * The node being checked.
+ * @param $admin
+ * Boolean to indicate whether the user can 'access administration pages'.
+ * @param $response
+ * The exptected HTTP response code.
*/
private function verifyForums($node_user, $node, $admin, $response = 200) {
$crumb = '›';
@@ -294,8 +312,6 @@ class ForumTestCase extends DrupalWebTestCase {
// Verify the forum blocks were displayed.
$this->drupalGet('');
$this->assertResponse(200);
- // This block never seems to display?
-// $this->assertText(t('Active forum topics'), t('[Active forum topics] Forum block was displayed'));
$this->assertText(t('New forum topics'), t('[New forum topics] Forum block was displayed'));
// View forum container page.
@@ -324,7 +340,8 @@ class ForumTestCase extends DrupalWebTestCase {
$edit['title'] = 'node/' . $node->nid;
$langcode = FIELD_LANGUAGE_NONE;
$edit["body[$langcode][0][value]"] = $this->randomName(256);
- $edit['taxonomy[1]'] = $this->root_forum['tid']; // Assumes the topic is initially associated with $forum.
+ // Assume the topic is initially associated with $forum.
+ $edit['taxonomy[1]'] = $this->root_forum['tid'];
$edit['shadow'] = TRUE;
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertRaw(t('Forum topic %title has been updated.', array('%title' => $edit['title'])), t('Forum node was edited'));
@@ -346,7 +363,8 @@ class ForumTestCase extends DrupalWebTestCase {
/**
* Verify display of forum page.
*
- * @param array $forum Forum array (a row from taxonomy_term_data table).
+ * @param $forum
+ * A row from taxonomy_term_data table in array.
*/
private function verifyForumView($forum, $parent = NULL) {
$crumb = '›';
@@ -379,7 +397,12 @@ class ForumTestCase extends DrupalWebTestCase {
/**
* View forum topics to test display of active forum block.
*
- * @param array $nids Forum node id array.
+ * @todo The logic here is completely incorrect, since the active
+ * forum topics block is determined by comments on the node, not by views.
+ * @todo DIE
+ *
+ * @param $nids
+ * An array of forum node IDs.
*/
private function viewForumTopics($nids) {
$crumb = '›';
diff --git a/modules/help/CVS/Entries b/modules/help/CVS/Entries
index df1b768..868acb2 100644
--- a/modules/help/CVS/Entries
+++ b/modules/help/CVS/Entries
@@ -1,8 +1,8 @@
/help-rtl.css/1.2/Thu Sep 3 08:50:25 2009//
/help.admin.inc/1.9/Thu Sep 3 08:50:25 2009//
-/help.api.php/1.5/Thu Sep 3 08:50:25 2009//
/help.css/1.2/Thu Sep 3 08:50:25 2009//
/help.info/1.8/Thu Sep 3 08:50:25 2009//
-/help.module/1.90/Thu Sep 3 08:50:26 2009//
/help.test/1.15/Thu Sep 3 08:50:26 2009//
+/help.api.php/1.7/Fri Oct 2 19:50:14 2009//
+/help.module/1.91/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/help/help.api.php b/modules/help/help.api.php
index 3839ed1..1fd0e38 100644
--- a/modules/help/help.api.php
+++ b/modules/help/help.api.php
@@ -1,5 +1,5 @@
l(t('user permissions'),
- * 'admin/settings/permission')));
- *
- * For a detailed usage example, see page_example.module.
+ * A localized string containing the help text.
*/
function hook_help($path, $arg) {
switch ($path) {
+ // Main module help for the block module
case 'admin/help#block':
- return '
' . t('Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. Blocks are usually generated automatically by modules (e.g., Recent Forum Topics), but administrators can also define custom blocks.') . '
';
+ return '
' . 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'))) . '
';
+ // Help for another path in the block module
case 'admin/structure/block':
- return t('
Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. They are usually generated automatically by modules, but administrators can create blocks manually.
-
If you want certain blocks to disable themselves temporarily during high server loads, check the "Throttle" box. You can configure the auto-throttle on the throttle configuration page after having enabled the throttle module.
-
You can configure the behavior of each block (for example, specifying on which pages and for what users it will appear) by clicking the "configure" link for each block.
' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the Save blocks button at the bottom of the page.') . '
';
}
}
diff --git a/modules/help/help.module b/modules/help/help.module
index 7f5d793..65e0745 100644
--- a/modules/help/help.module
+++ b/modules/help/help.module
@@ -1,5 +1,5 @@
' . t('Please follow these steps to set up and start using your website:') . '';
$output .= '';
- $output .= '
' . t('Configure your website Once logged in, visit the administration section, where you can customize and configure all aspects of your website.', array('@admin' => url('admin'), '@config' => url('admin/settings'))) . '
';
+ $output .= '
' . t('Configure your website Once logged in, visit the administration section, where you can customize and configure all aspects of your website.', array('@admin' => url('admin'), '@config' => url('admin/config'))) . '
';
$output .= '
' . t('Enable additional functionality Next, visit the module list and enable features which suit your specific needs. You can find additional modules in the Drupal modules download section.', array('@modules' => url('admin/config/modules'), '@download_modules' => 'http://drupal.org/project/modules')) . '
';
$output .= '
' . t('Customize your website design To change the "look and feel" of your website, visit the themes section. You may choose from one of the included themes or download additional themes from the Drupal themes download section.', array('@themes' => url('admin/appearance'), '@download_themes' => 'http://drupal.org/project/themes')) . '
';
$output .= '
' . t('Start posting content Finally, you can add new content for your website.', array('@content' => url('node/add'))) . '
';
diff --git a/modules/image/CVS/Entries b/modules/image/CVS/Entries
index ab40fa3..b2cedb1 100644
--- a/modules/image/CVS/Entries
+++ b/modules/image/CVS/Entries
@@ -2,9 +2,9 @@
/image.api.php/1.2/Thu Sep 3 08:50:26 2009//
/image.effects.inc/1.2/Thu Sep 3 08:50:26 2009//
/image.info/1.2/Thu Sep 3 08:50:26 2009//
-/image.install/1.2/Thu Sep 3 08:50:26 2009//
-/image.module/1.16/Thu Sep 3 08:50:26 2009//
/image.test/1.8/Thu Sep 3 08:50:26 2009//
/sample.png/1.1/Thu Sep 3 08:50:26 2009/-kb/
-/image.admin.inc/1.10/Sat Sep 5 15:25:49 2009//
+/image.admin.inc/1.11/Fri Oct 2 19:50:14 2009//
+/image.install/1.3/Fri Oct 2 19:50:14 2009//
+/image.module/1.17/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/image/image.admin.inc b/modules/image/image.admin.inc
index 004449c..f8e1443 100644
--- a/modules/image/image.admin.inc
+++ b/modules/image/image.admin.inc
@@ -1,5 +1,5 @@
$style['name']));
drupal_set_title($title, PASS_THROUGH);
$form_state['image_style'] = $style;
- $form = array(
- '#tree' => TRUE,
- '#attached' => array(
- 'css' => array(drupal_get_path('module', 'image') . '/image.admin.css' => array('preprocess' => FALSE)),
- ),
- );
+ $form['#tree'] = TRUE;
+ $form['#attached']['css'][drupal_get_path('module', 'image') . '/image.admin.css'] = array('preprocess' => FALSE);
// Allow the name of the style to be changed.
$form['name'] = array(
@@ -187,9 +183,7 @@ function image_style_form_submit($form, &$form_state) {
* @see image_style_add_form_submit()
* @see image_style_name_validate()
*/
-function image_style_add_form(&$form_state) {
- $form = array();
-
+function image_style_add_form($form, &$form_state) {
$form['name'] = array(
'#type' => 'textfield',
'#size' => '64',
@@ -243,9 +237,8 @@ function image_style_name_validate($element, $form_state) {
* @ingroup forms
* @see image_style_delete_form_submit()
*/
-function image_style_delete_form($form_state, $style) {
+function image_style_delete_form($form, $form_state, $style) {
$form_state['image_style'] = $style;
- $form = array();
$replacement_styles = array_diff_key(image_style_options(), array($style['name'] => ''));
$replacement_styles[''] = t('No replacement, just delete');
@@ -298,7 +291,7 @@ function image_style_delete_form_submit($form, &$form_state) {
* @see image_crop_form()
* @see image_effect_form_submit()
*/
-function image_effect_form(&$form_state, $style, $effect) {
+function image_effect_form($form, &$form_state, $style, $effect) {
if (!empty($effect['data'])) {
$title = t('Edit %label effect', array('%label' => $effect['label']));
}
@@ -315,12 +308,8 @@ function image_effect_form(&$form_state, $style, $effect) {
drupal_goto('admin/config/media/image-styles/edit/' . $style['name']);
}
- $form = array(
- '#tree' => TRUE,
- '#attached' => array(
- 'css' => array(drupal_get_path('module', 'image') . '/image.admin.css' => array('preprocess' => FALSE)),
- ),
- );
+ $form['#tree'] = TRUE;
+ $form['#attached']['css'][drupal_get_path('module', 'image') . '/image.admin.css'] = array('preprocess' => FALSE);
if (function_exists($effect['form callback'])) {
$form['data'] = call_user_func($effect['form callback'], $effect['data']);
}
@@ -365,11 +354,10 @@ function image_effect_form_submit($form, &$form_state) {
* @ingroup forms
* @see image_effect_delete_form_submit()
*/
-function image_effect_delete_form(&$form_state, $style, $effect) {
+function image_effect_delete_form($form, &$form_state, $style, $effect) {
$form_state['image_style'] = $style;
$form_state['image_effect'] = $effect;
- $form = array();
$question = t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $style['name'], '@effect' => $effect['label']));
return confirm_form($form, $question, 'admin/config/media/image-styles/edit/' . $style['name'], '', t('Delete'));
}
diff --git a/modules/image/image.install b/modules/image/image.install
index e87f198..9afd35b 100644
--- a/modules/image/image.install
+++ b/modules/image/image.install
@@ -1,5 +1,5 @@
fields(array(
'language' => 'en',
@@ -39,10 +36,8 @@ function locale_install() {
* Allow longer location.
*/
function locale_update_7000() {
- $ret = array();
- db_drop_index($ret, 'locales_source', 'source');
- db_add_index($ret, 'locales_source', 'source_context', array(array('source', 30), 'context'));
- return $ret;
+ db_drop_index('locales_source', 'source');
+ db_add_index('locales_source', 'source_context', array(array('source', 30), 'context'));
}
/**
@@ -83,8 +78,6 @@ function locale_uninstall() {
// try to query the unexisting {locales_source} and {locales_target} tables.
drupal_language_initialize();
- // Remove tables.
- drupal_uninstall_schema('locale');
}
/**
diff --git a/modules/locale/locale.module b/modules/locale/locale.module
index 7c88953..2d900eb 100644
--- a/modules/locale/locale.module
+++ b/modules/locale/locale.module
@@ -1,5 +1,5 @@
1 && user_access('administer users')) {
- return locale_language_selector_form($account);
- }
-}
-
-/**
- * Implement hook_user_form().
+ * Form builder callback to display language selection widget.
+ *
+ * @ingroup forms
+ * @see locale_form_alter()
*/
-function locale_user_form(&$edit, $account, $category) {
- // If we have more then one language and either creating a user on the
- // admin interface or edit the user, show the language selector.
- if (variable_get('language_count', 1) > 1 && $category == 'account') {
- return locale_language_selector_form($account);
- }
-}
-
-function locale_language_selector_form($user) {
+function locale_language_selector_form(&$form, &$form_state, $user) {
global $language;
$languages = language_list('enabled');
$languages = $languages[1];
// If the user is being created, we set the user language to the page language.
- $user_preferred_language = $user ? user_preferred_language($user) : $language;
+ $user_preferred_language = $user->uid ? user_preferred_language($user) : $language;
$names = array();
foreach ($languages as $langcode => $item) {
@@ -273,7 +257,6 @@ function locale_language_selector_form($user) {
'#options' => $names,
'#description' => ($mode == LANGUAGE_NEGOTIATION_PATH) ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."),
);
- return $form;
}
/**
@@ -306,9 +289,19 @@ function locale_form_node_type_form_alter(&$form, &$form_state) {
}
/**
- * Implement hook_form_alter(). Adds language fields to forms.
+ * Implement hook_form_alter().
+ *
+ * Adds language fields to forms.
*/
function locale_form_alter(&$form, &$form_state, $form_id) {
+ // Only alter user forms if there is more than one language.
+ if (variable_get('language_count', 1) > 1) {
+ // Display language selector when either creating a user on the admin
+ // interface or editing a user account.
+ if (($form_id == 'user_register' && user_access('administer users')) || ($form_id == 'user_profile_form' && $form['#user_category'] == 'account')) {
+ locale_language_selector_form($form, $form_state, $form['#user']);
+ }
+ }
if (isset($form['#id']) && $form['#id'] == 'node-form') {
if (isset($form['#node']->type) && variable_get('language_content_type_' . $form['#node']->type, 0)) {
$form['language'] = array(
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index cf14880..6c1e014 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -1,5 +1,5 @@
drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
// Set page content type to use multilingual support.
- $this->drupalGet('admin/structure/node-type/page');
+ $this->drupalGet('admin/structure/types/manage/page');
$this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.'));
$edit = array(
'language_content_type' => 1,
);
- $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type'));
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
$this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Page')), t('Page content type has been updated.'));
$this->drupalLogout();
diff --git a/modules/menu/CVS/Entries b/modules/menu/CVS/Entries
index 0d17224..fcc5e31 100644
--- a/modules/menu/CVS/Entries
+++ b/modules/menu/CVS/Entries
@@ -1,8 +1,8 @@
-/menu.api.php/1.9/Thu Sep 3 08:50:27 2009//
/menu.info/1.9/Thu Sep 3 08:50:27 2009//
-/menu.install/1.19/Thu Sep 3 08:50:27 2009//
/menu.js/1.2/Thu Sep 3 08:50:27 2009//
-/menu.test/1.20/Thu Sep 3 08:50:27 2009//
-/menu.admin.inc/1.57/Sat Sep 5 15:25:49 2009//
-/menu.module/1.203/Sat Sep 5 15:25:49 2009//
+/menu.admin.inc/1.60/Fri Oct 2 19:50:14 2009//
+/menu.api.php/1.12/Fri Oct 2 19:50:14 2009//
+/menu.install/1.20/Fri Oct 2 19:50:14 2009//
+/menu.module/1.205/Fri Oct 2 19:50:14 2009//
+/menu.test/1.22/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc
index 8905d87..f371b83 100644
--- a/modules/menu/menu.admin.inc
+++ b/modules/menu/menu.admin.inc
@@ -1,5 +1,5 @@
l(t('list links'), 'admin/structure/menu-customize/' . $menu['menu_name']));
- $row[] = array('data' => l(t('edit menu'), 'admin/structure/menu-customize/' . $menu['menu_name'] . '/edit'));
- $row[] = array('data' => l(t('add link'), 'admin/structure/menu-customize/' . $menu['menu_name'] . '/add'));
+ $row[] = array('data' => l(t('list links'), 'admin/structure/menu/manage/' . $menu['menu_name']));
+ $row[] = array('data' => l(t('edit menu'), 'admin/structure/menu/manage/' . $menu['menu_name'] . '/edit'));
+ $row[] = array('data' => l(t('add link'), 'admin/structure/menu/manage/' . $menu['menu_name'] . '/add'));
$rows[] = $row;
}
@@ -40,7 +40,7 @@ function theme_menu_admin_overview($title, $name, $description) {
* Shows for one menu the menu links accessible to the current user and
* relevant operations.
*/
-function menu_overview_form(&$form_state, $menu) {
+function menu_overview_form($form, &$form_state, $menu) {
global $menu_admin;
$sql = "
SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
@@ -60,7 +60,7 @@ function menu_overview_form(&$form_state, $menu) {
menu_tree_check_access($tree, $node_links);
$menu_admin = FALSE;
- $form = _menu_overview_tree_form($tree);
+ $form = array_merge($form, _menu_overview_tree_form($tree));
$form['#menu'] = $menu;
if (element_children($form)) {
$form['submit'] = array(
@@ -69,7 +69,7 @@ function menu_overview_form(&$form_state, $menu) {
);
}
else {
- $form['#empty_text'] = t('There are no menu links yet. Add link.', array('@link' => url('admin/structure/menu-customize/'. $form['#menu']['menu_name'] .'/add')));
+ $form['#empty_text'] = t('There are no menu links yet. Add link.', array('@link' => url('admin/structure/menu/manage/'. $form['#menu']['menu_name'] .'/add')));
}
return $form;
}
@@ -243,7 +243,7 @@ function theme_menu_overview_form($form) {
/**
* Menu callback; Build the menu link editing form.
*/
-function menu_edit_item(&$form_state, $type, $item, $menu) {
+function menu_edit_item($form, &$form_state, $type, $item, $menu) {
$form['menu'] = array(
'#type' => 'fieldset',
@@ -398,13 +398,13 @@ function menu_edit_item_submit($form, &$form_state) {
if (!menu_link_save($item)) {
drupal_set_message(t('There was an error saving the menu link.'), 'error');
}
- $form_state['redirect'] = 'admin/structure/menu-customize/' . $item['menu_name'];
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
}
/**
* Menu callback; Build the form that handles the adding/editing of a custom menu.
*/
-function menu_edit_menu(&$form_state, $type, $menu = array()) {
+function menu_edit_menu($form, &$form_state, $type, $menu = array()) {
$system_menus = menu_list_system_menus();
$menu += array('menu_name' => '', 'title' => '', 'description' => '');
@@ -478,7 +478,7 @@ function menu_edit_menu(&$form_state, $type, $menu = array()) {
* Submit function for the 'Delete' button on the menu editing form.
*/
function menu_custom_delete_submit($form, &$form_state) {
- $form_state['redirect'] = 'admin/structure/menu-customize/' . $form_state['values']['menu_name'] . '/delete';
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $form_state['values']['menu_name'] . '/delete';
}
/**
@@ -497,7 +497,7 @@ function menu_delete_menu_page($menu) {
/**
* Build a confirm form for deletion of a custom menu.
*/
-function menu_delete_menu_confirm(&$form_state, $menu) {
+function menu_delete_menu_confirm($form, &$form_state, $menu) {
$form['#menu'] = $menu;
$caption = '';
$num_links = db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField();
@@ -505,7 +505,7 @@ function menu_delete_menu_confirm(&$form_state, $menu) {
$caption .= '
' . format_plural($num_links, 'Warning: There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', 'Warning: There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $menu['title'])) . '
';
}
$caption .= '
' . t('This action cannot be undone.') . '
';
- return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu['title'])), 'admin/structure/menu-customize/' . $menu['menu_name'], $caption, t('Delete'));
+ return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu['title'])), 'admin/structure/menu/manage/' . $menu['menu_name'], $caption, t('Delete'));
}
/**
@@ -525,7 +525,7 @@ function menu_delete_menu_confirm_submit($form, &$form_state) {
menu_reset_item($item);
}
// Delete all links to the overview page for this menu.
- $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu-customize/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
+ $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $m) {
menu_link_delete($m['mlid']);
}
@@ -570,7 +570,7 @@ function menu_edit_menu_validate($form, &$form_state) {
// We will add 'menu-' to the menu name to help avoid name-space conflicts.
$item['menu_name'] = 'menu-' . $item['menu_name'];
$custom_exists = db_query('SELECT menu_name FROM {menu_custom} WHERE menu_name = :menu', array(':menu' => $item['menu_name']))->fetchField();
- $link_exists = db_query_range("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $item['menu_name']), 0, 1)->fetchField();
+ $link_exists = db_query_range("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu", 0, 1, array(':menu' => $item['menu_name']))->fetchField();
if ($custom_exists || $link_exists) {
form_set_error('menu_name', t('The menu already exists.'));
}
@@ -582,7 +582,7 @@ function menu_edit_menu_validate($form, &$form_state) {
*/
function menu_edit_menu_submit($form, &$form_state) {
$menu = $form_state['values'];
- $path = 'admin/structure/menu-customize/';
+ $path = 'admin/structure/menu/manage/';
if ($form['#insert']) {
// Add 'menu-' to the menu name to help avoid name-space conflicts.
$menu['menu_name'] = 'menu-' . $menu['menu_name'];
@@ -639,9 +639,9 @@ function menu_item_delete_page($item) {
/**
* Build a confirm form for deletion of a single menu link.
*/
-function menu_item_delete_form(&$form_state, $item) {
+function menu_item_delete_form($form, &$form_state, $item) {
$form['#item'] = $item;
- return confirm_form($form, t('Are you sure you want to delete the custom menu link %item?', array('%item' => $item['link_title'])), 'admin/structure/menu-customize/' . $item['menu_name']);
+ return confirm_form($form, t('Are you sure you want to delete the custom menu link %item?', array('%item' => $item['link_title'])), 'admin/structure/menu/manage/' . $item['menu_name']);
}
/**
@@ -653,15 +653,15 @@ function menu_item_delete_form_submit($form, &$form_state) {
$t_args = array('%title' => $item['link_title']);
drupal_set_message(t('The menu link %title has been deleted.', $t_args));
watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE);
- $form_state['redirect'] = 'admin/structure/menu-customize/' . $item['menu_name'];
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
}
/**
* Menu callback; reset a single modified menu link.
*/
-function menu_reset_item_confirm(&$form_state, $item) {
+function menu_reset_item_confirm($form, &$form_state, $item) {
$form['item'] = array('#type' => 'value', '#value' => $item);
- return confirm_form($form, t('Are you sure you want to reset the link %item to its default values?', array('%item' => $item['link_title'])), 'admin/structure/menu-customize/' . $item['menu_name'], t('Any customizations will be lost. This action cannot be undone.'), t('Reset'));
+ return confirm_form($form, t('Are you sure you want to reset the link %item to its default values?', array('%item' => $item['link_title'])), 'admin/structure/menu/manage/' . $item['menu_name'], t('Any customizations will be lost. This action cannot be undone.'), t('Reset'));
}
/**
@@ -671,7 +671,7 @@ function menu_reset_item_confirm_submit($form, &$form_state) {
$item = $form_state['values']['item'];
$new_item = menu_reset_item($item);
drupal_set_message(t('The menu link was reset to its default settings.'));
- $form_state['redirect'] = 'admin/structure/menu-customize/' . $new_item['menu_name'];
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $new_item['menu_name'];
}
/**
diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php
index 6e5e82b..c7c991e 100644
--- a/modules/menu/menu.api.php
+++ b/modules/menu/menu.api.php
@@ -1,5 +1,5 @@
'mymodule_abc_view',
+ * );
+ * }
+ *
+ * function mymodule_abc_view($ghi = 0, $jkl = '') {
+ * // ...
+ * }
+ * @endcode
+ * When the path 'abc/def' was requested, then no further arguments would be
+ * passed to the callback function, so $ghi and $jkl would take the default
+ * values as defined in the function signature.
+ * In case 'abc/def/123/foo' was requested, then $ghi would be '123' and $jkl
+ * would be 'foo'.
+ *
+ * In addition to optional path arguments, the definition for each path may
+ * specify a list of arguments for each callback function as an array. These
+ * argument lists may contain fixed/hard-coded argument values, but may also
+ * contain integers that correspond to path components. When integers are used
+ * and the callback function is called, the corresponding path components will
+ * be substituted. For example:
+ * @code
+ * function mymodule_menu() {
+ * $items['abc/def'] = array(
+ * 'page callback' => 'mymodule_abc_view',
+ * 'page arguments' => array(1, 'foo'),
+ * );
+ * }
+ * @endcode
+ * When the path 'abc/def' was requested, the callback function would get 'def'
+ * as first argument and (always) 'foo' as second argument.
+ * The integer 1 in an argument list would be replaced with 'def' and integer 0
+ * would be replaced with 'abc', i.e. path components are counted from zero.
+ * This allows to re-use a callback function for several different paths.
+ *
+ * Arguments may also be used to replace wildcards within paths. For example,
+ * when registering the path 'my-module/%/edit':
+ * @code
+ * $items['my-module/%/edit'] = array(
+ * 'page callback' => 'mymodule_abc_edit',
+ * 'page arguments' => array(1),
+ * );
+ * @endcode
+ * When the path 'my-module/foo/edit' is requested, then integer 1 will be
+ * replaced with 'foo' and passed to the callback function.
+ *
+ * Registered paths may also contain special "auto-loader" wildcard components
+ * in the form of '%mymodule_abc', where the '%' part means that this path
+ * component is a wildcard, and the 'mymodule_abc' part defines the prefix for a
+ * menu argument loader function, which here would be mymodule_abc_load().
+ * For example, when registering the path 'my-module/%mymodule_abc/edit':
+ * @code
+ * $items['my-module/%mymodule_abc/edit'] = array(
+ * 'page callback' => 'mymodule_abc_edit',
+ * 'page arguments' => array(1),
+ * );
+ * @endcode
+ * When the path 'my-module/123/edit' is requested, then the argument loader
+ * function mymodule_abc_load() will be invoked with the argument '123', and it
+ * is supposed to take that value to load and return data for "abc" having the
+ * internal id 123:
+ * @code
+ * function mymodule_abc_load($abc_id) {
+ * return db_query("SELECT * FROM {mymodule_abc} WHERE abc_id = :abc_id", array(':abc_id' => $abc_id))->fetchObject();
+ * }
+ * @endcode
+ * The returned data of the argument loader will be passed in place of the
+ * original path component to all callback functions referring to that (integer)
+ * component in their argument list.
+ * Menu argument loader functions may also be passed additional arguments; see
+ * "load arguments" below.
+ *
+ * If a registered path defines an argument list, then those defined arguments
+ * will always be passed first to the callback function. In case there are any
+ * further components contained in the requested path, then those will always
+ * come last.
+ *
+ * Special care should be taken for the page callback drupal_get_form(), because
+ * the callback function will always receive $form and &$form_state as the very
+ * first function arguments:
+ * @code
+ * function mymodule_abc_form($form, &$form_state) {
+ * // ...
+ * return $form;
+ * }
+ * @endcode
+ * See @link form_api Form API documentation @endlink for details.
+ *
+ * This hook is rarely called (for example, when modules are enabled), and
+ * its results are cached in the database.
*
* @return
* An array of menu items. Each menu item has a key corresponding to the
- * Drupal path being registered. The item is an associative array that may
- * contain the following key-value pairs:
+ * Drupal path being registered. The corresponding array value is an
+ * associative array that may contain the following key-value pairs:
* - "title": Required. The untranslated title of the menu item.
- * - "title callback": Function to generate the title, defaults to t().
+ * - "title callback": Function to generate the title; defaults to t().
* If you require only the raw string to be output, set this to FALSE.
- * - "title arguments": Arguments to send to t() or your custom callback.
+ * - "title arguments": Arguments to send to t() or your custom callback,
+ * with path component substitution as described above.
* - "description": The untranslated description of the menu item.
* - "page callback": The function to call to display a web page when the user
* visits the path. If omitted, the parent menu item's callback will be used
* instead.
* - "page arguments": An array of arguments to pass to the page callback
- * function. Integer values pass the corresponding URL component (see arg()).
- * - "access callback": A function returning a boolean value that determines
+ * function, with path component substitution as described above.
+ * - "access callback": A function returning a boolean value that determines
* whether the user has access rights to this menu item. Defaults to
* user_access() unless a value is inherited from a parent menu item.
* - "access arguments": An array of arguments to pass to the access callback
- * function. Integer values pass the corresponding URL component.
+ * function, with path component substitution as described above.
+ * - "theme callback": Optional. A function returning the machine-readable
+ * name of the theme that will be used to render the page. If the function
+ * returns nothing, the main site theme will be used. If no function is
+ * provided, the main site theme will also be used, unless a value is
+ * inherited from a parent menu item.
+ * - "theme arguments": An array of arguments to pass to the theme callback
+ * function, with path component substitution as described above.
* - "file": A file that will be included before the callbacks are accessed;
* this allows callback functions to be in separate files. The file should
* be relative to the implementing module's directory unless otherwise
- * specified by the "file path" option.
+ * specified by the "file path" option. Note: This does not apply to the
+ * 'access callback'.
* - "file path": The path to the folder containing the file specified in
* "file". This defaults to the path to the module implementing the hook.
* - "load arguments": An array of arguments to be passed to each of the
- * object loaders in the path. For example, for the router item at
- * node/%node/revisions/%/view, the array(1, 3) will call node_load() with
- * the arguments corresponding to the second and fourth URL argument;
- * as with other arguments, integers are automatically cast to URL
- * arguments. There are also two "magic" values: "%index" will correspond
- * to the URL index where the object's load function is specified; "%map"
- * will correspond to the full menu map, passed in by reference to the
- * load function.
- * - "weight": An integer that determines relative position of items in the
- * menu; higher-weighted items sink. Defaults to 0. When in doubt, leave
+ * wildcard object loaders in the path. For example, for the path
+ * node/%node/revisions/%/view, a "load arguments" value of array(1, 3) will
+ * call node_load() with the second and fourth path components passed in (as
+ * described above, integers are automatically replaced with path
+ * components). There are also two "magic" values: "%index" will correspond
+ * to the index of the wildcard path component, and "%map" will correspond
+ * to the full menu map, passed in by reference.
+ * - "weight": An integer that determines the relative position of items in
+ * the menu; higher-weighted items sink. Defaults to 0. When in doubt, leave
* this alone; the default alphabetical order is usually best.
* - "menu_name": Optional. Set this to a custom menu if you don't want your
* item to be placed in Navigation.
+ * - "tab_parent": For local task menu items, the path of the task's parent
+ * item; defaults to the same path without the last component (e.g., the
+ * default parent for 'admin/people/create' is 'admin/people').
+ * - "tab_root": For local task menu items, the path of the closest non-tab
+ * item; same default as "tab_parent".
+ * - "block callback": Name of a function used to render the block on the
+ * system administration page for this item (called with no arguments).
+ * If not provided, system_admin_menu_block() is used to generate it.
+ * - "position": Position of the block ('left' or 'right') on the system
+ * administration page for this item.
* - "type": A bitmask of flags describing properties of the menu item.
* Many shortcut bitmasks are provided as constants in menu.inc:
* - MENU_NORMAL_ITEM: Normal menu items show up in the menu tree and can be
* moved/hidden by the administrator.
* - MENU_CALLBACK: Callbacks simply register a path so that the correct
- * function is fired when the URL is accessed.
+ * function is fired when the path is accessed.
* - MENU_SUGGESTED_ITEM: Modules may "suggest" menu items that the
* administrator may enable.
* - MENU_LOCAL_TASK: Local tasks are rendered as tabs by default.
* - MENU_DEFAULT_LOCAL_TASK: Every set of local tasks should provide one
* "default" task, that links to the same path as its parent when clicked.
- * If the "type" key is omitted, MENU_NORMAL_ITEM is assumed.
+ * If the "type" element is omitted, MENU_NORMAL_ITEM is assumed.
+ *
* For a detailed usage example, see page_example.module.
* For comprehensive documentation on the menu system, see
* http://drupal.org/node/102338.
*/
function hook_menu() {
- $items = array();
-
$items['blog'] = array(
'title' => 'blogs',
'page callback' => 'blog_page',
@@ -147,6 +262,78 @@ function hook_translated_menu_link_alter(&$item, $map) {
}
}
+ /**
+ * Inform modules that a menu link has been created.
+ *
+ * This hook is used to notify module that menu items have been
+ * created. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param $link
+ * The $link record saved into the {menu_links} table.
+ * @return
+ * None.
+ *
+ * @see hook_menu_link_update()
+ * @see hook_menu_link_delete()
+ */
+function hook_menu_link_insert($link) {
+ // In our sample case, we track menu items as editing sections
+ // of the site. These are stored in our table as 'disabled' items.
+ $record['mlid'] = $link['mlid'];
+ $record['menu_name'] = $link['menu_name'];
+ $record['status'] = 0;
+ drupal_write_record('menu_example', $record);
+}
+
+/**
+ * Inform modules that a menu link has been updated.
+ *
+ * This hook is used to notify module that menu items have been
+ * updated. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param $link
+ * The $link record saved into the {menu_links} table.
+ * @return
+ * None.
+ *
+ * @see hook_menu_link_insert()
+ * @see hook_menu_link_delete()
+ */
+function hook_menu_link_update($link) {
+ // If the parent menu has changed, update our record.
+ $menu_name = db_result(db_query("SELECT mlid, menu_name, status FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $link['mlid'])));
+ if ($menu_name != $link['menu_name']) {
+ db_update('menu_example')
+ ->fields(array('menu_name' => $link['menu_name']))
+ ->condition('mlid', $link['mlid'])
+ ->execute();
+ }
+}
+
+/**
+ * Inform modules that a menu link has been deleted.
+ *
+ * This hook is used to notify module that menu items have been
+ * deleted. Contributed modules may use the information to perform
+ * actions based on the information entered into the menu system.
+ *
+ * @param $link
+ * The $link record saved into the {menu_links} table.
+ * @return
+ * None.
+ *
+ * @see hook_menu_link_insert()
+ * @see hook_menu_link_update()
+ */
+function hook_menu_link_delete($link) {
+ // Delete the record from our table.
+ db_delete('menu_example')
+ ->condition('mlid', $link['mlid'])
+ ->execute();
+}
+
/**
* @} End of "addtogroup hooks".
*/
diff --git a/modules/menu/menu.install b/modules/menu/menu.install
index e40ad73..bbf300f 100644
--- a/modules/menu/menu.install
+++ b/modules/menu/menu.install
@@ -1,5 +1,5 @@
'The Navigation menu contains links such as Recent posts (if the Tracker module is enabled). Non-administrative links are added to this menu by default by modules.',
@@ -31,8 +29,6 @@ function menu_install() {
* Implement hook_uninstall().
*/
function menu_uninstall() {
- // Remove tables.
- drupal_uninstall_schema('menu');
menu_rebuild();
}
diff --git a/modules/menu/menu.module b/modules/menu/menu.module
index 5225e7b..af02289 100644
--- a/modules/menu/menu.module
+++ b/modules/menu/menu.module
@@ -1,5 +1,5 @@
5,
'file' => 'menu.admin.inc',
);
- $items['admin/structure/menu-customize/%menu'] = array(
+ $items['admin/structure/menu/manage/%menu'] = array(
'title' => 'Customize menu',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_overview_form', 3),
+ 'page arguments' => array('menu_overview_form', 4),
'title callback' => 'menu_overview_title',
- 'title arguments' => array(3),
+ 'title arguments' => array(4),
'access arguments' => array('administer menu'),
'type' => MENU_CALLBACK,
'file' => 'menu.admin.inc',
);
- $items['admin/structure/menu-customize/%menu/list'] = array(
+ $items['admin/structure/menu/manage/%menu/list'] = array(
'title' => 'List links',
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
- $items['admin/structure/menu-customize/%menu/add'] = array(
+ $items['admin/structure/menu/manage/%menu/add'] = array(
'title' => 'Add link',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_item', 'add', NULL, 3),
+ 'page arguments' => array('menu_edit_item', 'add', NULL, 4),
'access arguments' => array('administer menu'),
'type' => MENU_LOCAL_ACTION,
'file' => 'menu.admin.inc',
);
- $items['admin/structure/menu-customize/%menu/edit'] = array(
+ $items['admin/structure/menu/manage/%menu/edit'] = array(
'title' => 'Edit menu',
'page callback' => 'drupal_get_form',
- 'page arguments' => array('menu_edit_menu', 'edit', 3),
+ 'page arguments' => array('menu_edit_menu', 'edit', 4),
'access arguments' => array('administer menu'),
'type' => MENU_LOCAL_TASK,
'file' => 'menu.admin.inc',
);
- $items['admin/structure/menu-customize/%menu/delete'] = array(
+ $items['admin/structure/menu/manage/%menu/delete'] = array(
'title' => 'Delete menu',
'page callback' => 'menu_delete_menu_page',
- 'page arguments' => array(3),
+ 'page arguments' => array(4),
'access arguments' => array('administer menu'),
'type' => MENU_CALLBACK,
'file' => 'menu.admin.inc',
@@ -168,7 +168,7 @@ function menu_theme() {
function menu_enable() {
menu_rebuild();
$base_link = db_query("SELECT mlid AS plid, menu_name FROM {menu_links} WHERE link_path = 'admin/structure/menu' AND module = 'system'")->fetchAssoc();
- $base_link['router_path'] = 'admin/structure/menu-customize/%';
+ $base_link['router_path'] = 'admin/structure/menu/manage/%';
$base_link['module'] = 'menu';
$result = db_query("SELECT * FROM {menu_custom}", array(), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $menu) {
@@ -176,7 +176,7 @@ function menu_enable() {
$link = $base_link;
$link['mlid'] = 0;
$link['link_title'] = $menu['title'];
- $link['link_path'] = 'admin/structure/menu-customize/' . $menu['menu_name'];
+ $link['link_path'] = 'admin/structure/menu/manage/' . $menu['menu_name'];
$menu_link = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND plid = :plid", array(
':path' => $link['link_path'],
':plid' => $link['plid']
@@ -365,17 +365,15 @@ function menu_node_prepare($node) {
$item = array();
if (isset($node->nid)) {
// Give priority to the default menu
- $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND menu_name = :menu_name AND module = 'menu' ORDER BY mlid ASC", array(
+ $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND menu_name = :menu_name AND module = 'menu' ORDER BY mlid ASC", 0, 1, array(
':path' => 'node/' . $node->nid,
':menu_name' => $menu_name,
- ), 0, 1)
- ->fetchField();
+ ))->fetchField();
// Check all menus if a link does not exist in the default menu.
if (!$mlid) {
- $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu' ORDER BY mlid ASC", array(
+ $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu' ORDER BY mlid ASC", 0, 1, array(
':path' => 'node/' . $node->nid,
- ), 0, 1)
- ->fetchField();
+ ))->fetchField();
}
if ($mlid) {
$item = menu_link_load($mlid);
diff --git a/modules/menu/menu.test b/modules/menu/menu.test
index d3d7be3..22989b7 100644
--- a/modules/menu/menu.test
+++ b/modules/menu/menu.test
@@ -1,5 +1,10 @@
menu['title'];
// Delete custom menu.
- $this->drupalPost("admin/structure/menu-customize/$menu_name/delete", array(), t('Delete'));
+ $this->drupalPost("admin/structure/menu/manage/$menu_name/delete", array(), t('Delete'));
$this->assertResponse(200);
$this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $title)), t('Custom menu was deleted'));
$this->assertFalse(menu_load($menu_name), 'Custom menu was deleted');
@@ -181,7 +186,7 @@ class MenuTestCase extends DrupalWebTestCase {
// Note in the UI the 'mlid:x[hidden]' form element maps to enabled, or
// NOT hidden.
$edit['mlid:' . $item1['mlid'] . '[hidden]'] = TRUE;
- $this->drupalPost('admin/structure/menu-customize/' . $item1['menu_name'], $edit, t('Save configuration'));
+ $this->drupalPost('admin/structure/menu/manage/' . $item1['menu_name'], $edit, t('Save configuration'));
// Verify in the database.
$hidden = db_query("SELECT hidden FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item1['mlid']))->fetchField();
@@ -202,7 +207,7 @@ class MenuTestCase extends DrupalWebTestCase {
*/
function addMenuLink($plid = 0, $link = '', $menu_name = 'navigation') {
// View add menu link page.
- $this->drupalGet("admin/structure/menu-customize/$menu_name/add");
+ $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
$this->assertResponse(200);
$title = '!link_' . $this->randomName(16);
@@ -217,7 +222,7 @@ class MenuTestCase extends DrupalWebTestCase {
);
// Add menu link.
- $this->drupalPost("admin/structure/menu-customize/$menu_name/add", $edit, t('Save'));
+ $this->drupalPost("admin/structure/menu/manage/$menu_name/add", $edit, t('Save'));
$this->assertResponse(200);
// Unlike most other modules, there is no confirmation message displayed.
@@ -254,7 +259,7 @@ class MenuTestCase extends DrupalWebTestCase {
'menu[link_path]' => $link_path,
'menu[link_title]' => 'title',
);
- $this->drupalPost("admin/structure/menu-customize/$menu_name/add", $edit, t('Save'));
+ $this->drupalPost("admin/structure/menu/manage/$menu_name/add", $edit, t('Save'));
$this->assertRaw(t("The path '@path' is either invalid or you do not have access to it.", array('@path' => $link_path)), 'Menu link was not created');
}
}
@@ -313,7 +318,7 @@ class MenuTestCase extends DrupalWebTestCase {
// Unlike most other modules, there is no confirmation message displayed.
// Verify menu link.
- $this->drupalGet('admin/structure/menu-customize/' . $item['menu_name']);
+ $this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']);
$this->assertText($title, 'Menu link was edited');
}
@@ -447,7 +452,7 @@ class MenuTestCase extends DrupalWebTestCase {
}
// View navigation menu customization node.
- $this->drupalGet('admin/structure/menu-customize/navigation');
+ $this->drupalGet('admin/structure/menu/manage/navigation');
$this->assertResponse($response);
if ($response == 200) {
$this->assertText(t('Navigation'), t('Navigation menu node was displayed'));
diff --git a/modules/node/CVS/Entries b/modules/node/CVS/Entries
index 2b47fcd..7187ab7 100644
--- a/modules/node/CVS/Entries
+++ b/modules/node/CVS/Entries
@@ -1,15 +1,15 @@
D/tests////
/content_types.js/1.7/Thu Sep 3 08:50:28 2009//
/node-rtl.css/1.3/Thu Sep 3 08:50:28 2009//
-/node.api.php/1.37/Thu Sep 3 08:50:28 2009//
/node.css/1.8/Thu Sep 3 08:50:28 2009//
/node.info/1.12/Thu Sep 3 08:50:28 2009//
-/node.install/1.29/Thu Sep 3 08:50:28 2009//
/node.js/1.4/Thu Sep 3 08:50:28 2009//
-/node.test/1.43/Thu Sep 3 08:50:28 2009//
-/node.tokens.inc/1.2/Thu Sep 3 08:50:28 2009//
-/node.tpl.php/1.21/Thu Sep 3 08:50:28 2009//
-/node.admin.inc/1.65/Sat Sep 5 13:40:16 2009//
-/node.module/1.1119/Sat Sep 5 13:40:16 2009//
-/content_types.inc/1.92/Sat Sep 5 15:25:49 2009//
-/node.pages.inc/1.79/Sat Sep 5 15:25:50 2009//
+/content_types.inc/1.95/Fri Oct 2 19:50:14 2009//
+/node.admin.inc/1.67/Fri Oct 2 19:50:14 2009//
+/node.api.php/1.38/Fri Oct 2 19:50:14 2009//
+/node.install/1.32/Fri Oct 2 19:50:14 2009//
+/node.module/1.1135/Fri Oct 2 19:50:14 2009//
+/node.pages.inc/1.83/Fri Oct 2 19:50:14 2009//
+/node.test/1.46/Fri Oct 2 19:50:14 2009//
+/node.tokens.inc/1.3/Fri Oct 2 19:50:14 2009//
+/node.tpl.php/1.22/Fri Oct 2 19:50:14 2009//
diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc
index f12df24..96f8e44 100644
--- a/modules/node/content_types.inc
+++ b/modules/node/content_types.inc
@@ -1,5 +1,5 @@
type);
$row = array(theme('node_admin_overview', $name, $type));
// Set the edit column.
- $row[] = array('data' => l(t('edit'), 'admin/structure/node-type/' . $type_url_str));
+ $row[] = array('data' => l(t('edit'), 'admin/structure/types/manage/' . $type_url_str));
// Set the delete column.
if ($type->custom) {
- $row[] = array('data' => l(t('delete'), 'admin/structure/node-type/' . $type_url_str . '/delete'));
+ $row[] = array('data' => l(t('delete'), 'admin/structure/types/manage/' . $type_url_str . '/delete'));
}
else {
$row[] = array('data' => '');
@@ -57,7 +57,7 @@ function theme_node_admin_overview($name, $type) {
/**
* Generates the node type editing form.
*/
-function node_type_form(&$form_state, $type = NULL) {
+function node_type_form($form, &$form_state, $type = NULL) {
if (!isset($type->type)) {
// This is a new type. Node module managed types are custom and unlocked.
$type = node_type_set_defaults(array('custom' => 1, 'locked' => 0));
@@ -316,7 +316,7 @@ function node_type_form_submit($form, &$form_state) {
$type->locked = $form_state['values']['locked'];
if ($op == t('Delete content type')) {
- $form_state['redirect'] = 'admin/structure/node-type/' . str_replace('_', '-', $type->old_type) . '/delete';
+ $form_state['redirect'] = 'admin/structure/types/manage/' . str_replace('_', '-', $type->old_type) . '/delete';
return;
}
@@ -350,6 +350,7 @@ function node_type_form_submit($form, &$form_state) {
}
node_types_rebuild();
+ menu_rebuild();
$t_args = array('%name' => $type->name);
if ($status == SAVED_UPDATED) {
@@ -414,7 +415,7 @@ function node_type_reset($type) {
/**
* Menu callback; delete a single content type.
*/
-function node_type_delete_confirm(&$form_state, $type) {
+function node_type_delete_confirm($form, &$form_state, $type) {
$form['type'] = array('#type' => 'value', '#value' => $type->type);
$form['name'] = array('#type' => 'value', '#value' => $type->name);
@@ -444,6 +445,7 @@ function node_type_delete_confirm_submit($form, &$form_state) {
watchdog('menu', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE);
node_types_rebuild();
+ menu_rebuild();
$form_state['redirect'] = 'admin/structure/types';
return;
diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc
index ca76445..a7cb90d 100644
--- a/modules/node/node.admin.inc
+++ b/modules/node/node.admin.inc
@@ -1,5 +1,5 @@
'
', '#suffix' => '
', '#tree' => TRUE);
// array_filter returns only elements with TRUE values
foreach ($nodes as $nid => $value) {
@@ -586,7 +586,6 @@ function node_multiple_delete_confirm_submit($form, &$form_state) {
drupal_set_message(t('Deleted @count posts.', array('@count' => $count)));
}
$form_state['redirect'] = 'admin/content';
- return;
}
/**
@@ -594,5 +593,5 @@ function node_multiple_delete_confirm_submit($form, &$form_state) {
*/
function node_modules_installed($modules) {
// Clear node type cache for node permissions.
- node_type_clear();
+ drupal_static_reset('_node_types_build');
}
diff --git a/modules/node/node.api.php b/modules/node/node.api.php
index 30d38ef..7bdb591 100644
--- a/modules/node/node.api.php
+++ b/modules/node/node.api.php
@@ -1,5 +1,5 @@
condition('vid', $node->vid)->execute();
if (!is_array($node->files)) {
return;
diff --git a/modules/node/node.install b/modules/node/node.install
index 2e5b3bb..25ece92 100644
--- a/modules/node/node.install
+++ b/modules/node/node.install
@@ -1,5 +1,5 @@
'varchar', 'length' => 255, 'not null' => TRUE));
-
- return $ret;
+ db_update('node_type')
+ ->fields(array('module' => 'node_content'))
+ ->condition('module', 'node')
+ ->execute();
}
/**
* Rename {node_revisions} table to {node_revision}.
*/
function node_update_7001() {
- $ret = array();
- db_rename_table($ret, 'node_revisions', 'node_revision');
- return $ret;
+ db_rename_table('node_revisions', 'node_revision');
}
/**
* Extend the node_promote_status index to include all fields required for the node page query.
*/
function node_update_7002() {
- $ret = array();
- db_drop_index($ret, 'node', 'node_promote_status');
- db_add_index($ret, 'node', 'node_frontpage', array('promote', 'status', 'sticky', 'created'));
- return $ret;
+ db_drop_index('node', 'node_promote_status');
+ db_add_index('node', 'node_frontpage', array('promote', 'status', 'sticky', 'created'));
}
/**
* Remove the node_counter if the statistics module is uninstalled.
*/
function node_update_7003() {
- $ret = array();
if (drupal_get_installed_schema_version('statistics') == SCHEMA_UNINSTALLED) {
- db_drop_table($ret, 'node_counter');
+ db_drop_table('node_counter');
}
- return $ret;
}
/**
@@ -408,7 +400,7 @@ function node_update_7004() {
// Map old preview setting to new values order.
$original_preview ? $original_preview = 2 : $original_preview = 1;
- node_type_clear();
+ drupal_static_reset('_node_types_build');
$type_list = node_type_get_types();
// Apply original settings to all types.
@@ -418,33 +410,29 @@ function node_update_7004() {
}
// Delete old variable but leave 'teaser_length' for aggregator module upgrade.
variable_del('node_preview');
-
- return array();
}
/**
* Add status/comment/promote and sticky columns to the {node_revision} table.
*/
function node_update_7005() {
- $ret = array();
foreach(array('status', 'comment', 'promote', 'sticky') as $column) {
- db_add_field($ret, 'node_revision', $column, array(
+ db_add_field('node_revision', $column, array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
));
}
- return $ret;
}
/**
* Convert body and teaser from node properties to fields, and migrate status/comment/promote and sticky columns to the {node_revision} table.
*/
function node_update_7006(&$context) {
- $ret = array('#finished' => 0);
+ $context['#finished'] = 0;
// Get node type info for every invocation.
- node_type_clear();
+ drupal_static_reset('_node_types_build');
$node_types = node_type_get_types();
if (!isset($context['total'])) {
@@ -506,7 +494,11 @@ function node_update_7006(&$context) {
$revision->body = substr($revision->body, strlen($break));
}
$node->body[FIELD_LANGUAGE_NONE][0]['value'] = $revision->body;
- $node->body[FIELD_LANGUAGE_NONE][0]['format'] = $revision->format;
+ // Explicitly store the current default text format if the revision
+ // did not have its own text format. Similar conversions for other
+ // core modules are performed in filter_update_7005(), but we do this
+ // one here since we are already migrating the data.
+ $node->body[FIELD_LANGUAGE_NONE][0]['format'] = !empty($revision->format) ? $revision->format : variable_get('filter_default_format', 1);
// This is a core update and no contrib modules are enabled yet, so
// we can assume default field storage for a faster update.
field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array());
@@ -532,33 +524,29 @@ function node_update_7006(&$context) {
}
}
- $ret['#finished'] = min(0.99, $context['count'] / $context['total']);
+ $context['#finished'] = min(0.99, $context['count'] / $context['total']);
}
if (!$found) {
// All nodes are processed.
- $ret[] = array('success' => TRUE, 'query' => "{$context['total']} node body and teaser properties migrated to the 'body' field.");
// Remove the now-obsolete body info from node_revision.
- db_drop_field($ret, 'node_revision', 'body');
- db_drop_field($ret, 'node_revision', 'teaser');
- db_drop_field($ret, 'node_revision', 'format');
+ db_drop_field('node_revision', 'body');
+ db_drop_field('node_revision', 'teaser');
+ db_drop_field('node_revision', 'format');
// We're done.
- $ret['#finished'] = 1;
+ $context['#finished'] = 1;
+ return t("!number node body and teaser properties migrated to the 'body' field.", array('!number' => $context['total']));
}
}
-
- return $ret;
}
/**
* Remove column min_word_count.
*/
function node_update_7007() {
- $ret = array();
- db_drop_field($ret, 'node_type', 'min_word_count');
- return $ret;
+ db_drop_field('node_type', 'min_word_count');
}
/**
diff --git a/modules/node/node.module b/modules/node/node.module
index 5d610cb..b89091d 100644
--- a/modules/node/node.module
+++ b/modules/node/node.module
@@ -1,5 +1,5 @@
' . t('Each piece of content is of a specific content type. Each content type can have different fields, behaviors, and permissions assigned to it.') . '';
- case 'admin/structure/node-type/' . $arg[3] . '/fields':
+ case 'admin/structure/types/manage/' . $arg[3] . '/fields':
return '
' . t('This form lets you add, edit, and arrange fields within the %type content type.', array('%type' => node_type_get_name($arg[3]))) . '
';
- case 'admin/structure/node-type/' . $arg[3] . '/display':
+ case 'admin/structure/types/manage/' . $arg[3] . '/display':
return '
' . t('This form lets you configure how fields and labels are displayed when %type content is viewed in teaser and full-page mode.', array('%type' => node_type_get_name($arg[3]))) . '
' . t('This form lets you configure how fields should be displayed when rendered %type content in the following contexts.', array('%type' => node_type_get_name($arg[3]))) . '
';
case 'node/%/revisions':
@@ -200,7 +200,7 @@ function node_entity_info() {
$return['node']['bundles'][$type] = array(
'label' => $name,
'admin' => array(
- 'path' => 'admin/structure/node-type/' . str_replace('_', '-', $type),
+ 'path' => 'admin/structure/types/manage/' . str_replace('_', '-', $type),
'access arguments' => array('administer content types'),
),
);
@@ -360,13 +360,6 @@ function _node_extract_type($node) {
return is_object($node) ? $node->type : $node;
}
-/**
- * Clear the statically cached node type information.
- */
-function node_type_clear() {
- drupal_static_reset('_node_types_build');
-}
-
/**
* Returns a list of all the available node types.
*
@@ -448,12 +441,13 @@ function node_type_get_name($node) {
}
/**
- * Resets the database cache of node types, and saves all new or non-modified
- * module-defined node types to the database.
+ * Resets the database cache of node types.
+ *
+ * All new or non-modified module-defined node types are saved to the database.
*/
function node_types_rebuild() {
// Reset and load updated node types.
- node_type_clear();
+ drupal_static_reset('_node_types_build');
foreach (node_type_get_types() as $type => $info) {
if (!empty($info->is_new)) {
node_type_save($info);
@@ -462,11 +456,6 @@ function node_types_rebuild() {
node_type_delete($info->type);
}
}
- // Reset cached node type information so that the next access
- // will use the updated data.
- node_type_clear();
- // This is required for proper menu items at node/add/type.
- menu_rebuild();
}
/**
@@ -481,7 +470,7 @@ function node_types_rebuild() {
function node_type_save($info) {
$is_existing = FALSE;
$existing_type = !empty($info->old_type) ? $info->old_type : $info->type;
- $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $existing_type), 0, 1)->fetchField();
+ $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField();
$type = node_type_set_defaults($info);
$fields = array(
@@ -510,7 +499,7 @@ function node_type_save($info) {
}
node_configure_fields($type);
module_invoke_all('node_type_update', $type);
- return SAVED_UPDATED;
+ $status = SAVED_UPDATED;
}
else {
$fields['orig_type'] = (string) $type->orig_type;
@@ -521,8 +510,13 @@ function node_type_save($info) {
field_attach_create_bundle($type->type);
node_configure_fields($type);
module_invoke_all('node_type_insert', $type);
- return SAVED_NEW;
+ $status = SAVED_NEW;
}
+
+ // Clear the node type cache.
+ drupal_static_reset('_node_types_build');
+
+ return $status;
}
/**
@@ -555,10 +549,7 @@ function node_configure_fields($type) {
'widget_type' => 'text_textarea_with_summary',
'settings' => array('display_summary' => TRUE),
- // With no UI in core, we have to define default
- // formatters for the teaser and full view.
- // This may change if the method of handling displays
- // is changed or if a UI gets into core.
+ // Define default formatters for the teaser and full view.
'display' => array(
'full' => array(
'label' => 'hidden',
@@ -596,6 +587,9 @@ function node_type_delete($type) {
->condition('type', $type)
->execute();
module_invoke_all('node_type_delete', $info);
+
+ // Clear the node type cache.
+ drupal_static_reset('_node_types_build');
}
/**
@@ -786,10 +780,11 @@ function node_load_multiple($nids = array(), $conditions = array(), $reset = FAL
* @return
* A fully-populated node object.
*/
-function node_load($nid, $vid = array(), $reset = FALSE) {
- $vid = isset($vid) ? array('vid' => $vid) : NULL;
- $node = node_load_multiple(array($nid), $vid, $reset);
- return $node ? $node[$nid] : FALSE;
+function node_load($nid = NULL, $vid = NULL, $reset = FALSE) {
+ $nids = (isset($nid) ? array($nid) : array());
+ $conditions = (isset($vid) ? array('vid' => $vid) : array());
+ $node = node_load_multiple($nids, $conditions, $reset);
+ return $node ? reset($node) : FALSE;
}
/**
@@ -1016,6 +1011,31 @@ function node_delete_multiple($nids) {
drupal_static_reset('node_load_multiple');
}
+/**
+ * Delete a node revision.
+ *
+ * @param $revision_id
+ * The revision ID to delete.
+ */
+function node_revision_delete($revision_id) {
+ if ($revision = node_load(NULL, $revision_id)) {
+ // Prevent deleting the current revision.
+ $node = node_load($revision->nid);
+ if ($revision_id == $node->vid) {
+ return FALSE;
+ }
+
+ db_delete('node_revision')
+ ->condition('nid', $revision->nid)
+ ->condition('vid', $revision->vid)
+ ->execute();
+ module_invoke_all('node_revision_delete', $revision);
+ field_attach_delete_revision('node', $revision);
+ return TRUE;
+ }
+ return FALSE;
+}
+
/**
* Generate an array for rendering the given node.
*
@@ -1030,9 +1050,13 @@ function node_delete_multiple($nids) {
function node_build($node, $build_mode = 'full') {
$node = (object)$node;
- $node = node_build_content($node, $build_mode);
+ // Populate $node->content with a render() array.
+ node_build_content($node, $build_mode);
$build = $node->content;
+ // We don't need duplicate rendering info in node->content.
+ unset($node->content);
+
$build += array(
'#theme' => 'node',
'#node' => $node,
@@ -1066,9 +1090,6 @@ function node_build($node, $build_mode = 'full') {
* @param $build_mode
* Build mode, e.g. 'full', 'teaser'...
*
- * @return
- * A structured array containing the individual elements
- * of the node's content.
*/
function node_build_content($node, $build_mode = 'full') {
// The 'view' hook can be implemented to overwrite the default function
@@ -1104,8 +1125,6 @@ function node_build_content($node, $build_mode = 'full') {
// Allow modules to modify the structured node.
drupal_alter('node_build', $node, $build_mode);
-
- return $node;
}
/**
@@ -1163,9 +1182,14 @@ function template_preprocess_node(&$variables) {
// Flatten the node object's member fields.
$variables = array_merge((array)$node, $variables);
+
+ // Helpful $content variable for templates.
+ foreach (element_children($variables['elements']) as $key) {
+ $variables['content'][$key] = $variables['elements'][$key];
+ }
// Make the field variables available with the appropriate language.
- field_attach_preprocess('node', $node, $node->content, $variables);
+ field_attach_preprocess('node', $node, $variables['content'], $variables);
// Display post information only on certain node types.
if (variable_get('node_submitted_' . $node->type, TRUE)) {
@@ -1309,8 +1333,8 @@ function node_search_reset() {
* Implement hook_search_status().
*/
function node_search_status() {
- $total = db_query('SELECT COUNT(*) FROM {node} WHERE status = %d', NODE_PUBLISHED)->fetchField();
- $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = %d AND d.sid IS NULL OR d.reindex <> 0", NODE_PUBLISHED)->fetchField();
+ $total = db_query('SELECT COUNT(*) FROM {node} WHERE status = :status', array(':status' => NODE_PUBLISHED))->fetchField();
+ $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = :status AND d.sid IS NULL OR d.reindex <> 0", array(':status' => NODE_PUBLISHED))->fetchField();
return array('remaining' => $remaining, 'total' => $total);
}
@@ -1382,7 +1406,7 @@ function node_search_execute($keys = NULL) {
foreach ($find as $item) {
// Render the node.
$node = node_load($item->sid);
- $node = node_build_content($node, 'search_result');
+ node_build_content($node, 'search_result');
$node->rendered = drupal_render($node->content);
// Fetch comments for snippet.
@@ -1486,13 +1510,12 @@ function node_user_cancel($edit, $account, $method) {
->condition('uid', $account->uid)
->execute()
->fetchCol();
- foreach ($nodes as $nid) {
- node_delete($nid);
- }
+ node_delete_multiple($nodes);
// Delete old revisions.
- db_delete('node_revision')
- ->condition('uid', $account->uid)
- ->execute();
+ $revisions = db_query('SELECT vid FROM {node_revision} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol();
+ foreach ($revisions as $revision) {
+ node_revision_delete($revision);
+ }
// Clean history.
db_delete('history')
->condition('uid', $account->uid)
@@ -1572,7 +1595,7 @@ function _node_add_access() {
function node_menu() {
$items['admin/content'] = array(
'title' => 'Content',
- 'description' => 'Find and manage content and comments.',
+ 'description' => 'Find and manage content.',
'page callback' => 'drupal_get_form',
'page arguments' => array('node_admin_content'),
'access arguments' => array('administer nodes'),
@@ -1581,6 +1604,7 @@ function node_menu() {
);
$items['admin/content/node'] = array(
'title' => 'Content',
+ 'description' => "View, edit, and delete your site's content.",
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
@@ -1628,6 +1652,7 @@ function node_menu() {
'access callback' => '_node_add_access',
'weight' => 1,
'menu_name' => 'management',
+ 'theme callback' => '_node_custom_theme',
'file' => 'node.pages.inc',
);
$items['rss.xml'] = array(
@@ -1636,8 +1661,9 @@ function node_menu() {
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
- // Reset internal static cache of _node_types_build, forces to rebuild the node type information.
- node_type_clear();
+ // Reset internal static cache of _node_types_build(), forces to rebuild the
+ // node type information.
+ drupal_static_reset('_node_types_build');
foreach (node_type_get_types() as $type) {
$type_url_str = str_replace('_', '-', $type->type);
$items['node/add/' . $type_url_str] = array(
@@ -1650,7 +1676,7 @@ function node_menu() {
'description' => $type->description,
'file' => 'node.pages.inc',
);
- $items['admin/structure/node-type/' . $type_url_str] = array(
+ $items['admin/structure/types/manage/' . $type_url_str] = array(
'title' => $type->name,
'page callback' => 'drupal_get_form',
'page arguments' => array('node_type_form', $type),
@@ -1658,11 +1684,11 @@ function node_menu() {
'type' => MENU_CALLBACK,
'file' => 'content_types.inc',
);
- $items['admin/structure/node-type/' . $type_url_str . '/edit'] = array(
+ $items['admin/structure/types/manage/' . $type_url_str . '/edit'] = array(
'title' => 'Edit',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
- $items['admin/structure/node-type/' . $type_url_str . '/delete'] = array(
+ $items['admin/structure/types/manage/' . $type_url_str . '/delete'] = array(
'title' => 'Delete',
'page arguments' => array('node_type_delete_confirm', $type),
'access arguments' => array('administer content types'),
@@ -1688,6 +1714,7 @@ function node_menu() {
'page arguments' => array(1),
'access callback' => 'node_access',
'access arguments' => array('update', 1),
+ 'theme callback' => '_node_custom_theme',
'weight' => 1,
'type' => MENU_LOCAL_TASK,
'file' => 'node.pages.inc',
@@ -1751,6 +1778,17 @@ function node_page_title($node) {
return $node->title;
}
+/**
+ * Theme callback for creating and editing nodes.
+ */
+function _node_custom_theme() {
+ // Use the administration theme if the site is configured to use it for
+ // nodes.
+ if (variable_get('node_admin_theme')) {
+ return variable_get('admin_theme');
+ }
+}
+
/**
* Implement hook_init().
*/
@@ -1821,7 +1859,7 @@ function node_feed($nids = FALSE, $channel = array()) {
->fetchCol();
}
- $item_length = variable_get('feed_item_length', 'teaser');
+ $item_length = variable_get('feed_item_length', 'fulltext');
$namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/');
$teaser = ($item_length == 'teaser');
@@ -1841,7 +1879,7 @@ function node_feed($nids = FALSE, $channel = array()) {
// The node gets built and modules add to or modify $node->rss_elements
// and $node->rss_namespaces.
- $node = node_build_content($node, 'rss');
+ node_build_content($node, 'rss');
if (!empty($node->rss_namespaces)) {
$namespaces = array_merge($namespaces, $node->rss_namespaces);
@@ -1870,7 +1908,7 @@ function node_feed($nids = FALSE, $channel = array()) {
$output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']);
$output .= "\n";
- drupal_set_header('Content-Type', 'application/rss+xml; charset=utf-8');
+ drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
print $output;
}
@@ -1985,7 +2023,7 @@ function _node_index_node($node) {
variable_set('node_cron_last', $node->changed);
// Render the node.
- $node = node_build_content($node, 'search_index');
+ node_build_content($node, 'search_index');
$node->rendered = drupal_render($node->content);
$text = '
' . check_plain($node->title) . '
' . $node->rendered;
@@ -2183,7 +2221,7 @@ function node_search_validate($form, &$form_state) {
* Optional, a user object representing the user for whom the operation is to
* be performed. Determines access for a user other than the current user.
* @return
- * TRUE if the operation may be performed.
+ * TRUE if the operation may be performed, FALSE otherwise.
*/
function node_access($op, $node, $account = NULL) {
global $user;
@@ -2232,13 +2270,14 @@ function node_access($op, $node, $account = NULL) {
// node_access table.
if ($op != 'create' && $node->nid) {
$query = db_select('node_access');
- $query->addExpression('COUNT(*)');
+ $query->addExpression('1');
$query->condition('grant_' . $op, 1, '>=');
$nids = db_or()->condition('nid', $node->nid);
if ($node->status) {
$nids->condition('nid', 0);
}
$query->condition($nids);
+ $query->range(0, 1);
$grants = db_or();
foreach (node_access_grants($op, $account) as $realm => $gids) {
@@ -2249,10 +2288,10 @@ function node_access($op, $node, $account = NULL) {
);
}
}
- if (count($grants) > 0 ) {
+ if (count($grants) > 0) {
$query->condition($grants);
}
- return $query
+ return (bool) $query
->execute()
->fetchField();
}
@@ -2353,61 +2392,6 @@ function node_permissions_get_configured_types() {
return $configured_types;
}
-/**
- * Generate an SQL join clause for use in fetching a node listing.
- *
- * @param $node_alias
- * If the node table has been given an SQL alias other than the default
- * "n", that must be passed here.
- * @param $node_access_alias
- * If the node_access table has been given an SQL alias other than the default
- * "na", that must be passed here.
- * @return
- * An SQL join clause.
- */
-function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') {
- if (user_access('bypass node access')) {
- return '';
- }
-
- return 'INNER JOIN {node_access} ' . $node_access_alias . ' ON ' . $node_access_alias . '.nid = ' . $node_alias . '.nid';
-}
-
-/**
- * Generate an SQL where clause for use in fetching a node listing.
- *
- * @param $op
- * The operation that must be allowed to return a node.
- * @param $node_access_alias
- * If the node_access table has been given an SQL alias other than the default
- * "na", that must be passed here.
- * @param $account
- * The user object for the user performing the operation. If omitted, the
- * current user is used.
- * @return
- * An SQL where clause.
- */
-function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $account = NULL) {
- if (user_access('bypass node access')) {
- return;
- }
-
- $grants = array();
- foreach (node_access_grants($op, $account) as $realm => $gids) {
- foreach ($gids as $gid) {
- $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')";
- }
- }
-
- $grants_sql = '';
- if (count($grants)) {
- $grants_sql = 'AND (' . implode(' OR ', $grants) . ')';
- }
-
- $sql = "$node_access_alias.grant_$op >= 1 $grants_sql";
- return $sql;
-}
-
/**
* Fetch an array of permission IDs granted to the given user ID.
*
@@ -2481,17 +2465,6 @@ function node_access_view_all_nodes() {
return $access;
}
-/**
- * Implement hook_db_rewrite_sql().
- */
-function node_db_rewrite_sql($query, $primary_table, $primary_field) {
- if ($primary_field == 'nid' && !node_access_view_all_nodes()) {
- $return['join'] = _node_access_join_sql($primary_table);
- $return['where'] = _node_access_where_sql();
- $return['distinct'] = 1;
- return $return;
- }
-}
/**
* Implement hook_query_TAG_alter().
@@ -2736,7 +2709,7 @@ function _node_access_rebuild_batch_operation(&$context) {
// Process the next 20 nodes.
$limit = 20;
- $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", array(':nid' => $context['sandbox']['current_node']), 0, $limit)->fetchCol();
+ $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", 0, $limit, array(':nid' => $context['sandbox']['current_node']))->fetchCol();
$nodes = node_load_multiple($nids, array(), TRUE);
foreach ($nodes as $node) {
// To preserve database integrity, only acquire grants if the node
@@ -2782,11 +2755,8 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) {
* Implement hook_form().
*/
function node_content_form($node, $form_state) {
-
$type = node_type_get_type($node);
- $form = array();
-
if ($type->has_title) {
$form['title'] = array(
'#type' => 'textfield',
@@ -2819,33 +2789,6 @@ function node_forms() {
return $forms;
}
-/**
- * Implement hook_hook_info().
- */
-function node_hook_info() {
- return array(
- 'node' => array(
- 'node' => array(
- 'presave' => array(
- 'runs when' => t('When either saving a new post or updating an existing post'),
- ),
- 'insert' => array(
- 'runs when' => t('After saving a new post'),
- ),
- 'update' => array(
- 'runs when' => t('After saving an updated post'),
- ),
- 'delete' => array(
- 'runs when' => t('After deleting a post')
- ),
- 'view' => array(
- 'runs when' => t('When content is viewed by an authenticated user')
- ),
- ),
- ),
- );
-}
-
/**
* Implement hook_action_info().
*/
@@ -2853,90 +2796,84 @@ function node_action_info() {
return array(
'node_publish_action' => array(
'type' => 'node',
- 'description' => t('Publish post'),
+ 'label' => t('Publish content'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
),
'node_unpublish_action' => array(
'type' => 'node',
- 'description' => t('Unpublish post'),
+ 'label' => t('Unpublish content'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete'
),
),
'node_make_sticky_action' => array(
'type' => 'node',
- 'description' => t('Make post sticky'),
+ 'label' => t('Make content sticky'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
),
'node_make_unsticky_action' => array(
'type' => 'node',
- 'description' => t('Make post unsticky'),
+ 'label' => t('Make content unsticky'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete'
),
),
'node_promote_action' => array(
'type' => 'node',
- 'description' => t('Promote post to front page'),
+ 'label' => t('Promote content to front page'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'comment_insert', 'comment_update'),
),
'node_unpromote_action' => array(
'type' => 'node',
- 'description' => t('Remove post from front page'),
+ 'label' => t('Remove content from front page'),
'configurable' => FALSE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete'
),
),
'node_assign_owner_action' => array(
'type' => 'node',
- 'description' => t('Change the author of a post'),
+ 'label' => t('Change the author of content'),
'configurable' => TRUE,
'behavior' => array('changes_node_property'),
- 'hooks' => array(
- 'any' => TRUE,
- 'node' => array('presave'),
- 'comment' => array('delete', 'insert', 'update'),
+ 'triggers' => array(
+ 'node_presave',
+ 'comment_insert',
+ 'comment_update',
+ 'comment_delete',
),
),
'node_save_action' => array(
'type' => 'node',
- 'description' => t('Save post'),
+ 'label' => t('Save content'),
'configurable' => FALSE,
- 'hooks' => array(
- 'comment' => array('delete', 'insert', 'update'),
- ),
+ 'triggers' => array('comment_delete', 'comment_insert', 'comment_update'),
),
'node_unpublish_by_keyword_action' => array(
'type' => 'node',
- 'description' => t('Unpublish post containing keyword(s)'),
+ 'label' => t('Unpublish content containing keyword(s)'),
'configurable' => TRUE,
- 'hooks' => array(
- 'node' => array('presave', 'insert', 'update'),
- ),
+ 'triggers' => array('node_presave', 'node_insert', 'node_update'),
),
);
}
@@ -3044,7 +2981,7 @@ function node_assign_owner_action_form($context) {
'#default_value' => $owner_name,
'#autocomplete_path' => 'user/autocomplete',
'#size' => '6',
- '#maxlength' => '7',
+ '#maxlength' => '60',
'#description' => $description,
);
}
@@ -3052,7 +2989,7 @@ function node_assign_owner_action_form($context) {
}
function node_assign_owner_action_validate($form, $form_state) {
- $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']), 0, 1)->fetchField();
+ $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField();
if (!$exists) {
form_set_error('owner_name', t('Please enter a valid username.'));
}
diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc
index 445f6b2..8d686bc 100644
--- a/modules/node/node.pages.inc
+++ b/modules/node/node.pages.inc
@@ -1,5 +1,5 @@
nid . '/delete', $destination);
@@ -453,7 +453,7 @@ function node_form_submit_build_node($form, &$form_state) {
/**
* Menu callback -- ask for confirmation of node deletion
*/
-function node_delete_confirm(&$form_state, $node) {
+function node_delete_confirm($form, &$form_state, $node) {
$form['nid'] = array(
'#type' => 'value',
'#value' => $node->nid,
@@ -536,7 +536,7 @@ function node_revision_overview($node) {
/**
* Ask for confirmation of the reversion to prevent against CSRF attacks.
*/
-function node_revision_revert_confirm($form_state, $node_revision) {
+function node_revision_revert_confirm($form, $form_state, $node_revision) {
$form['#node_revision'] = $node_revision;
return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel'));
}
@@ -556,19 +556,15 @@ function node_revision_revert_confirm_submit($form, &$form_state) {
$form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions';
}
-function node_revision_delete_confirm($form_state, $node_revision) {
+function node_revision_delete_confirm($form, $form_state, $node_revision) {
$form['#node_revision'] = $node_revision;
return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}
function node_revision_delete_confirm_submit($form, &$form_state) {
$node_revision = $form['#node_revision'];
- db_delete('node_revision')
- ->condition('nid', $node_revision->nid)
- ->condition('vid', $node_revision->vid)
- ->execute();
- module_invoke_all('node_delete_revision', $node_revision);
- field_attach_delete_revision('node', $node_revision);
+ node_revision_delete($node_revision->vid);
+
watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid));
drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($node_revision->revision_timestamp), '@type' => node_type_get_name($node_revision), '%title' => $node_revision->title)));
$form_state['redirect'] = 'node/' . $node_revision->nid;
diff --git a/modules/node/node.test b/modules/node/node.test
index 045707f..c56c45f 100644
--- a/modules/node/node.test
+++ b/modules/node/node.test
@@ -1,5 +1,5 @@
200,
);
- $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type'));
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
// Attempt to access the front page again and check if the summary is now only 200 characters in length.
$this->drupalGet("node");
$this->assertNoRaw($expected, t('Check that the summary is not longer than 200 characters'), 'Node');
@@ -522,7 +522,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase {
// Set page content type to display post information.
$edit = array();
$edit['node_submitted'] = TRUE;
- $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type'));
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
// Create a node.
$edit = array();
@@ -544,7 +544,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase {
// Set page content type to display post information.
$edit = array();
$edit['node_submitted'] = FALSE;
- $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type'));
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
// Create a node.
$edit = array();
@@ -840,6 +840,73 @@ class NodeTypeTestCase extends DrupalWebTestCase {
$this->assertEqual($node_types['article']->name, node_type_get_name('article'), t('Correct node type name has been returned.'));
$this->assertEqual($node_types['page']->base, node_type_get_base('page'), t('Correct node type base has been returned.'));
}
+
+ /**
+ * Test creating a content type.
+ */
+ function testNodeTypeCreation() {
+ $type = $this->drupalCreateContentType();
+
+ $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $type->type))->fetchField();
+ $this->assertTrue($type_exists, 'The new content type has been created in the database.');
+
+ // Login a test user.
+ $web_user = $this->drupalCreateUser(array('create ' . $type->name . ' content'));
+ $this->drupalLogin($web_user);
+
+ $this->drupalGet('node/add/' . str_replace('_', '-', $type->name));
+ $this->assertResponse(200, 'The new content type can be accessed at node/add.');
+ }
+
+ /**
+ * Test editing a node type using the UI.
+ */
+ function testNodeTypeEditing() {
+ $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types'));
+ $this->drupalLogin($web_user);
+
+ $instance = field_info_instance('body', 'page');
+ $this->assertEqual($instance['label'], 'Body', t('Body field was found.'));
+
+ // Verify that title and body fields are displayed.
+ $this->drupalGet('node/add/page');
+ $this->assertRaw('Title', t('Title field was found.'));
+ $this->assertRaw('Full text', t('Body field was found.'));
+
+ // Rename the title field and remove the body field.
+ $edit = array(
+ 'title_label' => 'Foo',
+ 'body_label' => '',
+ );
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
+ field_info_cache_clear();
+
+ $this->assertFalse(field_info_instance('body', 'page'), t('Body field was removed.'));
+ $this->drupalGet('node/add/page');
+ $this->assertRaw('Foo', t('New title label was displayed.'));
+ $this->assertNoRaw('Title', t('Old title label was not displayed.'));
+ $this->assertNoRaw('Full text', t('Body field was not found.'));
+
+ // Add the body field again and change the name, machine name and description.
+ $edit = array(
+ 'name' => 'Bar',
+ 'type' => 'bar',
+ 'description' => 'Lorem ipsum.',
+ 'body_label' => 'Baz',
+ );
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
+ field_info_cache_clear();
+
+ $instance = field_info_instance('body', 'bar');
+ $this->assertEqual($instance['label'], 'Baz', t('Body field was added.'));
+ $this->drupalGet('node/add');
+ $this->assertRaw('Bar', t('New name was displayed.'));
+ $this->assertRaw('Lorem ipsum', t('New description was displayed.'));
+ $this->clickLink('Bar');
+ $this->assertEqual(url('node/add/bar', array('absolute' => TRUE)), $this->getUrl(), t('New machine name was used in URL.'));
+ $this->assertRaw('Foo', t('Title field was found.'));
+ $this->assertRaw('Full text', t('Body field was found.'));
+ }
}
/**
diff --git a/modules/node/node.tokens.inc b/modules/node/node.tokens.inc
index 3068112..a23adeb 100644
--- a/modules/node/node.tokens.inc
+++ b/modules/node/node.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/node/node.tpl.php b/modules/node/node.tpl.php
index 0aaf9fd..4c4ca02 100644
--- a/modules/node/node.tpl.php
+++ b/modules/node/node.tpl.php
@@ -1,5 +1,5 @@
-
+
>
-
+
>
diff --git a/modules/openid/CVS/Entries b/modules/openid/CVS/Entries
index 8f8a561..c547ff3 100644
--- a/modules/openid/CVS/Entries
+++ b/modules/openid/CVS/Entries
@@ -1,12 +1,12 @@
D/tests////
-/login-bg.png/1.1/Thu Sep 3 08:50:28 2009/-kb/
-/openid.api.php/1.2/Thu Sep 3 08:50:28 2009//
/openid.css/1.8/Thu Sep 3 08:50:29 2009//
-/openid.inc/1.18/Thu Sep 3 08:50:29 2009//
/openid.info/1.7/Thu Sep 3 08:50:29 2009//
-/openid.install/1.7/Thu Sep 3 08:50:29 2009//
/openid.js/1.12/Thu Sep 3 08:50:29 2009//
-/openid.module/1.56/Thu Sep 3 08:50:29 2009//
-/openid.pages.inc/1.19/Thu Sep 3 08:50:29 2009//
-/openid.test/1.3/Thu Sep 3 08:50:29 2009//
/xrds.inc/1.3/Thu Sep 3 08:50:29 2009//
+/login-bg.png/1.2/Fri Oct 2 19:50:14 2009/-kb/
+/openid.api.php/1.3/Fri Oct 2 19:50:14 2009//
+/openid.inc/1.20/Fri Oct 2 19:50:14 2009//
+/openid.install/1.8/Fri Oct 2 19:50:14 2009//
+/openid.module/1.61/Fri Oct 2 19:50:14 2009//
+/openid.pages.inc/1.22/Fri Oct 2 19:50:14 2009//
+/openid.test/1.6/Fri Oct 2 19:50:14 2009//
diff --git a/modules/openid/login-bg.png b/modules/openid/login-bg.png
index 8eca055..ed46173 100644
Binary files a/modules/openid/login-bg.png and b/modules/openid/login-bg.png differ
diff --git a/modules/openid/openid.api.php b/modules/openid/openid.api.php
index c3409d5..ef405aa 100644
--- a/modules/openid/openid.api.php
+++ b/modules/openid/openid.api.php
@@ -1,5 +1,5 @@
$value) {
@@ -109,11 +108,15 @@ function openid_redirect_form(&$form_state, $url, $message) {
* Determine if the given identifier is an XRI ID.
*/
function _openid_is_xri($identifier) {
- $firstchar = substr($identifier, 0, 1);
- if ($firstchar == "@" || $firstchar == "=")
- return TRUE;
+ // Strip the xri:// scheme from the identifier if present.
+ if (stripos($identifier, 'xri://') !== FALSE) {
+ $identifier = substr($identifier, 6);
+ }
- if (stristr($identifier, 'xri://') !== FALSE) {
+
+ // Test whether the identifier starts with an XRI global context symbol or (.
+ $firstchar = substr($identifier, 0, 1);
+ if (strpos("=@+$!(", $firstchar) !== FALSE) {
return TRUE;
}
diff --git a/modules/openid/openid.install b/modules/openid/openid.install
index b9fb0d7..2ca732f 100644
--- a/modules/openid/openid.install
+++ b/modules/openid/openid.install
@@ -1,27 +1,11 @@
-1,
'#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE)),
);
- $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => drupal_get_destination())));
+ $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => user_login_destination())));
}
/**
- * Implement hook_form_alter(). Adds OpenID login to the login forms.
+ * Implement hook_form_alter().
+ *
+ * Adds OpenID login to the login forms.
*/
function openid_form_user_register_alter(&$form, &$form_state) {
if (isset($_SESSION['openid']['values'])) {
@@ -206,7 +208,7 @@ function openid_begin($claimed_id, $return_to = '', $form_values = array()) {
}
if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 . '/server', $services[0]['types'])) {
- $identity = 'http://specs.openid.net/auth/2.0/identifier_select';
+ $claimed_id = $identity = 'http://specs.openid.net/auth/2.0/identifier_select';
}
$authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']);
@@ -418,6 +420,8 @@ function openid_authentication($response) {
// Load global $user and perform final login tasks.
$form_state['uid'] = $account->uid;
user_login_submit(array(), $form_state);
+ // Let other modules act on OpenID login
+ module_invoke_all('openid_response', $response, $account);
}
}
else {
@@ -443,7 +447,7 @@ function openid_authentication($response) {
$_SESSION['openid']['values'] = $form_state['values'];
// We'll want to redirect back to the same place.
$destination = drupal_get_destination();
- unset($_REQUEST['destination']);
+ unset($_GET['destination']);
drupal_goto('user/register', $destination);
}
else {
@@ -458,8 +462,10 @@ function openid_authentication($response) {
// Load global $user and perform final login tasks.
$form_state['uid'] = $account->uid;
user_login_submit(array(), $form_state);
+ // Let other modules act on OpenID login
+ module_invoke_all('openid_response', $response, $account);
}
- drupal_redirect_form($form, $form_state['redirect']);
+ drupal_redirect_form($form_state);
}
else {
drupal_set_message(t('Only site administrators can create new user accounts.'), 'error');
diff --git a/modules/openid/openid.pages.inc b/modules/openid/openid.pages.inc
index 30009a2..746289f 100644
--- a/modules/openid/openid.pages.inc
+++ b/modules/openid/openid.pages.inc
@@ -1,5 +1,5 @@
$claimed_id)))->fetchField()) {
form_set_error('openid_identifier', t('That OpenID is already in use on this site.'));
}
- else {
- $return_to = url('user/' . arg(1) . '/openid', array('absolute' => TRUE));
- openid_begin($form_state['values']['openid_identifier'], $return_to);
- }
+}
+
+function openid_user_add_submit($form, &$form_state) {
+ $return_to = url('user/' . arg(1) . '/openid', array('absolute' => TRUE));
+ openid_begin($form_state['values']['openid_identifier'], $return_to);
}
/**
* Menu callback; Delete the specified OpenID identity from the system.
*/
-function openid_user_delete_form($form_state, $account, $aid = 0) {
+function openid_user_delete_form($form, $form_state, $account, $aid = 0) {
$authname = db_query("SELECT authname FROM {authmap} WHERE uid = :uid AND aid = :aid AND module = 'openid'", array(
':uid' => $account->uid,
':aid' => $aid,
@@ -101,14 +102,14 @@ function openid_user_delete_form($form_state, $account, $aid = 0) {
return confirm_form(array(), t('Are you sure you want to delete the OpenID %authname for %user?', array('%authname' => $authname, '%user' => $account->name)), 'user/' . $account->uid . '/openid');
}
-function openid_user_delete_form_submit(&$form_state, $form_values) {
+function openid_user_delete_form_submit($form, &$form_state) {
$query = db_delete('authmap')
- ->condition('uid', $form_state['#args'][0]->uid)
- ->condition('aid', $form_state['#args'][1])
+ ->condition('uid', $form_state['args'][0]->uid)
+ ->condition('aid', $form_state['args'][1])
->condition('module', 'openid')
->execute();
if ($query) {
drupal_set_message(t('OpenID deleted.'));
}
- $form_state['#redirect'] = 'user/' . $form_state['#args'][0]->uid . '/openid';
+ $form_state['redirect'] = 'user/' . $form_state['args'][0]->uid . '/openid';
}
diff --git a/modules/openid/openid.test b/modules/openid/openid.test
index 6b83ce7..898298d 100644
--- a/modules/openid/openid.test
+++ b/modules/openid/openid.test
@@ -1,5 +1,5 @@
drupalPost(NULL, array(), t('Send'));
- $this->assertText(t('My account'), t('User was logged in.'));
+ $this->assertText($this->web_user->name, t('User was logged in.'));
+
+ // Test logging in via the user/login page.
+ $this->drupalLogout();
+ $this->drupalPost('user/login', $edit, t('Log in'));
+
+ // Check we are on the OpenID redirect form.
+ $this->assertTitle(t('OpenID redirect'), t('OpenID redirect page was displayed.'));
+
+ // Submit form to the OpenID Provider Endpoint.
+ $this->drupalPost(NULL, array(), t('Send'));
+
+ $this->assertText($this->web_user->name, t('User was logged in.'));
+
+ // Verify user was redirected away from user/login to an accessible page.
+ $this->assertResponse(200);
}
/**
@@ -150,7 +165,7 @@ class OpenIDFunctionalTest extends DrupalWebTestCase {
// so the form is submitted manually instead.
$this->assertRaw('', t('JavaScript form submission found.'));
$this->drupalPost(NULL, array(), t('Send'));
- $this->assertText(t('My account'), t('User was logged in.'));
+ $this->assertText('johndoe', t('User was logged in.'));
$user = user_load_by_name('johndoe');
$this->assertTrue($user, t('User was found.'));
@@ -219,4 +234,34 @@ class OpenIDUnitTest extends DrupalWebTestCase {
$association->mac_key = "1234567890abcdefghij\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9";
$this->assertEqual(_openid_signature($association, $response, array('foo', 'bar')), 'QnKZQzSFstT+GNiJDFOptdcZjrc=', t('Expected signature calculated.'));
}
+
+ /**
+ * Test _openid_is_xri().
+ */
+ function testOpenidXRITest() {
+ // Test that the XRI test is according to OpenID Authentication 2.0,
+ // section 7.2. If the user-supplied string starts with xri:// it should be
+ // stripped and the resulting string should be treated as an XRI when it
+ // starts with "=", "@", "+", "$", "!" or "(".
+ $this->assertTrue(_openid_is_xri('xri://=foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.'));
+ $this->assertTrue(_openid_is_xri('xri://@foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.'));
+ $this->assertTrue(_openid_is_xri('xri://+foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.'));
+ $this->assertTrue(_openid_is_xri('xri://$foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.'));
+ $this->assertTrue(_openid_is_xri('xri://!foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme..'));
+ $this->assertTrue(_openid_is_xri('xri://(foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme..'));
+
+ $this->assertTrue(_openid_is_xri('=foo'), t('_openid_is_xri returned expected result for an xri identifier.'));
+ $this->assertTrue(_openid_is_xri('@foo'), t('_openid_is_xri returned expected result for an xri identifier.'));
+ $this->assertTrue(_openid_is_xri('+foo'), t('_openid_is_xri returned expected result for an xri identifier.'));
+ $this->assertTrue(_openid_is_xri('$foo'), t('_openid_is_xri returned expected result for an xri identifier.'));
+ $this->assertTrue(_openid_is_xri('!foo'), t('_openid_is_xri returned expected result for an xri identifier.'));
+ $this->assertTrue(_openid_is_xri('(foo'), t('_openid_is_xri returned expected result for an xri identifier.'));
+
+ $this->assertFalse(_openid_is_xri('foo'), t('_openid_is_xri returned expected result for an http URL.'));
+ $this->assertFalse(_openid_is_xri('xri://foo'), t('_openid_is_xri returned expected result for an http URL.'));
+ $this->assertFalse(_openid_is_xri('http://foo/'), t('_openid_is_xri returned expected result for an http URL.'));
+ $this->assertFalse(_openid_is_xri('http://example.com/'), t('_openid_is_xri returned expected result for an http URL.'));
+ $this->assertFalse(_openid_is_xri('user@example.com/'), t('_openid_is_xri returned expected result for an http URL.'));
+ $this->assertFalse(_openid_is_xri('http://user@example.com/'), t('_openid_is_xri returned expected result for an http URL.'));
+ }
}
diff --git a/modules/openid/tests/CVS/Entries b/modules/openid/tests/CVS/Entries
index 060ff99..b6649e0 100644
--- a/modules/openid/tests/CVS/Entries
+++ b/modules/openid/tests/CVS/Entries
@@ -1,4 +1,4 @@
/openid_test.info/1.1/Thu Sep 3 08:50:29 2009//
/openid_test.install/1.3/Thu Sep 3 08:50:29 2009//
-/openid_test.module/1.3/Thu Sep 3 08:50:29 2009//
+/openid_test.module/1.5/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/openid/tests/openid_test.module b/modules/openid/tests/openid_test.module
index 1cf7ef7..db2d2e1 100644
--- a/modules/openid/tests/openid_test.module
+++ b/modules/openid/tests/openid_test.module
@@ -1,5 +1,5 @@
@@ -89,7 +89,7 @@ function openid_test_yadis_xrds() {
* Menu callback; regular HTML page with an X-XRDS-Location HTTP header.
*/
function openid_test_yadis_x_xrds_location() {
- drupal_set_header('X-XRDS-Location', url('openid-test/yadis/xrds', array('absolute' => TRUE)));
+ drupal_add_http_header('X-XRDS-Location', url('openid-test/yadis/xrds', array('absolute' => TRUE)));
return t('This page includes an X-RDS-Location HTTP header containing the URL of an XRDS document.');
}
@@ -181,7 +181,7 @@ function _openid_test_endpoint_associate() {
// Respond to Relying Party in the special Key-Value Form Encoding (see OpenID
// Authentication 1.0, section 4.1.1).
- drupal_set_header('Content-Type', 'text/plain');
+ drupal_add_http_header('Content-Type', 'text/plain');
print _openid_create_message($response);
}
@@ -228,6 +228,6 @@ function _openid_test_endpoint_authenticate() {
// Put the signed message into the query string of a URL supplied by the
// Relying Party, and redirect the user.
- drupal_set_header('Content-Type', 'text/plain');
- header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => http_build_query($response, '', '&'), 'external' => TRUE)));
+ drupal_add_http_header('Content-Type', 'text/plain');
+ header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
}
diff --git a/modules/overlay/CVS/Entries b/modules/overlay/CVS/Entries
new file mode 100644
index 0000000..4ba62ab
--- /dev/null
+++ b/modules/overlay/CVS/Entries
@@ -0,0 +1,2 @@
+/overlay.info/1.1/Wed Sep 16 23:55:40 2009//
+D
diff --git a/modules/overlay/CVS/Repository b/modules/overlay/CVS/Repository
new file mode 100644
index 0000000..f9693da
--- /dev/null
+++ b/modules/overlay/CVS/Repository
@@ -0,0 +1 @@
+drupal/modules/overlay
diff --git a/modules/overlay/CVS/Root b/modules/overlay/CVS/Root
new file mode 100644
index 0000000..964e299
--- /dev/null
+++ b/modules/overlay/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal
diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info
new file mode 100644
index 0000000..e69de29
diff --git a/modules/path/CVS/Entries b/modules/path/CVS/Entries
index 8910ddb..9fc77f0 100644
--- a/modules/path/CVS/Entries
+++ b/modules/path/CVS/Entries
@@ -1,6 +1,6 @@
-/path.admin.inc/1.29/Thu Sep 3 08:50:29 2009//
/path.info/1.8/Thu Sep 3 08:50:29 2009//
/path.js/1.2/Thu Sep 3 08:50:29 2009//
/path.test/1.20/Thu Sep 3 08:50:29 2009//
/path.module/1.170/Sat Sep 5 15:25:50 2009//
+/path.admin.inc/1.31/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/path/path.admin.inc b/modules/path/path.admin.inc
index 178dfcc..fc6d3ab 100644
--- a/modules/path/path.admin.inc
+++ b/modules/path/path.admin.inc
@@ -1,5 +1,5 @@
:language', array(':language' => ''), 0, 1)->fetchField();
+ $alias_exists = (bool) db_query_range('SELECT 1 FROM {url_alias} WHERE language <> :language', 0, 1, array(':language' => ''))->fetchField();
$multilanguage = (module_exists('locale') || $alias_exists);
$header = array(
@@ -98,7 +98,7 @@ function path_admin_edit($pid = 0) {
* @see path_admin_form_validate()
* @see path_admin_form_submit()
*/
-function path_admin_form(&$form_state, $edit = array('src' => '', 'dst' => '', 'language' => '', 'pid' => NULL)) {
+function path_admin_form($form, &$form_state, $edit = array('src' => '', 'dst' => '', 'language' => '', 'pid' => NULL)) {
$form['#alias'] = $edit;
@@ -180,7 +180,7 @@ function path_admin_form_submit($form, &$form_state) {
/**
* Menu callback; confirms deleting an URL alias
*/
-function path_admin_delete_confirm($form_state, $pid) {
+function path_admin_delete_confirm($form, $form_state, $pid) {
$path = path_load($pid);
if (user_access('administer url aliases')) {
$form['pid'] = array('#type' => 'value', '#value' => $pid);
@@ -209,7 +209,7 @@ function path_admin_delete_confirm_submit($form, &$form_state) {
* @ingroup forms
* @see path_admin_filter_form_submit()
*/
-function path_admin_filter_form(&$form_state, $keys = '') {
+function path_admin_filter_form($form, &$form_state, $keys = '') {
$form['#attributes'] = array('class' => array('search-form'));
$form['basic'] = array('#type' => 'fieldset',
'#title' => t('Filter aliases')
diff --git a/modules/php/CVS/Entries b/modules/php/CVS/Entries
index 428fe0f..fb0e4c1 100644
--- a/modules/php/CVS/Entries
+++ b/modules/php/CVS/Entries
@@ -1,5 +1,5 @@
/php.info/1.7/Thu Sep 3 08:50:29 2009//
-/php.install/1.12/Thu Sep 3 08:50:29 2009//
-/php.module/1.20/Thu Sep 3 08:50:29 2009//
-/php.test/1.16/Thu Sep 3 08:50:29 2009//
+/php.install/1.14/Fri Oct 2 19:50:14 2009//
+/php.module/1.21/Fri Oct 2 19:50:14 2009//
+/php.test/1.17/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/php/php.install b/modules/php/php.install
index dd0d036..8942974 100644
--- a/modules/php/php.install
+++ b/modules/php/php.install
@@ -1,5 +1,5 @@
'PHP code'), 0, 1)->fetchField();
+ $format_exists = (bool) db_query_range('SELECT 1 FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'PHP code'))->fetchField();
// Add a PHP code text format, if it does not exist. Do this only for the
// first install (or if the format has been manually deleted) as there is no
// reliable method to identify the format in an uninstall hook or in
@@ -19,7 +19,6 @@ function php_install() {
$format = db_insert('filter_format')
->fields(array(
'name' => 'PHP code',
- 'roles' => '',
'cache' => 0,
))
->execute();
diff --git a/modules/php/php.module b/modules/php/php.module
index 7915ecc..d295c4b 100644
--- a/modules/php/php.module
+++ b/modules/php/php.module
@@ -1,5 +1,5 @@
drupalCreateUser(array('administer filters'));
$this->drupalLogin($admin_user);
- // Confirm that the PHP filter is #3.
- $this->drupalGet('admin/config/content/formats/3');
- $this->assertText('PHP code', t('On PHP code filter page.'));
+ // Confirm that the PHP code text format was inserted as the newest format
+ // on the site.
+ $newest_format_id = db_query("SELECT MAX(format) FROM {filter_format}")->fetchField();
+ $newest_format = filter_format_load($newest_format_id);
+ $this->assertEqual($newest_format->name, 'PHP code', t('PHP code text format was created.'));
+
+ // Store the format ID of the PHP code text format for later use.
+ $this->php_code_format = $newest_format_id;
}
/**
@@ -43,15 +50,12 @@ class PHPFilterTestCase extends PHPTestCase {
* Make sure that the PHP filter evaluates PHP code when used.
*/
function testPHPFilter() {
- // Setup PHP filter.
- $edit = array();
- $edit['roles[2]'] = TRUE; // Set authenticated users to have permission to use filter.
- $this->drupalPost(NULL, $edit, 'Save configuration');
- $this->assertText(t('The text format settings have been updated.'), t('PHP format available to authenticated users.'));
-
- // Create node with PHP filter enabled.
- $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content'));
+ // Log in as a user with permission to use the PHP code text format.
+ $php_code_permission = filter_permission_name(filter_format_load($this->php_code_format));
+ $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content', $php_code_permission));
$this->drupalLogin($web_user);
+
+ // Create a node with PHP code in it.
$node = $this->createNodeWithCode();
// Make sure that the PHP code shows up as text.
@@ -61,7 +65,7 @@ class PHPFilterTestCase extends PHPTestCase {
// Change filter to PHP filter and see that PHP code is evaluated.
$edit = array();
$langcode = FIELD_LANGUAGE_NONE;
- $edit["body[$langcode][0][value_format]"] = 3;
+ $edit["body[$langcode][0][value_format]"] = $this->php_code_format;
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.'));
@@ -98,6 +102,6 @@ class PHPAccessTestCase extends PHPTestCase {
// Make sure that user doesn't have access to filter.
$this->drupalGet('node/' . $node->nid . '/edit');
- $this->assertNoFieldByName('body_format', '3', t('Format not available.'));
+ $this->assertNoRaw('
>
' . t('The Field SQL Storage module stores Field API data in the database. It is the default field storage module, but other field storage modules may be available in the contributions repository.') . '
'; return $output; } } +/** + * Implement hook_field_storage_info(). + */ +function field_sql_storage_field_storage_info() { + return array( + 'field_sql_storage' => array( + 'label' => t('Default SQL storage'), + 'description' => t('Stores fields in the local SQL database, using per-field tables.'), + ), + ); +} + /** * Generate a table name for a field data table. * @@ -26,7 +38,7 @@ function field_sql_storage_help($path, $arg) { * A string containing the generated name for the database table */ function _field_sql_storage_tablename($field) { - return "field_data_{$field['field_name']}_{$field['id']}"; + return "field_data_{$field['field_name']}" . ($field['deleted'] ? "_{$field['id']}" : ''); } /** @@ -38,7 +50,7 @@ function _field_sql_storage_tablename($field) { * A string containing the generated name for the database table */ function _field_sql_storage_revision_tablename($field) { - return "field_revision_{$field['field_name']}_{$field['id']}"; + return "field_revision_{$field['field_name']}" . ($field['deleted'] ? "_{$field['id']}" : ''); } /** @@ -199,53 +211,95 @@ function _field_sql_storage_schema($field) { function field_sql_storage_field_storage_create_field($field) { $schema = _field_sql_storage_schema($field); foreach ($schema as $name => $table) { - db_create_table($ret, $name, $table); + db_create_table($name, $table); + } +} + +/** + * Implement hook_field_update_field_forbid(). + * + * Forbid any field update that changes column definitions if there is + * any data. + */ +function field_sql_storage_field_update_forbid($field, $prior_field, $has_data) { + if ($has_data && $field['columns'] != $prior_field['columns']) { + throw new FieldUpdateForbiddenException("field_sql_storage cannot change the schema for an existing field with data."); + } +} + +/** + * Implement hook_field_storage_update_field(). + */ +function field_sql_storage_field_storage_update_field($field, $prior_field, $has_data) { + if (! $has_data) { + // There is no data. Re-create the tables completely. + $prior_schema = _field_sql_storage_schema($prior_field); + foreach ($prior_schema as $name => $table) { + db_drop_table($name, $table); + } + $schema = _field_sql_storage_schema($field); + foreach ($schema as $name => $table) { + db_create_table($name, $table); + } + } + else { + // There is data, so there are no column changes. Drop all the + // prior indexes and create all the new ones, except for all the + // priors that exist unchanged. + $table = _field_sql_storage_tablename($prior_field); + $revision_table = _field_sql_storage_revision_tablename($prior_field); + foreach ($prior_field['indexes'] as $name => $columns) { + if (!isset($field['indexes'][$name]) || $columns != $field['indexes'][$name]) { + $real_name = _field_sql_storage_indexname($field['field_name'], $name); + db_drop_index($table, $real_name); + db_drop_index($revision_table, $real_name); + } + } + $table = _field_sql_storage_tablename($field); + $revision_table = _field_sql_storage_revision_tablename($field); + foreach ($field['indexes'] as $name => $columns) { + if (!isset($prior_field['indexes'][$name]) || $columns != $prior_field['indexes'][$name]) { + $real_name = _field_sql_storage_indexname($field['field_name'], $name); + $real_columns = array(); + foreach ($columns as $column_name) { + $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name); + } + db_add_index($table, $real_name, $real_columns); + db_add_index($revision_table, $real_name, $real_columns); + } + } } } /** * Implement hook_field_storage_delete_field(). */ -function field_sql_storage_field_storage_delete_field($field_name) { +function field_sql_storage_field_storage_delete_field($field) { // Mark all data associated with the field for deletion. - $field = field_info_field($field_name); + $field['deleted'] = 0; $table = _field_sql_storage_tablename($field); + $revision_table = _field_sql_storage_revision_tablename($field); db_update($table) ->fields(array('deleted' => 1)) ->execute(); + + // Move the table to a unique name while the table contents are being deleted. + $field['deleted'] = 1; + $new_table = _field_sql_storage_tablename($field); + $revision_new_table = _field_sql_storage_revision_tablename($field); + db_rename_table($table, $new_table); + db_rename_table($revision_table, $revision_new_table); + drupal_get_schema(NULL, TRUE); } /** * Implement hook_field_storage_load(). */ -function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_fields, $options) { +function field_sql_storage_field_storage_load($obj_type, $objects, $age, $fields, $options) { $etid = _field_sql_storage_etid($obj_type); $load_current = $age == FIELD_LOAD_CURRENT; - // Gather ids needed for each field. - $field_ids = array(); - $delta_count = array(); - foreach ($objects as $obj) { - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $obj); - - if ($options['deleted']) { - $instances = field_read_instances(array('bundle' => $bundle), array('include_deleted' => $options['deleted'])); - } - else { - $instances = field_info_instances($bundle); - } - - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - if (!isset($skip_fields[$instance['field_id']]) && (!isset($options['field_id']) || $options['field_id'] == $instance['field_id'])) { - $objects[$id]->{$field_name} = array(); - $field_ids[$instance['field_id']][] = $load_current ? $id : $vid; - $delta_count[$id][$field_name] = array(); - } - } - } - - foreach ($field_ids as $field_id => $ids) { + foreach ($fields as $field_id => $ids) { $field = field_info_field_by_id($field_id); $field_name = $field['field_name']; $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); @@ -263,12 +317,13 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f $results = $query->execute(); + $delta_count = array(); foreach ($results as $row) { - if (!isset($delta_count[$row->entity_id][$field_name][$row->language])) { - $delta_count[$row->entity_id][$field_name][$row->language] = 0; + if (!isset($delta_count[$row->entity_id][$row->language])) { + $delta_count[$row->entity_id][$row->language] = 0; } - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$field_name][$row->language] < $field['cardinality']) { + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->language] < $field['cardinality']) { $item = array(); // For each column declared by the field, populate the item // from the prefixed database column. @@ -279,7 +334,7 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f // Add the item to the field values for the entity. $objects[$row->entity_id]->{$field_name}[$row->language][] = $item; - $delta_count[$row->entity_id][$field_name][$row->language]++; + $delta_count[$row->entity_id][$row->language]++; } } } @@ -288,41 +343,30 @@ function field_sql_storage_field_storage_load($obj_type, $objects, $age, $skip_f /** * Implement hook_field_storage_write(). */ -function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fields) { - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); +function field_sql_storage_field_storage_write($obj_type, $object, $op, $fields) { + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); $etid = _field_sql_storage_etid($obj_type); - $instances = field_info_instances($bundle); - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - if (isset($skip_fields[$instance['field_id']])) { - continue; - } - - $field = field_info_field($field_name); + foreach ($fields as $field_id) { + $field = field_info_field_by_id($field_id); + $field_name = $field['field_name']; $table_name = _field_sql_storage_tablename($field); $revision_name = _field_sql_storage_revision_tablename($field); - // Leave the field untouched if $object comes with no $field_name property. - // Empty the field if $object->$field_name is NULL or an empty array. - - // Function property_exists() is slower, so we catch the more frequent cases - // where it's an empty array with the faster isset(). - if (isset($object->$field_name) || property_exists($object, $field_name)) { - $available_languages = field_multilingual_available_languages($obj_type, $field); - $available_translations = is_array($object->$field_name) ? array_intersect($available_languages, array_keys($object->$field_name)) : FALSE; - - // Delete and insert, rather than update, in case a value was added. - // If no translation is available, empty the field for all the available languages. - if ($op == FIELD_STORAGE_UPDATE && count($available_translations)) { - $languages = empty($object->$field_name) ? $available_languages : $available_translations; + $all_languages = field_multilingual_available_languages($obj_type, $field); + $field_languages = array_intersect($all_languages, array_keys((array) $object->$field_name)); + // Delete and insert, rather than update, in case a value was added. + if ($op == FIELD_STORAGE_UPDATE) { + // Delete languages present in the incoming $object->$field_name. + // Delete all languages if $object->$field_name is empty. + $languages = !empty($object->$field_name) ? $field_languages : $all_languages; + if ($languages) { db_delete($table_name) ->condition('etid', $etid) ->condition('entity_id', $id) ->condition('language', $languages, 'IN') ->execute(); - if (isset($vid)) { db_delete($revision_name) ->condition('etid', $etid) @@ -332,46 +376,48 @@ function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fi ->execute(); } } + } + + // Prepare the multi-insert query. + $do_insert = FALSE; + $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); + foreach ($field['columns'] as $column => $attributes) { + $columns[] = _field_sql_storage_columnname($field_name, $column); + } + $query = db_insert($table_name)->fields($columns); + if (isset($vid)) { + $revision_query = db_insert($revision_name)->fields($columns); + } - if (!empty($available_translations)) { - // Prepare the multi-insert query. - $columns = array('etid', 'entity_id', 'revision_id', 'bundle', 'delta', 'language'); + foreach ($field_languages as $langcode) { + $items = (array) $object->{$field_name}[$langcode]; + $delta_count = 0; + foreach ($items as $delta => $item) { + // We now know we have someting to insert. + $do_insert = TRUE; + $record = array( + 'etid' => $etid, + 'entity_id' => $id, + 'revision_id' => $vid, + 'bundle' => $bundle, + 'delta' => $delta, + 'language' => $langcode, + ); foreach ($field['columns'] as $column => $attributes) { - $columns[] = _field_sql_storage_columnname($field_name, $column); + $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL; } - $query = db_insert($table_name)->fields($columns); + $query->values($record); if (isset($vid)) { - $revision_query = db_insert($revision_name)->fields($columns); + $revision_query->values($record); } - foreach ($available_translations as $langcode) { - if ($items = $object->{$field_name}[$langcode]) { - $delta_count = 0; - foreach ($items as $delta => $item) { - $record = array( - 'etid' => $etid, - 'entity_id' => $id, - 'revision_id' => $vid, - 'bundle' => $bundle, - 'delta' => $delta, - 'language' => $langcode, - ); - foreach ($field['columns'] as $column => $attributes) { - $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL; - } - $query->values($record); - if (isset($vid)) { - $revision_query->values($record); - } - - if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { - break; - } - } - } + if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) { + break; } + } - // Execute the insert. + // Execute the query if we have values to insert. + if ($do_insert) { $query->execute(); if (isset($vid)) { $revision_query->execute(); @@ -386,14 +432,15 @@ function field_sql_storage_field_storage_write($obj_type, $object, $op, $skip_fi * * This function deletes data for all fields for an object from the database. */ -function field_sql_storage_field_storage_delete($obj_type, $object) { - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); +function field_sql_storage_field_storage_delete($obj_type, $object, $fields) { + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); $etid = _field_sql_storage_etid($obj_type); - $instances = field_info_instances($bundle); - foreach ($instances as $instance) { - $field = field_info_field($instance['field_name']); - field_sql_storage_field_storage_purge($obj_type, $object, $field, $instance); + foreach (field_info_instances($bundle) as $instance) { + if (isset($fields[$instance['field_id']])) { + $field = field_info_field_by_id($instance['field_id']); + field_sql_storage_field_storage_purge($obj_type, $object, $field, $instance); + } } } @@ -404,10 +451,9 @@ function field_sql_storage_field_storage_delete($obj_type, $object) { * an object. */ function field_sql_storage_field_storage_purge($obj_type, $object, $field, $instance) { - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); $etid = _field_sql_storage_etid($obj_type); - $field = field_info_field_by_id($field['id']); $table_name = _field_sql_storage_tablename($field); $revision_name = _field_sql_storage_revision_tablename($field); db_delete($table_name) @@ -483,16 +529,16 @@ function field_sql_storage_field_storage_query($field_id, $conditions, $count, & $value = _field_sql_storage_etid($value); } } - - $query->condition($column, $value, $operator); - + // Track condition on 'deleted'. if ($column == 'deleted') { - $deleted = $value; + $condition_deleted = TRUE; } + + $query->condition($column, $value, $operator); } // Exclude deleted data unless we have a condition on it. - if (!isset($deleted)) { + if (!isset($condition_deleted)) { $query->condition('deleted', 0); } @@ -520,7 +566,7 @@ function field_sql_storage_field_storage_query($field_id, $conditions, $count, & $id = ($load_current || empty($entity_type['object keys']['revision'])) ? $row->entity_id : $row->revision_id; if (!isset($return[$row->type][$id])) { - $return[$row->type][$id] = field_attach_create_stub_object($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); + $return[$row->type][$id] = field_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); $obj_count++; } } @@ -540,15 +586,13 @@ function field_sql_storage_field_storage_query($field_id, $conditions, $count, & * * This function actually deletes the data from the database. */ -function field_sql_storage_field_storage_delete_revision($obj_type, $object) { - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); +function field_sql_storage_field_storage_delete_revision($obj_type, $object, $fields) { + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); $etid = _field_sql_storage_etid($obj_type); if (isset($vid)) { - $instances = field_info_instances($bundle); - foreach ($instances as $instance) { - $field_name = $instance['field_name']; - $field = field_read_field($field_name); + foreach ($fields as $field_id) { + $field = field_info_field_by_id($field_id); $revision_name = _field_sql_storage_revision_tablename($field); db_delete($revision_name) ->condition('etid', $etid) @@ -564,37 +608,40 @@ function field_sql_storage_field_storage_delete_revision($obj_type, $object) { * * This function simply marks for deletion all data associated with the field. */ -function field_sql_storage_field_storage_delete_instance($field_name, $bundle) { - $field = field_read_field($field_name); +function field_sql_storage_field_storage_delete_instance($instance) { + $field = field_info_field($instance['field_name']); $table_name = _field_sql_storage_tablename($field); $revision_name = _field_sql_storage_revision_tablename($field); db_update($table_name) ->fields(array('deleted' => 1)) - ->condition('bundle', $bundle) + ->condition('bundle', $instance['bundle']) ->execute(); db_update($revision_name) ->fields(array('deleted' => 1)) - ->condition('bundle', $bundle) + ->condition('bundle', $instance['bundle']) ->execute(); } /** - * Implement hook_field_storage_rename_bundle(). + * Implement hook_field_attach_rename_bundle(). */ -function field_sql_storage_field_storage_rename_bundle($bundle_old, $bundle_new) { - $instances = field_info_instances($bundle_old); +function field_sql_storage_field_attach_rename_bundle($bundle_old, $bundle_new) { + // We need to account for deleted or inactive fields and instances. + $instances = field_read_instances(array('bundle' => $bundle_new), array('include_deleted' => TRUE, 'include_inactive' => TRUE)); foreach ($instances as $instance) { - $field = field_read_field($instance['field_name']); - $table_name = _field_sql_storage_tablename($field); - $revision_name = _field_sql_storage_revision_tablename($field); - db_update($table_name) - ->fields(array('bundle' => $bundle_new)) - ->condition('bundle', $bundle_old) - ->execute(); - db_update($revision_name) - ->fields(array('bundle' => $bundle_new)) - ->condition('bundle', $bundle_old) - ->execute(); + $field = field_info_field_by_id($instance['field_id']); + if ($field['storage']['type'] == 'field_sql_storage') { + $table_name = _field_sql_storage_tablename($field); + $revision_name = _field_sql_storage_revision_tablename($field); + db_update($table_name) + ->fields(array('bundle' => $bundle_new)) + ->condition('bundle', $bundle_old) + ->execute(); + db_update($revision_name) + ->fields(array('bundle' => $bundle_new)) + ->condition('bundle', $bundle_old) + ->execute(); + } } } @@ -605,10 +652,9 @@ function field_sql_storage_field_storage_rename_bundle($bundle_old, $bundle_new) * that is left is to delete the table. */ function field_sql_storage_field_storage_purge_field($field) { - $ret = array(); $table_name = _field_sql_storage_tablename($field); $revision_name = _field_sql_storage_revision_tablename($field); - db_drop_table($ret, $table_name); - db_drop_table($ret, $revision_name); + db_drop_table($table_name); + db_drop_table($revision_name); } diff --git a/modules/field/modules/field_sql_storage/field_sql_storage.test b/modules/field/modules/field_sql_storage/field_sql_storage.test index 4458136..fe8bb93 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.test +++ b/modules/field/modules/field_sql_storage/field_sql_storage.test @@ -1,5 +1,5 @@ field_name = drupal_strtolower($this->randomName() . '_field_name'); $this->field = array('field_name' => $this->field_name, 'type' => 'test_field', 'cardinality' => 4); $this->field = field_create_field($this->field); @@ -183,8 +183,8 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { $this->assertTrue(empty($rev_values), "All values for all revisions are stored in revision table {$this->revision_table}"); // Check that update leaves the field data untouched if - // $object->{$field_name} has no language key. - unset($entity->{$this->field_name}[$langcode]); + // $object->{$field_name} is absent. + unset($entity->{$this->field_name}); field_attach_update($entity_type, $entity); $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); foreach ($values as $delta => $value) { @@ -194,7 +194,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { } // Check that update with an empty $object->$field_name empties the field. - $entity->{$this->field_name}[$langcode] = NULL; + $entity->{$this->field_name} = NULL; field_attach_update($entity_type, $entity); $rows = db_select($this->table, 't')->fields('t')->execute()->fetchAllAssoc('delta', PDO::FETCH_ASSOC); $this->assertEqual(count($rows), 0, t("Update with an empty field_name entry empties the field.")); @@ -217,7 +217,7 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { $this->assertEqual($count, 0, 'Missing field results in no inserts'); // Insert: Field is NULL - $entity->{$this->field_name}[$langcode] = NULL; + $entity->{$this->field_name} = NULL; field_attach_insert($entity_type, $entity); $count = db_select($this->table) ->countQuery() @@ -294,4 +294,88 @@ class FieldSqlStorageTestCase extends DrupalWebTestCase { ->fetchField(); $this->assertEqual($count, 1, 'NULL field translation is wiped.'); } + + /** + * Test trying to update a field with data. + */ + function testUpdateFieldSchemaWithData() { + // Create a decimal 5.2 field and add some data. + $field = array('field_name' => 'decimal52', 'type' => 'number_decimal', 'settings' => array('precision' => 5, 'scale' => 2)); + $field = field_create_field($field); + $instance = array('field_name' => 'decimal52', 'bundle' => FIELD_TEST_BUNDLE); + $instance = field_create_instance($instance); + $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); + $entity->decimal52[FIELD_LANGUAGE_NONE][0]['value'] = '1.235'; + field_attach_insert('test_entity', $entity); + + // Attempt to update the field in a way that would work without data. + $field['settings']['scale'] = 3; + try { + field_update_field($field); + $this->fail(t('Cannot update field schema with data.')); + } + catch (FieldException $e) { + $this->pass(t('Cannot update field schema with data.')); + } + } + + /** + * Test adding and removing indexes while data is present. + */ + function testFieldUpdateIndexesWithData() { + // We do not have a db-agnostic inspection system in core yet, so + // for now we can only test this on mysql. + if (Database::getConnection()->databaseType() == 'mysql') { + // Create a decimal field. + $field_name = 'testfield'; + $field = array('field_name' => $field_name, 'type' => 'text'); + $field = field_create_field($field); + $instance = array('field_name' => $field_name, 'bundle' => FIELD_TEST_BUNDLE); + $instance = field_create_instance($instance); + $tables = array(_field_sql_storage_tablename($field), _field_sql_storage_revision_tablename($field)); + + // Verify the indexes we will create do not exist yet. + foreach ($tables as $table) { + $indexes = $this->getIndexes($table); + $this->assertTrue(empty($indexes['value']), t("No index named value exists in $table")); + $this->assertTrue(empty($indexes['value_format']), t("No index named value_format exists in $table")); + } + + // Add data so the table cannot be dropped. + $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); + $entity->{$field_name}[FIELD_LANGUAGE_NONE][0]['value'] = 'field data'; + field_attach_insert('test_entity', $entity); + + // Add an index + $field = array('field_name' => $field_name, 'indexes' => array('value' => array('value'))); + field_update_field($field); + foreach ($tables as $table) { + $indexes = $this->getIndexes($table); + $this->assertTrue($indexes["{$field_name}_value"] == array(1 => "{$field_name}_value"), t("Index on value created in $table")); + } + + // Add a different index, removing the existing custom one. + $field = array('field_name' => $field_name, 'indexes' => array('value_format' => array('value', 'format'))); + field_update_field($field); + foreach ($tables as $table) { + $indexes = $this->getIndexes($table); + $this->assertTrue($indexes["{$field_name}_value_format"] == array(1 => "{$field_name}_value", 2 => "{$field_name}_format"), t("Index on value_format created in $table")); + $this->assertTrue(empty($indexes["{$field_name}_value"]), t("Index on value removed in $table")); + } + + // Verify that the tables were not dropped. + $entity = field_test_create_stub_entity(0, 0, $instance['bundle']); + field_attach_load('test_entity', array(0 => $entity)); + $this->assertEqual($entity->{$field_name}[FIELD_LANGUAGE_NONE][0]['value'], 'field data', t("Index changes performed without dropping the tables")); + } + } + + function getIndexes($table) { + $indexes = array(); + $result = db_query("SHOW INDEXES FROM {" . $table . "}"); + foreach ($result as $row) { + $indexes[$row->key_name][$row->seq_in_index] = $row->column_name; + } + return $indexes; + } } diff --git a/modules/field/modules/list/CVS/Entries b/modules/field/modules/list/CVS/Entries index 091c401..8aacd4a 100644 --- a/modules/field/modules/list/CVS/Entries +++ b/modules/field/modules/list/CVS/Entries @@ -1,3 +1,3 @@ /list.info/1.4/Thu Sep 3 08:50:21 2009// -/list.module/1.13/Thu Sep 3 08:50:21 2009// +/list.module/1.15/Fri Oct 2 19:50:13 2009// D diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module index f1c4b67..894bc23 100644 --- a/modules/field/modules/list/list.module +++ b/modules/field/modules/list/list.module @@ -1,25 +1,11 @@ array( - 'arguments' => array('element' => NULL), - ), - 'field_formatter_list_key' => array( - 'arguments' => array('element' => NULL), - ), - ); -} - /** * Implement hook_field_info(). */ @@ -99,8 +85,14 @@ function list_field_schema($field) { /** * Implement hook_field_settings_form(). + * + * @todo: If $has_data, add a form validate function to verify that the + * new allowed values do not exclude any keys for which data already + * exists in the databae (use field_attach_query()) to find out. + * Implement the validate function via hook_field_update_forbid() so + * list.module does not depend on form submission. */ -function list_field_settings_form($field, $instance) { +function list_field_settings_form($field, $instance, $has_data) { $settings = $field['settings']; $form['allowed_values'] = array( diff --git a/modules/field/modules/number/CVS/Entries b/modules/field/modules/number/CVS/Entries index 0fb9165..86c8884 100644 --- a/modules/field/modules/number/CVS/Entries +++ b/modules/field/modules/number/CVS/Entries @@ -1,3 +1,3 @@ /number.info/1.4/Thu Sep 3 08:50:21 2009// -/number.module/1.15/Thu Sep 3 08:50:21 2009// +/number.module/1.20/Fri Oct 2 19:50:13 2009// D diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module index 18d3f89..22a4547 100644 --- a/modules/field/modules/number/number.module +++ b/modules/field/modules/number/number.module @@ -1,5 +1,5 @@ array('arguments' => array('element' => NULL)), - 'field_formatter_number_integer' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'), - 'field_formatter_number_decimal' => array('arguments' => array('element' => NULL), 'function' => 'theme_field_formatter_number'), - 'field_formatter_number_unformatted' => array('arguments' => array('element' => NULL)), ); } +/** + * Implement hook_theme_alter(). + */ +function number_theme_registry_alter(&$theme_registry) { + // The number_integer and number_decimal formatters use the same function. + $theme_registry['field_formatter_number_default']['function'] = 'theme_field_formatter_number'; + $theme_registry['field_formatter_number_decimal']['function'] = 'theme_field_formatter_number'; +} + /** * Implement hook_field_info(). */ @@ -28,22 +34,22 @@ function number_field_info() { 'description' => t('This field stores a number in the database as an integer.'), 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), 'default_widget' => 'number', - 'default_formatter' => 'number_integer', + 'default_formatter' => 'number_default', ), 'number_decimal' => array( 'label' => t('Decimal'), 'description' => t('This field stores a number in the database in a fixed decimal format.'), - 'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => ' .'), + 'settings' => array('precision' => 10, 'scale' => 2, 'decimal' => '.'), 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), 'default_widget' => 'number', - 'default_formatter' => 'number_integer', + 'default_formatter' => 'number_decimal', ), 'number_float' => array( 'label' => t('Float'), 'description' => t('This field stores a number in the database in a floating point format.'), 'instance_settings' => array('min' => '', 'max' => '', 'prefix' => '', 'suffix' => ''), 'default_widget' => 'number', - 'default_formatter' => 'number_integer', + 'default_formatter' => 'number_decimal', ), ); } @@ -90,7 +96,7 @@ function number_field_schema($field) { /** * Implement hook_field_settings_form(). */ -function number_field_settings_form($field, $instance) { +function number_field_settings_form($field, $instance, $has_data) { $settings = $field['settings']; $form = array(); @@ -101,6 +107,7 @@ function number_field_settings_form($field, $instance) { '#options' => drupal_map_assoc(range(10, 32)), '#default_value' => $settings['precision'], '#description' => t('The total number of digits to store in the database, including those to the right of the decimal.'), + '#disabled' => $has_data, ); $form['scale'] = array( '#type' => 'select', @@ -108,6 +115,7 @@ function number_field_settings_form($field, $instance) { '#options' => drupal_map_assoc(range(0, 10)), '#default_value' => $settings['scale'], '#description' => t('The number of digits to the right of the decimal.'), + '#disabled' => $has_data, ); $form['decimal'] = array( '#type' => 'select', @@ -204,7 +212,7 @@ function number_field_is_empty($item, $field) { */ function number_field_formatter_info() { return array( - 'number_integer' => array( + 'number_default' => array( 'label' => t('default'), 'field types' => array('number_integer'), 'settings' => array( @@ -286,32 +294,26 @@ function number_field_widget_info() { } /** - * Implement FAPI hook_elements(). - * - * Any FAPI callbacks needed for individual widgets can be declared here, - * and the element will be passed to those callbacks for processing. - * - * Drupal will automatically theme the element using a theme with - * the same name as the hook_elements key. + * Implement hook_element_info(). * * Includes a regex to check for valid values as an additional parameter * the validator can use. The regex can be overridden if necessary. */ -function number_elements() { - return array( - 'number' => array( - '#input' => TRUE, - '#columns' => array('value'), '#delta' => 0, - '#process' => array('number_elements_process'), - ), +function number_element_info() { + $types['number'] = array( + '#input' => TRUE, + '#columns' => array('value'), + '#delta' => 0, + '#process' => array('number_elements_process'), ); + return $types; } /** * Implement hook_field_widget(). * * Attach a single form element to the form. It will be built out and - * validated in the callback(s) listed in hook_elements. We build it + * validated in the callback(s) listed in hook_element_info(). We build it * out in the callbacks rather than here in hook_widget so it can be * plugged into any module that can provide it with valid * $field information. @@ -480,7 +482,7 @@ function number_decimal_validate($element, &$form_state) { form_set_error($error_field, t('Only numbers and the decimal character (%decimal) are allowed in %field.', array('%decimal' => $field['settings']['decimal'], '%field' => t($instance['label'])))); } else { - $value = str_replace($field['settings']['decimal'], ' .', $value); + $value = str_replace($field['settings']['decimal'], '.', $value); $value = round($value, $field['settings']['scale']); form_set_value($element[$field_key], $value, $form_state); } diff --git a/modules/field/modules/options/CVS/Entries b/modules/field/modules/options/CVS/Entries index 8c560e0..8ea6006 100644 --- a/modules/field/modules/options/CVS/Entries +++ b/modules/field/modules/options/CVS/Entries @@ -1,3 +1,3 @@ /options.info/1.3/Thu Sep 3 08:50:21 2009// -/options.module/1.9/Thu Sep 3 08:50:21 2009// +/options.module/1.10/Fri Oct 2 19:50:13 2009// D diff --git a/modules/field/modules/options/options.module b/modules/field/modules/options/options.module index a37f6eb..c621f48 100644 --- a/modules/field/modules/options/options.module +++ b/modules/field/modules/options/options.module @@ -1,5 +1,5 @@ array( - '#input' => TRUE, - '#columns' => array('value'), '#delta' => 0, - '#process' => array('options_select_elements_process'), - ), - 'options_buttons' => array( - '#input' => TRUE, - '#columns' => array('value'), '#delta' => 0, - '#process' => array('options_buttons_elements_process'), - ), - 'options_onoff' => array( - '#input' => TRUE, - '#columns' => array('value'), '#delta' => 0, - '#process' => array('options_onoff_elements_process'), - ), - ); +function options_element_info() { + $types['options_select'] = array( + '#input' => TRUE, + '#columns' => array('value'), + '#delta' => 0, + '#process' => array('options_select_elements_process'), + ); + $types['options_buttons'] = array( + '#input' => TRUE, + '#columns' => array('value'), + '#delta' => 0, + '#process' => array('options_buttons_elements_process'), + ); + $types['options_onoff'] = array( + '#input' => TRUE, + '#columns' => array('value'), + '#delta' => 0, + '#process' => array('options_onoff_elements_process'), + ); + return $types; } /** diff --git a/modules/field/modules/text/CVS/Entries b/modules/field/modules/text/CVS/Entries index 302aae8..211d3dd 100644 --- a/modules/field/modules/text/CVS/Entries +++ b/modules/field/modules/text/CVS/Entries @@ -1,4 +1,5 @@ /text.info/1.5/Thu Sep 3 08:50:21 2009// -/text.module/1.25/Thu Sep 3 08:50:21 2009// -/text.test/1.10/Thu Sep 3 08:50:21 2009// +/text.js/1.1/Fri Sep 11 13:30:49 2009// +/text.module/1.30/Fri Oct 2 19:50:13 2009// +/text.test/1.12/Fri Oct 2 19:50:13 2009// D diff --git a/modules/field/modules/text/text.js b/modules/field/modules/text/text.js new file mode 100644 index 0000000..7397a8d --- /dev/null +++ b/modules/field/modules/text/text.js @@ -0,0 +1,40 @@ +// $Id: text.js,v 1.1 2009/09/11 13:30:49 webchick Exp $ + +(function ($) { + +/** + * Auto-hide summary textarea if empty and show hide and unhide links. + */ +Drupal.behaviors.textTextareaSummary = { + attach: function (context, settings) { + $('textarea.text-textarea-summary:not(.text-textarea-summary-processed)', context).addClass('text-textarea-summary-processed').each(function () { + var $fieldset = $(this).closest('#body-wrapper'); + var $summary = $fieldset.find('div.text-summary-wrapper'); + var $summaryLabel = $summary.find('div.form-type-textarea label'); + var $full = $fieldset.find('div.text-full-wrapper'); + var $fullLabel = $full.find('div.form-type-textarea label'); + + // Setup the edit/hide summary link. + var $link = $('(' + Drupal.t('Hide summary') + ')').toggle( + function () { + $summary.hide(); + $(this).find('a').html(Drupal.t('Edit summary')).end().appendTo($fullLabel); + return false; + }, + function () { + $summary.show(); + $(this).find('a').html(Drupal.t('Hide summary')).end().appendTo($summaryLabel); + return false; + } + ).appendTo($summaryLabel); + + // If no summary is set, hide the summary field. + if ($(this).val() == '') { + $link.click(); + } + return; + }); + } +}; + +})(jQuery); diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index 2148d57..eae1c1b 100644 --- a/modules/field/modules/text/text.module +++ b/modules/field/modules/text/text.module @@ -1,5 +1,5 @@ array( 'arguments' => array('element' => NULL), ), - 'field_formatter_text_default' => array( - 'arguments' => array('element' => NULL), - ), - 'field_formatter_text_plain' => array( - 'arguments' => array('element' => NULL), - ), - 'field_formatter_text_trimmed' => array( - 'arguments' => array('element' => NULL), - ), - 'field_formatter_text_summary_or_trimmed' => array( - 'arguments' => array('element' => NULL), - ), ); } @@ -128,7 +116,7 @@ function text_field_schema($field) { /** * Implement hook_field_settings_form(). */ -function text_field_settings_form($field, $instance) { +function text_field_settings_form($field, $instance, $has_data) { $settings = $field['settings']; $form['max_length'] = array( @@ -138,6 +126,9 @@ function text_field_settings_form($field, $instance) { '#required' => FALSE, '#description' => t('The maximum length of the field in characters. Leave blank for an unlimited size.'), '#element_validate' => array('_element_validate_integer_positive'), + // @todo: If $has_data, add a validate handler that only allows + // max_length to increase. + '#disabled' => $has_data, ); return $form; @@ -330,7 +321,7 @@ function theme_field_formatter_text_trimmed($element) { /** * Theme function for 'summary or trimmed' field formatter for - * text_with_summary fields. This formatter returns the summary + * text_with_summary fields. This formatter returns the summary * element of the field or, if the summary is empty, the trimmed * version of the full element of the field. */ @@ -533,48 +524,44 @@ function text_field_widget_settings_form($field, $instance) { } /** - * Implement FAPI hook_elements(). - * - * Any FAPI callbacks needed for individual widgets can be declared here, - * and the element will be passed to those callbacks for processing. - * - * Drupal will automatically theme the element using a theme with - * the same name as the hook_elements key. + * Implement hook_element_info(). * * Autocomplete_path is not used by text_field_widget but other * widgets can use it (see nodereference and userreference). */ -function text_elements() { - return array( - 'text_textfield' => array( - '#input' => TRUE, - '#columns' => array('value'), '#delta' => 0, - '#process' => array('text_textfield_elements_process'), - '#theme_wrappers' => array('text_textfield'), - '#autocomplete_path' => FALSE, - ), - 'text_textarea' => array( - '#input' => TRUE, - '#columns' => array('value', 'format'), '#delta' => 0, - '#process' => array('text_textarea_elements_process'), - '#theme_wrappers' => array('text_textarea'), - '#filter_value' => FILTER_FORMAT_DEFAULT, - ), - 'text_textarea_with_summary' => array( - '#input' => TRUE, - '#columns' => array('value', 'format', 'summary'), '#delta' => 0, - '#process' => array('text_textarea_with_summary_process'), - '#theme_wrappers' => array('text_textarea'), - '#filter_value' => FILTER_FORMAT_DEFAULT, - ), +function text_element_info() { + $types['text_textfield'] = array( + '#input' => TRUE, + '#columns' => array('value'), + '#delta' => 0, + '#process' => array('text_textfield_elements_process'), + '#theme_wrappers' => array('text_textfield'), + '#autocomplete_path' => FALSE, + ); + $types['text_textarea'] = array( + '#input' => TRUE, + '#columns' => array('value', 'format'), + '#delta' => 0, + '#process' => array('text_textarea_elements_process'), + '#theme_wrappers' => array('text_textarea'), + '#filter_value' => filter_default_format(), + ); + $types['text_textarea_with_summary'] = array( + '#input' => TRUE, + '#columns' => array('value', 'format', 'summary'), + '#delta' => 0, + '#process' => array('text_textarea_with_summary_process'), + '#theme_wrappers' => array('text_textarea'), + '#filter_value' => filter_default_format(), ); + return $types; } /** * Implement hook_field_widget(). * * Attach a single form element to the form. It will be built out and - * validated in the callback(s) listed in hook_elements. We build it + * validated in the callback(s) listed in hook_element_info(). We build it * out in the callbacks rather than here in hook_field_widget so it can be * plugged into any module that can provide it with valid * $field information. @@ -667,7 +654,7 @@ function text_textfield_elements_process($element, $form_state, $form) { if (!empty($instance['settings']['text_processing'])) { $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; + $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); $element[$field_key]['#text_format'] = $format; } @@ -700,7 +687,7 @@ function text_textarea_elements_process($element, $form_state, $form) { if (!empty($instance['settings']['text_processing'])) { $filter_key = (count($element['#columns']) == 2) ? $element['#columns'][1] : 'format'; - $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : FILTER_FORMAT_DEFAULT; + $format = isset($element['#value'][$filter_key]) ? $element['#value'][$filter_key] : filter_default_format(); $element[$field_key]['#text_format'] = $format; } @@ -732,6 +719,10 @@ function text_textarea_with_summary_process($element, $form_state, $form) { '#description' => t('Leave blank to use trimmed value of full text as the summary.'), '#required' => $element['#required'], '#display' => $display, + '#attached' => array('js' => array(drupal_get_path('module', 'text') . '/text.js')), + '#attributes' => array('class' => array('text-textarea-summary')), + '#prefix' => '' . $this->randomName(); + $value = '' . $this->randomName() . ''; $edit = array( "{$this->field_name}[$langcode][0][value]" => $value, ); @@ -168,21 +179,31 @@ class TextFieldTestCase extends DrupalWebTestCase { $entity = field_test_entity_load($id); $entity->content = field_attach_view($entity_type, $entity); $this->content = drupal_render($entity->content); - $this->assertNoRaw($value, 'Filtered tags are not displayed'); - $this->assertRaw(str_replace('
', '', $value), t('Filtered value is displayed correctly')); + $this->assertNoRaw($value, t('HTML tags are not displayed.')); + $this->assertRaw(check_plain($value), t('Escaped HTML is displayed correctly.')); - // Allow the user to use the 'Full HTML' format. - db_update('filter_format')->fields(array('roles' => ',2,'))->condition('format', 2)->execute(); + // Create a new text format that does not escape HTML, and grant the user + // access to it. + $this->drupalLogin($this->admin_user); + $edit = array('name' => $this->randomName()); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + filter_formats_reset(); + $this->checkPermissions(array(), TRUE); + $format_id = db_query("SELECT format FROM {filter_format} WHERE name = :name", array(':name' => $edit['name']))->fetchField(); + $permission = filter_permission_name(filter_format_load($format_id)); + $rid = max(array_keys($this->web_user->roles)); + user_role_grant_permissions($rid, array($permission), TRUE); + $this->drupalLogin($this->web_user); // Display edition form. // We should now have a 'text format' selector. $this->drupalGet('test-entity/' . $id . '/edit'); $this->assertFieldByName("{$this->field_name}[$langcode][0][value]", '', t('Widget is displayed')); - $this->assertFieldByName("{$this->field_name}[$langcode][0][value_format]", '1', t('Format selector is displayed')); + $this->assertFieldByName("{$this->field_name}[$langcode][0][value_format]", '', t('Format selector is displayed')); - // Edit and change the format to 'Full HTML'. + // Edit and change the text format to the new one that was created. $edit = array( - "{$this->field_name}[$langcode][0][value_format]" => 2, + "{$this->field_name}[$langcode][0][value_format]" => $format_id, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('test_entity @id has been updated.', array('@id' => $id)), t('Entity was updated')); @@ -193,11 +214,6 @@ class TextFieldTestCase extends DrupalWebTestCase { $this->content = drupal_render($entity->content); $this->assertRaw($value, t('Value is displayed unfiltered')); } - - // Test formatters. - /** - * - */ } class TextSummaryTestCase extends DrupalWebTestCase { diff --git a/modules/field/theme/CVS/Entries b/modules/field/theme/CVS/Entries index bf69097..e6325a2 100644 --- a/modules/field/theme/CVS/Entries +++ b/modules/field/theme/CVS/Entries @@ -1,4 +1,4 @@ /field-rtl.css/1.1/Thu Sep 3 08:50:22 2009// /field.css/1.6/Thu Sep 3 08:50:22 2009// -/field.tpl.php/1.5/Thu Sep 3 08:50:22 2009// +/field.tpl.php/1.6/Fri Oct 2 19:50:13 2009// D diff --git a/modules/field/theme/field.tpl.php b/modules/field/theme/field.tpl.php index b8df04d..c3a59e7 100644 --- a/modules/field/theme/field.tpl.php +++ b/modules/field/theme/field.tpl.php @@ -1,5 +1,5 @@ -
' . t('These settings apply to the %field field everywhere it is used.', array('%field' => $instance['label'])) . '
'; + $has_data = field_has_data($field); + if ($has_data) { + $description = '' . t('These settings apply to the %field field everywhere it is used. Because the field already has data, some settings can no longer be changed.', array('%field' => $instance['label'])) . '
'; + } + else { + $description = '' . t('These settings apply to the %field field everywhere it is used.', array('%field' => $instance['label'])) . '
'; + } // Create a form structure for the field values. $form['field'] = array( @@ -1143,10 +1144,11 @@ function field_ui_field_edit_form(&$form_state, $obj_type, $bundle, $instance) { '#description' => $description, ); - // Add additional field settings from the field module. - $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance); + // Add additional field type settings. The field type module is + // responsible for not returning settings that cannot be changed if + // the field already has data. + $additions = module_invoke($field['module'], 'field_settings_form', $field, $instance, $has_data); if (is_array($additions)) { - // @todo Filter additional settings by whether they can be changed. $form['field']['settings'] = $additions; } @@ -1281,7 +1283,7 @@ function field_ui_field_edit_form_submit($form, &$form_state) { // Update any field settings that have changed. $field = field_info_field($instance_values['field_name']); $field = array_merge($field, $field_values); - field_ui_update_field($field); + field_update_field($field); // Move the default value from the sample widget to the default value field. if (isset($instance_values['default_value_widget'])) { diff --git a/modules/field_ui/field_ui.api.php b/modules/field_ui/field_ui.api.php index 48a62a2..9e1865a 100644 --- a/modules/field_ui/field_ui.api.php +++ b/modules/field_ui/field_ui.api.php @@ -1,5 +1,5 @@ 'textfield', @@ -85,7 +98,6 @@ function hook_field_instance_settings_form($field, $instance) { function hook_field_widget_settings_form($field, $instance) { $widget = $instance['widget']; $settings = $widget['settings']; - $form = array(); if ($widget['type'] == 'text_textfield') { $form['size'] = array( diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module index 7fcfaa4..b9596d8 100644 --- a/modules/field_ui/field_ui.module +++ b/modules/field_ui/field_ui.module @@ -1,5 +1,5 @@ $scheme_options, '#default_value' => $settings['uri_scheme'], '#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'), + '#disabled' => $has_data, ); $form['default_file'] = array( @@ -292,7 +293,7 @@ function file_field_update($obj_type, $object, $field, $instance, $langcode, &$i } // Delete items from original object. - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); $load_function = $obj_type . '_load'; $original = $load_function($id); @@ -312,7 +313,7 @@ function file_field_update($obj_type, $object, $field, $instance, $langcode, &$i * Implement hook_field_delete(). */ function file_field_delete($obj_type, $object, $field, $instance, $langcode, &$items) { - list($id, $vid, $bundle) = field_attach_extract_ids($obj_type, $object); + list($id, $vid, $bundle) = field_extract_ids($obj_type, $object); foreach ($items as $delta => $item) { // For hook_file_references(), remember that this is being deleted. $item['file_field_name'] = $field['field_name']; diff --git a/modules/file/file.module b/modules/file/file.module index 722e065..b85178b 100644 --- a/modules/file/file.module +++ b/modules/file/file.module @@ -1,5 +1,5 @@ TRUE, '#process' => array('file_managed_file_process'), '#value_callback' => 'file_managed_file_value', @@ -55,8 +53,7 @@ function file_elements() { 'js' => array($file_path . '/file.js'), ), ); - - return $elements; + return $types; } /** @@ -85,15 +82,6 @@ function file_theme() { 'file_upload_help' => array( 'arguments' => array('upload_validators' => NULL), ), - 'field_formatter_file_default' => array( - 'arguments' => array('element' => NULL), - ), - 'field_formatter_file_table' => array( - 'arguments' => array('element' => NULL), - ), - 'field_formatter_file_url_plain' => array( - 'arguments' => array('element' => NULL), - ), ); } diff --git a/modules/file/tests/CVS/Entries b/modules/file/tests/CVS/Entries index 111f91f..b27419f 100644 --- a/modules/file/tests/CVS/Entries +++ b/modules/file/tests/CVS/Entries @@ -1,4 +1,4 @@ -/file.test/1.1/Thu Sep 3 08:50:25 2009// /file_module_test.info/1.1/Thu Sep 3 08:50:25 2009// -/file_module_test.module/1.1/Thu Sep 3 08:50:25 2009// +/file.test/1.2/Fri Oct 2 19:50:14 2009// +/file_module_test.module/1.2/Fri Oct 2 19:50:14 2009// D diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test index 6b87fac..969a983 100644 --- a/modules/file/tests/file.test +++ b/modules/file/tests/file.test @@ -1,5 +1,5 @@ t('File field revision test'), - 'description' => t('Test creating and deleting revisions with files attached.'), - 'group' => t('File'), + 'name' => 'File field revision test', + 'description' => 'Test creating and deleting revisions with files attached.', + 'group' => 'File', ); } @@ -269,11 +269,11 @@ class FileFieldRevisionTestCase extends FileFieldTestCase { * Test class to check that formatters are working properly. */ class FileFieldDisplayTestCase extends FileFieldTestCase { - public function getInfo() { + public static function getInfo() { return array( - 'name' => t('File field display tests'), - 'description' => t('Test the display of file fields in node and views.'), - 'group' => t('File'), + 'name' => 'File field display tests', + 'description' => 'Test the display of file fields in node and views.', + 'group' => 'File', ); } @@ -323,11 +323,11 @@ class FileFieldValidateTestCase extends FileFieldTestCase { protected $field; protected $node_type; - public function getInfo() { + public static function getInfo() { return array( - 'name' => t('File field validation tests'), - 'description' => t('Tests validation functions such as file type, max file size, max size per node, and required.'), - 'group' => t('File'), + 'name' => 'File field validation tests', + 'description' => 'Tests validation functions such as file type, max file size, max size per node, and required.', + 'group' => 'File', ); } @@ -478,11 +478,11 @@ class FileFieldValidateTestCase extends FileFieldTestCase { * Test class to check that files are uploaded to proper locations. */ class FileFieldPathTestCase extends FileFieldTestCase { - function getInfo() { + public static function getInfo() { return array( - 'name' => t('File field file path tests'), - 'description' => t('Test that files are uploaded to the proper location with token support.'), - 'group' => t('File'), + 'name' => 'File field file path tests', + 'description' => 'Test that files are uploaded to the proper location with token support.', + 'group' => 'File', ); } diff --git a/modules/file/tests/file_module_test.module b/modules/file/tests/file_module_test.module index 4e2f541..535dd57 100644 --- a/modules/file/tests/file_module_test.module +++ b/modules/file/tests/file_module_test.module @@ -1,5 +1,5 @@ TRUE, - ); +function file_module_test_form($form, $form_state) { + $form['#tree'] = TRUE; $form['file'] = array( '#type' => 'managed_file', diff --git a/modules/filter/CVS/Entries b/modules/filter/CVS/Entries index 9e1e422..443ab35 100644 --- a/modules/filter/CVS/Entries +++ b/modules/filter/CVS/Entries @@ -1,9 +1,9 @@ -/filter.api.php/1.13/Thu Sep 3 08:50:25 2009// /filter.css/1.1/Thu Sep 3 08:50:25 2009// /filter.info/1.12/Thu Sep 3 08:50:25 2009// -/filter.install/1.19/Thu Sep 3 08:50:25 2009// -/filter.module/1.287/Thu Sep 3 08:50:25 2009// /filter.pages.inc/1.7/Thu Sep 3 08:50:25 2009// -/filter.test/1.39/Thu Sep 3 08:50:25 2009// -/filter.admin.inc/1.43/Sat Sep 5 13:40:16 2009// +/filter.admin.inc/1.47/Fri Oct 2 19:50:14 2009// +/filter.api.php/1.15/Fri Oct 2 19:50:14 2009// +/filter.install/1.21/Fri Oct 2 19:50:14 2009// +/filter.module/1.291/Fri Oct 2 19:50:14 2009// +/filter.test/1.43/Fri Oct 2 19:50:14 2009// D diff --git a/modules/filter/filter.admin.inc b/modules/filter/filter.admin.inc index 8e633bb..36b524d 100644 --- a/modules/filter/filter.admin.inc +++ b/modules/filter/filter.admin.inc @@ -1,5 +1,5 @@ TRUE); + $form['#tree'] = TRUE; foreach ($formats as $id => $format) { - $roles = array(); - foreach (user_roles() as $rid => $name) { - // Prepare a roles array with roles that may access the filter. - if (strpos($format->roles, ",$rid,") !== FALSE) { - $roles[] = $name; - } + // Check whether this is the fallback text format. This format is available + // to all roles and cannot be deleted via the admin interface. + $form['formats'][$id]['#is_fallback'] = ($id == $fallback_format); + if ($form['formats'][$id]['#is_fallback']) { + $form['formats'][$id]['name'] = array('#markup' => theme('placeholder', $format->name)); + $roles_markup = theme('placeholder', t('All roles may use this format')); + } + else { + $form['formats'][$id]['name'] = array('#markup' => check_plain($format->name)); + $roles = filter_get_roles_by_format($format); + $roles_markup = $roles ? implode(', ', $roles) : t('No roles may use this format'); } - $default = ($id == variable_get('filter_default_format', 1)); - $options[$id] = ''; - $form[$id]['name'] = array('#markup' => $format->name); - $form[$id]['roles'] = array('#markup' => $default ? t('All roles may use the default format') : ($roles ? implode(', ', $roles) : t('No roles may use this format'))); - $form[$id]['configure'] = array('#markup' => l(t('configure'), 'admin/config/content/formats/' . $id)); - $form[$id]['delete'] = array('#markup' => $default ? '' : l(t('delete'), 'admin/config/content/formats/delete/' . $id)); - $form[$id]['weight'] = array('#type' => 'weight', '#default_value' => $format->weight); + $form['formats'][$id]['roles'] = array('#markup' => $roles_markup); + $form['formats'][$id]['configure'] = array('#markup' => l(t('configure'), 'admin/config/content/formats/' . $id)); + $form['formats'][$id]['delete'] = array('#markup' => $form['formats'][$id]['#is_fallback'] ? '' : l(t('delete'), 'admin/config/content/formats/' . $id . '/delete')); + $form['formats'][$id]['weight'] = array('#type' => 'weight', '#default_value' => $format->weight); } - $form['default'] = array('#type' => 'radios', '#options' => $options, '#default_value' => variable_get('filter_default_format', 1)); $form['submit'] = array('#type' => 'submit', '#value' => t('Save changes')); return $form; } function filter_admin_overview_submit($form, &$form_state) { - // Process form submission to set the default format. - if (is_numeric($form_state['values']['default'])) { - drupal_set_message(t('Default format updated.')); - variable_set('filter_default_format', $form_state['values']['default']); - } - foreach ($form_state['values'] as $id => $data) { + foreach ($form_state['values']['formats'] as $id => $data) { if (is_array($data) && isset($data['weight'])) { // Only update if this is a form element with weight. db_update('filter_format') @@ -56,35 +50,31 @@ function filter_admin_overview_submit($form, &$form_state) { ->execute(); } } + filter_formats_reset(); drupal_set_message(t('The text format ordering has been saved.')); } /** - * Theme the admin overview form. + * Theme the text format administration overview form. * * @ingroup themeable */ function theme_filter_admin_overview($form) { $rows = array(); - foreach (element_children($form) as $id) { - $element = $form[$id]; - if (isset($element['roles']) && is_array($element['roles'])) { - $element['weight']['#attributes']['class'] = array('text-format-order-weight'); - $rows[] = array( - 'data' => array( - check_plain($element['name']['#markup']), - drupal_render($element['roles']), - drupal_render($form['default'][$id]), - drupal_render($element['weight']), - drupal_render($element['configure']), - drupal_render($element['delete']), - ), - 'class' => array('draggable'), - ); - unset($form[$id]); - } + foreach (element_children($form['formats']) as $id) { + $form['formats'][$id]['weight']['#attributes']['class'] = array('text-format-order-weight'); + $rows[] = array( + 'data' => array( + drupal_render($form['formats'][$id]['name']), + drupal_render($form['formats'][$id]['roles']), + drupal_render($form['formats'][$id]['weight']), + drupal_render($form['formats'][$id]['configure']), + drupal_render($form['formats'][$id]['delete']), + ), + 'class' => array('draggable'), + ); } - $header = array(t('Name'), t('Roles'), t('Default'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2)); + $header = array(t('Name'), t('Roles'), t('Weight'), array('data' => t('Operations'), 'colspan' => 2)); $output = theme('table', $header, $rows, array('id' => 'text-format-order')); $output .= drupal_render_children($form); @@ -99,7 +89,7 @@ function theme_filter_admin_overview($form) { function filter_admin_format_page($format = NULL) { if (!isset($format->name)) { drupal_set_title(t('Add text format'), PASS_THROUGH); - $format = (object)array('name' => '', 'roles' => '', 'format' => FILTER_FORMAT_DEFAULT); + $format = (object)array('name' => '', 'format' => 0); } return drupal_get_form('filter_admin_format_form', $format); } @@ -111,14 +101,14 @@ function filter_admin_format_page($format = NULL) { * @see filter_admin_format_form_validate() * @see filter_admin_format_form_submit() */ -function filter_admin_format_form(&$form_state, $format) { - $default = ($format->format == variable_get('filter_default_format', 1)); - if ($default) { - $help = t('All roles for the default format must be enabled and cannot be changed.'); - $form['default_format'] = array('#type' => 'hidden', '#value' => 1); +function filter_admin_format_form($form, &$form_state, $format) { + $is_fallback = ($format->format == filter_fallback_format()); + if ($is_fallback) { + $help = t('All roles for this text format must be enabled and cannot be changed.'); } - $form['name'] = array('#type' => 'textfield', + $form['name'] = array( + '#type' => 'textfield', '#title' => t('Name'), '#default_value' => $format->name, '#description' => t('Specify a unique name for this text format.'), @@ -128,23 +118,22 @@ function filter_admin_format_form(&$form_state, $format) { // Add a row of checkboxes for form group. $form['roles'] = array('#type' => 'fieldset', '#title' => t('Roles'), - '#description' => $default ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'), + '#description' => $is_fallback ? $help : t('Choose which roles may use this text format. Note that roles with the "administer filters" permission can always use all text formats.'), '#tree' => TRUE, ); - + $checked = filter_get_roles_by_format($format); foreach (user_roles() as $rid => $name) { - $checked = strpos($format->roles, ",$rid,") !== FALSE; $form['roles'][$rid] = array('#type' => 'checkbox', '#title' => $name, - '#default_value' => ($default || $checked), + '#default_value' => ($is_fallback || isset($checked[$rid])), ); - if ($default) { + if ($is_fallback) { $form['roles'][$rid]['#disabled'] = TRUE; } } // Table with filters $filter_info = filter_get_filters(); - $filters = filter_list_format($format->format); + $filters = filter_list_format($format->format, TRUE); $form['filters'] = array('#type' => 'fieldset', '#title' => t('Filters'), @@ -152,10 +141,10 @@ function filter_admin_format_form(&$form_state, $format) { '#tree' => TRUE, ); foreach ($filter_info as $name => $filter) { - $form['filters'][$name] = array( + $form['filters'][$name]['status'] = array( '#type' => 'checkbox', '#title' => $filter['title'], - '#default_value' => isset($filters[$name]), + '#default_value' => !empty($filters[$name]->status), '#description' => $filter['description'], ); } @@ -201,13 +190,19 @@ function filter_admin_format_form_submit($form, &$form_state) { $format->format = isset($form_state['values']['format']) ? $form_state['values']['format'] : NULL; $status = filter_format_save($format); + if ($permission = filter_permission_name($format)) { + foreach ($format->roles as $rid => $enabled) { + user_role_change_permissions($rid, array($permission => $enabled)); + } + } + switch ($status) { case SAVED_NEW: drupal_set_message(t('Added text format %format.', array('%format' => $format->name))); break; case SAVED_UPDATED: - drupal_set_message(t('The text format settings have been updated.')); + drupal_set_message(t('The text format %format has been updated.', array('%format' => $format->name))); break; } } @@ -218,27 +213,16 @@ function filter_admin_format_form_submit($form, &$form_state) { * @ingroup forms * @see filter_admin_delete_submit() */ -function filter_admin_delete(&$form_state, $format) { - if ($format) { - if ($format->format != variable_get('filter_default_format', 1)) { - $form['#format'] = $format; - - return confirm_form($form, - t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)), - 'admin/config/content/formats', - t('If you have any content left in this text format, it will be switched to the default text format. This action cannot be undone.'), - t('Delete'), - t('Cancel') - ); - } - else { - drupal_set_message(t('The default format cannot be deleted.')); - drupal_goto('admin/config/content/formats'); - } - } - else { - drupal_not_found(); - } +function filter_admin_delete($form, &$form_state, $format) { + $form['#format'] = $format; + + return confirm_form($form, + t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)), + 'admin/config/content/formats', + t('If you have any content left in this text format, it will be switched to the %fallback text format. This action cannot be undone.', array('%fallback' => filter_fallback_format_title())), + t('Delete'), + t('Cancel') + ); } /** @@ -268,7 +252,7 @@ function filter_admin_configure_page($format) { * * @ingroup forms */ -function filter_admin_configure(&$form_state, $format) { +function filter_admin_configure($form, &$form_state, $format) { $filters = filter_list_format($format->format); $filter_info = filter_get_filters(); @@ -278,7 +262,7 @@ function filter_admin_configure(&$form_state, $format) { // Pass along stored filter settings and default settings, but also the // format object and all filters to allow for complex implementations. $defaults = (isset($filter_info[$name]['default settings']) ? $filter_info[$name]['default settings'] : array()); - $settings_form = $filter_info[$name]['settings callback']($form_state, $filters[$name], $defaults, $format, $filters); + $settings_form = $filter_info[$name]['settings callback']($form, $form_state, $filters[$name], $defaults, $format, $filters); if (isset($settings_form) && is_array($settings_form)) { $form['settings'][$name] = array( '#type' => 'fieldset', @@ -338,7 +322,7 @@ function filter_admin_order_page($format) { * @see theme_filter_admin_order() * @see filter_admin_order_submit() */ -function filter_admin_order(&$form_state, $format = NULL) { +function filter_admin_order($form, &$form_state, $format = NULL) { // Get list (with forced refresh). $filters = filter_list_format($format->format); diff --git a/modules/filter/filter.api.php b/modules/filter/filter.api.php index 4c96f8b..8552f67 100644 --- a/modules/filter/filter.api.php +++ b/modules/filter/filter.api.php @@ -1,5 +1,5 @@ 'textfield', * '#title' => t('Maximum link text length'), @@ -236,20 +236,21 @@ function hook_filter_format_update($format) { * * It is recommended for modules to implement this hook, when they store * references to text formats to replace existing references to the deleted - * text format with the default format. + * text format with the fallback format. * * @param $format * The format object of the format being deleted. - * @param $default - * The format object of the site's default format. + * @param $fallback + * The format object of the site's fallback format, which is always available + * to all users. * * @see hook_filter_format_update(). * @see hook_filter_format_delete(). */ -function hook_filter_format_delete($format, $default) { - // Replace the deleted format with the default format. +function hook_filter_format_delete($format, $fallback) { + // Replace the deleted format with the fallback format. db_update('my_module_table') - ->fields(array('format' => $default->format)) + ->fields(array('format' => $fallback->format)) ->condition('format', $format->format) ->execute(); } diff --git a/modules/filter/filter.install b/modules/filter/filter.install index 4a78830..82ec17a 100644 --- a/modules/filter/filter.install +++ b/modules/filter/filter.install @@ -1,5 +1,5 @@ '', 'description' => 'Name of the text format (Filtered HTML).', ), - 'roles' => array( - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - 'description' => 'A comma-separated string of roles; references {role}.rid.', // This is bad since you can't use joins, nor index. - ), 'cache' => array( 'type' => 'int', 'not null' => TRUE, @@ -111,46 +104,53 @@ function filter_schema() { return $schema; } +/** + * @defgroup updates-6.x-to-7.x Filter updates from 6.x to 7.x + * @{ + */ + /** * Add a weight column to the filter formats table. */ function filter_update_7000() { - $ret = array(); - db_add_field($ret, 'filter_formats', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny')); - return $ret; + db_add_field('filter_formats', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny')); } /** * Break out "escape HTML filter" option to its own filter. */ function filter_update_7001() { - $ret = array(); $result = db_query("SELECT format FROM {filter_formats}"); + $insert = db_insert('filters')->fields(array('format', 'module', 'delta', 'weight')); + foreach ($result as $format) { // Deprecated constants FILTER_HTML_STRIP = 1 and FILTER_HTML_ESCAPE = 2. if (variable_get('filter_html_' . $format->format, 1) == 2) { - $ret[] = update_sql("INSERT INTO {filters} (format, module, delta, weight) VALUES (" . $format->format . ", 'filter', 4, 0)"); + $insert->values(array( + 'format' => $format->format, + 'filter' => 'filter', + 'delta' => 4, + 'weight' => 0, + )); } variable_del('filter_html_' . $format->format); } - return $ret; + + $insert->execute(); } /** * Rename {filters} table to {filter} and {filter_formats} table to {filter_format}. */ function filter_update_7002() { - $ret = array(); - db_rename_table($ret, 'filters', 'filter'); - db_rename_table($ret, 'filter_formats', 'filter_format'); - return $ret; + db_rename_table('filters', 'filter'); + db_rename_table('filter_formats', 'filter_format'); } /** * Remove hardcoded numeric deltas from all filters in core. */ function filter_update_7003() { - $ret = array(); // Get an array of the renamed filter deltas, organized by module. $renamed_deltas = array( 'filter' => array( @@ -166,9 +166,9 @@ function filter_update_7003() { ); // Rename field 'delta' to 'name'. - db_drop_unique_key($ret, 'filter', 'fmd'); - db_drop_index($ret, 'filter', 'list'); - db_change_field($ret, 'filter', 'delta', 'name', + db_drop_unique_key('filter', 'fmd'); + db_drop_index('filter', 'list'); + db_change_field('filter', 'delta', 'name', array( 'type' => 'varchar', 'length' => 32, @@ -189,10 +189,13 @@ function filter_update_7003() { // Loop through each filter and make changes to the core filter table. foreach ($renamed_deltas as $module => $deltas) { foreach ($deltas as $old_delta => $new_delta) { - $ret[] = update_sql("UPDATE {filter} SET name = '" . $new_delta . "' WHERE module = '" . $module . "' AND name = '" . $old_delta . "'"); + db_update('filter') + ->fields(array('name', $new_delta)) + ->condition('module', $module) + ->condition('name', $old_delta) + ->execute(); } } - return $ret; } /** @@ -204,10 +207,9 @@ function filter_update_7003() { * - Add {filter}.settings. */ function filter_update_7004() { - $ret = array(); - db_drop_field($ret, 'filter', 'fid'); - db_add_primary_key($ret, 'filter', array('format', 'name')); - db_add_field($ret, 'filter', 'status', + db_drop_field('filter', 'fid'); + db_add_primary_key('filter', array('format', 'name')); + db_add_field('filter', 'status', array( 'type' => 'int', 'not null' => TRUE, @@ -215,7 +217,7 @@ function filter_update_7004() { 'description' => 'Filter enabled status. (1 = enabled, 0 = disabled)', ) ); - db_add_field($ret, 'filter', 'settings', + db_add_field('filter', 'settings', array( 'type' => 'text', 'not null' => FALSE, @@ -226,7 +228,9 @@ function filter_update_7004() { ); // Enable all existing filters ({filter} contained only enabled previously). - $ret[] = update_sql("UPDATE {filter} SET status = 1"); + db_update('filter') + ->fields('status', '1') + ->execute(); // Move filter settings from system variables into {filter}.settings. $filters = db_query("SELECT * FROM {filter} WHERE module = :name", array(':name' => 'filter')); @@ -253,10 +257,95 @@ function filter_update_7004() { } } if (!empty($settings)) { - $ret[] = update_sql("UPDATE {filter} SET settings = '" . serialize($settings) . "' WHERE format = {$filter->format} AND name = '{$filter->name}'"); + db_upddate('filter') + ->fields(array('settings' => serialize($settings))) + ->condition('format', $filter->format) + ->condition('name', $filter->name) + ->execute(); + } + } +} + +/** + * Integrate text formats with the user permissions system. + * + * This function converts text format role assignments to use the new text + * format permissions introduced in Drupal 7, creates a fallback (plain text) + * format that is available to all users, and explicitly sets the text format + * in cases that used to rely on a single site-wide default. + */ +function filter_update_7005() { + + // Move role data from the filter system to the user permission system. + $all_roles = array_keys(user_roles()); + $default_format = variable_get('filter_default_format', 1); + $result = db_query("SELECT * FROM {filter_format}"); + foreach ($result as $format) { + // We need to assign the default format to all roles (regardless of what + // was stored in the database) to preserve the behavior of the site at the + // moment of the upgrade. + $format_roles = ($format->format == $default_format ? $all_roles : explode(',', $format->roles)); + foreach ($format_roles as $format_role) { + if (in_array($format_role, $all_roles)) { + user_role_grant_permissions($format_role, array(filter_permission_name($format))); + } } } - return $ret; + // Drop the roles field from the {filter_format} table. + db_drop_field('filter_format', 'roles'); + + // Add a fallback text format which outputs plain text and appears last on + // the list for all users. Generate a unique name for it, starting with + // "Plain text". + $start_name = 'Plain text'; + $format_name = $start_name; + while ($format = db_query('SELECT format FROM {filter_format} WHERE name = :name', array(':name' => $format_name))->fetchField()) { + $id = empty($id) ? 1 : $id + 1; + $format_name = $start_name . ' ' . $id; + } + $fallback_format = new stdClass(); + $fallback_format->name = $format_name; + $fallback_format->cache = 1; + $fallback_format->weight = 1; + // This format should output plain text, so we escape all HTML and apply the + // line break filter only. + $fallback_format->filters = array( + 'filter_html_escape' => array('status' => 1), + 'filter_autop' => array('status' => 1), + ); + filter_format_save($fallback_format); + variable_set('filter_fallback_format', $fallback_format->format); + drupal_set_message('A new Plain text format has been created which will be available to all users. You can configure this text format on the text format configuration page.'); + + // Move the former site-wide default text format to the top of the list, so + // that it continues to be the default text format for all users. + db_update('filter_format') + ->fields(array('weight' => -1)) + ->condition('format', $default_format) + ->execute(); + + // It was previously possible for a value of "0" to be stored in database + // tables to indicate that a particular piece of text should be filtered + // using the default text format. Therefore, we have to convert all such + // instances (in Drupal core) to explicitly use the appropriate format. + // Note that the update of the node body field is handled separately, in + // node_update_7006(). + foreach (array('block_custom', 'comment') as $table) { + if (db_table_exists($table)) { + db_update($table) + ->fields(array('format' => $default_format)) + ->condition('format', 0) + ->execute(); + } + } + + // We do not delete the 'filter_default_format' variable, since other modules + // may need it in their update functions. + // @todo This variable can be deleted in Drupal 8. } +/** + * @} End of "defgroup updates-6.x-to-7.x" + * The next series of updates should start at 8000. + */ diff --git a/modules/filter/filter.module b/modules/filter/filter.module index f661652..e11dc18 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -1,19 +1,11 @@ ' . t("The filter module allows administrators to configure text formats for use on your site. A text format defines the HTML tags, codes, and other input allowed in both content and comments, and is a key feature in guarding against potentially damaging input from malicious users. Two formats included by default are Filtered HTML (which allows only an administrator-approved subset of HTML tags) and Full HTML (which allows the full set of HTML tags). Additional formats may be created by an administrator.") . ''; $output .= '' . t('Each text format uses filters to manipulate text, and most formats apply several different filters to text in a specific order. Each filter is designed for a specific purpose, and generally either adds, removes or transforms elements within user-entered text before it is displayed. A filter does not change the actual content of a post, but instead, modifies it temporarily before it is displayed. A filter may remove unapproved HTML tags, for instance, while another automatically adds HTML to make links referenced in text clickable.') . '
'; - $output .= '' . t('Users with access to more than one text format can use the Text format fieldset to choose between available text formats when creating or editing multi-line content. Administrators determine the text formats available to each user role, select a default text format, and control the order of formats listed in the Text format fieldset.') . '
'; + $output .= '' . t('Users with access to more than one text format can use the Text format fieldset to choose between available text formats when creating or editing multi-line content. Administrators determine the text formats available to each user role and control the order of formats listed in the Text format fieldset.') . '
'; $output .= '' . t('For more information, see the online handbook entry for Filter module.', array('@filter' => 'http://drupal.org/handbook/modules/filter/')) . '
'; return $output; case 'admin/config/content/formats': - $output = '' . t('Use the list below to review the text formats available to each user role, to select a default text format, and to control the order of formats listed in the Text format fieldset. (The Text format fieldset is displayed below textareas when users with access to more than one text format create multi-line content.) The text format selected as Default is available to all users and, unless another format is selected, is applied to all content. All text formats are available to users in roles with the "administer filters" permission.') . '
'; - $output .= '' . t('Since text formats, if available, are presented in the same order as the list below, it may be helpful to arrange the formats in descending order of your preference for their use. Remember that your changes will not be saved until you click the Save changes button at the bottom of the page.') . '
'; + $output = '' . t('Use the list below to review the text formats available to each user role and to control the order of formats listed in the Text format fieldset. (The Text format fieldset is displayed below textareas when users with access to more than one text format create multi-line content.) All text formats are available to users in roles with the "administer filters" permission, and the special %fallback format is available to all users. You can configure access to other text formats on the permissions page.', array('%fallback' => filter_fallback_format_title(), '@url' => url('admin/config/people/permissions', array('fragment' => 'module-filter')))) . '
'; + $output .= '' . t('Since text formats, if available, are presented in the same order as the list below, and the default format for each user is the first one on the list for which that user has access, it may be helpful to arrange the formats in descending order of your preference for their use. Remember that your changes will not be saved until you click the Save changes button at the bottom of the page.') . '
'; return $output; case 'admin/config/content/formats/%': return '' . t('Every filter performs one particular change on the user input, for example stripping out malicious HTML or making URLs clickable. Choose which filters you want to apply to text in this format. If you notice some filters are causing conflicts in the output, you can rearrange them.', array('@rearrange' => url('admin/config/content/formats/' . $arg[4] . '/order'))) . '
'; @@ -70,6 +62,13 @@ function filter_theme() { * Implement hook_menu(). */ function filter_menu() { + $items['filter/tips'] = array( + 'title' => 'Compose tips', + 'page callback' => 'filter_tips_long', + 'access callback' => TRUE, + 'type' => MENU_SUGGESTED_ITEM, + 'file' => 'filter.pages.inc', + ); $items['admin/config/content/formats'] = array( 'title' => 'Text formats', 'description' => 'Configure how content input by users is filtered, including allowed HTML tags. Also allows enabling of module-provided filters.', @@ -90,13 +89,6 @@ function filter_menu() { 'weight' => 1, 'file' => 'filter.admin.inc', ); - $items['filter/tips'] = array( - 'title' => 'Compose tips', - 'page callback' => 'filter_tips_long', - 'access callback' => TRUE, - 'type' => MENU_SUGGESTED_ITEM, - 'file' => 'filter.pages.inc', - ); $items['admin/config/content/formats/%filter_format'] = array( 'type' => MENU_CALLBACK, 'title callback' => 'filter_admin_format_title', @@ -133,13 +125,28 @@ function filter_menu() { 'title' => 'Delete text format', 'page callback' => 'drupal_get_form', 'page arguments' => array('filter_admin_delete', 4), - 'access arguments' => array('administer filters'), + 'access callback' => '_filter_delete_format_access', + 'access arguments' => array(4), 'type' => MENU_CALLBACK, 'file' => 'filter.admin.inc', ); return $items; } +/** + * Access callback for deleting text formats. + * + * @param $format + * A text format object. + * @return + * TRUE if the text format can be deleted by the current user, FALSE + * otherwise. + */ +function _filter_delete_format_access($format) { + // The fallback format can never be deleted. + return user_access('administer filters') && ($format->format != filter_fallback_format()); +} + /** * Load a text format object from the database. * @@ -150,7 +157,8 @@ function filter_menu() { * A fully-populated text format object. */ function filter_format_load($format) { - return filter_formats($format); + $formats = filter_formats(); + return isset($formats[$format]) ? $formats[$format] : FALSE; } /** @@ -160,17 +168,6 @@ function filter_format_load($format) { * A format object. */ function filter_format_save($format) { - // We store the roles as a string for ease of use. - // We should always set all roles to TRUE when saving the default format. - // We use leading and trailing comma's to allow easy substring matching. - $roles = array_filter($format->roles); - if (!empty($format->format) && $format->format == variable_get('filter_default_format', 1)) { - $roles = ',' . implode(',', array_keys(user_roles())) . ','; - } - else { - $roles = ',' . implode(',', array_keys($roles)) . ','; - } - $format->roles = $roles; $format->name = trim($format->name); // Add a new text format. @@ -185,15 +182,14 @@ function filter_format_save($format) { // to the bottom. $current = filter_list_format($format->format); $filters = $format->filters; - - foreach ($filters as $name => $status) { + foreach ($filters as $name => $filter) { $fields = array(); // Add new filters to the bottom. $fields['weight'] = isset($current[$name]->weight) ? $current[$name]->weight : 10; - $fields['status'] = $status; + $fields['status'] = $filter['status']; // Only update settings if there are any. - if (!empty($format->settings[$name])) { - $fields['settings'] = serialize($format->settings[$name]); + if (!empty($filter['settings'])) { + $fields['settings'] = serialize($filter['settings']); } db_merge('filter') ->key(array( @@ -209,9 +205,17 @@ function filter_format_save($format) { } else { module_invoke_all('filter_format_update', $format); + // Explicitly indicate that the format was updated. We need to do this + // since if the filters were updated but the format object itself was not, + // the call to drupal_write_record() above would not return an indication + // that anything had changed. + $return = SAVED_UPDATED; + + // Clear the filter cache whenever a text format is updated. + cache_clear_all($format->format . ':', 'cache_filter', TRUE); } - cache_clear_all($format->format . ':', 'cache_filter', TRUE); + filter_formats_reset(); return $return; } @@ -231,9 +235,10 @@ function filter_format_delete($format) { ->execute(); // Allow modules to react on text format deletion. - $default = filter_format_load(variable_get('filter_default_format', 1)); - module_invoke_all('filter_format_delete', $format, $default); + $fallback = filter_format_load(filter_fallback_format()); + module_invoke_all('filter_format_delete', $format, $fallback); + filter_formats_reset(); cache_clear_all($format->format . ':', 'cache_filter', TRUE); } @@ -248,12 +253,42 @@ function filter_admin_format_title($format) { * Implement hook_permission(). */ function filter_permission() { - return array( - 'administer filters' => array( - 'title' => t('Administer filters'), - 'description' => t('Manage text formats and filters, and select which roles may use them. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))), - ), + $perms['administer filters'] = array( + 'title' => t('Administer filters'), + 'description' => t('Manage text formats and filters, and use any of them, without restriction, when entering or editing content. %warning', array('%warning' => t('Warning: Give to trusted roles only; this permission has security implications.'))), ); + + // Generate permissions for each text format. Warn the administrator that any + // of them are potentially unsafe. + foreach (filter_formats() as $format) { + $permission = filter_permission_name($format); + if (!empty($permission)) { + // Only link to the text format configuration page if the user who is + // viewing this will have access to that page. + $format_name_replacement = user_access('administer filters') ? l($format->name, 'admin/config/content/formats/' . $format->format) : theme('placeholder', $format->name); + $perms[$permission] = array( + 'title' => t("Use the %text_format text format", array('%text_format' => $format->name)), + 'description' => t('Use !text_format in forms when entering or editing content. %warning', array('!text_format' => $format_name_replacement, '%warning' => t('Warning: This permission may have security implications depending on how the text format is configured.'))), + ); + } + } + return $perms; +} + +/** + * Returns the machine-readable permission name for a provided text format. + * + * @param $format + * An object representing a text format. + * @return + * The machine-readable permission name, or FALSE if the provided text format + * is malformed or is the fallback format (which is available to all users). + */ +function filter_permission_name($format) { + if (isset($format->format) && $format->format != filter_fallback_format()) { + return 'use text format ' . $format->format; + } + return FALSE; } /** @@ -386,44 +421,143 @@ function _filter_html_escape_tips($filter, $format, $long = FALSE) { /** * @} End of "Tips callback for filters". */ + /** - * Retrieve a list of text formats. + * Retrieve a list of text formats, ordered by weight. + * + * @param $account + * (optional) If provided, only those formats that are allowed for this user + * account will be returned. All formats will be returned otherwise. + * @return + * An array of text format objects, keyed by the format ID and ordered by + * weight. + * + * @see filter_formats_reset() */ -function filter_formats($index = NULL) { - global $user; - static $formats; - - // Administrators can always use all text formats. - $all = user_access('administer filters'); - - if (!isset($formats)) { - $formats = array(); - - $query = db_select('filter_format', 'f'); - $query->addField('f', 'format', 'format'); - $query->addField('f', 'name', 'name'); - $query->addField('f', 'roles', 'roles'); - $query->addField('f', 'cache', 'cache'); - $query->addField('f', 'weight', 'weight'); - $query->orderBy('weight'); - - // Build query for selecting the format(s) based on the user's roles. - if (!$all) { - $or = db_or()->condition('format', variable_get('filter_default_format', 1)); - foreach ($user->roles as $rid => $role) { - $or->condition('roles', '%' . (int)$rid . '%', 'LIKE'); +function filter_formats($account = NULL) { + $formats = &drupal_static(__FUNCTION__, array()); + + // Statically cache all existing formats upfront. + if (!isset($formats['all'])) { + $formats['all'] = db_query('SELECT * FROM {filter_format} ORDER BY weight')->fetchAllAssoc('format'); + } + + // Build a list of user-specific formats. + if (isset($account) && !isset($formats['user'][$account->uid])) { + $formats['user'][$account->uid] = array(); + foreach ($formats['all'] as $format) { + if (filter_access($format, $account)) { + $formats['user'][$account->uid][$format->format] = $format; } - $query->condition($or); } + } + + return isset($account) ? $formats['user'][$account->uid] : $formats['all']; +} + +/** + * Resets the static cache of all text formats. + * + * @see filter_formats() + */ +function filter_formats_reset() { + drupal_static_reset('filter_list_format'); + drupal_static_reset('filter_formats'); +} - $formats = $query->execute()->fetchAllAssoc('format'); +/** + * Retrieves a list of roles that are allowed to use a given text format. + * + * @param $format + * An object representing the text format. + * @return + * An array of role names, keyed by role ID. + */ +function filter_get_roles_by_format($format) { + // Handle the fallback format upfront (all roles have access to this format). + if ($format->format == filter_fallback_format()) { + return user_roles(); } - if (isset($index)) { - return isset($formats[$index]) ? $formats[$index] : FALSE; + // Do not list any roles if the permission does not exist. + $permission = filter_permission_name($format); + return !empty($permission) ? user_roles(FALSE, $permission) : array(); +} + +/** + * Retrieves a list of text formats that are allowed for a given role. + * + * @param $rid + * The user role ID to retrieve text formats for. + * @return + * An array of text format objects that are allowed for the role, keyed by + * the text format ID and ordered by weight. + */ +function filter_get_formats_by_role($rid) { + $formats = array(); + foreach (filter_formats() as $format) { + $roles = filter_get_roles_by_format($format); + if (isset($roles[$rid])) { + $formats[$format->format] = $format; + } } return $formats; } +/** + * Returns the ID of the default text format for a particular user. + * + * The default text format is the first available format that the user is + * allowed to access, when the formats are ordered by weight. It should + * generally be used as a default choice when presenting the user with a list + * of possible text formats (for example, in a node creation form). + * + * Conversely, when existing content that does not have an assigned text format + * needs to be filtered for display, the default text format is the wrong + * choice, because it is not guaranteed to be consistent from user to user, and + * some trusted users may have an unsafe text format set by default, which + * should not be used on text of unknown origin. Instead, the fallback format + * returned by filter_fallback_format() should be used, since that is intended + * to be a safe, consistent format that is always available to all users. + * + * @param $account + * (optional) The user account to check. Defaults to the currently logged-in + * user. + * @return + * The ID of the user's default text format. + * + * @see filter_fallback_format() + */ +function filter_default_format($account = NULL) { + global $user; + if (!isset($account)) { + $account = $user; + } + // Get a list of formats for this user, ordered by weight. The first one + // available is the user's default format. + $format = array_shift(filter_formats($account)); + return $format->format; +} + +/** + * Returns the ID of the fallback text format that all users have access to. + */ +function filter_fallback_format() { + // This variable is automatically set in the database for all installations + // of Drupal. In the event that it gets deleted somehow, there is no safe + // default to return, since we do not want to risk making an existing (and + // potentially unsafe) text format on the site automatically available to all + // users. Returning NULL at least guarantees that this cannot happen. + return variable_get('filter_fallback_format'); +} + +/** + * Returns the title of the fallback text format. + */ +function filter_fallback_format_title() { + $fallback_format = filter_format_load(filter_fallback_format()); + return filter_admin_format_title($fallback_format); +} + /** * Return a list of all filters provided by modules. */ @@ -453,18 +587,11 @@ function _filter_list_cmp($a, $b) { return strcmp($a['title'], $b['title']); } -/** - * Resolve a format id, including the default format. - */ -function filter_resolve_format($format) { - return $format == FILTER_FORMAT_DEFAULT ? variable_get('filter_default_format', 1) : $format; -} /** * Check if text in a certain text format is allowed to be cached. */ function filter_format_allowcache($format) { static $cache = array(); - $format = filter_resolve_format($format); if (!isset($cache[$format])) { $cache[$format] = db_query('SELECT cache FROM {filter_format} WHERE format = :format', array(':format' => $format))->fetchField(); } @@ -472,47 +599,52 @@ function filter_format_allowcache($format) { } /** - * Retrieve a list of filters for a certain format. + * Retrieve a list of filters for a given text format. * * @param $format * The format ID. + * @param $include_disabled + * (optional) Boolean whether to retrieve all filters associated with the + * given format, including those that are disabled. Defaults to FALSE. * @return * An array of filter objects assosiated to the given format. */ -function filter_list_format($format) { - static $filters = array(); +function filter_list_format($format, $include_disabled = FALSE) { + $filters = &drupal_static(__FUNCTION__, array()); $filter_info = filter_get_filters(); - if (!isset($filters[$format])) { - $filters[$format] = array(); - $result = db_select('filter', 'filter') + if (!isset($filters[$format]) || $include_disabled) { + $format_filters = array(); + $query = db_select('filter', 'filter') ->fields('filter') ->condition('format', $format) - ->condition('status', 1) ->orderBy('weight') ->orderBy('module') - ->orderBy('name') - ->execute(); - foreach ($result as $filter) { - if (isset($filter_info[$filter->name])) { - $filter->title = $filter_info[$filter->name]['title']; + ->orderBy('name'); + if (!$include_disabled) { + $query->condition('status', 1); + } + $result = $query->execute()->fetchAllAssoc('name'); + foreach ($result as $name => $filter) { + if (isset($filter_info[$name])) { + $filter->title = $filter_info[$name]['title']; // Unpack stored filter settings. - if (isset($filter->settings)) { - $filter->settings = unserialize($filter->settings); - } - else { - $filter->settings = array(); - } + $filter->settings = (isset($filter->settings) ? unserialize($filter->settings) : array()); // Apply default filter settings. - if (isset($filter_info[$filter->name]['default settings'])) { - $filter->settings = array_merge($filter_info[$filter->name]['default settings'], $filter->settings); + if (isset($filter_info[$name]['default settings'])) { + $filter->settings = array_merge($filter_info[$name]['default settings'], $filter->settings); } - $filters[$format][$filter->name] = $filter; + $format_filters[$name] = $filter; } } + // Prevent statically caching of disabled filters. + if ($include_disabled) { + return $format_filters; + } + $filters[$format] = $format_filters; } - return $filters[$format]; + return isset($filters[$format]) ? $filters[$format] : array(); } /** @@ -536,8 +668,8 @@ function filter_list_format($format) { * @param $text * The text to be filtered. * @param $format - * The format of the text to be filtered. Specify FILTER_FORMAT_DEFAULT for - * the default format. + * The format of the text to be filtered. If no format is assigned, the + * fallback format will be used. * @param $langcode * Optional: the language code of the text to be filtered, e.g. 'en' for * English. This allows filters to be language aware so language specific @@ -547,8 +679,10 @@ function filter_list_format($format) { * The caller may set this to FALSE when the output is already cached * elsewhere to avoid duplicate cache lookups and storage. */ -function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $langcode = '', $cache = TRUE) { - $format = filter_resolve_format($format); +function check_markup($text, $format = NULL, $langcode = '', $cache = TRUE) { + if (empty($format)) { + $format = filter_fallback_format(); + } // Check for a cached version of this piece of text. $cache_id = $format . ':' . $langcode . ':' . md5($text); @@ -599,9 +733,16 @@ function check_markup($text, $format = FILTER_FORMAT_DEFAULT, $langcode = '', $c * @return * HTML for the form element. */ -function filter_form($selected_format = FILTER_FORMAT_DEFAULT, $weight = NULL, $parents = array('format')) { - $selected_format = filter_resolve_format($selected_format); - $formats = filter_formats(); +function filter_form($selected_format = NULL, $weight = NULL, $parents = array('format')) { + global $user; + + // Use the default format for this user if none was selected. + if (empty($selected_format)) { + $selected_format = filter_default_format($user); + } + + // Get a list of formats that the current user has access to. + $formats = filter_formats($user); drupal_add_js('misc/form.js'); drupal_add_css(drupal_get_path('module', 'filter') . '/filter.css'); @@ -644,17 +785,31 @@ function filter_form($selected_format = FILTER_FORMAT_DEFAULT, $weight = NULL, $ } /** - * Returns TRUE if the user is allowed to access this format. + * Checks if a user has access to a particular text format. + * + * @param $format + * An object representing the text format. + * @param $account + * (optional) The user account to check access for; if omitted, the currently + * logged-in user is used. + * + * @return + * Boolean TRUE if the user is allowed to access the given format. */ -function filter_access($format) { - $format = filter_resolve_format($format); - if (user_access('administer filters') || ($format == variable_get('filter_default_format', 1))) { - return TRUE; +function filter_access($format, $account = NULL) { + global $user; + if (!isset($account)) { + $account = $user; } - else { - $formats = filter_formats(); - return isset($formats[$format]); + // Handle special cases up front. All users have access to the fallback + // format, and administrators have access to all formats. + if (user_access('administer filters', $account) || $format->format == filter_fallback_format()) { + return TRUE; } + // Check the permission if one exists; otherwise, we have a non-existent + // format so we return FALSE. + $permission = filter_permission_name($format); + return !empty($permission) && user_access($permission, $account); } /** @@ -666,7 +821,9 @@ function filter_access($format) { * Helper function for fetching filter tips. */ function _filter_tips($format, $long = FALSE) { - $formats = filter_formats(); + global $user; + + $formats = filter_formats($user); $filter_info = filter_get_filters(); $tips = array(); @@ -809,7 +966,7 @@ function filter_filter_info() { /** * Settings callback for the HTML filter. */ -function _filter_html_settings(&$form_state, $filter, $defaults) { +function _filter_html_settings($form, &$form_state, $filter, $defaults) { $form['allowed_html'] = array( '#type' => 'textfield', '#title' => t('Allowed HTML tags'), @@ -855,7 +1012,7 @@ function _filter_html($text, $filter) { /** * Settings callback for URL filter. */ -function _filter_url_settings(&$form_state, $filter, $defaults) { +function _filter_url_settings($form, &$form_state, $filter, $defaults) { $form['filter_url_length'] = array( '#type' => 'textfield', '#title' => t('Maximum link text length'), @@ -1002,15 +1159,3 @@ function _filter_html_escape($text) { /** * @} End of "Standard filters". */ - -/** - * Implement hook_user_role_delete(). - * - * Remove deleted role from formats that use it. - */ -function filter_user_role_delete($role) { - db_update('filter_format') - ->expression('roles', 'REPLACE(roles, :rid, :replacement)', array(':rid' => ',' . $role->rid, ':replacement' => '')) - ->condition('roles', '%,' . $role->rid . '%', 'LIKE') - ->execute(); -} diff --git a/modules/filter/filter.test b/modules/filter/filter.test index 34a289f..7e8360d 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -1,5 +1,5 @@ admin_user = $this->drupalCreateUser(array('administer filters')); + $this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content')); + $this->drupalLogin($this->admin_user); + } + + function testFormatAdmin() { + // Add text format. + $this->drupalGet('admin/config/content/formats'); + $this->clickLink('Add text format'); + $edit = array( + 'name' => $this->randomName(), + ); + $this->drupalPost(NULL, $edit, t('Save configuration')); + + // Edit text format. + $format = $this->getFilter($edit['name']); + $this->drupalGet('admin/config/content/formats'); + $this->assertRaw('admin/config/content/formats/' . $format->format); + $this->drupalGet('admin/config/content/formats/' . $format->format); + $this->drupalPost(NULL, array(), t('Save configuration')); + + // Delete text format. + $this->drupalGet('admin/config/content/formats'); + $this->assertRaw('admin/config/content/formats/' . $format->format . '/delete'); + $this->drupalGet('admin/config/content/formats/' . $format->format . '/delete'); + $this->drupalPost(NULL, array(), t('Delete')); + + // Verify that deleted text format no longer exists. + $this->drupalGet('admin/config/content/formats/' . $format->format); + $this->assertResponse(404, t('Deleted text format no longer exists.')); + } + /** * Test filter administration functionality. */ @@ -19,20 +55,18 @@ class FilterAdminTestCase extends DrupalWebTestCase { // Line filter. $second_filter = 'filter_autop'; - // Create users. - $admin_user = $this->drupalCreateUser(array('administer filters')); - $web_user = $this->drupalCreateUser(array('create page content')); - $this->drupalLogin($admin_user); - - list($filtered, $full) = $this->checkFilterFormats(); + list($filtered, $full, $plain) = $this->checkFilterFormats(); - // Change default filter. - $edit = array(); - $edit['default'] = $full; - $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->assertText(t('Default format updated.'), t('Default filter updated successfully.')); + // Check that the fallback format exists and cannot be deleted. + $this->assertTrue(!empty($plain) && $plain == filter_fallback_format(), t('The fallback format is set to plain text.')); + $this->drupalGet('admin/config/content/formats'); + $this->assertNoRaw('admin/config/content/formats/' . $plain . '/delete', t('Delete link for the fallback format not found.')); + $this->drupalGet('admin/config/content/formats/' . $plain . '/delete'); + $this->assertResponse(403, t('The fallback format cannot be deleted.')); - $this->assertNoRaw('admin/config/content/formats/' . $full . '/delete', t('Delete link not found.')); + // Verify access permissions to Full HTML format. + $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.')); + $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.')); // Add an additional tag. $edit = array(); @@ -65,8 +99,8 @@ class FilterAdminTestCase extends DrupalWebTestCase { $edit = array(); $edit['name'] = $this->randomName(); $edit['roles[2]'] = 1; - $edit['filters[' . $second_filter . ']'] = TRUE; - $edit['filters[' . $first_filter . ']'] = TRUE; + $edit['filters[' . $second_filter . '][status]'] = TRUE; + $edit['filters[' . $first_filter . '][status]'] = TRUE; $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.')); @@ -75,44 +109,38 @@ class FilterAdminTestCase extends DrupalWebTestCase { if ($format !== NULL) { $this->assertFieldByName('roles[2]', '', t('Role found.')); - $this->assertFieldByName('filters[' . $second_filter . ']', '', t('Line break filter found.')); - $this->assertFieldByName('filters[' . $first_filter . ']', '', t('Url filter found.')); + $this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.')); + $this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.')); // Delete new filter. $this->drupalPost('admin/config/content/formats/' . $format->format . '/delete', array(), t('Delete')); $this->assertRaw(t('Deleted text format %format.', array('%format' => $edit['name'])), t('Format successfully deleted.')); } - // Change default filter back. - $edit = array(); - $edit['default'] = $filtered; - $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->assertText(t('Default format updated.'), t('Default filter updated successfully.')); - - $this->assertNoRaw('admin/config/content/formats/' . $filtered . '/delete', t('Delete link not found.')); - // Allow authenticated users on full HTML. + $format = filter_format_load($full); $edit = array(); $edit['roles[1]'] = 0; $edit['roles[2]'] = 1; $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration')); - $this->assertText(t('The text format settings have been updated.'), t('Full HTML format successfully updated.')); + $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.')); // Switch user. $this->drupalLogout(); - $this->drupalLogin($web_user); + $this->drupalLogin($this->web_user); $this->drupalGet('node/add/page'); $this->assertRaw('', t('Full HTML filter accessible.')); // Use filtered HTML and see if it removes tags that are not allowed. - $body = $this->randomName(); + $body = '' . $this->randomName() . ''; $extra_text = 'text'; + $text = $body . '' . t('Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. Blocks are usually generated automatically by modules (e.g., Recent Forum Topics), but administrators can also define custom blocks.') . '
'; + return '' . 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'))) . '
'; + // Help for another path in the block module case 'admin/structure/block': - return t('Blocks are boxes of content that may be rendered into certain regions of your web pages, for example, into sidebars. They are usually generated automatically by modules, but administrators can create blocks manually.
-If you want certain blocks to disable themselves temporarily during high server loads, check the "Throttle" box. You can configure the auto-throttle on the throttle configuration page after having enabled the throttle module.
-You can configure the behavior of each block (for example, specifying on which pages and for what users it will appear) by clicking the "configure" link for each block.
', array('@throttle' => url('admin/settings/throttle'))); + return '' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the Save blocks button at the bottom of the page.') . '
'; } } diff --git a/modules/help/help.module b/modules/help/help.module index 7f5d793..65e0745 100644 --- a/modules/help/help.module +++ b/modules/help/help.module @@ -1,5 +1,5 @@ ' . t('Please follow these steps to set up and start using your website:') . ''; $output .= ''; - $output .= '- ' . t('Configure your website Once logged in, visit the administration section, where you can customize and configure all aspects of your website.', array('@admin' => url('admin'), '@config' => url('admin/settings'))) . '
';
+ $output .= '- ' . t('Configure your website Once logged in, visit the administration section, where you can customize and configure all aspects of your website.', array('@admin' => url('admin'), '@config' => url('admin/config'))) . '
';
$output .= '- ' . t('Enable additional functionality Next, visit the module list and enable features which suit your specific needs. You can find additional modules in the Drupal modules download section.', array('@modules' => url('admin/config/modules'), '@download_modules' => 'http://drupal.org/project/modules')) . '
';
$output .= '- ' . t('Customize your website design To change the "look and feel" of your website, visit the themes section. You may choose from one of the included themes or download additional themes from the Drupal themes download section.', array('@themes' => url('admin/appearance'), '@download_themes' => 'http://drupal.org/project/themes')) . '
';
$output .= '- ' . t('Start posting content Finally, you can add new content for your website.', array('@content' => url('node/add'))) . '
';
diff --git a/modules/image/CVS/Entries b/modules/image/CVS/Entries
index ab40fa3..b2cedb1 100644
--- a/modules/image/CVS/Entries
+++ b/modules/image/CVS/Entries
@@ -2,9 +2,9 @@
/image.api.php/1.2/Thu Sep 3 08:50:26 2009//
/image.effects.inc/1.2/Thu Sep 3 08:50:26 2009//
/image.info/1.2/Thu Sep 3 08:50:26 2009//
-/image.install/1.2/Thu Sep 3 08:50:26 2009//
-/image.module/1.16/Thu Sep 3 08:50:26 2009//
/image.test/1.8/Thu Sep 3 08:50:26 2009//
/sample.png/1.1/Thu Sep 3 08:50:26 2009/-kb/
-/image.admin.inc/1.10/Sat Sep 5 15:25:49 2009//
+/image.admin.inc/1.11/Fri Oct 2 19:50:14 2009//
+/image.install/1.3/Fri Oct 2 19:50:14 2009//
+/image.module/1.17/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/image/image.admin.inc b/modules/image/image.admin.inc
index 004449c..f8e1443 100644
--- a/modules/image/image.admin.inc
+++ b/modules/image/image.admin.inc
@@ -1,5 +1,5 @@
$style['name']));
drupal_set_title($title, PASS_THROUGH);
$form_state['image_style'] = $style;
- $form = array(
- '#tree' => TRUE,
- '#attached' => array(
- 'css' => array(drupal_get_path('module', 'image') . '/image.admin.css' => array('preprocess' => FALSE)),
- ),
- );
+ $form['#tree'] = TRUE;
+ $form['#attached']['css'][drupal_get_path('module', 'image') . '/image.admin.css'] = array('preprocess' => FALSE);
// Allow the name of the style to be changed.
$form['name'] = array(
@@ -187,9 +183,7 @@ function image_style_form_submit($form, &$form_state) {
* @see image_style_add_form_submit()
* @see image_style_name_validate()
*/
-function image_style_add_form(&$form_state) {
- $form = array();
-
+function image_style_add_form($form, &$form_state) {
$form['name'] = array(
'#type' => 'textfield',
'#size' => '64',
@@ -243,9 +237,8 @@ function image_style_name_validate($element, $form_state) {
* @ingroup forms
* @see image_style_delete_form_submit()
*/
-function image_style_delete_form($form_state, $style) {
+function image_style_delete_form($form, $form_state, $style) {
$form_state['image_style'] = $style;
- $form = array();
$replacement_styles = array_diff_key(image_style_options(), array($style['name'] => ''));
$replacement_styles[''] = t('No replacement, just delete');
@@ -298,7 +291,7 @@ function image_style_delete_form_submit($form, &$form_state) {
* @see image_crop_form()
* @see image_effect_form_submit()
*/
-function image_effect_form(&$form_state, $style, $effect) {
+function image_effect_form($form, &$form_state, $style, $effect) {
if (!empty($effect['data'])) {
$title = t('Edit %label effect', array('%label' => $effect['label']));
}
@@ -315,12 +308,8 @@ function image_effect_form(&$form_state, $style, $effect) {
drupal_goto('admin/config/media/image-styles/edit/' . $style['name']);
}
- $form = array(
- '#tree' => TRUE,
- '#attached' => array(
- 'css' => array(drupal_get_path('module', 'image') . '/image.admin.css' => array('preprocess' => FALSE)),
- ),
- );
+ $form['#tree'] = TRUE;
+ $form['#attached']['css'][drupal_get_path('module', 'image') . '/image.admin.css'] = array('preprocess' => FALSE);
if (function_exists($effect['form callback'])) {
$form['data'] = call_user_func($effect['form callback'], $effect['data']);
}
@@ -365,11 +354,10 @@ function image_effect_form_submit($form, &$form_state) {
* @ingroup forms
* @see image_effect_delete_form_submit()
*/
-function image_effect_delete_form(&$form_state, $style, $effect) {
+function image_effect_delete_form($form, &$form_state, $style, $effect) {
$form_state['image_style'] = $style;
$form_state['image_effect'] = $effect;
- $form = array();
$question = t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $style['name'], '@effect' => $effect['label']));
return confirm_form($form, $question, 'admin/config/media/image-styles/edit/' . $style['name'], '', t('Delete'));
}
diff --git a/modules/image/image.install b/modules/image/image.install
index e87f198..9afd35b 100644
--- a/modules/image/image.install
+++ b/modules/image/image.install
@@ -1,5 +1,5 @@
fields(array(
'language' => 'en',
@@ -39,10 +36,8 @@ function locale_install() {
* Allow longer location.
*/
function locale_update_7000() {
- $ret = array();
- db_drop_index($ret, 'locales_source', 'source');
- db_add_index($ret, 'locales_source', 'source_context', array(array('source', 30), 'context'));
- return $ret;
+ db_drop_index('locales_source', 'source');
+ db_add_index('locales_source', 'source_context', array(array('source', 30), 'context'));
}
/**
@@ -83,8 +78,6 @@ function locale_uninstall() {
// try to query the unexisting {locales_source} and {locales_target} tables.
drupal_language_initialize();
- // Remove tables.
- drupal_uninstall_schema('locale');
}
/**
diff --git a/modules/locale/locale.module b/modules/locale/locale.module
index 7c88953..2d900eb 100644
--- a/modules/locale/locale.module
+++ b/modules/locale/locale.module
@@ -1,5 +1,5 @@
1 && user_access('administer users')) {
- return locale_language_selector_form($account);
- }
-}
-
-/**
- * Implement hook_user_form().
+ * Form builder callback to display language selection widget.
+ *
+ * @ingroup forms
+ * @see locale_form_alter()
*/
-function locale_user_form(&$edit, $account, $category) {
- // If we have more then one language and either creating a user on the
- // admin interface or edit the user, show the language selector.
- if (variable_get('language_count', 1) > 1 && $category == 'account') {
- return locale_language_selector_form($account);
- }
-}
-
-function locale_language_selector_form($user) {
+function locale_language_selector_form(&$form, &$form_state, $user) {
global $language;
$languages = language_list('enabled');
$languages = $languages[1];
// If the user is being created, we set the user language to the page language.
- $user_preferred_language = $user ? user_preferred_language($user) : $language;
+ $user_preferred_language = $user->uid ? user_preferred_language($user) : $language;
$names = array();
foreach ($languages as $langcode => $item) {
@@ -273,7 +257,6 @@ function locale_language_selector_form($user) {
'#options' => $names,
'#description' => ($mode == LANGUAGE_NEGOTIATION_PATH) ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."),
);
- return $form;
}
/**
@@ -306,9 +289,19 @@ function locale_form_node_type_form_alter(&$form, &$form_state) {
}
/**
- * Implement hook_form_alter(). Adds language fields to forms.
+ * Implement hook_form_alter().
+ *
+ * Adds language fields to forms.
*/
function locale_form_alter(&$form, &$form_state, $form_id) {
+ // Only alter user forms if there is more than one language.
+ if (variable_get('language_count', 1) > 1) {
+ // Display language selector when either creating a user on the admin
+ // interface or editing a user account.
+ if (($form_id == 'user_register' && user_access('administer users')) || ($form_id == 'user_profile_form' && $form['#user_category'] == 'account')) {
+ locale_language_selector_form($form, $form_state, $form['#user']);
+ }
+ }
if (isset($form['#id']) && $form['#id'] == 'node-form') {
if (isset($form['#node']->type) && variable_get('language_content_type_' . $form['#node']->type, 0)) {
$form['language'] = array(
diff --git a/modules/locale/locale.test b/modules/locale/locale.test
index cf14880..6c1e014 100644
--- a/modules/locale/locale.test
+++ b/modules/locale/locale.test
@@ -1,5 +1,5 @@
drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
// Set page content type to use multilingual support.
- $this->drupalGet('admin/structure/node-type/page');
+ $this->drupalGet('admin/structure/types/manage/page');
$this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.'));
$edit = array(
'language_content_type' => 1,
);
- $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type'));
+ $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
$this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Page')), t('Page content type has been updated.'));
$this->drupalLogout();
diff --git a/modules/menu/CVS/Entries b/modules/menu/CVS/Entries
index 0d17224..fcc5e31 100644
--- a/modules/menu/CVS/Entries
+++ b/modules/menu/CVS/Entries
@@ -1,8 +1,8 @@
-/menu.api.php/1.9/Thu Sep 3 08:50:27 2009//
/menu.info/1.9/Thu Sep 3 08:50:27 2009//
-/menu.install/1.19/Thu Sep 3 08:50:27 2009//
/menu.js/1.2/Thu Sep 3 08:50:27 2009//
-/menu.test/1.20/Thu Sep 3 08:50:27 2009//
-/menu.admin.inc/1.57/Sat Sep 5 15:25:49 2009//
-/menu.module/1.203/Sat Sep 5 15:25:49 2009//
+/menu.admin.inc/1.60/Fri Oct 2 19:50:14 2009//
+/menu.api.php/1.12/Fri Oct 2 19:50:14 2009//
+/menu.install/1.20/Fri Oct 2 19:50:14 2009//
+/menu.module/1.205/Fri Oct 2 19:50:14 2009//
+/menu.test/1.22/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc
index 8905d87..f371b83 100644
--- a/modules/menu/menu.admin.inc
+++ b/modules/menu/menu.admin.inc
@@ -1,5 +1,5 @@
l(t('list links'), 'admin/structure/menu-customize/' . $menu['menu_name']));
- $row[] = array('data' => l(t('edit menu'), 'admin/structure/menu-customize/' . $menu['menu_name'] . '/edit'));
- $row[] = array('data' => l(t('add link'), 'admin/structure/menu-customize/' . $menu['menu_name'] . '/add'));
+ $row[] = array('data' => l(t('list links'), 'admin/structure/menu/manage/' . $menu['menu_name']));
+ $row[] = array('data' => l(t('edit menu'), 'admin/structure/menu/manage/' . $menu['menu_name'] . '/edit'));
+ $row[] = array('data' => l(t('add link'), 'admin/structure/menu/manage/' . $menu['menu_name'] . '/add'));
$rows[] = $row;
}
@@ -40,7 +40,7 @@ function theme_menu_admin_overview($title, $name, $description) {
* Shows for one menu the menu links accessible to the current user and
* relevant operations.
*/
-function menu_overview_form(&$form_state, $menu) {
+function menu_overview_form($form, &$form_state, $menu) {
global $menu_admin;
$sql = "
SELECT m.load_functions, m.to_arg_functions, m.access_callback, m.access_arguments, m.page_callback, m.page_arguments, m.title, m.title_callback, m.title_arguments, m.type, m.description, ml.*
@@ -60,7 +60,7 @@ function menu_overview_form(&$form_state, $menu) {
menu_tree_check_access($tree, $node_links);
$menu_admin = FALSE;
- $form = _menu_overview_tree_form($tree);
+ $form = array_merge($form, _menu_overview_tree_form($tree));
$form['#menu'] = $menu;
if (element_children($form)) {
$form['submit'] = array(
@@ -69,7 +69,7 @@ function menu_overview_form(&$form_state, $menu) {
);
}
else {
- $form['#empty_text'] = t('There are no menu links yet. Add link.', array('@link' => url('admin/structure/menu-customize/'. $form['#menu']['menu_name'] .'/add')));
+ $form['#empty_text'] = t('There are no menu links yet. Add link.', array('@link' => url('admin/structure/menu/manage/'. $form['#menu']['menu_name'] .'/add')));
}
return $form;
}
@@ -243,7 +243,7 @@ function theme_menu_overview_form($form) {
/**
* Menu callback; Build the menu link editing form.
*/
-function menu_edit_item(&$form_state, $type, $item, $menu) {
+function menu_edit_item($form, &$form_state, $type, $item, $menu) {
$form['menu'] = array(
'#type' => 'fieldset',
@@ -398,13 +398,13 @@ function menu_edit_item_submit($form, &$form_state) {
if (!menu_link_save($item)) {
drupal_set_message(t('There was an error saving the menu link.'), 'error');
}
- $form_state['redirect'] = 'admin/structure/menu-customize/' . $item['menu_name'];
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
}
/**
* Menu callback; Build the form that handles the adding/editing of a custom menu.
*/
-function menu_edit_menu(&$form_state, $type, $menu = array()) {
+function menu_edit_menu($form, &$form_state, $type, $menu = array()) {
$system_menus = menu_list_system_menus();
$menu += array('menu_name' => '', 'title' => '', 'description' => '');
@@ -478,7 +478,7 @@ function menu_edit_menu(&$form_state, $type, $menu = array()) {
* Submit function for the 'Delete' button on the menu editing form.
*/
function menu_custom_delete_submit($form, &$form_state) {
- $form_state['redirect'] = 'admin/structure/menu-customize/' . $form_state['values']['menu_name'] . '/delete';
+ $form_state['redirect'] = 'admin/structure/menu/manage/' . $form_state['values']['menu_name'] . '/delete';
}
/**
@@ -497,7 +497,7 @@ function menu_delete_menu_page($menu) {
/**
* Build a confirm form for deletion of a custom menu.
*/
-function menu_delete_menu_confirm(&$form_state, $menu) {
+function menu_delete_menu_confirm($form, &$form_state, $menu) {
$form['#menu'] = $menu;
$caption = '';
$num_links = db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField();
@@ -505,7 +505,7 @@ function menu_delete_menu_confirm(&$form_state, $menu) {
$caption .= '', $menu_name = 'navigation') {
// View add menu link page.
- $this->drupalGet("admin/structure/menu-customize/$menu_name/add");
+ $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
$this->assertResponse(200);
$title = '!link_' . $this->randomName(16);
@@ -217,7 +222,7 @@ class MenuTestCase extends DrupalWebTestCase {
);
// Add menu link.
- $this->drupalPost("admin/structure/menu-customize/$menu_name/add", $edit, t('Save'));
+ $this->drupalPost("admin/structure/menu/manage/$menu_name/add", $edit, t('Save'));
$this->assertResponse(200);
// Unlike most other modules, there is no confirmation message displayed.
@@ -254,7 +259,7 @@ class MenuTestCase extends DrupalWebTestCase {
'menu[link_path]' => $link_path,
'menu[link_title]' => 'title',
);
- $this->drupalPost("admin/structure/menu-customize/$menu_name/add", $edit, t('Save'));
+ $this->drupalPost("admin/structure/menu/manage/$menu_name/add", $edit, t('Save'));
$this->assertRaw(t("The path '@path' is either invalid or you do not have access to it.", array('@path' => $link_path)), 'Menu link was not created');
}
}
@@ -313,7 +318,7 @@ class MenuTestCase extends DrupalWebTestCase {
// Unlike most other modules, there is no confirmation message displayed.
// Verify menu link.
- $this->drupalGet('admin/structure/menu-customize/' . $item['menu_name']);
+ $this->drupalGet('admin/structure/menu/manage/' . $item['menu_name']);
$this->assertText($title, 'Menu link was edited');
}
@@ -447,7 +452,7 @@ class MenuTestCase extends DrupalWebTestCase {
}
// View navigation menu customization node.
- $this->drupalGet('admin/structure/menu-customize/navigation');
+ $this->drupalGet('admin/structure/menu/manage/navigation');
$this->assertResponse($response);
if ($response == 200) {
$this->assertText(t('Navigation'), t('Navigation menu node was displayed'));
diff --git a/modules/node/CVS/Entries b/modules/node/CVS/Entries
index 2b47fcd..7187ab7 100644
--- a/modules/node/CVS/Entries
+++ b/modules/node/CVS/Entries
@@ -1,15 +1,15 @@
D/tests////
/content_types.js/1.7/Thu Sep 3 08:50:28 2009//
/node-rtl.css/1.3/Thu Sep 3 08:50:28 2009//
-/node.api.php/1.37/Thu Sep 3 08:50:28 2009//
/node.css/1.8/Thu Sep 3 08:50:28 2009//
/node.info/1.12/Thu Sep 3 08:50:28 2009//
-/node.install/1.29/Thu Sep 3 08:50:28 2009//
/node.js/1.4/Thu Sep 3 08:50:28 2009//
-/node.test/1.43/Thu Sep 3 08:50:28 2009//
-/node.tokens.inc/1.2/Thu Sep 3 08:50:28 2009//
-/node.tpl.php/1.21/Thu Sep 3 08:50:28 2009//
-/node.admin.inc/1.65/Sat Sep 5 13:40:16 2009//
-/node.module/1.1119/Sat Sep 5 13:40:16 2009//
-/content_types.inc/1.92/Sat Sep 5 15:25:49 2009//
-/node.pages.inc/1.79/Sat Sep 5 15:25:50 2009//
+/content_types.inc/1.95/Fri Oct 2 19:50:14 2009//
+/node.admin.inc/1.67/Fri Oct 2 19:50:14 2009//
+/node.api.php/1.38/Fri Oct 2 19:50:14 2009//
+/node.install/1.32/Fri Oct 2 19:50:14 2009//
+/node.module/1.1135/Fri Oct 2 19:50:14 2009//
+/node.pages.inc/1.83/Fri Oct 2 19:50:14 2009//
+/node.test/1.46/Fri Oct 2 19:50:14 2009//
+/node.tokens.inc/1.3/Fri Oct 2 19:50:14 2009//
+/node.tpl.php/1.22/Fri Oct 2 19:50:14 2009//
diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc
index f12df24..96f8e44 100644
--- a/modules/node/content_types.inc
+++ b/modules/node/content_types.inc
@@ -1,5 +1,5 @@
type);
$row = array(theme('node_admin_overview', $name, $type));
// Set the edit column.
- $row[] = array('data' => l(t('edit'), 'admin/structure/node-type/' . $type_url_str));
+ $row[] = array('data' => l(t('edit'), 'admin/structure/types/manage/' . $type_url_str));
// Set the delete column.
if ($type->custom) {
- $row[] = array('data' => l(t('delete'), 'admin/structure/node-type/' . $type_url_str . '/delete'));
+ $row[] = array('data' => l(t('delete'), 'admin/structure/types/manage/' . $type_url_str . '/delete'));
}
else {
$row[] = array('data' => '');
@@ -57,7 +57,7 @@ function theme_node_admin_overview($name, $type) {
/**
* Generates the node type editing form.
*/
-function node_type_form(&$form_state, $type = NULL) {
+function node_type_form($form, &$form_state, $type = NULL) {
if (!isset($type->type)) {
// This is a new type. Node module managed types are custom and unlocked.
$type = node_type_set_defaults(array('custom' => 1, 'locked' => 0));
@@ -316,7 +316,7 @@ function node_type_form_submit($form, &$form_state) {
$type->locked = $form_state['values']['locked'];
if ($op == t('Delete content type')) {
- $form_state['redirect'] = 'admin/structure/node-type/' . str_replace('_', '-', $type->old_type) . '/delete';
+ $form_state['redirect'] = 'admin/structure/types/manage/' . str_replace('_', '-', $type->old_type) . '/delete';
return;
}
@@ -350,6 +350,7 @@ function node_type_form_submit($form, &$form_state) {
}
node_types_rebuild();
+ menu_rebuild();
$t_args = array('%name' => $type->name);
if ($status == SAVED_UPDATED) {
@@ -414,7 +415,7 @@ function node_type_reset($type) {
/**
* Menu callback; delete a single content type.
*/
-function node_type_delete_confirm(&$form_state, $type) {
+function node_type_delete_confirm($form, &$form_state, $type) {
$form['type'] = array('#type' => 'value', '#value' => $type->type);
$form['name'] = array('#type' => 'value', '#value' => $type->name);
@@ -444,6 +445,7 @@ function node_type_delete_confirm_submit($form, &$form_state) {
watchdog('menu', 'Deleted content type %name.', $t_args, WATCHDOG_NOTICE);
node_types_rebuild();
+ menu_rebuild();
$form_state['redirect'] = 'admin/structure/types';
return;
diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc
index ca76445..a7cb90d 100644
--- a/modules/node/node.admin.inc
+++ b/modules/node/node.admin.inc
@@ -1,5 +1,5 @@
'
+>
-
@@ -89,7 +89,7 @@ function openid_test_yadis_xrds() {
* Menu callback; regular HTML page with an X-XRDS-Location HTTP header.
*/
function openid_test_yadis_x_xrds_location() {
- drupal_set_header('X-XRDS-Location', url('openid-test/yadis/xrds', array('absolute' => TRUE)));
+ drupal_add_http_header('X-XRDS-Location', url('openid-test/yadis/xrds', array('absolute' => TRUE)));
return t('This page includes an X-RDS-Location HTTP header containing the URL of an XRDS document.');
}
@@ -181,7 +181,7 @@ function _openid_test_endpoint_associate() {
// Respond to Relying Party in the special Key-Value Form Encoding (see OpenID
// Authentication 1.0, section 4.1.1).
- drupal_set_header('Content-Type', 'text/plain');
+ drupal_add_http_header('Content-Type', 'text/plain');
print _openid_create_message($response);
}
@@ -228,6 +228,6 @@ function _openid_test_endpoint_authenticate() {
// Put the signed message into the query string of a URL supplied by the
// Relying Party, and redirect the user.
- drupal_set_header('Content-Type', 'text/plain');
- header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => http_build_query($response, '', '&'), 'external' => TRUE)));
+ drupal_add_http_header('Content-Type', 'text/plain');
+ header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
}
diff --git a/modules/overlay/CVS/Entries b/modules/overlay/CVS/Entries
new file mode 100644
index 0000000..4ba62ab
--- /dev/null
+++ b/modules/overlay/CVS/Entries
@@ -0,0 +1,2 @@
+/overlay.info/1.1/Wed Sep 16 23:55:40 2009//
+D
diff --git a/modules/overlay/CVS/Repository b/modules/overlay/CVS/Repository
new file mode 100644
index 0000000..f9693da
--- /dev/null
+++ b/modules/overlay/CVS/Repository
@@ -0,0 +1 @@
+drupal/modules/overlay
diff --git a/modules/overlay/CVS/Root b/modules/overlay/CVS/Root
new file mode 100644
index 0000000..964e299
--- /dev/null
+++ b/modules/overlay/CVS/Root
@@ -0,0 +1 @@
+:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal
diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info
new file mode 100644
index 0000000..e69de29
diff --git a/modules/path/CVS/Entries b/modules/path/CVS/Entries
index 8910ddb..9fc77f0 100644
--- a/modules/path/CVS/Entries
+++ b/modules/path/CVS/Entries
@@ -1,6 +1,6 @@
-/path.admin.inc/1.29/Thu Sep 3 08:50:29 2009//
/path.info/1.8/Thu Sep 3 08:50:29 2009//
/path.js/1.2/Thu Sep 3 08:50:29 2009//
/path.test/1.20/Thu Sep 3 08:50:29 2009//
/path.module/1.170/Sat Sep 5 15:25:50 2009//
+/path.admin.inc/1.31/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/path/path.admin.inc b/modules/path/path.admin.inc
index 178dfcc..fc6d3ab 100644
--- a/modules/path/path.admin.inc
+++ b/modules/path/path.admin.inc
@@ -1,5 +1,5 @@
:language', array(':language' => ''), 0, 1)->fetchField();
+ $alias_exists = (bool) db_query_range('SELECT 1 FROM {url_alias} WHERE language <> :language', 0, 1, array(':language' => ''))->fetchField();
$multilanguage = (module_exists('locale') || $alias_exists);
$header = array(
@@ -98,7 +98,7 @@ function path_admin_edit($pid = 0) {
* @see path_admin_form_validate()
* @see path_admin_form_submit()
*/
-function path_admin_form(&$form_state, $edit = array('src' => '', 'dst' => '', 'language' => '', 'pid' => NULL)) {
+function path_admin_form($form, &$form_state, $edit = array('src' => '', 'dst' => '', 'language' => '', 'pid' => NULL)) {
$form['#alias'] = $edit;
@@ -180,7 +180,7 @@ function path_admin_form_submit($form, &$form_state) {
/**
* Menu callback; confirms deleting an URL alias
*/
-function path_admin_delete_confirm($form_state, $pid) {
+function path_admin_delete_confirm($form, $form_state, $pid) {
$path = path_load($pid);
if (user_access('administer url aliases')) {
$form['pid'] = array('#type' => 'value', '#value' => $pid);
@@ -209,7 +209,7 @@ function path_admin_delete_confirm_submit($form, &$form_state) {
* @ingroup forms
* @see path_admin_filter_form_submit()
*/
-function path_admin_filter_form(&$form_state, $keys = '') {
+function path_admin_filter_form($form, &$form_state, $keys = '') {
$form['#attributes'] = array('class' => array('search-form'));
$form['basic'] = array('#type' => 'fieldset',
'#title' => t('Filter aliases')
diff --git a/modules/php/CVS/Entries b/modules/php/CVS/Entries
index 428fe0f..fb0e4c1 100644
--- a/modules/php/CVS/Entries
+++ b/modules/php/CVS/Entries
@@ -1,5 +1,5 @@
/php.info/1.7/Thu Sep 3 08:50:29 2009//
-/php.install/1.12/Thu Sep 3 08:50:29 2009//
-/php.module/1.20/Thu Sep 3 08:50:29 2009//
-/php.test/1.16/Thu Sep 3 08:50:29 2009//
+/php.install/1.14/Fri Oct 2 19:50:14 2009//
+/php.module/1.21/Fri Oct 2 19:50:14 2009//
+/php.test/1.17/Fri Oct 2 19:50:14 2009//
D
diff --git a/modules/php/php.install b/modules/php/php.install
index dd0d036..8942974 100644
--- a/modules/php/php.install
+++ b/modules/php/php.install
@@ -1,5 +1,5 @@
'PHP code'), 0, 1)->fetchField();
+ $format_exists = (bool) db_query_range('SELECT 1 FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'PHP code'))->fetchField();
// Add a PHP code text format, if it does not exist. Do this only for the
// first install (or if the format has been manually deleted) as there is no
// reliable method to identify the format in an uninstall hook or in
@@ -19,7 +19,6 @@ function php_install() {
$format = db_insert('filter_format')
->fields(array(
'name' => 'PHP code',
- 'roles' => '',
'cache' => 0,
))
->execute();
diff --git a/modules/php/php.module b/modules/php/php.module
index 7915ecc..d295c4b 100644
--- a/modules/php/php.module
+++ b/modules/php/php.module
@@ -1,5 +1,5 @@
drupalCreateUser(array('administer filters'));
$this->drupalLogin($admin_user);
- // Confirm that the PHP filter is #3.
- $this->drupalGet('admin/config/content/formats/3');
- $this->assertText('PHP code', t('On PHP code filter page.'));
+ // Confirm that the PHP code text format was inserted as the newest format
+ // on the site.
+ $newest_format_id = db_query("SELECT MAX(format) FROM {filter_format}")->fetchField();
+ $newest_format = filter_format_load($newest_format_id);
+ $this->assertEqual($newest_format->name, 'PHP code', t('PHP code text format was created.'));
+
+ // Store the format ID of the PHP code text format for later use.
+ $this->php_code_format = $newest_format_id;
}
/**
@@ -43,15 +50,12 @@ class PHPFilterTestCase extends PHPTestCase {
* Make sure that the PHP filter evaluates PHP code when used.
*/
function testPHPFilter() {
- // Setup PHP filter.
- $edit = array();
- $edit['roles[2]'] = TRUE; // Set authenticated users to have permission to use filter.
- $this->drupalPost(NULL, $edit, 'Save configuration');
- $this->assertText(t('The text format settings have been updated.'), t('PHP format available to authenticated users.'));
-
- // Create node with PHP filter enabled.
- $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content'));
+ // Log in as a user with permission to use the PHP code text format.
+ $php_code_permission = filter_permission_name(filter_format_load($this->php_code_format));
+ $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content', $php_code_permission));
$this->drupalLogin($web_user);
+
+ // Create a node with PHP code in it.
$node = $this->createNodeWithCode();
// Make sure that the PHP code shows up as text.
@@ -61,7 +65,7 @@ class PHPFilterTestCase extends PHPTestCase {
// Change filter to PHP filter and see that PHP code is evaluated.
$edit = array();
$langcode = FIELD_LANGUAGE_NONE;
- $edit["body[$langcode][0][value_format]"] = 3;
+ $edit["body[$langcode][0][value_format]"] = $this->php_code_format;
$this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
$this->assertRaw(t('Page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.'));
@@ -98,6 +102,6 @@ class PHPAccessTestCase extends PHPTestCase {
// Make sure that user doesn't have access to filter.
$this->drupalGet('node/' . $node->nid . '/edit');
- $this->assertNoFieldByName('body_format', '3', t('Format not available.'));
+ $this->assertNoRaw('
' . format_plural($num_links, 'Warning: There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', 'Warning: There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $menu['title'])) . '
'; } $caption .= '' . t('This action cannot be undone.') . '
'; - return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu['title'])), 'admin/structure/menu-customize/' . $menu['menu_name'], $caption, t('Delete')); + return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu['title'])), 'admin/structure/menu/manage/' . $menu['menu_name'], $caption, t('Delete')); } /** @@ -525,7 +525,7 @@ function menu_delete_menu_confirm_submit($form, &$form_state) { menu_reset_item($item); } // Delete all links to the overview page for this menu. - $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu-customize/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC)); + $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $m) { menu_link_delete($m['mlid']); } @@ -570,7 +570,7 @@ function menu_edit_menu_validate($form, &$form_state) { // We will add 'menu-' to the menu name to help avoid name-space conflicts. $item['menu_name'] = 'menu-' . $item['menu_name']; $custom_exists = db_query('SELECT menu_name FROM {menu_custom} WHERE menu_name = :menu', array(':menu' => $item['menu_name']))->fetchField(); - $link_exists = db_query_range("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $item['menu_name']), 0, 1)->fetchField(); + $link_exists = db_query_range("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu", 0, 1, array(':menu' => $item['menu_name']))->fetchField(); if ($custom_exists || $link_exists) { form_set_error('menu_name', t('The menu already exists.')); } @@ -582,7 +582,7 @@ function menu_edit_menu_validate($form, &$form_state) { */ function menu_edit_menu_submit($form, &$form_state) { $menu = $form_state['values']; - $path = 'admin/structure/menu-customize/'; + $path = 'admin/structure/menu/manage/'; if ($form['#insert']) { // Add 'menu-' to the menu name to help avoid name-space conflicts. $menu['menu_name'] = 'menu-' . $menu['menu_name']; @@ -639,9 +639,9 @@ function menu_item_delete_page($item) { /** * Build a confirm form for deletion of a single menu link. */ -function menu_item_delete_form(&$form_state, $item) { +function menu_item_delete_form($form, &$form_state, $item) { $form['#item'] = $item; - return confirm_form($form, t('Are you sure you want to delete the custom menu link %item?', array('%item' => $item['link_title'])), 'admin/structure/menu-customize/' . $item['menu_name']); + return confirm_form($form, t('Are you sure you want to delete the custom menu link %item?', array('%item' => $item['link_title'])), 'admin/structure/menu/manage/' . $item['menu_name']); } /** @@ -653,15 +653,15 @@ function menu_item_delete_form_submit($form, &$form_state) { $t_args = array('%title' => $item['link_title']); drupal_set_message(t('The menu link %title has been deleted.', $t_args)); watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE); - $form_state['redirect'] = 'admin/structure/menu-customize/' . $item['menu_name']; + $form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name']; } /** * Menu callback; reset a single modified menu link. */ -function menu_reset_item_confirm(&$form_state, $item) { +function menu_reset_item_confirm($form, &$form_state, $item) { $form['item'] = array('#type' => 'value', '#value' => $item); - return confirm_form($form, t('Are you sure you want to reset the link %item to its default values?', array('%item' => $item['link_title'])), 'admin/structure/menu-customize/' . $item['menu_name'], t('Any customizations will be lost. This action cannot be undone.'), t('Reset')); + return confirm_form($form, t('Are you sure you want to reset the link %item to its default values?', array('%item' => $item['link_title'])), 'admin/structure/menu/manage/' . $item['menu_name'], t('Any customizations will be lost. This action cannot be undone.'), t('Reset')); } /** @@ -671,7 +671,7 @@ function menu_reset_item_confirm_submit($form, &$form_state) { $item = $form_state['values']['item']; $new_item = menu_reset_item($item); drupal_set_message(t('The menu link was reset to its default settings.')); - $form_state['redirect'] = 'admin/structure/menu-customize/' . $new_item['menu_name']; + $form_state['redirect'] = 'admin/structure/menu/manage/' . $new_item['menu_name']; } /** diff --git a/modules/menu/menu.api.php b/modules/menu/menu.api.php index 6e5e82b..c7c991e 100644 --- a/modules/menu/menu.api.php +++ b/modules/menu/menu.api.php @@ -1,5 +1,5 @@ 'mymodule_abc_view', + * ); + * } + * + * function mymodule_abc_view($ghi = 0, $jkl = '') { + * // ... + * } + * @endcode + * When the path 'abc/def' was requested, then no further arguments would be + * passed to the callback function, so $ghi and $jkl would take the default + * values as defined in the function signature. + * In case 'abc/def/123/foo' was requested, then $ghi would be '123' and $jkl + * would be 'foo'. + * + * In addition to optional path arguments, the definition for each path may + * specify a list of arguments for each callback function as an array. These + * argument lists may contain fixed/hard-coded argument values, but may also + * contain integers that correspond to path components. When integers are used + * and the callback function is called, the corresponding path components will + * be substituted. For example: + * @code + * function mymodule_menu() { + * $items['abc/def'] = array( + * 'page callback' => 'mymodule_abc_view', + * 'page arguments' => array(1, 'foo'), + * ); + * } + * @endcode + * When the path 'abc/def' was requested, the callback function would get 'def' + * as first argument and (always) 'foo' as second argument. + * The integer 1 in an argument list would be replaced with 'def' and integer 0 + * would be replaced with 'abc', i.e. path components are counted from zero. + * This allows to re-use a callback function for several different paths. + * + * Arguments may also be used to replace wildcards within paths. For example, + * when registering the path 'my-module/%/edit': + * @code + * $items['my-module/%/edit'] = array( + * 'page callback' => 'mymodule_abc_edit', + * 'page arguments' => array(1), + * ); + * @endcode + * When the path 'my-module/foo/edit' is requested, then integer 1 will be + * replaced with 'foo' and passed to the callback function. + * + * Registered paths may also contain special "auto-loader" wildcard components + * in the form of '%mymodule_abc', where the '%' part means that this path + * component is a wildcard, and the 'mymodule_abc' part defines the prefix for a + * menu argument loader function, which here would be mymodule_abc_load(). + * For example, when registering the path 'my-module/%mymodule_abc/edit': + * @code + * $items['my-module/%mymodule_abc/edit'] = array( + * 'page callback' => 'mymodule_abc_edit', + * 'page arguments' => array(1), + * ); + * @endcode + * When the path 'my-module/123/edit' is requested, then the argument loader + * function mymodule_abc_load() will be invoked with the argument '123', and it + * is supposed to take that value to load and return data for "abc" having the + * internal id 123: + * @code + * function mymodule_abc_load($abc_id) { + * return db_query("SELECT * FROM {mymodule_abc} WHERE abc_id = :abc_id", array(':abc_id' => $abc_id))->fetchObject(); + * } + * @endcode + * The returned data of the argument loader will be passed in place of the + * original path component to all callback functions referring to that (integer) + * component in their argument list. + * Menu argument loader functions may also be passed additional arguments; see + * "load arguments" below. + * + * If a registered path defines an argument list, then those defined arguments + * will always be passed first to the callback function. In case there are any + * further components contained in the requested path, then those will always + * come last. + * + * Special care should be taken for the page callback drupal_get_form(), because + * the callback function will always receive $form and &$form_state as the very + * first function arguments: + * @code + * function mymodule_abc_form($form, &$form_state) { + * // ... + * return $form; + * } + * @endcode + * See @link form_api Form API documentation @endlink for details. + * + * This hook is rarely called (for example, when modules are enabled), and + * its results are cached in the database. * * @return * An array of menu items. Each menu item has a key corresponding to the - * Drupal path being registered. The item is an associative array that may - * contain the following key-value pairs: + * Drupal path being registered. The corresponding array value is an + * associative array that may contain the following key-value pairs: * - "title": Required. The untranslated title of the menu item. - * - "title callback": Function to generate the title, defaults to t(). + * - "title callback": Function to generate the title; defaults to t(). * If you require only the raw string to be output, set this to FALSE. - * - "title arguments": Arguments to send to t() or your custom callback. + * - "title arguments": Arguments to send to t() or your custom callback, + * with path component substitution as described above. * - "description": The untranslated description of the menu item. * - "page callback": The function to call to display a web page when the user * visits the path. If omitted, the parent menu item's callback will be used * instead. * - "page arguments": An array of arguments to pass to the page callback - * function. Integer values pass the corresponding URL component (see arg()). - * - "access callback": A function returning a boolean value that determines + * function, with path component substitution as described above. + * - "access callback": A function returning a boolean value that determines * whether the user has access rights to this menu item. Defaults to * user_access() unless a value is inherited from a parent menu item. * - "access arguments": An array of arguments to pass to the access callback - * function. Integer values pass the corresponding URL component. + * function, with path component substitution as described above. + * - "theme callback": Optional. A function returning the machine-readable + * name of the theme that will be used to render the page. If the function + * returns nothing, the main site theme will be used. If no function is + * provided, the main site theme will also be used, unless a value is + * inherited from a parent menu item. + * - "theme arguments": An array of arguments to pass to the theme callback + * function, with path component substitution as described above. * - "file": A file that will be included before the callbacks are accessed; * this allows callback functions to be in separate files. The file should * be relative to the implementing module's directory unless otherwise - * specified by the "file path" option. + * specified by the "file path" option. Note: This does not apply to the + * 'access callback'. * - "file path": The path to the folder containing the file specified in * "file". This defaults to the path to the module implementing the hook. * - "load arguments": An array of arguments to be passed to each of the - * object loaders in the path. For example, for the router item at - * node/%node/revisions/%/view, the array(1, 3) will call node_load() with - * the arguments corresponding to the second and fourth URL argument; - * as with other arguments, integers are automatically cast to URL - * arguments. There are also two "magic" values: "%index" will correspond - * to the URL index where the object's load function is specified; "%map" - * will correspond to the full menu map, passed in by reference to the - * load function. - * - "weight": An integer that determines relative position of items in the - * menu; higher-weighted items sink. Defaults to 0. When in doubt, leave + * wildcard object loaders in the path. For example, for the path + * node/%node/revisions/%/view, a "load arguments" value of array(1, 3) will + * call node_load() with the second and fourth path components passed in (as + * described above, integers are automatically replaced with path + * components). There are also two "magic" values: "%index" will correspond + * to the index of the wildcard path component, and "%map" will correspond + * to the full menu map, passed in by reference. + * - "weight": An integer that determines the relative position of items in + * the menu; higher-weighted items sink. Defaults to 0. When in doubt, leave * this alone; the default alphabetical order is usually best. * - "menu_name": Optional. Set this to a custom menu if you don't want your * item to be placed in Navigation. + * - "tab_parent": For local task menu items, the path of the task's parent + * item; defaults to the same path without the last component (e.g., the + * default parent for 'admin/people/create' is 'admin/people'). + * - "tab_root": For local task menu items, the path of the closest non-tab + * item; same default as "tab_parent". + * - "block callback": Name of a function used to render the block on the + * system administration page for this item (called with no arguments). + * If not provided, system_admin_menu_block() is used to generate it. + * - "position": Position of the block ('left' or 'right') on the system + * administration page for this item. * - "type": A bitmask of flags describing properties of the menu item. * Many shortcut bitmasks are provided as constants in menu.inc: * - MENU_NORMAL_ITEM: Normal menu items show up in the menu tree and can be * moved/hidden by the administrator. * - MENU_CALLBACK: Callbacks simply register a path so that the correct - * function is fired when the URL is accessed. + * function is fired when the path is accessed. * - MENU_SUGGESTED_ITEM: Modules may "suggest" menu items that the * administrator may enable. * - MENU_LOCAL_TASK: Local tasks are rendered as tabs by default. * - MENU_DEFAULT_LOCAL_TASK: Every set of local tasks should provide one * "default" task, that links to the same path as its parent when clicked. - * If the "type" key is omitted, MENU_NORMAL_ITEM is assumed. + * If the "type" element is omitted, MENU_NORMAL_ITEM is assumed. + * * For a detailed usage example, see page_example.module. * For comprehensive documentation on the menu system, see * http://drupal.org/node/102338. */ function hook_menu() { - $items = array(); - $items['blog'] = array( 'title' => 'blogs', 'page callback' => 'blog_page', @@ -147,6 +262,78 @@ function hook_translated_menu_link_alter(&$item, $map) { } } + /** + * Inform modules that a menu link has been created. + * + * This hook is used to notify module that menu items have been + * created. Contributed modules may use the information to perform + * actions based on the information entered into the menu system. + * + * @param $link + * The $link record saved into the {menu_links} table. + * @return + * None. + * + * @see hook_menu_link_update() + * @see hook_menu_link_delete() + */ +function hook_menu_link_insert($link) { + // In our sample case, we track menu items as editing sections + // of the site. These are stored in our table as 'disabled' items. + $record['mlid'] = $link['mlid']; + $record['menu_name'] = $link['menu_name']; + $record['status'] = 0; + drupal_write_record('menu_example', $record); +} + +/** + * Inform modules that a menu link has been updated. + * + * This hook is used to notify module that menu items have been + * updated. Contributed modules may use the information to perform + * actions based on the information entered into the menu system. + * + * @param $link + * The $link record saved into the {menu_links} table. + * @return + * None. + * + * @see hook_menu_link_insert() + * @see hook_menu_link_delete() + */ +function hook_menu_link_update($link) { + // If the parent menu has changed, update our record. + $menu_name = db_result(db_query("SELECT mlid, menu_name, status FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $link['mlid']))); + if ($menu_name != $link['menu_name']) { + db_update('menu_example') + ->fields(array('menu_name' => $link['menu_name'])) + ->condition('mlid', $link['mlid']) + ->execute(); + } +} + +/** + * Inform modules that a menu link has been deleted. + * + * This hook is used to notify module that menu items have been + * deleted. Contributed modules may use the information to perform + * actions based on the information entered into the menu system. + * + * @param $link + * The $link record saved into the {menu_links} table. + * @return + * None. + * + * @see hook_menu_link_insert() + * @see hook_menu_link_update() + */ +function hook_menu_link_delete($link) { + // Delete the record from our table. + db_delete('menu_example') + ->condition('mlid', $link['mlid']) + ->execute(); +} + /** * @} End of "addtogroup hooks". */ diff --git a/modules/menu/menu.install b/modules/menu/menu.install index e40ad73..bbf300f 100644 --- a/modules/menu/menu.install +++ b/modules/menu/menu.install @@ -1,5 +1,5 @@ 'The Navigation menu contains links such as Recent posts (if the Tracker module is enabled). Non-administrative links are added to this menu by default by modules.', @@ -31,8 +29,6 @@ function menu_install() { * Implement hook_uninstall(). */ function menu_uninstall() { - // Remove tables. - drupal_uninstall_schema('menu'); menu_rebuild(); } diff --git a/modules/menu/menu.module b/modules/menu/menu.module index 5225e7b..af02289 100644 --- a/modules/menu/menu.module +++ b/modules/menu/menu.module @@ -1,5 +1,5 @@ 5, 'file' => 'menu.admin.inc', ); - $items['admin/structure/menu-customize/%menu'] = array( + $items['admin/structure/menu/manage/%menu'] = array( 'title' => 'Customize menu', 'page callback' => 'drupal_get_form', - 'page arguments' => array('menu_overview_form', 3), + 'page arguments' => array('menu_overview_form', 4), 'title callback' => 'menu_overview_title', - 'title arguments' => array(3), + 'title arguments' => array(4), 'access arguments' => array('administer menu'), 'type' => MENU_CALLBACK, 'file' => 'menu.admin.inc', ); - $items['admin/structure/menu-customize/%menu/list'] = array( + $items['admin/structure/menu/manage/%menu/list'] = array( 'title' => 'List links', 'weight' => -10, 'type' => MENU_DEFAULT_LOCAL_TASK, ); - $items['admin/structure/menu-customize/%menu/add'] = array( + $items['admin/structure/menu/manage/%menu/add'] = array( 'title' => 'Add link', 'page callback' => 'drupal_get_form', - 'page arguments' => array('menu_edit_item', 'add', NULL, 3), + 'page arguments' => array('menu_edit_item', 'add', NULL, 4), 'access arguments' => array('administer menu'), 'type' => MENU_LOCAL_ACTION, 'file' => 'menu.admin.inc', ); - $items['admin/structure/menu-customize/%menu/edit'] = array( + $items['admin/structure/menu/manage/%menu/edit'] = array( 'title' => 'Edit menu', 'page callback' => 'drupal_get_form', - 'page arguments' => array('menu_edit_menu', 'edit', 3), + 'page arguments' => array('menu_edit_menu', 'edit', 4), 'access arguments' => array('administer menu'), 'type' => MENU_LOCAL_TASK, 'file' => 'menu.admin.inc', ); - $items['admin/structure/menu-customize/%menu/delete'] = array( + $items['admin/structure/menu/manage/%menu/delete'] = array( 'title' => 'Delete menu', 'page callback' => 'menu_delete_menu_page', - 'page arguments' => array(3), + 'page arguments' => array(4), 'access arguments' => array('administer menu'), 'type' => MENU_CALLBACK, 'file' => 'menu.admin.inc', @@ -168,7 +168,7 @@ function menu_theme() { function menu_enable() { menu_rebuild(); $base_link = db_query("SELECT mlid AS plid, menu_name FROM {menu_links} WHERE link_path = 'admin/structure/menu' AND module = 'system'")->fetchAssoc(); - $base_link['router_path'] = 'admin/structure/menu-customize/%'; + $base_link['router_path'] = 'admin/structure/menu/manage/%'; $base_link['module'] = 'menu'; $result = db_query("SELECT * FROM {menu_custom}", array(), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $menu) { @@ -176,7 +176,7 @@ function menu_enable() { $link = $base_link; $link['mlid'] = 0; $link['link_title'] = $menu['title']; - $link['link_path'] = 'admin/structure/menu-customize/' . $menu['menu_name']; + $link['link_path'] = 'admin/structure/menu/manage/' . $menu['menu_name']; $menu_link = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND plid = :plid", array( ':path' => $link['link_path'], ':plid' => $link['plid'] @@ -365,17 +365,15 @@ function menu_node_prepare($node) { $item = array(); if (isset($node->nid)) { // Give priority to the default menu - $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND menu_name = :menu_name AND module = 'menu' ORDER BY mlid ASC", array( + $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND menu_name = :menu_name AND module = 'menu' ORDER BY mlid ASC", 0, 1, array( ':path' => 'node/' . $node->nid, ':menu_name' => $menu_name, - ), 0, 1) - ->fetchField(); + ))->fetchField(); // Check all menus if a link does not exist in the default menu. if (!$mlid) { - $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu' ORDER BY mlid ASC", array( + $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = :path AND module = 'menu' ORDER BY mlid ASC", 0, 1, array( ':path' => 'node/' . $node->nid, - ), 0, 1) - ->fetchField(); + ))->fetchField(); } if ($mlid) { $item = menu_link_load($mlid); diff --git a/modules/menu/menu.test b/modules/menu/menu.test index d3d7be3..22989b7 100644 --- a/modules/menu/menu.test +++ b/modules/menu/menu.test @@ -1,5 +1,10 @@ menu['title']; // Delete custom menu. - $this->drupalPost("admin/structure/menu-customize/$menu_name/delete", array(), t('Delete')); + $this->drupalPost("admin/structure/menu/manage/$menu_name/delete", array(), t('Delete')); $this->assertResponse(200); $this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $title)), t('Custom menu was deleted')); $this->assertFalse(menu_load($menu_name), 'Custom menu was deleted'); @@ -181,7 +186,7 @@ class MenuTestCase extends DrupalWebTestCase { // Note in the UI the 'mlid:x[hidden]' form element maps to enabled, or // NOT hidden. $edit['mlid:' . $item1['mlid'] . '[hidden]'] = TRUE; - $this->drupalPost('admin/structure/menu-customize/' . $item1['menu_name'], $edit, t('Save configuration')); + $this->drupalPost('admin/structure/menu/manage/' . $item1['menu_name'], $edit, t('Save configuration')); // Verify in the database. $hidden = db_query("SELECT hidden FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item1['mlid']))->fetchField(); @@ -202,7 +207,7 @@ class MenuTestCase extends DrupalWebTestCase { */ function addMenuLink($plid = 0, $link = '', '#suffix' => '
', '#tree' => TRUE); // array_filter returns only elements with TRUE values foreach ($nodes as $nid => $value) { @@ -586,7 +586,6 @@ function node_multiple_delete_confirm_submit($form, &$form_state) { drupal_set_message(t('Deleted @count posts.', array('@count' => $count))); } $form_state['redirect'] = 'admin/content'; - return; } /** @@ -594,5 +593,5 @@ function node_multiple_delete_confirm_submit($form, &$form_state) { */ function node_modules_installed($modules) { // Clear node type cache for node permissions. - node_type_clear(); + drupal_static_reset('_node_types_build'); } diff --git a/modules/node/node.api.php b/modules/node/node.api.php index 30d38ef..7bdb591 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -1,5 +1,5 @@ condition('vid', $node->vid)->execute(); if (!is_array($node->files)) { return; diff --git a/modules/node/node.install b/modules/node/node.install index 2e5b3bb..25ece92 100644 --- a/modules/node/node.install +++ b/modules/node/node.install @@ -1,5 +1,5 @@ 'varchar', 'length' => 255, 'not null' => TRUE)); - - return $ret; + db_update('node_type') + ->fields(array('module' => 'node_content')) + ->condition('module', 'node') + ->execute(); } /** * Rename {node_revisions} table to {node_revision}. */ function node_update_7001() { - $ret = array(); - db_rename_table($ret, 'node_revisions', 'node_revision'); - return $ret; + db_rename_table('node_revisions', 'node_revision'); } /** * Extend the node_promote_status index to include all fields required for the node page query. */ function node_update_7002() { - $ret = array(); - db_drop_index($ret, 'node', 'node_promote_status'); - db_add_index($ret, 'node', 'node_frontpage', array('promote', 'status', 'sticky', 'created')); - return $ret; + db_drop_index('node', 'node_promote_status'); + db_add_index('node', 'node_frontpage', array('promote', 'status', 'sticky', 'created')); } /** * Remove the node_counter if the statistics module is uninstalled. */ function node_update_7003() { - $ret = array(); if (drupal_get_installed_schema_version('statistics') == SCHEMA_UNINSTALLED) { - db_drop_table($ret, 'node_counter'); + db_drop_table('node_counter'); } - return $ret; } /** @@ -408,7 +400,7 @@ function node_update_7004() { // Map old preview setting to new values order. $original_preview ? $original_preview = 2 : $original_preview = 1; - node_type_clear(); + drupal_static_reset('_node_types_build'); $type_list = node_type_get_types(); // Apply original settings to all types. @@ -418,33 +410,29 @@ function node_update_7004() { } // Delete old variable but leave 'teaser_length' for aggregator module upgrade. variable_del('node_preview'); - - return array(); } /** * Add status/comment/promote and sticky columns to the {node_revision} table. */ function node_update_7005() { - $ret = array(); foreach(array('status', 'comment', 'promote', 'sticky') as $column) { - db_add_field($ret, 'node_revision', $column, array( + db_add_field('node_revision', $column, array( 'type' => 'int', 'not null' => TRUE, 'default' => 0, )); } - return $ret; } /** * Convert body and teaser from node properties to fields, and migrate status/comment/promote and sticky columns to the {node_revision} table. */ function node_update_7006(&$context) { - $ret = array('#finished' => 0); + $context['#finished'] = 0; // Get node type info for every invocation. - node_type_clear(); + drupal_static_reset('_node_types_build'); $node_types = node_type_get_types(); if (!isset($context['total'])) { @@ -506,7 +494,11 @@ function node_update_7006(&$context) { $revision->body = substr($revision->body, strlen($break)); } $node->body[FIELD_LANGUAGE_NONE][0]['value'] = $revision->body; - $node->body[FIELD_LANGUAGE_NONE][0]['format'] = $revision->format; + // Explicitly store the current default text format if the revision + // did not have its own text format. Similar conversions for other + // core modules are performed in filter_update_7005(), but we do this + // one here since we are already migrating the data. + $node->body[FIELD_LANGUAGE_NONE][0]['format'] = !empty($revision->format) ? $revision->format : variable_get('filter_default_format', 1); // This is a core update and no contrib modules are enabled yet, so // we can assume default field storage for a faster update. field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array()); @@ -532,33 +524,29 @@ function node_update_7006(&$context) { } } - $ret['#finished'] = min(0.99, $context['count'] / $context['total']); + $context['#finished'] = min(0.99, $context['count'] / $context['total']); } if (!$found) { // All nodes are processed. - $ret[] = array('success' => TRUE, 'query' => "{$context['total']} node body and teaser properties migrated to the 'body' field."); // Remove the now-obsolete body info from node_revision. - db_drop_field($ret, 'node_revision', 'body'); - db_drop_field($ret, 'node_revision', 'teaser'); - db_drop_field($ret, 'node_revision', 'format'); + db_drop_field('node_revision', 'body'); + db_drop_field('node_revision', 'teaser'); + db_drop_field('node_revision', 'format'); // We're done. - $ret['#finished'] = 1; + $context['#finished'] = 1; + return t("!number node body and teaser properties migrated to the 'body' field.", array('!number' => $context['total'])); } } - - return $ret; } /** * Remove column min_word_count. */ function node_update_7007() { - $ret = array(); - db_drop_field($ret, 'node_type', 'min_word_count'); - return $ret; + db_drop_field('node_type', 'min_word_count'); } /** diff --git a/modules/node/node.module b/modules/node/node.module index 5d610cb..b89091d 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -1,5 +1,5 @@ ' . t('Each piece of content is of a specific content type. Each content type can have different fields, behaviors, and permissions assigned to it.') . ''; - case 'admin/structure/node-type/' . $arg[3] . '/fields': + case 'admin/structure/types/manage/' . $arg[3] . '/fields': return '' . t('This form lets you add, edit, and arrange fields within the %type content type.', array('%type' => node_type_get_name($arg[3]))) . '
'; - case 'admin/structure/node-type/' . $arg[3] . '/display': + case 'admin/structure/types/manage/' . $arg[3] . '/display': return '' . t('This form lets you configure how fields and labels are displayed when %type content is viewed in teaser and full-page mode.', array('%type' => node_type_get_name($arg[3]))) . '
'; - case 'admin/structure/node-type/' . $arg[3] . '/display/' . $arg[5]: + case 'admin/structure/types/manage/' . $arg[3] . '/display/' . $arg[5]: return '' . t('This form lets you configure how fields should be displayed when rendered %type content in the following contexts.', array('%type' => node_type_get_name($arg[3]))) . '
'; case 'node/%/revisions': @@ -200,7 +200,7 @@ function node_entity_info() { $return['node']['bundles'][$type] = array( 'label' => $name, 'admin' => array( - 'path' => 'admin/structure/node-type/' . str_replace('_', '-', $type), + 'path' => 'admin/structure/types/manage/' . str_replace('_', '-', $type), 'access arguments' => array('administer content types'), ), ); @@ -360,13 +360,6 @@ function _node_extract_type($node) { return is_object($node) ? $node->type : $node; } -/** - * Clear the statically cached node type information. - */ -function node_type_clear() { - drupal_static_reset('_node_types_build'); -} - /** * Returns a list of all the available node types. * @@ -448,12 +441,13 @@ function node_type_get_name($node) { } /** - * Resets the database cache of node types, and saves all new or non-modified - * module-defined node types to the database. + * Resets the database cache of node types. + * + * All new or non-modified module-defined node types are saved to the database. */ function node_types_rebuild() { // Reset and load updated node types. - node_type_clear(); + drupal_static_reset('_node_types_build'); foreach (node_type_get_types() as $type => $info) { if (!empty($info->is_new)) { node_type_save($info); @@ -462,11 +456,6 @@ function node_types_rebuild() { node_type_delete($info->type); } } - // Reset cached node type information so that the next access - // will use the updated data. - node_type_clear(); - // This is required for proper menu items at node/add/type. - menu_rebuild(); } /** @@ -481,7 +470,7 @@ function node_types_rebuild() { function node_type_save($info) { $is_existing = FALSE; $existing_type = !empty($info->old_type) ? $info->old_type : $info->type; - $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $existing_type), 0, 1)->fetchField(); + $is_existing = (bool) db_query_range('SELECT 1 FROM {node_type} WHERE type = :type', 0, 1, array(':type' => $existing_type))->fetchField(); $type = node_type_set_defaults($info); $fields = array( @@ -510,7 +499,7 @@ function node_type_save($info) { } node_configure_fields($type); module_invoke_all('node_type_update', $type); - return SAVED_UPDATED; + $status = SAVED_UPDATED; } else { $fields['orig_type'] = (string) $type->orig_type; @@ -521,8 +510,13 @@ function node_type_save($info) { field_attach_create_bundle($type->type); node_configure_fields($type); module_invoke_all('node_type_insert', $type); - return SAVED_NEW; + $status = SAVED_NEW; } + + // Clear the node type cache. + drupal_static_reset('_node_types_build'); + + return $status; } /** @@ -555,10 +549,7 @@ function node_configure_fields($type) { 'widget_type' => 'text_textarea_with_summary', 'settings' => array('display_summary' => TRUE), - // With no UI in core, we have to define default - // formatters for the teaser and full view. - // This may change if the method of handling displays - // is changed or if a UI gets into core. + // Define default formatters for the teaser and full view. 'display' => array( 'full' => array( 'label' => 'hidden', @@ -596,6 +587,9 @@ function node_type_delete($type) { ->condition('type', $type) ->execute(); module_invoke_all('node_type_delete', $info); + + // Clear the node type cache. + drupal_static_reset('_node_types_build'); } /** @@ -786,10 +780,11 @@ function node_load_multiple($nids = array(), $conditions = array(), $reset = FAL * @return * A fully-populated node object. */ -function node_load($nid, $vid = array(), $reset = FALSE) { - $vid = isset($vid) ? array('vid' => $vid) : NULL; - $node = node_load_multiple(array($nid), $vid, $reset); - return $node ? $node[$nid] : FALSE; +function node_load($nid = NULL, $vid = NULL, $reset = FALSE) { + $nids = (isset($nid) ? array($nid) : array()); + $conditions = (isset($vid) ? array('vid' => $vid) : array()); + $node = node_load_multiple($nids, $conditions, $reset); + return $node ? reset($node) : FALSE; } /** @@ -1016,6 +1011,31 @@ function node_delete_multiple($nids) { drupal_static_reset('node_load_multiple'); } +/** + * Delete a node revision. + * + * @param $revision_id + * The revision ID to delete. + */ +function node_revision_delete($revision_id) { + if ($revision = node_load(NULL, $revision_id)) { + // Prevent deleting the current revision. + $node = node_load($revision->nid); + if ($revision_id == $node->vid) { + return FALSE; + } + + db_delete('node_revision') + ->condition('nid', $revision->nid) + ->condition('vid', $revision->vid) + ->execute(); + module_invoke_all('node_revision_delete', $revision); + field_attach_delete_revision('node', $revision); + return TRUE; + } + return FALSE; +} + /** * Generate an array for rendering the given node. * @@ -1030,9 +1050,13 @@ function node_delete_multiple($nids) { function node_build($node, $build_mode = 'full') { $node = (object)$node; - $node = node_build_content($node, $build_mode); + // Populate $node->content with a render() array. + node_build_content($node, $build_mode); $build = $node->content; + // We don't need duplicate rendering info in node->content. + unset($node->content); + $build += array( '#theme' => 'node', '#node' => $node, @@ -1066,9 +1090,6 @@ function node_build($node, $build_mode = 'full') { * @param $build_mode * Build mode, e.g. 'full', 'teaser'... * - * @return - * A structured array containing the individual elements - * of the node's content. */ function node_build_content($node, $build_mode = 'full') { // The 'view' hook can be implemented to overwrite the default function @@ -1104,8 +1125,6 @@ function node_build_content($node, $build_mode = 'full') { // Allow modules to modify the structured node. drupal_alter('node_build', $node, $build_mode); - - return $node; } /** @@ -1163,9 +1182,14 @@ function template_preprocess_node(&$variables) { // Flatten the node object's member fields. $variables = array_merge((array)$node, $variables); + + // Helpful $content variable for templates. + foreach (element_children($variables['elements']) as $key) { + $variables['content'][$key] = $variables['elements'][$key]; + } // Make the field variables available with the appropriate language. - field_attach_preprocess('node', $node, $node->content, $variables); + field_attach_preprocess('node', $node, $variables['content'], $variables); // Display post information only on certain node types. if (variable_get('node_submitted_' . $node->type, TRUE)) { @@ -1309,8 +1333,8 @@ function node_search_reset() { * Implement hook_search_status(). */ function node_search_status() { - $total = db_query('SELECT COUNT(*) FROM {node} WHERE status = %d', NODE_PUBLISHED)->fetchField(); - $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = %d AND d.sid IS NULL OR d.reindex <> 0", NODE_PUBLISHED)->fetchField(); + $total = db_query('SELECT COUNT(*) FROM {node} WHERE status = :status', array(':status' => NODE_PUBLISHED))->fetchField(); + $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = :status AND d.sid IS NULL OR d.reindex <> 0", array(':status' => NODE_PUBLISHED))->fetchField(); return array('remaining' => $remaining, 'total' => $total); } @@ -1382,7 +1406,7 @@ function node_search_execute($keys = NULL) { foreach ($find as $item) { // Render the node. $node = node_load($item->sid); - $node = node_build_content($node, 'search_result'); + node_build_content($node, 'search_result'); $node->rendered = drupal_render($node->content); // Fetch comments for snippet. @@ -1486,13 +1510,12 @@ function node_user_cancel($edit, $account, $method) { ->condition('uid', $account->uid) ->execute() ->fetchCol(); - foreach ($nodes as $nid) { - node_delete($nid); - } + node_delete_multiple($nodes); // Delete old revisions. - db_delete('node_revision') - ->condition('uid', $account->uid) - ->execute(); + $revisions = db_query('SELECT vid FROM {node_revision} WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol(); + foreach ($revisions as $revision) { + node_revision_delete($revision); + } // Clean history. db_delete('history') ->condition('uid', $account->uid) @@ -1572,7 +1595,7 @@ function _node_add_access() { function node_menu() { $items['admin/content'] = array( 'title' => 'Content', - 'description' => 'Find and manage content and comments.', + 'description' => 'Find and manage content.', 'page callback' => 'drupal_get_form', 'page arguments' => array('node_admin_content'), 'access arguments' => array('administer nodes'), @@ -1581,6 +1604,7 @@ function node_menu() { ); $items['admin/content/node'] = array( 'title' => 'Content', + 'description' => "View, edit, and delete your site's content.", 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ); @@ -1628,6 +1652,7 @@ function node_menu() { 'access callback' => '_node_add_access', 'weight' => 1, 'menu_name' => 'management', + 'theme callback' => '_node_custom_theme', 'file' => 'node.pages.inc', ); $items['rss.xml'] = array( @@ -1636,8 +1661,9 @@ function node_menu() { 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); - // Reset internal static cache of _node_types_build, forces to rebuild the node type information. - node_type_clear(); + // Reset internal static cache of _node_types_build(), forces to rebuild the + // node type information. + drupal_static_reset('_node_types_build'); foreach (node_type_get_types() as $type) { $type_url_str = str_replace('_', '-', $type->type); $items['node/add/' . $type_url_str] = array( @@ -1650,7 +1676,7 @@ function node_menu() { 'description' => $type->description, 'file' => 'node.pages.inc', ); - $items['admin/structure/node-type/' . $type_url_str] = array( + $items['admin/structure/types/manage/' . $type_url_str] = array( 'title' => $type->name, 'page callback' => 'drupal_get_form', 'page arguments' => array('node_type_form', $type), @@ -1658,11 +1684,11 @@ function node_menu() { 'type' => MENU_CALLBACK, 'file' => 'content_types.inc', ); - $items['admin/structure/node-type/' . $type_url_str . '/edit'] = array( + $items['admin/structure/types/manage/' . $type_url_str . '/edit'] = array( 'title' => 'Edit', 'type' => MENU_DEFAULT_LOCAL_TASK, ); - $items['admin/structure/node-type/' . $type_url_str . '/delete'] = array( + $items['admin/structure/types/manage/' . $type_url_str . '/delete'] = array( 'title' => 'Delete', 'page arguments' => array('node_type_delete_confirm', $type), 'access arguments' => array('administer content types'), @@ -1688,6 +1714,7 @@ function node_menu() { 'page arguments' => array(1), 'access callback' => 'node_access', 'access arguments' => array('update', 1), + 'theme callback' => '_node_custom_theme', 'weight' => 1, 'type' => MENU_LOCAL_TASK, 'file' => 'node.pages.inc', @@ -1751,6 +1778,17 @@ function node_page_title($node) { return $node->title; } +/** + * Theme callback for creating and editing nodes. + */ +function _node_custom_theme() { + // Use the administration theme if the site is configured to use it for + // nodes. + if (variable_get('node_admin_theme')) { + return variable_get('admin_theme'); + } +} + /** * Implement hook_init(). */ @@ -1821,7 +1859,7 @@ function node_feed($nids = FALSE, $channel = array()) { ->fetchCol(); } - $item_length = variable_get('feed_item_length', 'teaser'); + $item_length = variable_get('feed_item_length', 'fulltext'); $namespaces = array('xmlns:dc' => 'http://purl.org/dc/elements/1.1/'); $teaser = ($item_length == 'teaser'); @@ -1841,7 +1879,7 @@ function node_feed($nids = FALSE, $channel = array()) { // The node gets built and modules add to or modify $node->rss_elements // and $node->rss_namespaces. - $node = node_build_content($node, 'rss'); + node_build_content($node, 'rss'); if (!empty($node->rss_namespaces)) { $namespaces = array_merge($namespaces, $node->rss_namespaces); @@ -1870,7 +1908,7 @@ function node_feed($nids = FALSE, $channel = array()) { $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']); $output .= "\n"; - drupal_set_header('Content-Type', 'application/rss+xml; charset=utf-8'); + drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8'); print $output; } @@ -1985,7 +2023,7 @@ function _node_index_node($node) { variable_set('node_cron_last', $node->changed); // Render the node. - $node = node_build_content($node, 'search_index'); + node_build_content($node, 'search_index'); $node->rendered = drupal_render($node->content); $text = '' . check_plain($node->title) . '
' . $node->rendered; @@ -2183,7 +2221,7 @@ function node_search_validate($form, &$form_state) { * Optional, a user object representing the user for whom the operation is to * be performed. Determines access for a user other than the current user. * @return - * TRUE if the operation may be performed. + * TRUE if the operation may be performed, FALSE otherwise. */ function node_access($op, $node, $account = NULL) { global $user; @@ -2232,13 +2270,14 @@ function node_access($op, $node, $account = NULL) { // node_access table. if ($op != 'create' && $node->nid) { $query = db_select('node_access'); - $query->addExpression('COUNT(*)'); + $query->addExpression('1'); $query->condition('grant_' . $op, 1, '>='); $nids = db_or()->condition('nid', $node->nid); if ($node->status) { $nids->condition('nid', 0); } $query->condition($nids); + $query->range(0, 1); $grants = db_or(); foreach (node_access_grants($op, $account) as $realm => $gids) { @@ -2249,10 +2288,10 @@ function node_access($op, $node, $account = NULL) { ); } } - if (count($grants) > 0 ) { + if (count($grants) > 0) { $query->condition($grants); } - return $query + return (bool) $query ->execute() ->fetchField(); } @@ -2353,61 +2392,6 @@ function node_permissions_get_configured_types() { return $configured_types; } -/** - * Generate an SQL join clause for use in fetching a node listing. - * - * @param $node_alias - * If the node table has been given an SQL alias other than the default - * "n", that must be passed here. - * @param $node_access_alias - * If the node_access table has been given an SQL alias other than the default - * "na", that must be passed here. - * @return - * An SQL join clause. - */ -function _node_access_join_sql($node_alias = 'n', $node_access_alias = 'na') { - if (user_access('bypass node access')) { - return ''; - } - - return 'INNER JOIN {node_access} ' . $node_access_alias . ' ON ' . $node_access_alias . '.nid = ' . $node_alias . '.nid'; -} - -/** - * Generate an SQL where clause for use in fetching a node listing. - * - * @param $op - * The operation that must be allowed to return a node. - * @param $node_access_alias - * If the node_access table has been given an SQL alias other than the default - * "na", that must be passed here. - * @param $account - * The user object for the user performing the operation. If omitted, the - * current user is used. - * @return - * An SQL where clause. - */ -function _node_access_where_sql($op = 'view', $node_access_alias = 'na', $account = NULL) { - if (user_access('bypass node access')) { - return; - } - - $grants = array(); - foreach (node_access_grants($op, $account) as $realm => $gids) { - foreach ($gids as $gid) { - $grants[] = "($node_access_alias.gid = $gid AND $node_access_alias.realm = '$realm')"; - } - } - - $grants_sql = ''; - if (count($grants)) { - $grants_sql = 'AND (' . implode(' OR ', $grants) . ')'; - } - - $sql = "$node_access_alias.grant_$op >= 1 $grants_sql"; - return $sql; -} - /** * Fetch an array of permission IDs granted to the given user ID. * @@ -2481,17 +2465,6 @@ function node_access_view_all_nodes() { return $access; } -/** - * Implement hook_db_rewrite_sql(). - */ -function node_db_rewrite_sql($query, $primary_table, $primary_field) { - if ($primary_field == 'nid' && !node_access_view_all_nodes()) { - $return['join'] = _node_access_join_sql($primary_table); - $return['where'] = _node_access_where_sql(); - $return['distinct'] = 1; - return $return; - } -} /** * Implement hook_query_TAG_alter(). @@ -2736,7 +2709,7 @@ function _node_access_rebuild_batch_operation(&$context) { // Process the next 20 nodes. $limit = 20; - $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", array(':nid' => $context['sandbox']['current_node']), 0, $limit)->fetchCol(); + $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", 0, $limit, array(':nid' => $context['sandbox']['current_node']))->fetchCol(); $nodes = node_load_multiple($nids, array(), TRUE); foreach ($nodes as $node) { // To preserve database integrity, only acquire grants if the node @@ -2782,11 +2755,8 @@ function _node_access_rebuild_batch_finished($success, $results, $operations) { * Implement hook_form(). */ function node_content_form($node, $form_state) { - $type = node_type_get_type($node); - $form = array(); - if ($type->has_title) { $form['title'] = array( '#type' => 'textfield', @@ -2819,33 +2789,6 @@ function node_forms() { return $forms; } -/** - * Implement hook_hook_info(). - */ -function node_hook_info() { - return array( - 'node' => array( - 'node' => array( - 'presave' => array( - 'runs when' => t('When either saving a new post or updating an existing post'), - ), - 'insert' => array( - 'runs when' => t('After saving a new post'), - ), - 'update' => array( - 'runs when' => t('After saving an updated post'), - ), - 'delete' => array( - 'runs when' => t('After deleting a post') - ), - 'view' => array( - 'runs when' => t('When content is viewed by an authenticated user') - ), - ), - ), - ); -} - /** * Implement hook_action_info(). */ @@ -2853,90 +2796,84 @@ function node_action_info() { return array( 'node_publish_action' => array( 'type' => 'node', - 'description' => t('Publish post'), + 'label' => t('Publish content'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( - 'node' => array('presave'), - 'comment' => array('insert', 'update'), - ), + 'triggers' => array('node_presave', 'comment_insert', 'comment_update'), ), 'node_unpublish_action' => array( 'type' => 'node', - 'description' => t('Unpublish post'), + 'label' => t('Unpublish content'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( - 'node' => array('presave'), - 'comment' => array('delete', 'insert', 'update'), + 'triggers' => array( + 'node_presave', + 'comment_insert', + 'comment_update', + 'comment_delete' ), ), 'node_make_sticky_action' => array( 'type' => 'node', - 'description' => t('Make post sticky'), + 'label' => t('Make content sticky'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( - 'node' => array('presave'), - 'comment' => array('insert', 'update'), - ), + 'triggers' => array('node_presave', 'comment_insert', 'comment_update'), ), 'node_make_unsticky_action' => array( 'type' => 'node', - 'description' => t('Make post unsticky'), + 'label' => t('Make content unsticky'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( - 'node' => array('presave'), - 'comment' => array('delete', 'insert', 'update'), + 'triggers' => array( + 'node_presave', + 'comment_insert', + 'comment_update', + 'comment_delete' ), ), 'node_promote_action' => array( 'type' => 'node', - 'description' => t('Promote post to front page'), + 'label' => t('Promote content to front page'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( - 'node' => array('presave'), - 'comment' => array('insert', 'update'), - ), + 'triggers' => array('node_presave', 'comment_insert', 'comment_update'), ), 'node_unpromote_action' => array( 'type' => 'node', - 'description' => t('Remove post from front page'), + 'label' => t('Remove content from front page'), 'configurable' => FALSE, 'behavior' => array('changes_node_property'), - 'hooks' => array( - 'node' => array('presave'), - 'comment' => array('delete', 'insert', 'update'), + 'triggers' => array( + 'node_presave', + 'comment_insert', + 'comment_update', + 'comment_delete' ), ), 'node_assign_owner_action' => array( 'type' => 'node', - 'description' => t('Change the author of a post'), + 'label' => t('Change the author of content'), 'configurable' => TRUE, 'behavior' => array('changes_node_property'), - 'hooks' => array( - 'any' => TRUE, - 'node' => array('presave'), - 'comment' => array('delete', 'insert', 'update'), + 'triggers' => array( + 'node_presave', + 'comment_insert', + 'comment_update', + 'comment_delete', ), ), 'node_save_action' => array( 'type' => 'node', - 'description' => t('Save post'), + 'label' => t('Save content'), 'configurable' => FALSE, - 'hooks' => array( - 'comment' => array('delete', 'insert', 'update'), - ), + 'triggers' => array('comment_delete', 'comment_insert', 'comment_update'), ), 'node_unpublish_by_keyword_action' => array( 'type' => 'node', - 'description' => t('Unpublish post containing keyword(s)'), + 'label' => t('Unpublish content containing keyword(s)'), 'configurable' => TRUE, - 'hooks' => array( - 'node' => array('presave', 'insert', 'update'), - ), + 'triggers' => array('node_presave', 'node_insert', 'node_update'), ), ); } @@ -3044,7 +2981,7 @@ function node_assign_owner_action_form($context) { '#default_value' => $owner_name, '#autocomplete_path' => 'user/autocomplete', '#size' => '6', - '#maxlength' => '7', + '#maxlength' => '60', '#description' => $description, ); } @@ -3052,7 +2989,7 @@ function node_assign_owner_action_form($context) { } function node_assign_owner_action_validate($form, $form_state) { - $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', array(':name' => $form_state['values']['owner_name']), 0, 1)->fetchField(); + $exists = (bool) db_query_range('SELECT 1 FROM {users} WHERE name = :name', 0, 1, array(':name' => $form_state['values']['owner_name']))->fetchField(); if (!$exists) { form_set_error('owner_name', t('Please enter a valid username.')); } diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index 445f6b2..8d686bc 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -1,5 +1,5 @@ nid . '/delete', $destination); @@ -453,7 +453,7 @@ function node_form_submit_build_node($form, &$form_state) { /** * Menu callback -- ask for confirmation of node deletion */ -function node_delete_confirm(&$form_state, $node) { +function node_delete_confirm($form, &$form_state, $node) { $form['nid'] = array( '#type' => 'value', '#value' => $node->nid, @@ -536,7 +536,7 @@ function node_revision_overview($node) { /** * Ask for confirmation of the reversion to prevent against CSRF attacks. */ -function node_revision_revert_confirm($form_state, $node_revision) { +function node_revision_revert_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to revert to the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', '', t('Revert'), t('Cancel')); } @@ -556,19 +556,15 @@ function node_revision_revert_confirm_submit($form, &$form_state) { $form_state['redirect'] = 'node/' . $node_revision->nid . '/revisions'; } -function node_revision_delete_confirm($form_state, $node_revision) { +function node_revision_delete_confirm($form, $form_state, $node_revision) { $form['#node_revision'] = $node_revision; return confirm_form($form, t('Are you sure you want to delete the revision from %revision-date?', array('%revision-date' => format_date($node_revision->revision_timestamp))), 'node/' . $node_revision->nid . '/revisions', t('This action cannot be undone.'), t('Delete'), t('Cancel')); } function node_revision_delete_confirm_submit($form, &$form_state) { $node_revision = $form['#node_revision']; - db_delete('node_revision') - ->condition('nid', $node_revision->nid) - ->condition('vid', $node_revision->vid) - ->execute(); - module_invoke_all('node_delete_revision', $node_revision); - field_attach_delete_revision('node', $node_revision); + node_revision_delete($node_revision->vid); + watchdog('content', '@type: deleted %title revision %revision.', array('@type' => $node_revision->type, '%title' => $node_revision->title, '%revision' => $node_revision->vid)); drupal_set_message(t('Revision from %revision-date of @type %title has been deleted.', array('%revision-date' => format_date($node_revision->revision_timestamp), '@type' => node_type_get_name($node_revision), '%title' => $node_revision->title))); $form_state['redirect'] = 'node/' . $node_revision->nid; diff --git a/modules/node/node.test b/modules/node/node.test index 045707f..c56c45f 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -1,5 +1,5 @@ 200, ); - $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type')); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); // Attempt to access the front page again and check if the summary is now only 200 characters in length. $this->drupalGet("node"); $this->assertNoRaw($expected, t('Check that the summary is not longer than 200 characters'), 'Node'); @@ -522,7 +522,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { // Set page content type to display post information. $edit = array(); $edit['node_submitted'] = TRUE; - $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type')); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); // Create a node. $edit = array(); @@ -544,7 +544,7 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { // Set page content type to display post information. $edit = array(); $edit['node_submitted'] = FALSE; - $this->drupalPost('admin/structure/node-type/page', $edit, t('Save content type')); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); // Create a node. $edit = array(); @@ -840,6 +840,73 @@ class NodeTypeTestCase extends DrupalWebTestCase { $this->assertEqual($node_types['article']->name, node_type_get_name('article'), t('Correct node type name has been returned.')); $this->assertEqual($node_types['page']->base, node_type_get_base('page'), t('Correct node type base has been returned.')); } + + /** + * Test creating a content type. + */ + function testNodeTypeCreation() { + $type = $this->drupalCreateContentType(); + + $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $type->type))->fetchField(); + $this->assertTrue($type_exists, 'The new content type has been created in the database.'); + + // Login a test user. + $web_user = $this->drupalCreateUser(array('create ' . $type->name . ' content')); + $this->drupalLogin($web_user); + + $this->drupalGet('node/add/' . str_replace('_', '-', $type->name)); + $this->assertResponse(200, 'The new content type can be accessed at node/add.'); + } + + /** + * Test editing a node type using the UI. + */ + function testNodeTypeEditing() { + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); + $this->drupalLogin($web_user); + + $instance = field_info_instance('body', 'page'); + $this->assertEqual($instance['label'], 'Body', t('Body field was found.')); + + // Verify that title and body fields are displayed. + $this->drupalGet('node/add/page'); + $this->assertRaw('Title', t('Title field was found.')); + $this->assertRaw('Full text', t('Body field was found.')); + + // Rename the title field and remove the body field. + $edit = array( + 'title_label' => 'Foo', + 'body_label' => '', + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + field_info_cache_clear(); + + $this->assertFalse(field_info_instance('body', 'page'), t('Body field was removed.')); + $this->drupalGet('node/add/page'); + $this->assertRaw('Foo', t('New title label was displayed.')); + $this->assertNoRaw('Title', t('Old title label was not displayed.')); + $this->assertNoRaw('Full text', t('Body field was not found.')); + + // Add the body field again and change the name, machine name and description. + $edit = array( + 'name' => 'Bar', + 'type' => 'bar', + 'description' => 'Lorem ipsum.', + 'body_label' => 'Baz', + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + field_info_cache_clear(); + + $instance = field_info_instance('body', 'bar'); + $this->assertEqual($instance['label'], 'Baz', t('Body field was added.')); + $this->drupalGet('node/add'); + $this->assertRaw('Bar', t('New name was displayed.')); + $this->assertRaw('Lorem ipsum', t('New description was displayed.')); + $this->clickLink('Bar'); + $this->assertEqual(url('node/add/bar', array('absolute' => TRUE)), $this->getUrl(), t('New machine name was used in URL.')); + $this->assertRaw('Foo', t('Title field was found.')); + $this->assertRaw('Full text', t('Body field was found.')); + } } /** diff --git a/modules/node/node.tokens.inc b/modules/node/node.tokens.inc index 3068112..a23adeb 100644 --- a/modules/node/node.tokens.inc +++ b/modules/node/node.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/node/node.tpl.php b/modules/node/node.tpl.php index 0aaf9fd..4c4ca02 100644 --- a/modules/node/node.tpl.php +++ b/modules/node/node.tpl.php @@ -1,5 +1,5 @@ -+
>
diff --git a/modules/openid/CVS/Entries b/modules/openid/CVS/Entries index 8f8a561..c547ff3 100644 --- a/modules/openid/CVS/Entries +++ b/modules/openid/CVS/Entries @@ -1,12 +1,12 @@ D/tests//// -/login-bg.png/1.1/Thu Sep 3 08:50:28 2009/-kb/ -/openid.api.php/1.2/Thu Sep 3 08:50:28 2009// /openid.css/1.8/Thu Sep 3 08:50:29 2009// -/openid.inc/1.18/Thu Sep 3 08:50:29 2009// /openid.info/1.7/Thu Sep 3 08:50:29 2009// -/openid.install/1.7/Thu Sep 3 08:50:29 2009// /openid.js/1.12/Thu Sep 3 08:50:29 2009// -/openid.module/1.56/Thu Sep 3 08:50:29 2009// -/openid.pages.inc/1.19/Thu Sep 3 08:50:29 2009// -/openid.test/1.3/Thu Sep 3 08:50:29 2009// /xrds.inc/1.3/Thu Sep 3 08:50:29 2009// +/login-bg.png/1.2/Fri Oct 2 19:50:14 2009/-kb/ +/openid.api.php/1.3/Fri Oct 2 19:50:14 2009// +/openid.inc/1.20/Fri Oct 2 19:50:14 2009// +/openid.install/1.8/Fri Oct 2 19:50:14 2009// +/openid.module/1.61/Fri Oct 2 19:50:14 2009// +/openid.pages.inc/1.22/Fri Oct 2 19:50:14 2009// +/openid.test/1.6/Fri Oct 2 19:50:14 2009// diff --git a/modules/openid/login-bg.png b/modules/openid/login-bg.png index 8eca055..ed46173 100644 Binary files a/modules/openid/login-bg.png and b/modules/openid/login-bg.png differ diff --git a/modules/openid/openid.api.php b/modules/openid/openid.api.php index c3409d5..ef405aa 100644 --- a/modules/openid/openid.api.php +++ b/modules/openid/openid.api.php @@ -1,5 +1,5 @@ $value) { @@ -109,11 +108,15 @@ function openid_redirect_form(&$form_state, $url, $message) { * Determine if the given identifier is an XRI ID. */ function _openid_is_xri($identifier) { - $firstchar = substr($identifier, 0, 1); - if ($firstchar == "@" || $firstchar == "=") - return TRUE; + // Strip the xri:// scheme from the identifier if present. + if (stripos($identifier, 'xri://') !== FALSE) { + $identifier = substr($identifier, 6); + } - if (stristr($identifier, 'xri://') !== FALSE) { + + // Test whether the identifier starts with an XRI global context symbol or (. + $firstchar = substr($identifier, 0, 1); + if (strpos("=@+$!(", $firstchar) !== FALSE) { return TRUE; } diff --git a/modules/openid/openid.install b/modules/openid/openid.install index b9fb0d7..2ca732f 100644 --- a/modules/openid/openid.install +++ b/modules/openid/openid.install @@ -1,27 +1,11 @@ -1, '#description' => l(t('What is OpenID?'), 'http://openid.net/', array('external' => TRUE)), ); - $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => drupal_get_destination()))); + $form['openid.return_to'] = array('#type' => 'hidden', '#value' => url('openid/authenticate', array('absolute' => TRUE, 'query' => user_login_destination()))); } /** - * Implement hook_form_alter(). Adds OpenID login to the login forms. + * Implement hook_form_alter(). + * + * Adds OpenID login to the login forms. */ function openid_form_user_register_alter(&$form, &$form_state) { if (isset($_SESSION['openid']['values'])) { @@ -206,7 +208,7 @@ function openid_begin($claimed_id, $return_to = '', $form_values = array()) { } if (isset($services[0]['types']) && is_array($services[0]['types']) && in_array(OPENID_NS_2_0 . '/server', $services[0]['types'])) { - $identity = 'http://specs.openid.net/auth/2.0/identifier_select'; + $claimed_id = $identity = 'http://specs.openid.net/auth/2.0/identifier_select'; } $authn_request = openid_authentication_request($claimed_id, $identity, $return_to, $assoc_handle, $services[0]['version']); @@ -418,6 +420,8 @@ function openid_authentication($response) { // Load global $user and perform final login tasks. $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); + // Let other modules act on OpenID login + module_invoke_all('openid_response', $response, $account); } } else { @@ -443,7 +447,7 @@ function openid_authentication($response) { $_SESSION['openid']['values'] = $form_state['values']; // We'll want to redirect back to the same place. $destination = drupal_get_destination(); - unset($_REQUEST['destination']); + unset($_GET['destination']); drupal_goto('user/register', $destination); } else { @@ -458,8 +462,10 @@ function openid_authentication($response) { // Load global $user and perform final login tasks. $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); + // Let other modules act on OpenID login + module_invoke_all('openid_response', $response, $account); } - drupal_redirect_form($form, $form_state['redirect']); + drupal_redirect_form($form_state); } else { drupal_set_message(t('Only site administrators can create new user accounts.'), 'error'); diff --git a/modules/openid/openid.pages.inc b/modules/openid/openid.pages.inc index 30009a2..746289f 100644 --- a/modules/openid/openid.pages.inc +++ b/modules/openid/openid.pages.inc @@ -1,5 +1,5 @@ $claimed_id)))->fetchField()) { form_set_error('openid_identifier', t('That OpenID is already in use on this site.')); } - else { - $return_to = url('user/' . arg(1) . '/openid', array('absolute' => TRUE)); - openid_begin($form_state['values']['openid_identifier'], $return_to); - } +} + +function openid_user_add_submit($form, &$form_state) { + $return_to = url('user/' . arg(1) . '/openid', array('absolute' => TRUE)); + openid_begin($form_state['values']['openid_identifier'], $return_to); } /** * Menu callback; Delete the specified OpenID identity from the system. */ -function openid_user_delete_form($form_state, $account, $aid = 0) { +function openid_user_delete_form($form, $form_state, $account, $aid = 0) { $authname = db_query("SELECT authname FROM {authmap} WHERE uid = :uid AND aid = :aid AND module = 'openid'", array( ':uid' => $account->uid, ':aid' => $aid, @@ -101,14 +102,14 @@ function openid_user_delete_form($form_state, $account, $aid = 0) { return confirm_form(array(), t('Are you sure you want to delete the OpenID %authname for %user?', array('%authname' => $authname, '%user' => $account->name)), 'user/' . $account->uid . '/openid'); } -function openid_user_delete_form_submit(&$form_state, $form_values) { +function openid_user_delete_form_submit($form, &$form_state) { $query = db_delete('authmap') - ->condition('uid', $form_state['#args'][0]->uid) - ->condition('aid', $form_state['#args'][1]) + ->condition('uid', $form_state['args'][0]->uid) + ->condition('aid', $form_state['args'][1]) ->condition('module', 'openid') ->execute(); if ($query) { drupal_set_message(t('OpenID deleted.')); } - $form_state['#redirect'] = 'user/' . $form_state['#args'][0]->uid . '/openid'; + $form_state['redirect'] = 'user/' . $form_state['args'][0]->uid . '/openid'; } diff --git a/modules/openid/openid.test b/modules/openid/openid.test index 6b83ce7..898298d 100644 --- a/modules/openid/openid.test +++ b/modules/openid/openid.test @@ -1,5 +1,5 @@ drupalPost(NULL, array(), t('Send')); - $this->assertText(t('My account'), t('User was logged in.')); + $this->assertText($this->web_user->name, t('User was logged in.')); + + // Test logging in via the user/login page. + $this->drupalLogout(); + $this->drupalPost('user/login', $edit, t('Log in')); + + // Check we are on the OpenID redirect form. + $this->assertTitle(t('OpenID redirect'), t('OpenID redirect page was displayed.')); + + // Submit form to the OpenID Provider Endpoint. + $this->drupalPost(NULL, array(), t('Send')); + + $this->assertText($this->web_user->name, t('User was logged in.')); + + // Verify user was redirected away from user/login to an accessible page. + $this->assertResponse(200); } /** @@ -150,7 +165,7 @@ class OpenIDFunctionalTest extends DrupalWebTestCase { // so the form is submitted manually instead. $this->assertRaw('', t('JavaScript form submission found.')); $this->drupalPost(NULL, array(), t('Send')); - $this->assertText(t('My account'), t('User was logged in.')); + $this->assertText('johndoe', t('User was logged in.')); $user = user_load_by_name('johndoe'); $this->assertTrue($user, t('User was found.')); @@ -219,4 +234,34 @@ class OpenIDUnitTest extends DrupalWebTestCase { $association->mac_key = "1234567890abcdefghij\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9"; $this->assertEqual(_openid_signature($association, $response, array('foo', 'bar')), 'QnKZQzSFstT+GNiJDFOptdcZjrc=', t('Expected signature calculated.')); } + + /** + * Test _openid_is_xri(). + */ + function testOpenidXRITest() { + // Test that the XRI test is according to OpenID Authentication 2.0, + // section 7.2. If the user-supplied string starts with xri:// it should be + // stripped and the resulting string should be treated as an XRI when it + // starts with "=", "@", "+", "$", "!" or "(". + $this->assertTrue(_openid_is_xri('xri://=foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.')); + $this->assertTrue(_openid_is_xri('xri://@foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.')); + $this->assertTrue(_openid_is_xri('xri://+foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.')); + $this->assertTrue(_openid_is_xri('xri://$foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme.')); + $this->assertTrue(_openid_is_xri('xri://!foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme..')); + $this->assertTrue(_openid_is_xri('xri://(foo'), t('_openid_is_xri returned expected result for an xri identifier with xri scheme..')); + + $this->assertTrue(_openid_is_xri('=foo'), t('_openid_is_xri returned expected result for an xri identifier.')); + $this->assertTrue(_openid_is_xri('@foo'), t('_openid_is_xri returned expected result for an xri identifier.')); + $this->assertTrue(_openid_is_xri('+foo'), t('_openid_is_xri returned expected result for an xri identifier.')); + $this->assertTrue(_openid_is_xri('$foo'), t('_openid_is_xri returned expected result for an xri identifier.')); + $this->assertTrue(_openid_is_xri('!foo'), t('_openid_is_xri returned expected result for an xri identifier.')); + $this->assertTrue(_openid_is_xri('(foo'), t('_openid_is_xri returned expected result for an xri identifier.')); + + $this->assertFalse(_openid_is_xri('foo'), t('_openid_is_xri returned expected result for an http URL.')); + $this->assertFalse(_openid_is_xri('xri://foo'), t('_openid_is_xri returned expected result for an http URL.')); + $this->assertFalse(_openid_is_xri('http://foo/'), t('_openid_is_xri returned expected result for an http URL.')); + $this->assertFalse(_openid_is_xri('http://example.com/'), t('_openid_is_xri returned expected result for an http URL.')); + $this->assertFalse(_openid_is_xri('user@example.com/'), t('_openid_is_xri returned expected result for an http URL.')); + $this->assertFalse(_openid_is_xri('http://user@example.com/'), t('_openid_is_xri returned expected result for an http URL.')); + } } diff --git a/modules/openid/tests/CVS/Entries b/modules/openid/tests/CVS/Entries index 060ff99..b6649e0 100644 --- a/modules/openid/tests/CVS/Entries +++ b/modules/openid/tests/CVS/Entries @@ -1,4 +1,4 @@ /openid_test.info/1.1/Thu Sep 3 08:50:29 2009// /openid_test.install/1.3/Thu Sep 3 08:50:29 2009// -/openid_test.module/1.3/Thu Sep 3 08:50:29 2009// +/openid_test.module/1.5/Fri Oct 2 19:50:14 2009// D diff --git a/modules/openid/tests/openid_test.module b/modules/openid/tests/openid_test.module index 1cf7ef7..db2d2e1 100644 --- a/modules/openid/tests/openid_test.module +++ b/modules/openid/tests/openid_test.module @@ -1,5 +1,5 @@