Commit e2a6a3ed authored by Dries's avatar Dries

- Patch #298600 by chx, justinrandell, Damien, et al: make module_implements...

- Patch #298600 by chx, justinrandell, Damien, et al: make module_implements work regardless of bootstrap phase.
parent e6a57469
......@@ -1406,18 +1406,7 @@ function drupal_function_exists($function) {
return TRUE;
}
$file = db_query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array(
':name' => $function,
':type' => 'function',
))
->fetchField();
if ($file) {
require_once DRUPAL_ROOT . '/' . $file;
$checked[$function] = function_exists($function);
if ($checked[$function]) {
registry_mark_code('function', $function);
}
}
$checked[$function] = _registry_check_code('function', $function);
return $checked[$function];
}
......@@ -1455,7 +1444,7 @@ function drupal_autoload_class($class) {
}
/**
* Helper for registry_check_{interface, class}.
* Helper to check for a resource in the registry.
*/
function _registry_check_code($type, $name) {
$file = db_query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array(
......@@ -1468,6 +1457,7 @@ function _registry_check_code($type, $name) {
registry_mark_code($type, $name);
return TRUE;
}
return FALSE;
}
/**
......@@ -1508,31 +1498,6 @@ function registry_rebuild() {
_registry_rebuild();
}
/**
* Save hook implementations cache.
*
* @param $hook
* Array with the hook name and list of modules that implement it.
* @param $write_to_persistent_cache
* Whether to write to the persistent cache.
*/
function registry_cache_hook_implementations($hook, $write_to_persistent_cache = FALSE) {
static $implementations;
if ($hook) {
// Newer is always better, so overwrite anything that's come before.
$implementations[$hook['hook']] = $hook['modules'];
}
if ($write_to_persistent_cache === TRUE) {
// Only write this to cache if the implementations data we are going to cache
// is different to what we loaded earlier in the request.
if ($implementations != module_implements()) {
cache_set('hooks', $implementations, 'cache_registry');
}
}
}
/**
* Save the files required by the registry for this path.
*/
......
......@@ -1608,7 +1608,7 @@ function drupal_page_footer() {
module_invoke_all('exit');
registry_cache_hook_implementations(FALSE, TRUE);
module_implements(MODULE_IMPLEMENTS_WRITE_CACHE);
registry_cache_path_files();
}
......@@ -2668,8 +2668,6 @@ function _drupal_bootstrap_full() {
fix_gpc_magic();
// Load all enabled modules
module_load_all();
// Rebuild the module hook cache
module_implements('', NULL, TRUE);
// Let all modules take action before menu system handles the request
// We do not want this while running update.php.
......
......@@ -1728,7 +1728,7 @@ function menu_router_build($reset = FALSE) {
// We need to manually call each module so that we can know which module
// a given item came from.
$callbacks = array();
foreach (module_implements('menu', NULL, TRUE) as $module) {
foreach (module_implements('menu', TRUE) as $module) {
$router_items = call_user_func($module . '_menu');
if (isset($router_items) && is_array($router_items)) {
foreach (array_keys($router_items) as $path) {
......
......@@ -6,6 +6,17 @@
* API for loading and interacting with Drupal modules.
*/
/**
* Pass this to module_implements when its cache needs to be written.
*/
define('MODULE_IMPLEMENTS_WRITE_CACHE', -1);
/**
* Pass this to module_implements when its cache needs to be cleared.
*/
define('MODULE_IMPLEMENTS_CLEAR_CACHE', -2);
/**
* Load all the modules that have been enabled in the system table.
*/
......@@ -26,8 +37,8 @@ function module_load_all() {
* Whether to return the reduced set of modules loaded in "bootstrap mode"
* for cached pages. See bootstrap.inc.
* @param $sort
* By default, modules are ordered by weight and filename, settings this option
* to TRUE, module list will be ordered by module name.
* By default, modules are ordered by weight and filename. Set this option to
* TRUE to return a module list ordered only by module name.
* @param $fixed_list
* (Optional) Override the module list with the given modules. Stays until the
* next call with $refresh = TRUE.
......@@ -391,47 +402,102 @@ function module_hook($module, $hook) {
* Determine which modules are implementing a hook.
*
* @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
* The name of the hook (e.g. "help" or "menu"). Special cases:
* MODULE_IMPLEMENTS_CLEAR_CACHE: Force the stored list of hook
* implementations to be regenerated (such as after enabling a new module,
* before processing hook_enable). Note that if $refresh is TRUE this function
* will always return NULL.
* before processing hook_enable).
* MODULE_IMPLEMENTS_WRITE_CACHE: Write the stored list of hook
* implementations into the cache_registry table.
* @param $sort
* By default, modules are ordered by weight and filename. By setting this
* option to TRUE, modules will be ordered by module name.
* @return
* An array with the names of the modules which are implementing this hook.
* If $hook is NULL then it will return the implementation cache.
* All enabled modules are taken into consideration and the files containing
* the implementations are loaded as necessary.
*/
function module_implements($hook = NULL, $sort = FALSE, $refresh = FALSE) {
static $implementations = array();
function module_implements($hook, $sort = FALSE) {
static $implementations = array(), $sorted_implementations = array(), $loaded = array(), $cached_hooks = 0;
if (!isset($hook)) {
return $implementations;
if (defined('MAINTENANCE_MODE')) {
return _module_implements_maintenance($hook, $sort);
}
if ($refresh) {
if ($hook === MODULE_IMPLEMENTS_CLEAR_CACHE) {
$implementations = array();
$sorted_implementations = array();
$loaded = array();
$cached_hooks = 0;
cache_clear_all('hooks', 'cache_registry');
return;
}
if (!defined('MAINTENANCE_MODE') && empty($implementations) && ($cache = cache_get('hooks', 'cache_registry'))) {
$implementations = $cache->data;
if ($hook === MODULE_IMPLEMENTS_WRITE_CACHE) {
// Only write this to cache if we loaded new implementations.
if (count($implementations) > $cached_hooks) {
cache_set('hooks', $implementations, 'cache_registry');
}
return;
}
if ($hook) {
if (!isset($loaded[$hook])) {
if (empty($implementations) && ($cache = cache_get('hooks', 'cache_registry'))) {
$implementations = $cache->data;
$cached_hooks = count($implementations);
}
if (!isset($implementations[$hook])) {
$implementations[$hook] = array();
foreach (module_list() as $module) {
if (module_hook($module, $hook)) {
$implementations[$hook][] = $module;
}
$implementations[$hook] = db_query("SELECT module FROM {registry} WHERE type = 'function' AND suffix = :hook ORDER BY weight, module", array(':hook' => $hook))->fetchCol();
}
foreach ($implementations[$hook] as $module) {
$function = $module . '_' . $hook;
if (!function_exists($function)) {
drupal_function_exists($function);
}
}
registry_cache_hook_implementations(array('hook' => $hook, 'modules' => $implementations[$hook]));
$loaded[$hook] = TRUE;
}
if ($sort) {
if (!isset($sorted_implementations[$hook])) {
$sorted_implementations[$hook] = $implementations[$hook];
sort($sorted_implementations[$hook]);
}
return $sorted_implementations[$hook];
}
else {
return $implementations[$hook];
}
}
/**
* This is the maintenance version of module_implements for internal use only.
*
* This function is called whenever MAINTENANCE_MODE is defined and is a
* safe code path for Drupal installation or upgrade because it does not use
* the database, instead it uses module_list. @see module_list $fixed_list on
* how to make module_list also DB independent.
*
* @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.
* Only enabled and already loaded modules are taken into consideration.
*/
function _module_implements_maintenance($hook, $sort = FALSE) {
$implementations = array();
foreach (module_list() as $module) {
$function = $module . '_' . $hook;
if (function_exists($function)) {
$implementations[] = $module;
}
if ($sort) {
sort($implementations);
}
}
return $implementations;
}
/**
* Invoke a hook in a particular module.
*
......
......@@ -45,12 +45,12 @@ function _registry_rebuild() {
if ($module->status) {
$dir = dirname($module->filename);
foreach ($module->info['files'] as $file) {
$files["$dir/$file"] = array();
$files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
}
}
}
foreach (file_scan_directory('includes', '/\.inc$/') as $filename => $file) {
$files["$filename"] = array();
$files["$filename"] = array('module' => '', 'weight' => 0);
}
foreach (registry_get_parsed_files() as $filename => $file) {
......@@ -71,6 +71,7 @@ function _registry_rebuild() {
}
_registry_parse_files($files);
module_implements(MODULE_IMPLEMENTS_CLEAR_CACHE);
cache_clear_all('*', 'cache_registry', TRUE);
}
......@@ -99,7 +100,7 @@ function _registry_parse_files($files) {
if ($new_file || $md5 != $file['md5']) {
// We update the md5 after we've saved the files resources rather than here, so if we
// don't make it through this rebuild, the next run will reparse the file.
_registry_parse_file($filename, $contents);
_registry_parse_file($filename, $contents, $file['module'], $file['weight']);
$file['md5'] = $md5;
db_merge('registry_file')
->key(array('filename' => $filename))
......@@ -116,8 +117,12 @@ function _registry_parse_files($files) {
* Name of the file we are going to parse.
* @param $contents
* Contents of the file we are going to parse as a string.
* @param $module
* (optional) Name of the module this file belongs to.
* @param $weight
* (optional) Weight of the module.
*/
function _registry_parse_file($filename, $contents) {
function _registry_parse_file($filename, $contents, $module = '', $weight = 0) {
static $map = array(T_FUNCTION => 'function', T_CLASS => 'class', T_INTERFACE => 'interface');
// Delete registry entries for this file, so we can insert the new resources.
db_delete('registry')
......@@ -129,6 +134,21 @@ function _registry_parse_file($filename, $contents) {
if (is_array($token) && isset($map[$token[0]])) {
$type = $map[$token[0]];
if ($resource_name = _registry_get_resource_name($tokens, $type)) {
$suffix = '';
// Collect the part of the function name after the module name,
// so that we can query the registry for possible hook implementations.
if ($type == 'function' && !empty($module)) {
$n = strlen($module);
if (substr($resource_name, 0, $n) == $module) {
$suffix = substr($resource_name, $n + 1);
}
}
$fields = array(
'filename' => $filename,
'module' => $module,
'suffix' => $suffix,
'weight' => $weight,
);
// Because some systems, such as cache, currently use duplicate function
// names in separate files an insert query cannot be used here as it
// would cause a key constraint violation. Instead we use a merge query.
......@@ -140,7 +160,7 @@ function _registry_parse_file($filename, $contents) {
// function names have been purged from Drupal.
db_merge('registry')
->key(array('name' => $resource_name, 'type' => $type))
->fields(array('filename' => $filename))
->fields($fields)
->execute();
// We skip the body because classes may contain functions.
......
......@@ -122,11 +122,9 @@ class RegistryParseFilesTestCase extends DrupalWebTestCase {
function getFiles() {
$files = array();
foreach ($this->fileTypes as $fileType) {
$files[$this->$fileType->fileName] = array('module' => '', 'weight' => 0);
if ($fileType == 'existing_changed') {
$files[$this->$fileType->fileName] = array('md5' => $this->$fileType->fakeMD5);
}
else {
$files[$this->$fileType->fileName] = array();
$files[$this->$fileType->fileName]['md5'] = $this->$fileType->fakeMD5;
}
}
return $files;
......
......@@ -1092,8 +1092,31 @@ function system_schema() {
'length' => 255,
'not null' => TRUE,
),
'module' => array(
'description' => t('Name of the module the file belongs to.'),
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => ''
),
'suffix' => array(
'description' => t("The part of the function name after the module, which is the hook this function implements, if any."),
'type' => 'varchar',
'length' => 68,
'not null' => TRUE,
'default' => ''
),
'weight' => array(
'description' => t("The order in which this module's hooks should be invoked relative to other modules. Equal-weighted modules are ordered by name."),
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('name', 'type'),
'indexes' => array(
'hook' => array('type', 'suffix', 'weight', 'module'),
),
);
$schema['registry_file'] = array(
......@@ -2931,8 +2954,14 @@ function system_update_7006() {
'name' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
'type' => array('type' => 'varchar', 'length' => 9, 'not null' => TRUE, 'default' => ''),
'filename' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
'module' => array('type' => 'varchar', 'length' => 255, 'not null' => TRUE, 'default' => ''),
'suffix' => array('type' => 'varchar', 'length' => 69, 'not null' => TRUE, 'default' => ''),
'weight' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
),
'primary key' => array('name', 'type'),
'indexes' => array(
'hook' => array('type', 'suffix', 'weight', 'module'),
),
);
$schema['registry_file'] = array(
'fields' => array(
......
......@@ -935,7 +935,7 @@ function system_check_directory($form_element) {
*/
function system_get_files_database(&$files, $type) {
// Extract current files from database.
$result = db_query("SELECT filename, name, type, status, schema_version FROM {system} WHERE type = '%s'", $type);
$result = db_query("SELECT filename, name, type, status, schema_version, weight FROM {system} WHERE type = '%s'", $type);
while ($file = db_fetch_object($result)) {
if (isset($files[$file->name]) && is_object($files[$file->name])) {
$file->old_filename = $file->filename;
......
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