annotate includes/module.inc @ 20:e3d20ebd63d1 tip

Added tag 6.9 for changeset 3edae6ecd6c6
author Franck Deroche <franck@defr.org>
date Thu, 15 Jan 2009 10:16:10 +0100
parents c1f4ac30525a
children
rev   line source
webmaster@1 1 <?php
webmaster@1 2 // $Id: module.inc,v 1.115 2007/12/27 12:31:05 goba Exp $
webmaster@1 3
webmaster@1 4 /**
webmaster@1 5 * @file
webmaster@1 6 * API for loading and interacting with Drupal modules.
webmaster@1 7 */
webmaster@1 8
webmaster@1 9 /**
webmaster@1 10 * Load all the modules that have been enabled in the system table.
webmaster@1 11 */
webmaster@1 12 function module_load_all() {
webmaster@1 13 foreach (module_list(TRUE, FALSE) as $module) {
webmaster@1 14 drupal_load('module', $module);
webmaster@1 15 }
webmaster@1 16 }
webmaster@1 17
webmaster@1 18 /**
webmaster@1 19 * Call a function repeatedly with each module in turn as an argument.
webmaster@1 20 */
webmaster@1 21 function module_iterate($function, $argument = '') {
webmaster@1 22 foreach (module_list() as $name) {
webmaster@1 23 $function($name, $argument);
webmaster@1 24 }
webmaster@1 25 }
webmaster@1 26
webmaster@1 27 /**
webmaster@1 28 * Collect a list of all loaded modules. During the bootstrap, return only
webmaster@1 29 * vital modules. See bootstrap.inc
webmaster@1 30 *
webmaster@1 31 * @param $refresh
webmaster@1 32 * Whether to force the module list to be regenerated (such as after the
webmaster@1 33 * administrator has changed the system settings).
webmaster@1 34 * @param $bootstrap
webmaster@1 35 * Whether to return the reduced set of modules loaded in "bootstrap mode"
webmaster@1 36 * for cached pages. See bootstrap.inc.
webmaster@1 37 * @param $sort
webmaster@1 38 * By default, modules are ordered by weight and filename, settings this option
webmaster@1 39 * to TRUE, module list will be ordered by module name.
webmaster@1 40 * @param $fixed_list
webmaster@1 41 * (Optional) Override the module list with the given modules. Stays until the
webmaster@1 42 * next call with $refresh = TRUE.
webmaster@1 43 * @return
webmaster@1 44 * An associative array whose keys and values are the names of all loaded
webmaster@1 45 * modules.
webmaster@1 46 */
webmaster@1 47 function module_list($refresh = FALSE, $bootstrap = TRUE, $sort = FALSE, $fixed_list = NULL) {
webmaster@1 48 static $list, $sorted_list;
webmaster@1 49
webmaster@1 50 if ($refresh || $fixed_list) {
webmaster@1 51 unset($sorted_list);
webmaster@1 52 $list = array();
webmaster@1 53 if ($fixed_list) {
webmaster@1 54 foreach ($fixed_list as $name => $module) {
webmaster@1 55 drupal_get_filename('module', $name, $module['filename']);
webmaster@1 56 $list[$name] = $name;
webmaster@1 57 }
webmaster@1 58 }
webmaster@1 59 else {
webmaster@1 60 if ($bootstrap) {
webmaster@1 61 $result = db_query("SELECT name, filename, throttle FROM {system} WHERE type = 'module' AND status = 1 AND bootstrap = 1 ORDER BY weight ASC, filename ASC");
webmaster@1 62 }
webmaster@1 63 else {
webmaster@1 64 $result = db_query("SELECT name, filename, throttle FROM {system} WHERE type = 'module' AND status = 1 ORDER BY weight ASC, filename ASC");
webmaster@1 65 }
webmaster@1 66 while ($module = db_fetch_object($result)) {
webmaster@1 67 if (file_exists($module->filename)) {
webmaster@1 68 // Determine the current throttle status and see if the module should be
webmaster@1 69 // loaded based on server load. We have to directly access the throttle
webmaster@1 70 // variables, since throttle.module may not be loaded yet.
webmaster@1 71 $throttle = ($module->throttle && variable_get('throttle_level', 0) > 0);
webmaster@1 72 if (!$throttle) {
webmaster@1 73 drupal_get_filename('module', $module->name, $module->filename);
webmaster@1 74 $list[$module->name] = $module->name;
webmaster@1 75 }
webmaster@1 76 }
webmaster@1 77 }
webmaster@1 78 }
webmaster@1 79 }
webmaster@1 80 if ($sort) {
webmaster@1 81 if (!isset($sorted_list)) {
webmaster@1 82 $sorted_list = $list;
webmaster@1 83 ksort($sorted_list);
webmaster@1 84 }
webmaster@1 85 return $sorted_list;
webmaster@1 86 }
webmaster@1 87 return $list;
webmaster@1 88 }
webmaster@1 89
webmaster@1 90 /**
webmaster@1 91 * Rebuild the database cache of module files.
webmaster@1 92 *
webmaster@1 93 * @return
webmaster@1 94 * The array of filesystem objects used to rebuild the cache.
webmaster@1 95 */
webmaster@1 96 function module_rebuild_cache() {
webmaster@1 97 // Get current list of modules
webmaster@1 98 $files = drupal_system_listing('\.module$', 'modules', 'name', 0);
webmaster@1 99
webmaster@1 100 // Extract current files from database.
webmaster@1 101 system_get_files_database($files, 'module');
webmaster@1 102
webmaster@1 103 ksort($files);
webmaster@1 104
webmaster@1 105 // Set defaults for module info
webmaster@1 106 $defaults = array(
webmaster@1 107 'dependencies' => array(),
webmaster@1 108 'dependents' => array(),
webmaster@1 109 'description' => '',
webmaster@1 110 'version' => NULL,
webmaster@1 111 'php' => DRUPAL_MINIMUM_PHP,
webmaster@1 112 );
webmaster@1 113
webmaster@1 114 foreach ($files as $filename => $file) {
webmaster@1 115 // Look for the info file.
webmaster@1 116 $file->info = drupal_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info');
webmaster@1 117
webmaster@1 118 // Skip modules that don't provide info.
webmaster@1 119 if (empty($file->info)) {
webmaster@1 120 unset($files[$filename]);
webmaster@1 121 continue;
webmaster@1 122 }
webmaster@1 123 // Merge in defaults and save.
webmaster@1 124 $files[$filename]->info = $file->info + $defaults;
webmaster@1 125
webmaster@1 126 // Invoke hook_system_info_alter() to give installed modules a chance to
webmaster@1 127 // modify the data in the .info files if necessary.
webmaster@1 128 drupal_alter('system_info', $files[$filename]->info, $files[$filename]);
webmaster@1 129
webmaster@1 130 // Log the critical hooks implemented by this module.
webmaster@1 131 $bootstrap = 0;
webmaster@1 132 foreach (bootstrap_hooks() as $hook) {
webmaster@1 133 if (module_hook($file->name, $hook)) {
webmaster@1 134 $bootstrap = 1;
webmaster@1 135 break;
webmaster@1 136 }
webmaster@1 137 }
webmaster@1 138
webmaster@1 139 // Update the contents of the system table:
webmaster@1 140 if (isset($file->status) || (isset($file->old_filename) && $file->old_filename != $file->filename)) {
webmaster@1 141 db_query("UPDATE {system} SET info = '%s', name = '%s', filename = '%s', bootstrap = %d WHERE filename = '%s'", serialize($files[$filename]->info), $file->name, $file->filename, $bootstrap, $file->old_filename);
webmaster@1 142 }
webmaster@1 143 else {
webmaster@1 144 // This is a new module.
webmaster@1 145 $files[$filename]->status = 0;
webmaster@1 146 $files[$filename]->throttle = 0;
webmaster@1 147 db_query("INSERT INTO {system} (name, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, serialize($files[$filename]->info), 'module', $file->filename, 0, 0, $bootstrap);
webmaster@1 148 }
webmaster@1 149 }
webmaster@1 150 $files = _module_build_dependencies($files);
webmaster@1 151 return $files;
webmaster@1 152 }
webmaster@1 153
webmaster@1 154 /**
webmaster@1 155 * Find dependencies any level deep and fill in dependents information too.
webmaster@1 156 *
webmaster@1 157 * If module A depends on B which in turn depends on C then this function will
webmaster@1 158 * add C to the list of modules A depends on. This will be repeated until
webmaster@1 159 * module A has a list of all modules it depends on. If it depends on itself,
webmaster@1 160 * called a circular dependency, that's marked by adding a nonexistent module,
webmaster@1 161 * called -circular- to this list of modules. Because this does not exist,
webmaster@1 162 * it'll be impossible to switch module A on.
webmaster@1 163 *
webmaster@1 164 * Also we fill in a dependents array in $file->info. Using the names above,
webmaster@1 165 * the dependents array of module B lists A.
webmaster@1 166 *
webmaster@1 167 * @param $files
webmaster@1 168 * The array of filesystem objects used to rebuild the cache.
webmaster@1 169 * @return
webmaster@1 170 * The same array with dependencies and dependents added where applicable.
webmaster@1 171 */
webmaster@1 172 function _module_build_dependencies($files) {
webmaster@1 173 do {
webmaster@1 174 $new_dependency = FALSE;
webmaster@1 175 foreach ($files as $filename => $file) {
webmaster@1 176 // We will modify this object (module A, see doxygen for module A, B, C).
webmaster@1 177 $file = &$files[$filename];
webmaster@1 178 if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
webmaster@1 179 foreach ($file->info['dependencies'] as $dependency_name) {
webmaster@1 180 // This is a nonexistent module.
webmaster@1 181 if ($dependency_name == '-circular-' || !isset($files[$dependency_name])) {
webmaster@1 182 continue;
webmaster@1 183 }
webmaster@1 184 // $dependency_name is module B (again, see doxygen).
webmaster@1 185 $files[$dependency_name]->info['dependents'][$filename] = $filename;
webmaster@1 186 $dependency = $files[$dependency_name];
webmaster@1 187 if (isset($dependency->info['dependencies']) && is_array($dependency->info['dependencies'])) {
webmaster@1 188 // Let's find possible C modules.
webmaster@1 189 foreach ($dependency->info['dependencies'] as $candidate) {
webmaster@1 190 if (array_search($candidate, $file->info['dependencies']) === FALSE) {
webmaster@1 191 // Is this a circular dependency?
webmaster@1 192 if ($candidate == $filename) {
webmaster@1 193 // As a module name can not contain dashes, this makes
webmaster@1 194 // impossible to switch on the module.
webmaster@1 195 $candidate = '-circular-';
webmaster@1 196 // Do not display the message or add -circular- more than once.
webmaster@1 197 if (array_search($candidate, $file->info['dependencies']) !== FALSE) {
webmaster@1 198 continue;
webmaster@1 199 }
webmaster@1 200 drupal_set_message(t('%module is part of a circular dependency. This is not supported and you will not be able to switch it on.', array('%module' => $file->info['name'])), 'error');
webmaster@1 201 }
webmaster@1 202 else {
webmaster@1 203 // We added a new dependency to module A. The next loop will
webmaster@1 204 // be able to use this as "B module" thus finding even
webmaster@1 205 // deeper dependencies.
webmaster@1 206 $new_dependency = TRUE;
webmaster@1 207 }
webmaster@1 208 $file->info['dependencies'][] = $candidate;
webmaster@1 209 }
webmaster@1 210 }
webmaster@1 211 }
webmaster@1 212 }
webmaster@1 213 }
webmaster@1 214 // Don't forget to break the reference.
webmaster@1 215 unset($file);
webmaster@1 216 }
webmaster@1 217 } while ($new_dependency);
webmaster@1 218 return $files;
webmaster@1 219 }
webmaster@1 220
webmaster@1 221 /**
webmaster@1 222 * Determine whether a given module exists.
webmaster@1 223 *
webmaster@1 224 * @param $module
webmaster@1 225 * The name of the module (without the .module extension).
webmaster@1 226 * @return
webmaster@1 227 * TRUE if the module is both installed and enabled.
webmaster@1 228 */
webmaster@1 229 function module_exists($module) {
webmaster@1 230 $list = module_list();
webmaster@1 231 return array_key_exists($module, $list);
webmaster@1 232 }
webmaster@1 233
webmaster@1 234 /**
webmaster@1 235 * Load a module's installation hooks.
webmaster@1 236 */
webmaster@1 237 function module_load_install($module) {
webmaster@1 238 // Make sure the installation API is available
webmaster@1 239 include_once './includes/install.inc';
webmaster@1 240
webmaster@1 241 module_load_include('install', $module);
webmaster@1 242 }
webmaster@1 243
webmaster@1 244 /**
webmaster@1 245 * Load a module include file.
webmaster@1 246 *
webmaster@1 247 * @param $type
webmaster@1 248 * The include file's type (file extension).
webmaster@1 249 * @param $module
webmaster@1 250 * The module to which the include file belongs.
webmaster@1 251 * @param $name
webmaster@1 252 * Optionally, specify the file name. If not set, the module's name is used.
webmaster@1 253 */
webmaster@1 254 function module_load_include($type, $module, $name = NULL) {
webmaster@1 255 if (empty($name)) {
webmaster@1 256 $name = $module;
webmaster@1 257 }
webmaster@1 258
webmaster@1 259 $file = './'. drupal_get_path('module', $module) ."/$name.$type";
webmaster@1 260
webmaster@1 261 if (is_file($file)) {
webmaster@1 262 require_once $file;
webmaster@1 263 }
webmaster@1 264 else {
webmaster@1 265 return FALSE;
webmaster@1 266 }
webmaster@1 267 }
webmaster@1 268
webmaster@1 269 /**
webmaster@1 270 * Load an include file for each of the modules that have been enabled in
webmaster@1 271 * the system table.
webmaster@1 272 */
webmaster@1 273 function module_load_all_includes($type, $name = NULL) {
webmaster@1 274 $modules = module_list();
webmaster@1 275 foreach ($modules as $module) {
webmaster@1 276 module_load_include($type, $module, $name);
webmaster@1 277 }
webmaster@1 278 }
webmaster@1 279
webmaster@1 280 /**
webmaster@1 281 * Enable a given list of modules.
webmaster@1 282 *
webmaster@1 283 * @param $module_list
webmaster@1 284 * An array of module names.
webmaster@1 285 */
webmaster@1 286 function module_enable($module_list) {
webmaster@1 287 $invoke_modules = array();
webmaster@1 288 foreach ($module_list as $module) {
webmaster@1 289 $existing = db_fetch_object(db_query("SELECT status FROM {system} WHERE type = '%s' AND name = '%s'", 'module', $module));
webmaster@1 290 if ($existing->status == 0) {
webmaster@1 291 module_load_install($module);
webmaster@1 292 db_query("UPDATE {system} SET status = %d, throttle = %d WHERE type = '%s' AND name = '%s'", 1, 0, 'module', $module);
webmaster@1 293 drupal_load('module', $module);
webmaster@1 294 $invoke_modules[] = $module;
webmaster@1 295 }
webmaster@1 296 }
webmaster@1 297
webmaster@1 298 if (!empty($invoke_modules)) {
webmaster@1 299 // Refresh the module list to include the new enabled module.
webmaster@1 300 module_list(TRUE, FALSE);
webmaster@1 301 // Force to regenerate the stored list of hook implementations.
webmaster@1 302 module_implements('', FALSE, TRUE);
webmaster@1 303 }
webmaster@1 304
webmaster@1 305 foreach ($invoke_modules as $module) {
webmaster@1 306 module_invoke($module, 'enable');
webmaster@1 307 // Check if node_access table needs rebuilding.
webmaster@1 308 // We check for the existence of node_access_needs_rebuild() since
webmaster@1 309 // at install time, module_enable() could be called while node.module
webmaster@1 310 // is not enabled yet.
webmaster@1 311 if (function_exists('node_access_needs_rebuild') && !node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
webmaster@1 312 node_access_needs_rebuild(TRUE);
webmaster@1 313 }
webmaster@1 314 }
webmaster@1 315 }
webmaster@1 316
webmaster@1 317 /**
webmaster@1 318 * Disable a given set of modules.
webmaster@1 319 *
webmaster@1 320 * @param $module_list
webmaster@1 321 * An array of module names.
webmaster@1 322 */
webmaster@1 323 function module_disable($module_list) {
webmaster@1 324 $invoke_modules = array();
webmaster@1 325 foreach ($module_list as $module) {
webmaster@1 326 if (module_exists($module)) {
webmaster@1 327 // Check if node_access table needs rebuilding.
webmaster@1 328 if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
webmaster@1 329 node_access_needs_rebuild(TRUE);
webmaster@1 330 }
webmaster@1 331
webmaster@1 332 module_load_install($module);
webmaster@1 333 module_invoke($module, 'disable');
webmaster@1 334 db_query("UPDATE {system} SET status = %d, throttle = %d WHERE type = '%s' AND name = '%s'", 0, 0, 'module', $module);
webmaster@1 335 $invoke_modules[] = $module;
webmaster@1 336 }
webmaster@1 337 }
webmaster@1 338
webmaster@1 339 if (!empty($invoke_modules)) {
webmaster@1 340 // Refresh the module list to exclude the disabled modules.
webmaster@1 341 module_list(TRUE, FALSE);
webmaster@1 342 // Force to regenerate the stored list of hook implementations.
webmaster@1 343 module_implements('', FALSE, TRUE);
webmaster@1 344 }
webmaster@1 345
webmaster@1 346 // If there remains no more node_access module, rebuilding will be
webmaster@1 347 // straightforward, we can do it right now.
webmaster@1 348 if (node_access_needs_rebuild() && count(module_implements('node_grants')) == 0) {
webmaster@1 349 node_access_rebuild();
webmaster@1 350 }
webmaster@1 351 }
webmaster@1 352
webmaster@1 353 /**
webmaster@1 354 * @defgroup hooks Hooks
webmaster@1 355 * @{
webmaster@1 356 * Allow modules to interact with the Drupal core.
webmaster@1 357 *
webmaster@1 358 * Drupal's module system is based on the concept of "hooks". A hook is a PHP
webmaster@1 359 * function that is named foo_bar(), where "foo" is the name of the module (whose
webmaster@1 360 * filename is thus foo.module) and "bar" is the name of the hook. Each hook has
webmaster@1 361 * a defined set of parameters and a specified result type.
webmaster@1 362 *
webmaster@1 363 * To extend Drupal, a module need simply implement a hook. When Drupal wishes to
webmaster@1 364 * allow intervention from modules, it determines which modules implement a hook
webmaster@1 365 * and call that hook in all enabled modules that implement it.
webmaster@1 366 *
webmaster@1 367 * The available hooks to implement are explained here in the Hooks section of
webmaster@1 368 * the developer documentation. The string "hook" is used as a placeholder for
webmaster@1 369 * the module name is the hook definitions. For example, if the module file is
webmaster@1 370 * called example.module, then hook_help() as implemented by that module would be
webmaster@1 371 * defined as example_help().
webmaster@1 372 */
webmaster@1 373
webmaster@1 374 /**
webmaster@1 375 * Determine whether a module implements a hook.
webmaster@1 376 *
webmaster@1 377 * @param $module
webmaster@1 378 * The name of the module (without the .module extension).
webmaster@1 379 * @param $hook
webmaster@1 380 * The name of the hook (e.g. "help" or "menu").
webmaster@1 381 * @return
webmaster@1 382 * TRUE if the module is both installed and enabled, and the hook is
webmaster@1 383 * implemented in that module.
webmaster@1 384 */
webmaster@1 385 function module_hook($module, $hook) {
webmaster@1 386 return function_exists($module .'_'. $hook);
webmaster@1 387 }
webmaster@1 388
webmaster@1 389 /**
webmaster@1 390 * Determine which modules are implementing a hook.
webmaster@1 391 *
webmaster@1 392 * @param $hook
webmaster@1 393 * The name of the hook (e.g. "help" or "menu").
webmaster@1 394 * @param $sort
webmaster@1 395 * By default, modules are ordered by weight and filename, settings this option
webmaster@1 396 * to TRUE, module list will be ordered by module name.
webmaster@1 397 * @param $refresh
webmaster@1 398 * For internal use only: Whether to force the stored list of hook
webmaster@1 399 * implementations to be regenerated (such as after enabling a new module,
webmaster@1 400 * before processing hook_enable).
webmaster@1 401 * @return
webmaster@1 402 * An array with the names of the modules which are implementing this hook.
webmaster@1 403 */
webmaster@1 404 function module_implements($hook, $sort = FALSE, $refresh = FALSE) {
webmaster@1 405 static $implementations;
webmaster@1 406
webmaster@1 407 if ($refresh) {
webmaster@1 408 $implementations = array();
webmaster@1 409 return;
webmaster@1 410 }
webmaster@1 411
webmaster@1 412 if (!isset($implementations[$hook])) {
webmaster@1 413 $implementations[$hook] = array();
webmaster@1 414 $list = module_list(FALSE, TRUE, $sort);
webmaster@1 415 foreach ($list as $module) {
webmaster@1 416 if (module_hook($module, $hook)) {
webmaster@1 417 $implementations[$hook][] = $module;
webmaster@1 418 }
webmaster@1 419 }
webmaster@1 420 }
webmaster@1 421
webmaster@1 422 // The explicit cast forces a copy to be made. This is needed because
webmaster@1 423 // $implementations[$hook] is only a reference to an element of
webmaster@1 424 // $implementations and if there are nested foreaches (due to nested node
webmaster@1 425 // API calls, for example), they would both manipulate the same array's
webmaster@1 426 // references, which causes some modules' hooks not to be called.
webmaster@1 427 // See also http://www.zend.com/zend/art/ref-count.php.
webmaster@1 428 return (array)$implementations[$hook];
webmaster@1 429 }
webmaster@1 430
webmaster@1 431 /**
webmaster@1 432 * Invoke a hook in a particular module.
webmaster@1 433 *
webmaster@1 434 * @param $module
webmaster@1 435 * The name of the module (without the .module extension).
webmaster@1 436 * @param $hook
webmaster@1 437 * The name of the hook to invoke.
webmaster@1 438 * @param ...
webmaster@1 439 * Arguments to pass to the hook implementation.
webmaster@1 440 * @return
webmaster@1 441 * The return value of the hook implementation.
webmaster@1 442 */
webmaster@1 443 function module_invoke() {
webmaster@1 444 $args = func_get_args();
webmaster@1 445 $module = $args[0];
webmaster@1 446 $hook = $args[1];
webmaster@1 447 unset($args[0], $args[1]);
webmaster@1 448 $function = $module .'_'. $hook;
webmaster@1 449 if (module_hook($module, $hook)) {
webmaster@1 450 return call_user_func_array($function, $args);
webmaster@1 451 }
webmaster@1 452 }
webmaster@1 453 /**
webmaster@1 454 * Invoke a hook in all enabled modules that implement it.
webmaster@1 455 *
webmaster@1 456 * @param $hook
webmaster@1 457 * The name of the hook to invoke.
webmaster@1 458 * @param ...
webmaster@1 459 * Arguments to pass to the hook.
webmaster@1 460 * @return
webmaster@1 461 * An array of return values of the hook implementations. If modules return
webmaster@1 462 * arrays from their implementations, those are merged into one array.
webmaster@1 463 */
webmaster@1 464 function module_invoke_all() {
webmaster@1 465 $args = func_get_args();
webmaster@1 466 $hook = $args[0];
webmaster@1 467 unset($args[0]);
webmaster@1 468 $return = array();
webmaster@1 469 foreach (module_implements($hook) as $module) {
webmaster@1 470 $function = $module .'_'. $hook;
webmaster@1 471 $result = call_user_func_array($function, $args);
webmaster@1 472 if (isset($result) && is_array($result)) {
webmaster@1 473 $return = array_merge_recursive($return, $result);
webmaster@1 474 }
webmaster@1 475 else if (isset($result)) {
webmaster@1 476 $return[] = $result;
webmaster@1 477 }
webmaster@1 478 }
webmaster@1 479
webmaster@1 480 return $return;
webmaster@1 481 }
webmaster@1 482
webmaster@1 483 /**
webmaster@1 484 * @} End of "defgroup hooks".
webmaster@1 485 */
webmaster@1 486
webmaster@1 487 /**
webmaster@1 488 * Array of modules required by core.
webmaster@1 489 */
webmaster@1 490 function drupal_required_modules() {
webmaster@1 491 return array('block', 'filter', 'node', 'system', 'user');
webmaster@1 492 }