Index: includes/database/select.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/select.inc,v retrieving revision 1.12 diff -u -F^f -p -r1.12 select.inc --- includes/database/select.inc 30 Dec 2008 16:43:14 -0000 1.12 +++ includes/database/select.inc 2 Jan 2009 03:35:20 -0000 @@ -291,7 +291,13 @@ class SelectQuery extends Query implemen } public function execute() { + // Modules may alter all queries or only those having a particular tag. drupal_alter('query', $this); + if (isset($this->alterTags)) { + foreach ($this->alterTags as $tag => $value) { + drupal_alter("query_$tag", $this); + } + } $args = $this->getArguments(); Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1008 diff -u -F^f -p -r1.1008 node.module --- modules/node/node.module 31 Dec 2008 12:02:22 -0000 1.1008 +++ modules/node/node.module 2 Jan 2009 03:35:21 -0000 @@ -2324,17 +2324,23 @@ function node_db_rewrite_sql($query, $pr } /** - * Implementation of hook_query_alter(). + * Implementation of hook_query_TAG_alter(). */ -function node_query_alter(QueryAlterableInterface $query) { - if ($query->hasTag('node_access') && !node_access_view_all_nodes()) { +function node_query_node_access_alter(QueryAlterableInterface $query) { + // Skip the extra expensive alterations if site has no node access control modules. + if (!node_access_view_all_nodes()) { + // Prevent duplicate records. $query->distinct(); + // The recognized operations are 'view', 'update', 'delete'. if (!$op = $query->getMetaData('op')) { $op = 'view'; } + // Skip the extra joins and conditions for node admins. if (!user_access('bypass node access')) { + // The node_access table has the access grants for any given node. $access_alias = $query->join('node_access', 'na', 'na.nid = n.nid'); $or = db_or(); + // If any grant exists for the specified user, then user has access to the node for the specified operation. foreach (node_access_grants($op, $query->getMetaData('account')) as $realm => $gids) { foreach ($gids as $gid) { $or->condition(db_and() Index: modules/simpletest/tests/database_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.module,v retrieving revision 1.4 diff -u -F^f -p -r1.4 database_test.module --- modules/simpletest/tests/database_test.module 12 Dec 2008 16:21:58 -0000 1.4 +++ modules/simpletest/tests/database_test.module 2 Jan 2009 03:35:21 -0000 @@ -4,16 +4,12 @@ /** * Implementation of hook_query_alter(). */ -function database_test_query_alter(SelectQuery $query) { +function database_test_query_alter(QueryAlterableInterface $query) { if ($query->hasTag('database_test_alter_add_range')) { $query->range(0, 2); } - if ($query->hasTag('database_test_alter_remove_range')) { - $query->range(); - } - if ($query->hasTag('database_test_alter_add_join')) { $people_alias = $query->join('test', 'people', "test_task.pid=people.id"); $name_field = $query->addField('people', 'name', 'name'); @@ -36,6 +32,14 @@ function database_test_query_alter(Selec } } + +/** + * Implementation of hook_query_TAG_alter(). Called by DatabaseTestCase::testAlterRemoveRange. + */ +function database_test_query_database_test_alter_remove_range_alter(QueryAlterableInterface $query) { + $query->range(); +} + /** * Implementation of hook_menu(). */ Index: modules/simpletest/tests/database_test.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/database_test.test,v retrieving revision 1.33 diff -u -F^f -p -r1.33 database_test.test --- modules/simpletest/tests/database_test.test 30 Dec 2008 16:43:18 -0000 1.33 +++ modules/simpletest/tests/database_test.test 2 Jan 2009 03:35:21 -0000 @@ -1695,7 +1695,7 @@ class DatabaseAlter2TestCase extends Dat } /** - * Test that we can remove a range() value from a query. + * Test that we can remove a range() value from a query. This also tests hook_query_TAG_alter(). */ function testAlterRemoveRange() { $query = db_select('test'); Index: modules/system/system.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v retrieving revision 1.8 diff -u -F^f -p -r1.8 system.api.php --- modules/system/system.api.php 31 Dec 2008 11:08:47 -0000 1.8 +++ modules/system/system.api.php 2 Jan 2009 03:35:21 -0000 @@ -1375,6 +1375,68 @@ function hook_schema_alter(&$schema) { } /** + * Perform alterations to a structured query. + * + * Structured (aka dynamic) queries that have tags associated may be altered by any module + * before the query is executed. + * + * @see hook_query_TAG_alter() + * @see node_query_node_access_alter() + * + * @param $query + * A Query object describing the composite parts of a SQL query. + * @return + * None. + */ +function hook_query_alter(QueryAlterableInterface $query) { + +} + +/** + * Perform alterations to a structured query for a given tag. + * + * @see hook_query_alter() + * @see node_query_node_access_alter() + * + * @param $query + * An Query object describing the composite parts of a SQL query. + * @return + * None. + */ +function hook_query_TAG_alter(QueryAlterableInterface $query) { + // Skip the extra expensive alterations if site has no node access control modules. + if (!node_access_view_all_nodes()) { + // Prevent duplicates records. + $query->distinct(); + // The recognized operations are 'view', 'update', 'delete'. + if (!$op = $query->getMetaData('op')) { + $op = 'view'; + } + // Skip the extra joins and conditions for node admins. + if (!user_access('bypass node access')) { + // The node_access table has the access grants for any given node. + $access_alias = $query->join('node_access', 'na', 'na.nid = n.nid'); + $or = db_or(); + // If any grant exists for the specified user, then user has access to the node for the specified operation. + foreach (node_access_grants($op, $query->getMetaData('account')) as $realm => $gids) { + foreach ($gids as $gid) { + $or->condition(db_and() + ->condition("{$access_alias}.gid", $gid) + ->condition("{$access_alias}.realm", $realm) + ); + } + } + + if (count($or->conditions())) { + $query->condition($or); + } + + $query->condition("{$access_alias}.grant_$op", 1, '>='); + } + } +} + +/** * Install the current version of the database schema, and any other setup tasks. * * The hook will be called the first time a module is installed, and the