Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1145 diff -u -p -r1.1145 common.inc --- includes/common.inc 7 Apr 2010 17:30:43 -0000 1.1145 +++ includes/common.inc 9 Apr 2010 22:54:05 -0000 @@ -4634,8 +4634,14 @@ function drupal_system_listing($mask, $d * (optional) An additional variable that is passed by reference. If more * context needs to be provided to implementations, then this should be an * keyed array as described above. + * @param $ids + * (optional) An array of ids for executing specific implementations in + * addition to generic ones. For example, when the Drupal Form API is using + * drupal_alter() to execute both hook_form_alter() and + * hook_form_FORM_ID_alter() implementations, it uses 'form' as $type and + * passes array($form_id) as $ids. */ -function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { +function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, $ids = array()) { // Use the advanced drupal_static() pattern, since this is called very often. static $drupal_static_fast; if (!isset($drupal_static_fast)) { @@ -4646,11 +4652,40 @@ function drupal_alter($type, &$data, &$c // Some alter hooks are invoked many times per page request, so statically // cache the list of functions to call, and on subsequent calls, iterate // through them quickly. - if (!isset($functions[$type])) { - $functions[$type] = array(); + $cid = empty($ids) ? $type : ($type . ':' . implode(',', $ids)); + if (!isset($functions[$cid])) { + $functions[$cid] = array(); $hook = $type . '_alter'; - foreach (module_implements($hook) as $module) { - $functions[$type][] = $module . '_' . $hook; + $modules = module_implements($hook); + if (empty($ids)) { + foreach ($modules as $module) { + $functions[$cid][] = $module . '_' . $hook; + } + } + else { + $modules_with_id_implementations = array(); + foreach ($ids as $id) { + $modules_with_id_implementations = array_merge($modules_with_id_implementations, module_implements($type . '_' . $id . '_alter')); + } + // If any modules implement an id-specific hook that do not implement the + // generic hook, we need to add them to the $modules array in their + // appropriate order. + if (array_diff($modules_with_id_implementations, $modules)) { + // Order the modules by the order returned by module_list(). + $modules = array_intersect(module_list(), array_merge($modules, $modules_with_id_implementations)); + } + foreach ($modules as $module) { + $function = $module . '_' . $hook; + if (function_exists($function)) { + $functions[$cid][] = $function; + } + foreach ($ids as $id) { + $function = $module . '_' . $type . '_' . $id . '_alter'; + if (function_exists($function)) { + $functions[$cid][] = $function; + } + } + } } // Allow the theme to alter variables after the theme system has been // initialized. @@ -4664,12 +4699,18 @@ function drupal_alter($type, &$data, &$c foreach ($theme_keys as $theme_key) { $function = $theme_key . '_' . $hook; if (function_exists($function)) { - $functions[$type][] = $function; + $functions[$cid][] = $function; + } + foreach ($ids as $id) { + $function = $theme_key . '_' . $type . '_' . $id . '_alter'; + if (function_exists($function)) { + $functions[$cid][] = $function; + } } } } } - foreach ($functions[$type] as $function) { + foreach ($functions[$cid] as $function) { $function($data, $context1, $context2); } } Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.449 diff -u -p -r1.449 form.inc --- includes/form.inc 7 Apr 2010 04:39:59 -0000 1.449 +++ includes/form.inc 9 Apr 2010 22:54:05 -0000 @@ -761,11 +761,8 @@ function drupal_prepare_form($form_id, & } } - // Invoke hook_form_FORM_ID_alter() implementations. - drupal_alter('form_' . $form_id, $form, $form_state); - - // Invoke hook_form_alter() implementations. - drupal_alter('form', $form, $form_state, $form_id); + // Invoke hook_form_alter() and hook_form_FORM_ID_alter() implementations. + drupal_alter('form', $form, $form_state, $form_id, array($form_id)); } Index: includes/database/select.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/database/select.inc,v retrieving revision 1.34 diff -u -p -r1.34 select.inc --- includes/database/select.inc 17 Feb 2010 05:24:53 -0000 1.34 +++ includes/database/select.inc 9 Apr 2010 22:54:06 -0000 @@ -1069,10 +1069,8 @@ class SelectQuery extends Query implemen // Modules may alter all queries or only those having a particular tag. if (isset($this->alterTags)) { - drupal_alter('query', $query); - foreach ($this->alterTags as $tag => $value) { - drupal_alter("query_$tag", $query); - } + $null = NULL; + drupal_alter('query', $query, $null, $null, array_keys($this->alterTags)); } return $this->prepared = TRUE; } Index: modules/block/block.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.api.php,v retrieving revision 1.10 diff -u -p -r1.10 block.api.php --- modules/block/block.api.php 28 Mar 2010 11:16:29 -0000 1.10 +++ modules/block/block.api.php 9 Apr 2010 22:54:07 -0000 @@ -194,10 +194,6 @@ function hook_block_view_alter(&$data, $ * Modules can implement hook_block_view_MODULE_DELTA_alter() to modify a * specific block, rather than implementing hook_block_view_alter(). * - * Note that this hook fires before hook_block_view_alter(). Therefore, all - * implementations of hook_block_view_MODULE_DELTA_alter() will run before all - * implementations of hook_block_view_alter(), regardless of the module order. - * * @param $data * An array of data, as returned from the hook_block_view() implementation of * the module that defined the block: Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.415 diff -u -p -r1.415 block.module --- modules/block/block.module 28 Mar 2010 11:16:29 -0000 1.415 +++ modules/block/block.module 9 Apr 2010 22:54:07 -0000 @@ -740,8 +740,8 @@ function _block_render_blocks($region_bl // Allow modules to modify the block before it is viewed, via either // hook_block_view_MODULE_DELTA_alter() or hook_block_view_alter(). - drupal_alter("block_view_{$block->module}_{$block->delta}", $array, $block); - drupal_alter('block_view', $array, $block); + $null = NULL; + drupal_alter('block_view', $array, $block, $null, array($block->module . '_' . $block->delta)); if (isset($cid)) { cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY); Index: modules/simpletest/tests/form.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v retrieving revision 1.46 diff -u -p -r1.46 form.test --- modules/simpletest/tests/form.test 7 Apr 2010 17:30:43 -0000 1.46 +++ modules/simpletest/tests/form.test 9 Apr 2010 22:54:11 -0000 @@ -179,6 +179,40 @@ class FormsTestCase extends DrupalWebTes } /** + * Test form alter hooks. + */ +class FormAlterTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Form alter hooks', + 'description' => 'Tests hook_form_alter() and hook_form_FORM_ID_alter().', + 'group' => 'Form API', + ); + } + + function setUp() { + parent::setUp('form_test'); + } + + /** + * Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter(). + */ + function testExecutionOrder() { + $this->drupalGet('form-test/alter'); + // Ensure that the order is first by module, then for a given module, the + // id-specific one after the generic one. + $expected = array( + 'block_form_form_test_alter_form_alter() executed.', + 'form_test_form_alter() executed.', + 'form_test_form_form_test_alter_form_alter() executed.', + 'system_form_form_test_alter_form_alter() executed.', + ); + $content = preg_replace('/\s+/', ' ', filter_xss($this->content, array())); + $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, t('Form alter hooks executed in the expected order.')); + } +} + +/** * Test form validation handlers. */ class FormValidationTestCase extends DrupalWebTestCase { Index: modules/simpletest/tests/form_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v retrieving revision 1.35 diff -u -p -r1.35 form_test.module --- modules/simpletest/tests/form_test.module 7 Apr 2010 17:30:43 -0000 1.35 +++ modules/simpletest/tests/form_test.module 9 Apr 2010 22:54:11 -0000 @@ -10,6 +10,13 @@ * Implements hook_menu(). */ function form_test_menu() { + $items['form-test/alter'] = array( + 'title' => 'Form altering test', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_alter_form'), + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); $items['form-test/validate'] = array( 'title' => 'Form validation handlers test', 'page callback' => 'drupal_get_form', @@ -142,6 +149,46 @@ function form_test_menu() { } /** + * Form builder for testing hook_form_alter() and hook_form_FORM_ID_alter(). + */ +function form_test_alter_form($form, &$form_state) { + // Elements can be added as needed for future testing needs, but for now, + // we're only testing alter hooks that do not require any elements added by + // this function. + return $form; +} + +/** + * Implements hook_form_FORM_ID_alter() on behalf of block.module. + */ +function block_form_form_test_alter_form_alter(&$form, &$form_state) { + drupal_set_message(t('block_form_form_test_alter_form_alter() executed.')); +} + +/** + * Implements hook_form_alter(). + */ +function form_test_form_alter(&$form, &$form_state, $form_id) { + if ($form_id == 'form_test_alter_form') { + drupal_set_message(t('form_test_form_alter() executed.')); + } +} + +/** + * Implements hook_form_FORM_ID_alter(). + */ +function form_test_form_form_test_alter_form_alter(&$form, &$form_state) { + drupal_set_message(t('form_test_form_form_test_alter_form_alter() executed.')); +} + +/** + * Implements hook_form_FORM_ID_alter() on behalf of system.module. + */ +function system_form_form_test_alter_form_alter(&$form, &$form_state) { + drupal_set_message(t('system_form_form_test_alter_form_alter() executed.')); +} + +/** * Form builder for testing drupal_validate_form(). * * Serves for testing form processing and alterations by form validation Index: modules/system/system.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v retrieving revision 1.150 diff -u -p -r1.150 system.api.php --- modules/system/system.api.php 6 Apr 2010 19:49:03 -0000 1.150 +++ modules/system/system.api.php 9 Apr 2010 22:54:11 -0000 @@ -774,10 +774,6 @@ function hook_form_alter(&$form, &$form_ * rather than implementing hook_form_alter() and checking the form ID, or * using long switch statements to alter multiple forms. * - * Note that this hook fires before hook_form_alter(). Therefore all - * implementations of hook_form_FORM_ID_alter() will run before all implementations - * of hook_form_alter(), regardless of the module order. - * * @param $form * Nested array of form elements that comprise the form. * @param $form_state