1 /** 2 * Passes alterable variables to specific hook_TYPE_alter() implementations. 3 * 4 * This dispatch function hands off the passed-in variables to type-specific 5 * hook_TYPE_alter() implementations in modules. It ensures a consistent 6 * interface for all altering operations. 7 * 8 * A maximum of 2 alterable arguments is supported (a third is supported for 9 * legacy reasons, but should not be used in new code). In case more arguments 10 * need to be passed and alterable, modules provide additional variables 11 * assigned by reference in the last $context argument: 12 * @code 13 * $context = array( 14 * 'alterable' => &$alterable, 15 * 'unalterable' => $unalterable, 16 * 'foo' => 'bar', 17 * ); 18 * drupal_alter('mymodule_data', $alterable1, $alterable2, $context); 19 * @endcode 20 * 21 * Note that objects are always passed by reference in PHP5. If it is absolutely 22 * required that no implementation alters a passed object in $context, then an 23 * object needs to be cloned: 24 * @code 25 * $context = array( 26 * 'unalterable_object' => clone $object, 27 * ); 28 * drupal_alter('mymodule_data', $data, $context); 29 * @endcode 30 * 31 * @param $type 32 * A string describing the type of the alterable $data. 'form', 'links', 33 * 'node_content', and so on are several examples. Alternatively can be an 34 * array, in which case hook_TYPE_alter() is invoked for each value in the 35 * array, ordered first by module, and then for each module, in the order of 36 * values in $type. For example, when Form API is using drupal_alter() to 37 * execute both hook_form_alter() and hook_form_FORM_ID_alter() 38 * implementations, it passes array('form', 'form_' . $form_id) for $type. 39 * @param $data 40 * The variable that will be passed to hook_TYPE_alter() implementations to be 41 * altered. The type of this variable depends on the value of the $type 42 * argument. For example, when altering a 'form', $data will be a structured 43 * array. When altering a 'profile', $data will be an object. 44 * @param $context1 45 * (optional) An additional variable that is passed by reference. 46 * @param $context2 47 * (optional) An additional variable that is passed by reference. If more 48 * context needs to be provided to implementations, then this should be an 49 * associative array as described above. 50 * @param $context3 51 * (optional) An additional variable that is passed by reference. This 52 * parameter is deprecated and will not exist in Drupal 8; consequently, it 53 * should not be used for new Drupal 7 code either. It is here only for 54 * backwards compatibility with older code that passed additional arguments 55 * to drupal_alter(). 56 */ 57 function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL, &$context3 = NULL) { 58 // Use the advanced drupal_static() pattern, since this is called very often. 59 static $drupal_static_fast; 60 if (!isset($drupal_static_fast)) { 61 $drupal_static_fast['functions'] = &drupal_static(__FUNCTION__); 62 } 63 $functions = &$drupal_static_fast['functions']; 64 65 // Most of the time, $type is passed as a string, so for performance, 66 // normalize it to that. When passed as an array, usually the first item in 67 // the array is a generic type, and additional items in the array are more 68 // specific variants of it, as in the case of array('form', 'form_FORM_ID'). 69 if (is_array($type)) { 70 $cid = implode(',', $type); 71 $extra_types = $type; 72 $type = array_shift($extra_types); 73 // Allow if statements in this function to use the faster isset() rather 74 // than !empty() both when $type is passed as a string, or as an array with 75 // one item. 76 if (empty($extra_types)) { 77 unset($extra_types); 78 } 79 } 80 else { 81 $cid = $type; 82 } 83 84 // Some alter hooks are invoked many times per page request, so statically 85 // cache the list of functions to call, and on subsequent calls, iterate 86 // through them quickly. 87 if (!isset($functions[$cid])) { 88 $functions[$cid] = array(); 89 $hook = $type . '_alter'; 90 $modules = module_implements($hook); 91 if (!isset($extra_types)) { 92 // For the more common case of a single hook, we do not need to call 93 // function_exists(), since module_implements() returns only modules with 94 // implementations. 95 foreach ($modules as $module) { 96 $functions[$cid][] = $module . '_' . $hook; 97 } 98 } 99 else { 100 // For multiple hooks, we need $modules to contain every module that 101 // implements at least one of them. 102 $extra_modules = array(); 103 foreach ($extra_types as $extra_type) { 104 $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter')); 105 } 106 // If any modules implement one of the extra hooks that do not implement 107 // the primary hook, we need to add them to the $modules array in their 108 // appropriate order. module_implements() can only return ordered 109 // implementations of a single hook. To get the ordered implementations 110 // of multiple hooks, we mimic the module_implements() logic of first 111 // ordering by module_list(), and then calling 112 // drupal_alter('module_implements'). 113 if (array_diff($extra_modules, $modules)) { 114 // Merge the arrays and order by module_list(). 115 $modules = array_intersect(module_list(), array_merge($modules, $extra_modules)); 116 // Since module_implements() already took care of loading the necessary 117 // include files, we can safely pass FALSE for the array values. 118 $implementations = array_fill_keys($modules, FALSE); 119 // Let modules adjust the order solely based on the primary hook. This 120 // ensures the same module order regardless of whether this if block 121 // runs. Calling drupal_alter() recursively in this way does not result 122 // in an infinite loop, because this call is for a single $type, so we 123 // won't end up in this code block again. 124 drupal_alter('module_implements', $implementations, $hook); 125 $modules = array_keys($implementations); 126 } 127 foreach ($modules as $module) { 128 // Since $modules is a merged array, for any given module, we do not 129 // know whether it has any particular implementation, so we need a 130 // function_exists(). 131 $function = $module . '_' . $hook; 132 if (function_exists($function)) { 133 $functions[$cid][] = $function; 134 } 135 foreach ($extra_types as $extra_type) { 136 $function = $module . '_' . $extra_type . '_alter'; 137 if (function_exists($function)) { 138 $functions[$cid][] = $function; 139 } 140 } 141 } 142 } 143 // Allow the theme to alter variables after the theme system has been 144 // initialized. 145 global $theme, $base_theme_info; 146 if (isset($theme)) { 147 $theme_keys = array(); 148 foreach ($base_theme_info as $base) { 149 $theme_keys[] = $base->name; 150 } 151 $theme_keys[] = $theme; 152 foreach ($theme_keys as $theme_key) { 153 $function = $theme_key . '_' . $hook; 154 if (function_exists($function)) { 155 $functions[$cid][] = $function; 156 } 157 if (isset($extra_types)) { 158 foreach ($extra_types as $extra_type) { 159 $function = $theme_key . '_' . $extra_type . '_alter'; 160 if (function_exists($function)) { 161 $functions[$cid][] = $function; 162 } 163 } 164 } 165 } 166 } 167 } 168 169 foreach ($functions[$cid] as $function) { 170 $function($data, $context1, $context2, $context3); 171 } 172 }
drupal_alter()函数提供一个对钩子系统的调用,实现对系统变量的自定义修改。例如:当系统处于维护状态时,网站应该只允许管理员访问,一般访问者只能看到“系统处于维护状态”这样的提示信息。一般访问者访问网站时,若是在变更维护状态之前已经登录网站,这时user模块提供一个钩子,实现访问者的自动注销。
// menu.inc的501-508行 $site_status = variable_get('maintenance_mode') ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE; drupal_alter('menu_site_status', $site_status , $_GET['q']);
drupal_alter()会调用形如hook_TYPE_alter这样的钩子,此例子中是user模块的user_menu_site_status_alter()函数:
// user.module的1834-1874行 function user_menu_site_status_alter(&$menu_site_status, $path) { if ($menu_site_status == MENU_SITE_OFFLINE) { // If the site is offline, log out unprivileged users. if (user_is_logged_in() && !user_access('access site in maintenance mode')) { module_load_include('pages.inc', 'user', 'user'); user_logout(); } ... ... } ... }
第57行:注意参数$data,是引用定义的,也可以理解为传址。再看一下user_menu_site_status_alter()钩子的参数,这里的$menu_site_status也是引用定义,和$data刚好对应。也就是说,在user_menu_site_status_alter()中修改$menu_site_status的值,也就是在修改$data的值。
以此类推,drupal_alter()定义了三个context参数,也都是引用定义的。所以,在钩子函数中如果有需要,也可以修改这三个context参数的值:
function hook_menu_site_status_alter(&$menu_site_status, &$context1, &$context2, &$context3) { $context1 = 'new value'; ... ... }
第143-144行:drupal_alter()被调用时,theme系统有可能准备就绪,也有可能尚未就绪。通过全局变量$theme可以判断theme系统是否就绪:
global $theme; if (isset($theme)) { // theme系统准备就绪 } else { // theme系统尚未就绪 }
除了module可以提供钩子(格式如:module_TYPE_alter)修改变量值以外,theme也可以提供钩子(格式:theme_TYPE_alter)修改变量值。所以,drupal必须要求模块和主题命名的唯一。