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