Commit 19a45ce5 authored by Dries's avatar Dries

- Patch #765860 by effulgentsia, dww, dereine, mikey_p, sun: make...

- Patch #765860 by effulgentsia, dww, dereine, mikey_p, sun: make drupal_alter() support multiple alter hooks executed by module weight.
parent 748c3103
......@@ -1069,10 +1069,11 @@ public function preExecute(SelectQueryInterface $query = NULL) {
// Modules may alter all queries or only those having a particular tag.
if (isset($this->alterTags)) {
drupal_alter('query', $query);
$hooks = array('query');
foreach ($this->alterTags as $tag => $value) {
drupal_alter("query_$tag", $query);
$hooks[] = 'query_' . $tag;
}
drupal_alter($hooks, $query);
}
return $this->prepared = TRUE;
}
......
......@@ -774,11 +774,8 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
}
}
// 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(array('form', 'form_' . $form_id), $form, $form_state, $form_id);
}
......
......@@ -786,7 +786,12 @@ function drupal_required_modules() {
*
* @param $type
* A string describing the data type of the alterable $data. 'form', 'links',
* 'node_content', and so on are several examples.
* 'node_content', and so on are several examples. Alternatively can be an
* array, in which case hook_TYPE_alter() is invoked for each value in the
* array, ordered first by module, and then for each module, in the order of
* values in $type. For example, when Form API is using drupal_alter() to
* execute both hook_form_alter() and hook_form_FORM_ID_alter()
* implementations, it passes array('form', 'form_' . $form_id) for $type.
* @param &$data
* The primary data to be altered.
* @param &$context1
......@@ -804,14 +809,69 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
}
$functions = &$drupal_static_fast['functions'];
// Most of the time, $type is passed as a string, so for performance,
// normalize it to that. When passed as an array, usually the first item in
// the array is a generic type, and additional items in the array are more
// specific variants of it, as in the case of array('form', 'form_FORM_ID').
if (is_array($type)) {
$cid = implode(',', $type);
$extra_types = $type;
$type = array_shift($extra_types);
// Allow if statements in this function to use the faster isset() rather
// than !empty() both when $type is passed as a string, or as an array with
// one item.
if (empty($extra_types)) {
unset($extra_types);
}
}
else {
$cid = $type;
}
// 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();
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 (!isset($extra_types)) {
// For the more common case of a single hook, we do not need to call
// function_exists(), since module_implements() returns only modules with
// implementations.
foreach ($modules as $module) {
$functions[$cid][] = $module . '_' . $hook;
}
}
else {
// For multiple hooks, we need $modules to contain every module that
// implements at least one of them.
$extra_modules = array();
foreach ($extra_types as $extra_type) {
$extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter'));
}
// If any modules implement one of the extra hooks that do not implement
// the primary hook, we need to add them to the $modules array in their
// appropriate order.
if (array_diff($extra_modules, $modules)) {
// Order the modules by the order returned by module_list().
$modules = array_intersect(module_list(), array_merge($modules, $extra_modules));
}
foreach ($modules as $module) {
// Since $modules is a merged array, for any given module, we do not
// know whether it has any particular implementation, so we need a
// function_exists().
$function = $module . '_' . $hook;
if (function_exists($function)) {
$functions[$cid][] = $function;
}
foreach ($extra_types as $extra_type) {
$function = $module . '_' . $extra_type . '_alter';
if (function_exists($function)) {
$functions[$cid][] = $function;
}
}
}
}
// Allow the theme to alter variables after the theme system has been
// initialized.
......@@ -825,12 +885,22 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
foreach ($theme_keys as $theme_key) {
$function = $theme_key . '_' . $hook;
if (function_exists($function)) {
$functions[$type][] = $function;
$functions[$cid][] = $function;
}
if (isset($extra_types)) {
foreach ($extra_types as $extra_type) {
$function = $theme_key . '_' . $extra_type . '_alter';
if (function_exists($function)) {
$functions[$cid][] = $function;
}
}
}
}
}
}
foreach ($functions[$type] as $function) {
foreach ($functions[$cid] as $function) {
$function($data, $context1, $context2);
}
}
......@@ -192,7 +192,7 @@ function hook_block_view($delta = '') {
* - delta: The identifier for the block within that module, as defined within
* hook_block_info().
*
* @see hook_block_view_alter()
* @see hook_block_view_MODULE_DELTA_alter()
* @see hook_block_view()
*/
function hook_block_view_alter(&$data, $block) {
......@@ -213,10 +213,6 @@ function hook_block_view_alter(&$data, $block) {
* 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:
......
......@@ -762,9 +762,8 @@ function _block_render_blocks($region_blocks) {
$array = module_invoke($block->module, 'block_view', $block->delta);
// 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);
// hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter().
drupal_alter(array('block_view', "block_view_{$block->module}_{$block->delta}"), $array, $block);
if (isset($cid)) {
cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
......
......@@ -209,6 +209,40 @@ class FormsTestCase extends DrupalWebTestCase {
}
}
/**
* 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.
*/
......
......@@ -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',
......@@ -141,6 +148,46 @@ function form_test_menu() {
return $items;
}
/**
* 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('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('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('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('system_form_form_test_alter_form_alter() executed.');
}
/**
* Form builder for testing drupal_validate_form().
*
......
......@@ -1241,7 +1241,11 @@ function hook_page_alter(&$page) {
* altering a node form, the node object retrieved at from $form['#node'].
*
* Note that instead of hook_form_alter(), which is called for all forms, you
* can also use hook_form_FORM_ID_alter() to alter a specific form.
* can also use hook_form_FORM_ID_alter() to alter a specific form. For each
* module (in system weight order) the general form alter hook implementation
* is invoked first, then the form ID specific alter implementation is called.
* After all module hook implementations are invoked, the hook_form_alter()
* implementations from themes are invoked in the same manner.
*
* @param $form
* Nested array of form elements that comprise the form.
......@@ -1250,6 +1254,8 @@ function hook_page_alter(&$page) {
* @param $form_id
* String representing the name of the form itself. Typically this is the
* name of the function that generated the form.
*
* @see hook_form_FORM_ID_alter()
*/
function hook_form_alter(&$form, &$form_state, $form_id) {
if (isset($form['type']) && $form['type']['#value'] . '_node_settings' == $form_id) {
......@@ -1269,15 +1275,12 @@ function hook_form_alter(&$form, &$form_state, $form_id) {
* 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
* A keyed array containing the current state of the form.
*
* @see hook_form_alter()
* @see drupal_prepare_form()
*/
function hook_form_FORM_ID_alter(&$form, &$form_state) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment