=== added file 'includes/module_admin.inc' --- includes/module_admin.inc 1970-01-01 00:00:00 +0000 +++ includes/module_admin.inc 2007-04-08 03:15:12 +0000 @@ -0,0 +1,312 @@ + $file) { + $file->info = _module_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info'); + // Skip modules that don't provide info. + if (empty($file->info)) { + unset($files[$filename]); + continue; + } + $files[$filename]->info = $file->info; + + // log the critical hooks implemented by this module + $bootstrap = 0; + foreach (bootstrap_hooks() as $hook) { + if (module_hook($file->name, $hook)) { + $bootstrap = 1; + break; + } + } + + // Update the contents of the system table: + // TODO: We shouldn't actually need this description field anymore. Remove me next release. + if (isset($file->status) || (isset($file->old_filename) && $file->old_filename != $file->filename)) { + db_query("UPDATE {system} SET description = '%s', name = '%s', filename = '%s', bootstrap = %d WHERE filename = '%s'", $file->info['description'], $file->name, $file->filename, $bootstrap, $file->old_filename); + } + else { + // This is a new module. + $files[$filename]->status = 0; + $files[$filename]->throttle = 0; + db_query("INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, $file->info['description'], 'module', $file->filename, 0, 0, $bootstrap); + } + } + $files = _module_build_dependents($files); + return $files; +} + +/** + * Find dependents; modules that are required by other modules. + * Adds an array of dependents to the $file->info array. + * + * @return + * The list of files array with dependents added where applicable. + */ +function _module_build_dependents($files) { + foreach ($files as $filename => $file) { + if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) { + foreach ($file->info['dependencies'] as $dependency) { + if (!empty($files[$dependency]) && is_array($files[$dependency]->info)) { + if (!isset($files[$dependency]->info['dependents'])) { + $files[$dependency]->info['dependents'] = array(); + } + $files[$dependency]->info['dependents'][] = $filename; + } + } + } + } + return $files; +} + +/** + * Parse Drupal info file format. + * Uses ini parser provided by php's parse_ini_file(). + * + * Files should use the ini format to specify values. + * e.g. + * key = "value" + * key2 = value2 + * + * Some things to be aware of: + * - This function is NOT for placing arbitrary module-specific settings. Use variable_get() + * and variable_set() for that. + * - You may not use double-quotes in a value. + * + * Information stored in the module.info file: + * name - The real name of the module for display purposes. + * description - A brief description of the module. + * dependencies - A space delimited list of the short names (shortname) of other modules this module depends on. + * package - The name of the package of modules this module belongs to. + * + * Example of .info file: + * name = Forum + * description = Enables threaded discussions about general topics. + * dependencies = taxonomy comment + * package = Core - optional + * + * @param $filename + * The file we are parsing. Accepts file with relative or absolute path. + * @return + * The info array. + */ +function _module_parse_info_file($filename) { + $info = array(); + + if (file_exists($filename)) { + $info = parse_ini_file($filename); + + if (isset($info['dependencies'])) { + $info['dependencies'] = explode(" ", $info['dependencies']); + } + else { + $info['dependencies'] = NULL; + } + } + return $info; +} + +function _module_enable($module_list) { + $invoke_modules = array(); + foreach ($module_list as $module) { + $existing = db_fetch_object(db_query("SELECT name, status FROM {system} WHERE type = 'module' AND name = '%s'", $module)); + if ($existing->status === '0') { + module_load_install($module); + db_query("UPDATE {system} SET status = 1, throttle = 0 WHERE type = 'module' AND name = '%s'", $module); + drupal_load('module', $module); + $invoke_modules[] = $module; + } + } + + if (!empty($invoke_modules)) { + // Refresh the module list to include the new enabled module. + module_list(TRUE, FALSE); + // Force to regenerate the stored list of hook implementations. + module_build_registry_cache(); + } + + foreach ($invoke_modules as $module) { + module_invoke($module, 'enable'); + } +} + +function _module_disable($module_list) { + $invoke_modules = array(); + foreach ($module_list as $module) { + if (module_exists($module)) { + module_load_install($module); + module_invoke($module, 'disable'); + db_query("UPDATE {system} SET status = 0, throttle = 0 WHERE type = 'module' AND name = '%s'", $module); + $invoke_modules[] = $module; + } + } + + if (!empty($invoke_modules)) { + // Refresh the module list to exclude the disabled modules. + module_list(TRUE, FALSE); + // Force to regenerate the stored list of hook implementations. + module_build_registry_cache(); + } +} + +/** + * @defgroup hooks Hooks + * @{ + * Allow modules to interact with the Drupal core. + * + * Drupal's module system is based on the concept of "hooks". A hook is a PHP + * function that is named foo_bar(), where "foo" is the name of the module (whose + * filename is thus foo.module) and "bar" is the name of the hook. Each hook has + * a defined set of parameters and a specified result type. + * + * To extend Drupal, a module need simply implement a hook. When Drupal wishes to + * allow intervention from modules, it determines which modules implement a hook + * and call that hook in all enabled modules that implement it. + * + * The available hooks to implement are explained here in the Hooks section of + * the developer documentation. The string "hook" is used as a placeholder for + * the module name is the hook definitions. For example, if the module file is + * called example.module, then hook_help() as implemented by that module would be + * defined as example_help(). + */ + +/** + * Write the registry cache into the database. + */ +function _module_save_registry_cache() { + // TODO: use a separate cache for this. + cache_set('module_registry', 'cache', serialize(_module_get_registry_cache())); +} + + +/** + * Clear the module registry cache. + */ +function _module_clear_registry_cache() { + _module_set_registry_cache('@reset'); + module_get_hooks(TRUE); + +} + +/** + * 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. + * + * @param $hook + * an array in the following format: + * + * '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_set_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($reset = FALSE) { + static $cache = array(); + if ($reset) { + $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(TRUE, FALSE) as $module) { + $hooks = module_invoke_hook($module, 'hooks'); + if (is_array($hooks)) { + $cache = array_merge($cache, $hooks); + } + } + } + return $cache; +} === modified file 'includes/install.inc' --- includes/install.inc 2007-03-27 05:13:53 +0000 +++ includes/install.inc 2007-04-08 03:15:12 +0000 @@ -103,10 +103,7 @@ function drupal_install_profile_name() { if (!isset($name)) { // Load profile details. - $function = $profile .'_profile_details'; - if (function_exists($function)) { - $details = $function(); - } + $details = module_invoke_hook($profile, 'profile_details'); $name = isset($details['name']) ? $details['name'] : 'Drupal'; } @@ -147,8 +144,7 @@ function drupal_detect_database_types() foreach (array('mysql', 'mysqli', 'pgsql') as $type) { if (file_exists('./includes/install.'. $type .'.inc')) { include_once './includes/install.'. $type .'.inc'; - $function = $type .'_is_available'; - if ($function()) { + if (module_invoke_hook($type, 'is_available')) { $databases[$type] = $type; } } @@ -271,8 +267,7 @@ function drupal_verify_profile($profile, require_once($profile_file); // Get a list of modules required by this profile. - $function = $profile .'_profile_modules'; - $module_list = array_merge(drupal_required_modules(), $function(), ($locale ? array('locale') : array())); + $module_list = array_merge(drupal_required_modules(), module_invoke_hook($profile, 'profile_modules'), ($locale ? array('locale') : array())); // Get a list of modules that exist in Drupal's assorted subdirectories. $present_modules = array(); @@ -308,7 +303,7 @@ function drupal_install_profile($profile $system_path = dirname(drupal_get_filename('module', 'system', NULL)); require_once './' . $system_path . '/system.install'; - module_invoke('system', 'install'); + module_invoke_hook('system', 'install'); $system_versions = drupal_get_schema_versions('system'); $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED; db_query("INSERT INTO {system} (filename, name, type, description, status, throttle, bootstrap, schema_version) VALUES('%s', '%s', 'module', '', 1, 0, 0, %d)", $system_path . '/system.module', 'system', $system_version); @@ -319,6 +314,8 @@ function drupal_install_profile($profile // Install schemas for profile and all its modules. module_rebuild_cache(); drupal_install_modules($module_list); + // Force to regenerate the stored list of hook implementations. + module_build_registry_cache(); } @@ -335,7 +332,7 @@ function drupal_install_modules($module_ foreach ($module_list as $module) { if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { module_load_install($module); - module_invoke($module, 'install'); + module_invoke_hook($module, 'install'); $versions = drupal_get_schema_versions($module); drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED); $enable_modules[] = $module; @@ -353,7 +350,7 @@ function drupal_install_modules($module_ */ function drupal_uninstall_module($module) { module_load_install($module); - module_invoke($module, 'uninstall'); + module_invoke_hook($module, 'uninstall'); drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED); } @@ -680,8 +677,7 @@ function drupal_check_profile($profile) require_once($profile_file); // Get a list of modules required by this profile. - $function = $profile .'_profile_modules'; - $module_list = array_unique(array_merge(drupal_required_modules(), $function())); + $module_list = array_unique(array_merge(drupal_required_modules(), module_invoke_hook($profile, 'profile_modules'))); // Get a list of all .install files. $installs = drupal_get_install_files($module_list); @@ -690,9 +686,7 @@ function drupal_check_profile($profile) $requirements = array(); foreach ($installs as $install) { require_once $install->filename; - if (module_hook($install->name, 'requirements')) { - $requirements = array_merge($requirements, module_invoke($install->name, 'requirements', 'install')); - } + $requirements = array_merge($requirements, (array)module_invoke_hook($install->name, 'requirements', 'install')); } return $requirements; } @@ -720,7 +714,7 @@ function drupal_check_module($module) { require_once $install[$module]->filename; // Check requirements - $requirements = module_invoke($module, 'requirements', 'install'); + $requirements = module_invoke_hook($module, 'requirements', 'install'); if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) { // Print any error messages foreach ($requirements as $requirement) { === modified file 'includes/module.inc' --- includes/module.inc 2007-02-04 21:20:50 +0000 +++ includes/module.inc 2007-04-08 03:31:07 +0000 @@ -37,48 +37,37 @@ function module_iterate($function, $argu * @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 $fixed_list - * (Optional) Override the module list with the given modules. Stays until the - * next call with $refresh = TRUE. * @return * An associative array whose keys and values are the names of all loaded * modules. */ -function module_list($refresh = FALSE, $bootstrap = TRUE, $sort = FALSE, $fixed_list = NULL) { +function module_list($refresh = FALSE, $bootstrap = TRUE, $sort = FALSE) { static $list, $sorted_list; - if ($refresh || $fixed_list) { - unset($sorted_list); + if ($refresh) { + $sorted_list = array(); $list = array(); - if ($fixed_list) { - foreach ($fixed_list as $name => $module) { - drupal_get_filename('module', $name, $module['filename']); - $list[$name] = $name; - } + if ($bootstrap) { + $result = db_query("SELECT name, filename, throttle, bootstrap FROM {system} WHERE type = 'module' AND status = 1 AND bootstrap = 1 ORDER BY weight ASC, filename ASC"); } else { - if ($bootstrap) { - $result = db_query("SELECT name, filename, throttle, bootstrap FROM {system} WHERE type = 'module' AND status = 1 AND bootstrap = 1 ORDER BY weight ASC, filename ASC"); - } - else { - $result = db_query("SELECT name, filename, throttle, bootstrap FROM {system} WHERE type = 'module' AND status = 1 ORDER BY weight ASC, filename ASC"); - } - while ($module = db_fetch_object($result)) { - if (file_exists($module->filename)) { - // Determine the current throttle status and see if the module should be - // loaded based on server load. We have to directly access the throttle - // variables, since throttle.module may not be loaded yet. - $throttle = ($module->throttle && variable_get('throttle_level', 0) > 0); - if (!$throttle) { - drupal_get_filename('module', $module->name, $module->filename); - $list[$module->name] = $module->name; - } + $result = db_query("SELECT name, filename, throttle, bootstrap FROM {system} WHERE type = 'module' AND status = 1 ORDER BY weight ASC, filename ASC"); + } + while ($module = db_fetch_object($result)) { + if (file_exists($module->filename)) { + // Determine the current throttle status and see if the module should be + // loaded based on server load. We have to directly access the throttle + // variables, since throttle.module may not be loaded yet. + $throttle = ($module->throttle && variable_get('throttle_level', 0) > 0); + if (!$throttle) { + drupal_get_filename('module', $module->name, $module->filename); + $list[$module->name] = $module->name; } } } } if ($sort) { - if (!isset($sorted_list)) { + if (empty($sorted_list)) { $sorted_list = $list; ksort($sorted_list); } @@ -88,125 +77,6 @@ function module_list($refresh = FALSE, $ } /** - * Rebuild the database cache of module files. - * - * @return - * The array of filesystem objects used to rebuild the cache. - */ -function module_rebuild_cache() { - // Get current list of modules - $files = drupal_system_listing('\.module$', 'modules', 'name', 0); - - // Extract current files from database. - system_get_files_database($files, 'module'); - - ksort($files); - - foreach ($files as $filename => $file) { - $file->info = _module_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info'); - // Skip modules that don't provide info. - if (empty($file->info)) { - unset($files[$filename]); - continue; - } - $files[$filename]->info = $file->info; - - // log the critical hooks implemented by this module - $bootstrap = 0; - foreach (bootstrap_hooks() as $hook) { - if (module_hook($file->name, $hook)) { - $bootstrap = 1; - break; - } - } - - // Update the contents of the system table: - // TODO: We shouldn't actually need this description field anymore. Remove me next release. - if (isset($file->status) || (isset($file->old_filename) && $file->old_filename != $file->filename)) { - db_query("UPDATE {system} SET description = '%s', name = '%s', filename = '%s', bootstrap = %d WHERE filename = '%s'", $file->info['description'], $file->name, $file->filename, $bootstrap, $file->old_filename); - } - else { - // This is a new module. - $files[$filename]->status = 0; - $files[$filename]->throttle = 0; - db_query("INSERT INTO {system} (name, description, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, $file->info['description'], 'module', $file->filename, 0, 0, $bootstrap); - } - } - $files = _module_build_dependents($files); - return $files; -} - -/** - * Find dependents; modules that are required by other modules. - * Adds an array of dependents to the $file->info array. - * - * @return - * The list of files array with dependents added where applicable. - */ -function _module_build_dependents($files) { - foreach ($files as $filename => $file) { - if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) { - foreach ($file->info['dependencies'] as $dependency) { - if (!empty($files[$dependency]) && is_array($files[$dependency]->info)) { - if (!isset($files[$dependency]->info['dependents'])) { - $files[$dependency]->info['dependents'] = array(); - } - $files[$dependency]->info['dependents'][] = $filename; - } - } - } - } - return $files; -} - -/** - * Parse Drupal info file format. - * Uses ini parser provided by php's parse_ini_file(). - * - * Files should use the ini format to specify values. - * e.g. - * key = "value" - * key2 = value2 - * - * Some things to be aware of: - * - This function is NOT for placing arbitrary module-specific settings. Use variable_get() - * and variable_set() for that. - * - You may not use double-quotes in a value. - * - * Information stored in the module.info file: - * name - The real name of the module for display purposes. - * description - A brief description of the module. - * dependencies - A space delimited list of the short names (shortname) of other modules this module depends on. - * package - The name of the package of modules this module belongs to. - * - * Example of .info file: - * name = Forum - * description = Enables threaded discussions about general topics. - * dependencies = taxonomy comment - * package = Core - optional - * - * @param $filename - * The file we are parsing. Accepts file with relative or absolute path. - * @return - * The info array. - */ -function _module_parse_info_file($filename) { - $info = array(); - - if (file_exists($filename)) { - $info = parse_ini_file($filename); - - if (isset($info['dependencies'])) { - $info['dependencies'] = explode(" ", $info['dependencies']); - } - else { - $info['dependencies'] = NULL; - } - } - return $info; -} - -/** * Determine whether a given module exists. * * @param $module @@ -220,153 +90,121 @@ function module_exists($module) { } /** - * Load a module's installation hooks. + * Determine whether a module implements a hook. + * + * @param $module + * The name of the module (without the .module extension). + * @param $hook + * The name of the hook (e.g. "help" or "menu"). + * @return + * TRUE if the module is both installed and enabled, and the hook is + * implemented in that module. */ -function module_load_install($module) { - // Make sure the installation API is available - include_once './includes/install.inc'; - - $install_file = './'. drupal_get_path('module', $module) .'/'. $module .'.install'; - if (is_file($install_file)) { - include_once $install_file; - } +function module_hook($module, $hook) { + $registry = _module_get_registry_cache(); + return isset($registry[$hook][$module]) && is_array($registry[$hook][$module]); } /** - * Enable a given list of modules. + * 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_list - * An array of module names. + * @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_enable($module_list) { - $invoke_modules = array(); - foreach ($module_list as $module) { - $existing = db_fetch_object(db_query("SELECT name, status FROM {system} WHERE type = 'module' AND name = '%s'", $module)); - if ($existing->status === '0') { - module_load_install($module); - db_query("UPDATE {system} SET status = 1, throttle = 0 WHERE type = 'module' AND name = '%s'", $module); - drupal_load('module', $module); - $invoke_modules[] = $module; - } - } - - if (!empty($invoke_modules)) { - // 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); - } +function module_get_function($module, $hook) { + $registry = _module_get_registry_cache(); - foreach ($invoke_modules as $module) { - module_invoke($module, 'enable'); + 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']; } } /** - * Disable a given set of modules. + * Determine which modules are implementing a hook. * - * @param $module_list - * An array of module names. + * @param $hook + * The name of the hook (e.g. "help" or "menu"). + * @param $sort + * By default, modules are ordered by weight and filename, settings this option + * to TRUE, module list will be ordered by module name. + * @return + * An array with the names of the modules which are implementing this hook. */ -function module_disable($module_list) { - $invoke_modules = array(); - foreach ($module_list as $module) { - if (module_exists($module)) { - module_load_install($module); - module_invoke($module, 'disable'); - db_query("UPDATE {system} SET status = 0, throttle = 0 WHERE type = 'module' AND name = '%s'", $module); - $invoke_modules[] = $module; - } - } +function module_implements($hook, $sort = FALSE) { + $registry = _module_get_registry_cache(); - if (!empty($invoke_modules)) { - // 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); + if (isset($registry[$hook])) { + return array_keys($registry[$hook]); } + return array(); } /** - * @defgroup hooks Hooks - * @{ - * Allow modules to interact with the Drupal core. - * - * Drupal's module system is based on the concept of "hooks". A hook is a PHP - * function that is named foo_bar(), where "foo" is the name of the module (whose - * filename is thus foo.module) and "bar" is the name of the hook. Each hook has - * a defined set of parameters and a specified result type. - * - * To extend Drupal, a module need simply implement a hook. When Drupal wishes to - * allow intervention from modules, it determines which modules implement a hook - * and call that hook in all enabled modules that implement it. - * - * The available hooks to implement are explained here in the Hooks section of - * the developer documentation. The string "hook" is used as a placeholder for - * the module name is the hook definitions. For example, if the module file is - * called example.module, then hook_help() as implemented by that module would be - * defined as example_help(). - */ - -/** - * Determine whether a module implements a hook. + * 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 (e.g. "help" or "menu"). + * The name of the hook to invoke. + * @param ... + * Arguments to pass to the hook implementation. * @return - * TRUE if the module is both installed and enabled, and the hook is - * implemented in that module. + * The return value of the hook implementation. */ -function module_hook($module, $hook) { - return function_exists($module .'_'. $hook); +function module_invoke() { + $args = func_get_args(); + $module = array_shift($args); + $hook = array_shift($args); + + if ($function = module_get_function($module, $hook)) { + return call_user_func_array($function, $args); + } } /** - * Determine which modules are implementing a hook. + * Invoke a hook in a particular module. This function passes the first + * parameter directly so it may be a reference. * + * @param $module + * The name of the module (without the .module extension). * @param $hook - * The name of the hook (e.g. "help" or "menu"). - * @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). + * The name of the hook to invoke. + * @param $ref1 + * The first parameter that will be passed as a reference; it is + * required. + * @param ... + * Arguments to pass to the hook implementation. * @return - * An array with the names of the modules which are implementing this hook. + * The return value of the hook implementation. */ -function module_implements($hook, $sort = FALSE, $refresh = FALSE) { - static $implementations; - - if ($refresh) { - unset($implementations); - return; - } +function module_invoke_ref1($module, $hook, &$ref1) { + // Take the full function args, and remove the first 2 items. + $args = array_splice(func_get_args(), 0, 2); + + // Put our reference back into the array as a reference. + $args[0] = &$ref1; - 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; - } - } + if ($function = module_get_function($module, $hook)) { + return call_user_func_array($function, $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]; } /** - * 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,17 +215,36 @@ 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); } } + +function _module_invoke_all($hook, &$args) { + $return = array(); + foreach (module_implements($hook) as $module) { + $function = module_get_function($module, $hook); + $result = call_user_func_array($function, $args); + + if (isset($result) && is_array($result)) { + $return = array_merge($return, $result); + } + else if (isset($result)) { + $return[] = $result; + } + } + + return $return; +} /** - * Invoke a hook in all enabled modules that implement it. + * Invoke a hook in all enabled modules that implement it. Because references + * don't survive func_get_args without help, this function passes all values + * by value, not reference. * * @param $hook * The name of the hook to invoke. @@ -400,19 +257,107 @@ function module_invoke() { function module_invoke_all() { $args = func_get_args(); $hook = array_shift($args); - $return = array(); - foreach (module_implements($hook) as $module) { - $function = $module .'_'. $hook; - $result = call_user_func_array($function, $args); - if (isset($result) && is_array($result)) { - $return = array_merge($return, $result); - } - else if (isset($result)) { - $return[] = $result; + + return _module_invoke_all($hook, $args); +} + +/** + * Invoke a hook in all enabled modules that implement it. The first + * argument is required and will be passed as a reference. + * + * @param $hook + * The name of the hook to invoke. + * @param &$ref1 + * An argument that will be passed as a reference. + * @param ... + * Arguments to pass to the hook. + * @return + * An array of return values of the hook implementations. If modules return + * arrays from their implementations, those are merged into one array. + */ +function module_invoke_all_ref1($hook, &$ref1) { + // Take the full function args, and remove the first item. + $args = func_get_args(); + array_shift($args); + // Put our reference back into the array -- as a reference -- + // and overwriting $hook. + $args[0] = &$ref1; + + return _module_invoke_all($hook, $args); +} + +/** + * Invoke a hook in all enabled modules that implement it. The first + * two arguments are required and will be passed as references. + * + * @param $hook + * The name of the hook to invoke. + * @param &$ref1 + * An argument that will be passed as a reference. + * @param &$ref2 + * A second argument that will be passed as a reference. + * @param ... + * Arguments to pass to the hook. + * @return + * An array of return values of the hook implementations. If modules return + * arrays from their implementations, those are merged into one array. + */ +function module_invoke_all_ref2($hook, &$ref1, &$ref2) { + // Take the full function args, and remove the first item. + $args = array_splice(func_get_args(), 0, 1); + // Put our reference back into the array -- as a reference -- + // and overwriting $hook. + $args[0] = &$ref1; + $args[1] = &$ref2; + + return _module_invoke_all($hook, $args); +} + +/** + * Add a module's combined information into the registry cache. + */ +function _module_set_registry_cache($registry, $module = NULL, $info = NULL) { + return _module_get_registry_cache($registry, $module, $info); +} + +function _module_get_registry_cache($registry = NULL, $module = NULL, $info = NULL) { + static $registry_storage = NULL; + if (!isset($registry)) { + if (!isset($registry_storage)) { + _module_load_registry_cache(); } + return $registry_storage; } - return $return; + if ($registry == '@reset') { + $registry_storage = array(); + return; + } + + if (is_array($registry)) { + $registry_storage = $registry; + return; + } + + $registry_storage[$registry][$module] = $info; + + return $registry_storage; +} + +/** + * 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_set_registry_cache(unserialize($registry->data)); + } + else { + include_once './includes/module_admin.inc'; + module_build_registry_cache(); + } } /** @@ -425,3 +370,50 @@ function module_invoke_all() { function drupal_required_modules() { return array('block', 'filter', 'node', 'system', 'user', 'watchdog'); } + +/** + * Rebuild the database cache of module files. + * + * @return + * The array of filesystem objects used to rebuild the cache. + */ +function module_rebuild_cache() { + include_once './includes/module_admin.inc'; + return _module_rebuild_cache(); +} + +/** + * Enable a given list of modules. + * + * @param $module_list + * An array of module names. + */ +function module_enable($module_list) { + include_once './includes/module_admin.inc'; + _module_enable($module_list); +} + +/** + * Disable a given set of modules. + * + * @param $module_list + * An array of module names. + */ +function module_disable($module_list) { + include_once './includes/module_admin.inc'; + _module_disable($module_list); +} + +/** + * Load a module's installation hooks. + */ +function module_load_install($module) { + // Make sure the installation API is available + include_once './includes/install.inc'; + + $install_file = './'. drupal_get_path('module', $module) .'/'. $module .'.install'; + if (is_file($install_file)) { + include_once $install_file; + } +} + === modified file 'includes/theme.inc' --- includes/theme.inc 2007-04-06 14:31:51 +0000 +++ includes/theme.inc 2007-04-08 03:15:12 +0000 @@ -177,6 +177,7 @@ function _theme_process_registry(&$cache */ function _theme_build_registry($theme, $theme_engine) { $cache = array(); + foreach (module_implements('theme') as $module) { _theme_process_registry($cache, $module, 'module'); } === modified file 'install.php' --- install.php 2007-03-25 23:34:17 +0000 +++ install.php 2007-04-08 03:33:48 +0000 @@ -2,6 +2,7 @@ // $Id: install.php,v 1.40 2007/03/25 23:34:17 dries Exp $ require_once './includes/install.inc'; +require_once './includes/module.inc'; /** * The Drupal installation happens in a series of steps. We begin by verifying @@ -39,10 +40,13 @@ function install_main() { } // Load module basics (needed for hook invokes). - include_once './includes/module.inc'; - $module_list['system']['filename'] = 'modules/system/system.module'; - $module_list['filter']['filename'] = 'modules/filter/filter.module'; - module_list(TRUE, FALSE, FALSE, $module_list); + $registry = array( + 'elements' => array('system' => array('function' => 'system_elements', 'file' => NULL)), + ); + _module_set_registry_cache($registry); + // Do not even try to consult the database about these modules. + drupal_get_filename('module', 'system', 'modules/system/system.module'); + drupal_get_filename('module', 'filter', 'modules/filter/filter.module'); drupal_load('module', 'system'); drupal_load('module', 'filter'); === modified file 'modules/block/block.module' --- modules/block/block.module 2007-04-06 13:27:20 +0000 +++ modules/block/block.module 2007-04-08 03:15:12 +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) { === modified file 'modules/comment/comment.module' --- modules/comment/comment.module 2007-04-06 13:27:20 +0000 +++ modules/comment/comment.module 2007-04-08 03:15:12 +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) { @@ -344,9 +350,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. @@ -2062,4 +2066,3 @@ function int2vancode($i = 0) { function vancode2int($c = '00') { return base_convert(substr($c, 1), 36, 10); } - === modified file 'modules/filter/filter.module' --- modules/filter/filter.module 2007-04-06 13:27:20 +0000 +++ modules/filter/filter.module 2007-04-08 03:15:12 +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) { @@ -1508,4 +1514,3 @@ function filter_xss_bad_protocol($string /** * @} End of "Standard filters". */ - === modified file 'modules/node/node.module' --- modules/node/node.module 2007-04-06 13:27:20 +0000 +++ modules/node/node.module 2007-04-08 03:15:12 +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', 'nodeapi', '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'); + module_register_hook('init'); +} +/** * Implementation of hook_help(). */ function node_help($section) { @@ -455,9 +485,6 @@ 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. - } return module_hook($module, $hook); } @@ -476,10 +503,7 @@ 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; + $function = module_get_function($module, $hook); return ($function($node, $a2, $a3, $a4)); } } @@ -497,18 +521,7 @@ function node_invoke(&$node, $hook, $a2 * The returned value of the invoked hooks. */ function node_invoke_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { - $return = array(); - foreach (module_implements('nodeapi') as $name) { - $function = $name .'_nodeapi'; - $result = $function($node, $op, $a3, $a4); - if (isset($result) && is_array($result)) { - $return = array_merge($return, $result); - } - else if (isset($result)) { - $return[] = $result; - } - } - return $return; + return module_invoke_all_ref1('nodeapi', $node, $op, $a3, $a4); } /** @@ -2676,9 +2689,6 @@ 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. - } $access = module_invoke($module, 'access', $op, $node); if (!is_null($access)) { return $access; === modified file 'modules/search/search.module' --- modules/search/search.module 2007-04-06 13:27:20 +0000 +++ modules/search/search.module 2007-04-08 03:15:12 +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) { === modified file 'modules/system/system.module' --- modules/system/system.module 2007-04-06 14:31:51 +0000 +++ modules/system/system.module 2007-04-08 03:15:12 +0000 @@ -87,6 +87,19 @@ 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', + 'menu', 'ping', 'requirements', 'theme', 'throttle', 'uninstall', + 'xmlrpc'); +} +/** * Implementation of hook_elements(). */ function system_elements() { === modified file 'modules/taxonomy/taxonomy.module' --- modules/taxonomy/taxonomy.module 2007-04-06 13:27:20 +0000 +++ modules/taxonomy/taxonomy.module 2007-04-08 03:15:12 +0000 @@ -7,6 +7,12 @@ */ /** + * Implementation of hook_hooks + */ +function taxonomy_hooks() { + return array('taxonomy'); +} +/** * Implementation of hook_perm(). */ function taxonomy_perm() { === modified file 'modules/user/user.module' --- modules/user/user.module 2007-04-06 14:27:22 +0000 +++ modules/user/user.module 2007-04-08 03:15:12 +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 @@ -2804,4 +2810,3 @@ function user_forms() { $forms['user_admin_new_role']['callback'] = 'user_admin_role'; return $forms; } - === modified file 'update.php' --- update.php 2007-04-02 15:15:50 +0000 +++ update.php 2007-04-08 03:15:12 +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'])) {