Index: update.php =================================================================== RCS file: /cvs/drupal/drupal/update.php,v retrieving revision 1.211 diff -u -p -r1.211 update.php --- update.php 25 Dec 2006 21:22:03 -0000 1.211 +++ update.php 5 Feb 2007 17:57:01 -0000 @@ -288,7 +288,7 @@ function update_fix_watchdog() { * TRUE if the update was finished. Otherwise, FALSE. */ function update_data($module, $number) { - $ret = module_invoke($module, 'update_'. $number); + $ret = module_invoke_hook($module, 'update_'. $number); // Assume the update finished unless the update results indicate otherwise. $finished = 1; if (isset($ret['#finished'])) { Index: includes/module.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/module.inc,v retrieving revision 1.98 diff -u -p -r1.98 module.inc --- includes/module.inc 4 Feb 2007 21:20:50 -0000 1.98 +++ includes/module.inc 5 Feb 2007 17:57:01 -0000 @@ -254,7 +254,7 @@ function module_enable($module_list) { // Refresh the module list to include the new enabled module. module_list(TRUE, FALSE); // Force to regenerate the stored list of hook implementations. - module_implements('', FALSE, TRUE); + _module_build_registry_cache(); } foreach ($invoke_modules as $module) { @@ -283,7 +283,7 @@ function module_disable($module_list) { // Refresh the module list to exclude the disabled modules. module_list(TRUE, FALSE); // Force to regenerate the stored list of hook implementations. - module_implements('', FALSE, TRUE); + module_build_registry_cache(); } } @@ -320,7 +320,31 @@ function module_disable($module_list) { * implemented in that module. */ function module_hook($module, $hook) { - return function_exists($module .'_'. $hook); + $registry = _module_get_registry_cache(); + return isset($registry[$hook][$module]) && is_array($registry[$hook][$module]); +} + +/** + * Get the actual function to be used in for a module's hook. If this + * hook requires an external file, include that file. + * + * @param $module + * The name of the module (without the .module extension). + * @param $hook + * The name of the hook (e.g. "help" or "menu"). + * @return + * The name of the function to call to implement that hook. + */ +function module_get_function($module, $hook) { + $registry = _module_get_registry_cache(); + + if (isset($registry[$hook][$module]) && is_array($registry[$hook][$module])) { + $info = $registry[$hook][$module]; + if (!empty($info['file'])) { + include_once(drupal_get_path('module', $module) .'/'. $info[$file]); + } + return $info['function']; + } } /** @@ -331,42 +355,47 @@ function module_hook($module, $hook) { * @param $sort * By default, modules are ordered by weight and filename, settings this option * to TRUE, module list will be ordered by module name. - * @param $refresh - * For internal use only: Whether to force the stored list of hook - * implementations to be regenerated (such as after enabling a new module, - * before processing hook_enable). * @return * An array with the names of the modules which are implementing this hook. */ -function module_implements($hook, $sort = FALSE, $refresh = FALSE) { - static $implementations; +function module_implements($hook, $sort = FALSE) { + $registry = _module_get_registry_cache(); - if ($refresh) { - unset($implementations); - return; + if (isset($registry[$hook])) { + return array_keys($registry[$hook]); } + return array(); +} - if (!isset($implementations[$hook])) { - $implementations[$hook] = array(); - $list = module_list(FALSE, TRUE, $sort); - foreach ($list as $module) { - if (module_hook($module, $hook)) { - $implementations[$hook][] = $module; - } - } - } +/** + * Invoke a hook in a particular module. This function is unable to pass + * references, however, so it is sometimes necessary to implement it + * yourself. + * + * @param $module + * The name of the module (without the .module extension). + * @param $hook + * The name of the hook to invoke. + * @param ... + * Arguments to pass to the hook implementation. + * @return + * The return value of the hook implementation. + */ +function module_invoke() { + $args = func_get_args(); + $module = array_shift($args); + $hook = array_shift($args); - // The explicit cast forces a copy to be made. This is needed because - // $implementations[$hook] is only a reference to an element of - // $implementations and if there are nested foreaches (due to nested node - // API calls, for example), they would both manipulate the same array's - // references, which causes some modules' hooks not to be called. - // See also http://www.zend.com/zend/art/ref-count.php. - return (array)$implementations[$hook]; + if ($function = module_get_function($module, $hook)) { + return call_user_func_array($function, $args); + } } /** - * Invoke a hook in a particular module. + * Invokes a predefined hook. This is similar to module_invoke, but it does + * not check the hook registry; instead it directly checks the hook, as there + * are a few hooks that are not registered, such as 'register', 'hooks' and + * 'update'. * * @param $module * The name of the module (without the .module extension). @@ -377,12 +406,12 @@ function module_implements($hook, $sort * @return * The return value of the hook implementation. */ -function module_invoke() { +function module_invoke_hook() { $args = func_get_args(); $module = array_shift($args); $hook = array_shift($args); $function = $module .'_'. $hook; - if (module_hook($module, $hook)) { + if (function_exists($function)) { return call_user_func_array($function, $args); } } @@ -402,8 +431,9 @@ function module_invoke_all() { $hook = array_shift($args); $return = array(); foreach (module_implements($hook) as $module) { - $function = $module .'_'. $hook; + $function = module_get_function($module, $hook); $result = call_user_func_array($function, $args); + if (isset($result) && is_array($result)) { $return = array_merge($return, $result); } @@ -416,6 +446,190 @@ function module_invoke_all() { } /** + * Clear the module registry cache. + */ +function _module_clear_registry_cache() { + // pass through for static. + _module_add_registry_cache('@reset'); +} + +/** + * Get the current registry cache + */ +function _module_get_registry_cache($load = TRUE) { + // pass through for static. + $arg = $load ? '@load' : NULL; + return _module_add_registry_cache($arg); +} + +/** + * Add a module's combined information into the registry cache. + */ +function _module_add_registry_cache($hook = NULL, $module = NULL, $info = NULL) { + static $registry = NULL; + if ($hook == '@load') { + if ($registry === NULL) { + _module_load_registry_cache(); + } + return $registry; + } + + if ($hook === NULL) { + return $registry; + } + + if ($hook == '@reset') { + $registry = array(); + return; + } + + if (is_array($hook)) { + $registry = $hook; + return; + } + + $registry[$hook][$module] = $info; + + return $registry; +} + +/** + * Get the registry cache from the database; if it doesn't exist, build + * it. + */ +function _module_load_registry_cache() { + // TODO: use a separate cache for this. + $registry = cache_get('module_registry', 'cache'); + if (isset($registry->data)) { + _module_add_registry_cache(unserialize($registry->data)); + } + else { + module_build_registry_cache(); + } +} + +/** + * Write the registry cache into the database. + */ +function _module_save_registry_cache() { + $registry = _module_get_registry_cache(FALSE); + // TODO: use a separate cache for this. + cache_set('module_registry', 'cache', serialize($registry)); +} + + +/** + * Rebuild the hook registry cache. + */ +function module_build_registry_cache() { + _module_clear_registry_cache(); + $files = drupal_system_listing('\.module$', 'modules', 'name', 0); + // Extract current files from database. + system_get_files_database($files, 'module'); + + foreach ($files as $filename => $file) { + if (isset($file->status)) { + _module_build_registry($file); + } + } + + _module_save_registry_cache(); +} + +/** + * Collect information about what hooks and hooks a module provides. + * + * @param $module + * The module information as read by drupal_listing. + */ +function _module_build_registry($module) { + // This should become if module_implements_hook and module_hook + _module_register_current_module($module); + // This skips the typical module_implements routine because at this point + // that data no longer exists. + $function = $module->name .'_register_hooks'; + if (function_exists($function)) { + $function(); + } + else { + // If the module doesn't implement the register hook, assume the + // default hooks. + module_register_default_hooks(); + } +} + +/** + * Set or get which module is currently being registered. + */ +function _module_register_current_module($module = NULL) { + static $cache = NULL; + if ($module !== NULL) { + $cache = $module; + } + return $cache; +} + +/** + * Register a hook; should only be called from within a module's + * registry hook. + * + * 'hook_name' => array( + * 'module_name' => array( + * 'function' => function name + * 'file' => (optional) filename to be loaded + * ) + * ) + * +*/ +function module_register_hook($hook, $function = NULL, $file = NULL) { + $module = _module_register_current_module(); + if ($function === NULL) { + $function = $module->name . '_' . $hook; + } + + // files registered by modules must be in the module's filepath. + if ($file) { + $file = dirname($module->filename) . '/' . $file; + } + _module_add_registry_cache($hook, $module->name, array('function' => $function, 'file' => $file)); +} + +/** + * Scan through all known hooks and register them for the current + * module. + */ +function module_register_default_hooks() { + $module = _module_register_current_module(); + + foreach (module_get_hooks() as $hook) { + $function = $module->name .'_'. $hook; + if (function_exists($function)) { + module_register_hook($hook, $function); + } + } +} + +/** + * Get a list of all hooks defined in the system. + */ +function module_get_hooks() { + static $cache = array(); + + // This is a special hook, which operates as the hold hook mechanism did, + // which is that it's a well-known named function to tell Drupal what hooks + // a given module utilizes. + if (!$cache) { + foreach (module_list(FALSE, FALSE) as $module) { + $hooks = module_invoke_hook($module, 'hooks'); + if (is_array($hooks)) { + $cache = array_merge($cache, $hooks); + } + } + } + return $cache; +} + +/** * @} End of "defgroup hooks". */ Index: modules/block/block.module =================================================================== RCS file: /cvs/drupal/drupal/modules/block/block.module,v retrieving revision 1.251 diff -u -p -r1.251 block.module --- modules/block/block.module 31 Jan 2007 15:49:22 -0000 1.251 +++ modules/block/block.module 5 Feb 2007 17:57:03 -0000 @@ -13,6 +13,12 @@ define('BLOCK_REGION_NONE', -1); /** + * Implementation of hook_hooks + */ +function block_hooks() { + return array('block'); +} +/** * Implementation of hook_help(). */ function block_help($section) { Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.523 diff -u -p -r1.523 comment.module --- modules/comment/comment.module 31 Jan 2007 15:49:23 -0000 1.523 +++ modules/comment/comment.module 5 Feb 2007 17:57:04 -0000 @@ -121,6 +121,12 @@ define('COMMENT_PREVIEW_OPTIONAL', 0); define('COMMENT_PREVIEW_REQUIRED', 1); /** + * Implementation of hook_hooks + */ +function comment_hooks() { + return array('comment'); +} +/** * Implementation of hook_help(). */ function comment_help($section) { @@ -297,9 +303,7 @@ function theme_comment_block() { */ function comment_link($type, $node = NULL, $teaser = FALSE) { $links = array(); - if ($type == 'node' && $node->comment) { - if ($teaser) { // Main page: display the number of comments that have been posted. @@ -1994,4 +1998,3 @@ function comment_invoke_comment(&$commen function vancode2int($c = '00') { return base_convert(substr($c, 1), 36, 10); } - Index: modules/filter/filter.module =================================================================== RCS file: /cvs/drupal/drupal/modules/filter/filter.module,v retrieving revision 1.162 diff -u -p -r1.162 filter.module --- modules/filter/filter.module 31 Jan 2007 15:49:24 -0000 1.162 +++ modules/filter/filter.module 5 Feb 2007 17:57:06 -0000 @@ -15,6 +15,12 @@ define('FILTER_HTML_STRIP', 1); define('FILTER_HTML_ESCAPE', 2); /** + * Implementation of hook_hooks + */ +function filter_hooks() { + return array('filter', 'filter_tips'); +} +/** * Implementation of hook_help(). */ function filter_help($section) { @@ -1484,4 +1490,3 @@ function filter_xss_bad_protocol($string /** * @} End of "Standard filters". */ - Index: modules/menu/menu.module =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v retrieving revision 1.102 diff -u -p -r1.102 menu.module --- modules/menu/menu.module 31 Jan 2007 16:38:34 -0000 1.102 +++ modules/menu/menu.module 5 Feb 2007 17:57:07 -0000 @@ -7,6 +7,12 @@ */ /** + * Implementation of hook_hooks + */ +function menu_hooks() { + return array('menu'); +} +/** * Implementation of hook_help(). */ function menu_help($section) { Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.780 diff -u -p -r1.780 node.module --- modules/node/node.module 31 Jan 2007 15:49:25 -0000 1.780 +++ modules/node/node.module 5 Feb 2007 17:57:10 -0000 @@ -10,6 +10,36 @@ define('NODE_NEW_LIMIT', time() - 30 * 24 * 60 * 60); /** + * Implementation of hook_hooks + */ +function node_hooks() { + return array('access', 'delete', 'insert', 'load', + 'node_access_records', 'node_info', 'node_grants', 'node_operations', + 'node_type', 'prepare', 'submit', 'validate', 'view'); +} + +/** + * node pollutes its own namespace. + */ +function node_register_hooks() { + module_register_hook('access', 'node_content_access'); + module_register_hook('form', 'node_content_form'); + module_register_hook('help'); + module_register_hook('perm'); + module_register_hook('cron'); + module_register_hook('search'); + module_register_hook('user'); + module_register_hook('menu'); + module_register_hook('block'); + module_register_hook('update_index'); + module_register_hook('form_alter'); + module_register_hook('node_operations'); + module_register_hook('db_rewrite_sql'); + module_register_hook('forms'); + module_register_hook('link'); + +} +/** * Implementation of hook_help(). */ function node_help($section) { @@ -414,9 +444,9 @@ function _node_type_set_defaults($info) */ function node_hook(&$node, $hook) { $module = node_get_types('module', $node); - if ($module == 'node') { - $module = 'node_content'; // Avoid function name collisions. - } +// if ($module == 'node') { +// $module = 'node_content'; // Avoid function name collisions. +// } return module_hook($module, $hook); } @@ -435,10 +465,10 @@ function node_hook(&$node, $hook) { function node_invoke(&$node, $hook, $a2 = NULL, $a3 = NULL, $a4 = NULL) { if (node_hook($node, $hook)) { $module = node_get_types('module', $node); - if ($module == 'node') { - $module = 'node_content'; // Avoid function name collisions. - } - $function = $module .'_'. $hook; + //if ($module == 'node') { + // $module = 'node_content'; // Avoid function name collisions. + //} + $function = module_get_function($module, $hook); return ($function($node, $a2, $a3, $a4)); } } @@ -458,7 +488,7 @@ function node_invoke(&$node, $hook, $a2 function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { $return = array(); foreach (module_implements('nodeapi') as $name) { - $function = $name .'_nodeapi'; + $function = module_get_function($name, 'nodeapi'); $result = $function($node, $op, $a3, $a4); if (isset($result) && is_array($result)) { $return = array_merge($return, $result); @@ -673,7 +703,6 @@ function node_view($node, $teaser = FALS if ($links) { $node->links = module_invoke_all('link', 'node', $node, !$page); - foreach (module_implements('link_alter') AS $module) { $function = $module .'_link_alter'; $function($node, $node->links); @@ -2641,9 +2670,9 @@ function node_access($op, $node) { // Can't use node_invoke(), because the access hook takes the $op parameter // before the $node parameter. $module = node_get_types('module', $node); - if ($module == 'node') { - $module = 'node_content'; // Avoid function name collisions. - } + //if ($module == 'node') { + // $module = 'node_content'; // Avoid function name collisions. + //} $access = module_invoke($module, 'access', $op, $node); if (!is_null($access)) { return $access; Index: modules/search/search.module =================================================================== RCS file: /cvs/drupal/drupal/modules/search/search.module,v retrieving revision 1.211 diff -u -p -r1.211 search.module --- modules/search/search.module 31 Jan 2007 21:26:56 -0000 1.211 +++ modules/search/search.module 5 Feb 2007 17:57:11 -0000 @@ -91,6 +91,13 @@ define('PREG_CLASS_CJK', '\x{3041}-\x{30 '\x{4e00}-\x{9fbb}\x{f900}-\x{fad9}'); /** + * Implementation of hook_hooks + */ +function search_hooks() { + // search: search, search_item, update_index + return array('search', 'search_item', 'update_index'); +} +/** * Implementation of hook_help(). */ function search_help($section) { Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.448 diff -u -p -r1.448 system.module --- modules/system/system.module 4 Feb 2007 21:20:50 -0000 1.448 +++ modules/system/system.module 5 Feb 2007 17:57:13 -0000 @@ -51,6 +51,18 @@ function system_perm() { } /** + * Implementation of hook_hooks + */ +function system_hooks() { + // filter: filter, filter_tips, + // comment: comment + // search: search, search_item, update_index + return array('cron', 'db_rewrite_sql', 'disable', 'elements', 'enable', + 'exit', 'file_download', 'form', 'form_alter', 'forms', + 'help', 'init', 'install', 'link', 'link_alter', 'mail_alter', + 'ping', 'requirements', 'throttle', 'uninstall', 'xmlrpc'); +} +/** * Implementation of hook_elements(). */ function system_elements() { Index: modules/taxonomy/taxonomy.module =================================================================== RCS file: /cvs/drupal/drupal/modules/taxonomy/taxonomy.module,v retrieving revision 1.337 diff -u -p -r1.337 taxonomy.module --- modules/taxonomy/taxonomy.module 31 Jan 2007 21:26:56 -0000 1.337 +++ modules/taxonomy/taxonomy.module 5 Feb 2007 17:57:15 -0000 @@ -7,6 +7,12 @@ */ /** + * Implementation of hook_hooks + */ +function taxonomy_hooks() { + return array('taxonomy'); +} +/** * Implementation of hook_perm(). */ function taxonomy_perm() { Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.753 diff -u -p -r1.753 user.module --- modules/user/user.module 2 Feb 2007 15:25:25 -0000 1.753 +++ modules/user/user.module 5 Feb 2007 17:57:18 -0000 @@ -10,6 +10,12 @@ define('USERNAME_MAX_LENGTH', 60); define('EMAIL_MAX_LENGTH', 64); /** + * Implementation of hook_hooks + */ +function user_hooks() { + return array('auth', 'perm', 'profile_alter', 'user', 'user_operations'); +} +/** * Invokes hook_user() in every module. * * We cannot use module_invoke() for this, because the arguments need to @@ -2746,4 +2752,3 @@ function user_forms() { $forms['user_admin_new_role']['callback'] = 'user_admin_role'; return $forms; } -