Package base :: Package includes :: Module plugin
[hide private]

Source Code for Module base.includes.plugin

  1  #!/usr/bin/env python 
  2  # $Id: module.inc,v 1.122 2008/08/02 19:01:02 dries Exp $ 
  3   
  4  """ 
  5    API for loading and interacting with Drupal plugins. 
  6   
  7    @package includes 
  8    @see <a href='http://drupy.net'>Drupy Homepage</a> 
  9    @see <a href='http://drupal.org'>Drupal Homepage</a> 
 10    @note Drupy is a port of the Drupal project. 
 11    @note This file was ported from Drupal's includes/module.inc 
 12    @author Brendon Crawford 
 13    @copyright 2008 Brendon Crawford 
 14    @contact message144 at users dot sourceforge dot net 
 15    @created 2008-01-10 
 16    @version 0.1 
 17    @note License: 
 18   
 19      This program is free software; you can redistribute it and/or 
 20      modify it under the terms of the GNU General Public License 
 21      as published by the Free Software Foundation; either version 2 
 22      of the License, or (at your option) any later version. 
 23   
 24      This program is distributed in the hope that it will be useful, 
 25      but WITHOUT ANY WARRANTY; without even the implied warranty of 
 26      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 27      GNU General Public License for more details. 
 28   
 29      You should have received a copy of the GNU General Public License 
 30      along with this program; if not, write to: 
 31       
 32      The Free Software Foundation, Inc., 
 33      51 Franklin Street, Fifth Floor, 
 34      Boston, MA  02110-1301, 
 35      USA 
 36  """ 
 37   
 38  __version__ = "$Revision: 1 $" 
 39   
 40  from lib.drupy import DrupyPHP as php 
 41  from lib.drupy import DrupyImport 
 42  import bootstrap as lib_bootstrap 
 43  import database as lib_database 
 44  import cache as lib_cache 
 45  #import install as inc_install 
 46   
 47  # 
 48  # Internal use 
 49  # 
 50  plugins = {} 
 51   
52 -def load_all():
53 """ 54 Load all the plugins that have been enabled in the system table. 55 """ 56 for plugin_ in list_(True, False): 57 lib_bootstrap.drupal_load('plugin', plugin_)
58 59 60
61 -def list_(refresh = False, bootstrap = True, sort = False, \ 62 fixed_list = None):
63 """ 64 Collect a list of all loaded plugins. During the bootstrap, return only 65 vital plugins. See bootstrap.inc 66 67 @param refresh 68 Whether to force the plugin list to be regenerated (such as after the 69 administrator has changed the system settings). 70 @param bootstrap 71 Whether to return the reduced set of plugins loaded in "bootstrap mode" 72 for cached pages. See bootstrap.inc. 73 @param sort 74 By default, plugins are ordered by weight and filename, 75 settings this option 76 to True, plugin list will be ordered by plugin name. 77 @param fixed_list 78 (Optional) Override the plugin list with the given plugins. 79 Stays until the 80 next call with refresh = True. 81 @return 82 An associative array whose keys and values are the names of all loaded 83 plugins. 84 """ 85 php.static(list_, 'list_', {}) 86 php.static(list_, 'sorted_list') 87 if (refresh or fixed_list): 88 list_.sorted_list = None 89 list_.list_ = {} 90 if (fixed_list): 91 for name,plugin in fixed_list.items(): 92 lib_bootstrap.drupal_get_filename('plugin', name, plugin['filename']) 93 list_.list_[name] = name 94 else: 95 if (bootstrap): 96 result = lib_database.query(\ 97 "SELECT name, filename FROM {system} " + \ 98 "WHERE type = 'plugin' AND status = 1 AND " + \ 99 "bootstrap = 1 ORDER BY weight ASC, filename ASC") 100 else: 101 result = lib_database.query(\ 102 "SELECT name, filename FROM {system} " + \ 103 "WHERE type = 'plugin' AND status = 1 " + \ 104 "ORDER BY weight ASC, filename ASC") 105 while True: 106 plugin_ = lib_database.fetch_object(result) 107 if (plugin_ == None or plugin_ == False): 108 break 109 if (DrupyImport.exists(plugin_.filename)): 110 lib_bootstrap.drupal_get_filename('plugin', \ 111 plugin_.name, plugin_.filename) 112 list_.list_[plugin_.name] = plugin_.name 113 if (sort): 114 if (list_.sorted_list == None): 115 list_.sorted_list = plugin_list.list_ 116 p.ksort(list_.sorted_list) 117 return list_.sorted_list 118 return list_.list_
119 120 121
122 -def rebuild_cache():
123 """ 124 Rebuild the database cache of plugin files. 125 126 @return 127 The array of filesystem objects used to rebuild the cache. 128 """ 129 # Get current list of plugins 130 files = drupal_system_listing('\.plugin$', 'plugins', 'name', 0) 131 # Extract current files from database. 132 system_get_files_database(files, 'plugin') 133 ksort(files) 134 # Set defaults for plugin info 135 defaults = { 136 'dependencies' : [], 137 'dependents' : [], 138 'description' : '', 139 'version' : None, 140 'php' : DRUPAL_MINIMUM_PHP, 141 } 142 for filename,file in files.items(): 143 # Look for the info file. 144 file.info = drupal_parse_info_file(php.dirname(file.filename) + '/' + \ 145 file.name + '.info') 146 # Skip plugins that don't provide info. 147 if (php.empty(file.info)): 148 del(files[filename]) 149 continue 150 # Merge in defaults and save. 151 files[filename].info = file.info + defaults 152 # Invoke hook_system_info_alter() to give installed plugins a chance to 153 # modify the data in the .info files if necessary. 154 drupal_alter('system_info', files[filename].info, files[filename]) 155 # Log the critical hooks implemented by this plugin. 156 bootstrap = 0 157 for hook in bootstrap_hooks(): 158 if (plugin_hook(file.name, hook)): 159 bootstrap = 1 160 break 161 # Update the contents of the system table: 162 if (php.isset(file, 'status') or (php.isset(file, 'old_filename') and \ 163 file.old_filename != file.filename)): 164 db_query(\ 165 "UPDATE {system} SET info = '%s', name = '%s', " + \ 166 "filename = '%s', bootstrap = %d WHERE filename = '%s'", \ 167 php.serialize(files[filename].info), file.name, \ 168 file.filename, bootstrap, file.old_filename) 169 else: 170 # This is a new plugin. 171 files[filename].status = 0 172 db_query(\ 173 "INSERT INTO {system} (name, info, type, " + \ 174 "filename, status, bootstrap) VALUES " + \ 175 "('%s', '%s', '%s', '%s', %d, %d)", \ 176 file.name, php.serialize(files[filename].info), \ 177 'plugin', file.filename, 0, bootstrap) 178 files = _plugin_build_dependencies(files) 179 return files
180 181 182
183 -def _build_dependencies(files):
184 """ 185 Find dependencies any level deep and fill in dependents information too. 186 187 If plugin A depends on B which in turn depends on C then this function will 188 add C to the list of plugins A depends on. This will be repeated until 189 plugin A has a list of all plugins it depends on. If it depends on itself, 190 called a circular dependency, that's marked by adding a nonexistent plugin, 191 called -circular- to this list of plugins. Because this does not exist, 192 it'll be impossible to switch plugin A on. 193 194 Also we fill in a dependents array in file.info. Using the names above, 195 the dependents array of plugin B lists A. 196 197 @param files 198 The array of filesystem objects used to rebuild the cache. 199 @return 200 The same array with dependencies and dependents added where applicable. 201 """ 202 while True: 203 new_dependency = False 204 for filename,file in files.items(): 205 # We will modify this object (plugin A, see doxygen for plugin A, B, C). 206 file = files[filename] 207 if (php.isset(file.info, 'dependencies') and \ 208 php.is_array(file.info, 'dependencies')): 209 for dependency_name in file.info['dependencies']: 210 # This is a nonexistent plugin. 211 if (dependency_name == '-circular-' or \ 212 not php.isset(files[dependency_name])): 213 continue 214 # dependency_name is plugin B (again, see doxygen). 215 files[dependency_name].info['dependents'][filename] = filename 216 dependency = files[dependency_name] 217 if (php.isset(dependency.info['dependencies']) and \ 218 php.is_array(dependency.info['dependencies'])): 219 # Let's find possible C plugins. 220 for candidate in dependency.info['dependencies']: 221 if (array_search(candidate, file.info['dependencies']) == False): 222 # Is this a circular dependency? 223 if (candidate == filename): 224 # As a plugin name can not contain dashes, this makes 225 # impossible to switch on the plugin. 226 candidate = '-circular-' 227 # Do not display the message or add -circular- 228 # more than once. 229 if (array_search(candidate, \ 230 file.info['dependencies']) != False): 231 continue 232 drupal_set_message(\ 233 t('%plugin is part of a circular dependency. ' + \ 234 'This is not supported and you will not ' + \ 235 'be able to switch it on.', \ 236 {'%plugin' : file.info['name']}), 'error') 237 else: 238 # We added a new dependency to plugin A. The next loop will 239 # be able to use this as "B plugin" thus finding even 240 # deeper dependencies. 241 new_dependency = True 242 file.info['dependencies'].append( candidate ) 243 # Don't forget to break the reference. 244 del(file) 245 if (not new_dependency): 246 break 247 return files
248 249 250
251 -def exists(plugin_):
252 """ 253 Determine whether a given plugin exists. 254 255 @param plugin 256 The name of the plugin (without the .plugin extension). 257 @return 258 True if the plugin is both installed and enabled. 259 """ 260 list_ = plugin_list() 261 return php.isset(list_, plugin_)
262 263
264 -def load_install(plugin_):
265 """ 266 Load a plugin's installation hooks. 267 """ 268 # Make sure the installation API is available 269 plugin_load_include('install', plugin_)
270 271 272
273 -def load_include(type_, plugin_, name = None):
274 """ 275 Load a plugin include file. 276 277 @param type 278 The include file's type (file extension). 279 @param plugin 280 The plugin to which the include file belongs. 281 @param name 282 Optionally, specify the file name. If not set, the plugin's name is used. 283 """ 284 if (php.empty(name)): 285 name = plugin_ 286 file = './' + drupal_get_path('plugin', plugin) + "/name.type" 287 if (php.is_file(file)): 288 php.require_once( file ) 289 return file 290 else: 291 return False
292 293 294
295 -def load_all_includes(type_, name = None):
296 """ 297 Load an include file for each of the plugins that have been enabled in 298 the system table. 299 """ 300 plugins = plugin_list() 301 for plugin_ in plugins: 302 plugin_load_include(type_, plugin_, name)
303 304 305
306 -def enable(plugin_list_):
307 """ 308 Enable a given list of plugins. 309 310 @param plugin_list 311 An array of plugin names. 312 """ 313 invoke_plugins = [] 314 for plugin_ in plugin_list_: 315 existing = db_fetch_object(db_query(\ 316 "SELECT status FROM {system} " + \ 317 "WHERE type = '%s' AND name = '%s'", 'plugin', plugin)) 318 if (existing.status == 0): 319 plugin_load_install(plugin_) 320 db_query(\ 321 "UPDATE {system} SET status = %d " + \ 322 "WHERE type = '%s' AND name = '%s'", 1, 'plugin', plugin_) 323 drupal_load('plugin', plugin_) 324 invoke_plugins.append( plugin ) 325 if (not php.empty(invoke_plugins)): 326 # Refresh the plugin list to include the new enabled plugin. 327 plugin_list(True, False) 328 # Force to regenerate the stored list of hook implementations. 329 registry_rebuild() 330 for plugin_ in invoke_plugins: 331 plugin_invoke(plugin_, 'enable') 332 # Check if node_access table needs rebuilding. 333 # We check for the existence of node_access_needs_rebuild() since 334 # at install time, plugin_enable() could be called while node.plugin 335 # is not enabled yet. 336 if (drupal_function_exists('node_access_needs_rebuild') and \ 337 not node_access_needs_rebuild() and \ 338 plugin_hook(plugin_, 'node_grants')): 339 node_access_needs_rebuild(True)
340 341 342
343 -def disable(plugin_list_):
344 """ 345 Disable a given set of plugins. 346 347 @param plugin_list 348 An array of plugin names. 349 """ 350 invoke_plugins = [] 351 for plugin_ in plugin_list_: 352 if (plugin_exists(plugin_)): 353 # Check if node_access table needs rebuilding. 354 if (not node_access_needs_rebuild() and plugin_hook(plugin_, \ 355 'node_grants')): 356 node_access_needs_rebuild(True) 357 plugin_load_install(plugin_) 358 plugin_invoke(plugin_, 'disable') 359 db_query(\ 360 "UPDATE {system} SET status = %d " + \ 361 "WHERE type = '%s' AND name = '%s'", 0, 'plugin', plugin_) 362 invoke_plugins.append(plugin) 363 if (not php.empty(invoke_plugins)): 364 # Refresh the plugin list to exclude the disabled plugins. 365 plugin_list(True, False) 366 # Force to regenerate the stored list of hook implementations. 367 registry_rebuild() 368 # If there remains no more node_access plugin, rebuilding will be 369 # straightforward, we can do it right now. 370 if (node_access_needs_rebuild() and \ 371 php.count(plugin_implements('node_grants')) == 0): 372 node_access_rebuild()
373 374 375 376 # 377 # @defgroup hooks Hooks 378 # @{ 379 # 380 # Allow plugins to interact with the Drupal core. 381 # 382 # Drupal's plugin system is based on the concept of "hooks". A hook is a PHP 383 # function that is named foo_bar(), where "foo" 384 # is the name of the plugin (whose 385 # filename is thus foo.plugin) and "bar" is the name of the hook. Each hook has 386 # a defined set of parameters and a specified result type. 387 # 388 # To extend Drupal, a plugin need simply implement a hook. When Drupal 389 # wishes to 390 # allow intervention from plugins, it determines which plugins implement a hook 391 # and call that hook in all enabled plugins that implement it. 392 # 393 # The available hooks to implement are explained here in the Hooks section of 394 # the developer documentation. The string "hook" is used as a placeholder for 395 # the plugin name is the hook definitions. For example, if the plugin file is 396 # called example.plugin, then hook_help() as implemented by that plugin 397 # would be 398 # defined as example_help(). 399 # 400 401
402 -def hook(plugin_, hook_):
403 """ 404 Determine whether a plugin implements a hook. 405 406 @param plugin 407 The name of the plugin (without the .plugin extension). 408 @param hook 409 The name of the hook (e.g. "help" or "menu"). 410 @return 411 True if the plugin is both installed and enabled, and the hook is 412 implemented in that plugin. 413 """ 414 global plugins 415 function = hook_; 416 if (lib_bootstrap.MAINTENANCE_MODE is True): 417 return php.function_exists(function, plugins[plugin_]) 418 else: 419 return lib_bootstrap.drupal_function_exists(function, plugins[plugin_]);
420 421 422
423 -def implements(hook_, sort = False, refresh = False):
424 """ 425 Determine which plugins are implementing a hook. 426 427 @param hook 428 The name of the hook (e.g. "help" or "menu"). 429 @param sort 430 By default, plugins are ordered by weight and filename, 431 settings this option 432 to True, plugin list will be ordered by plugin name. 433 @param refresh 434 For internal use only: Whether to force the stored list of hook 435 implementations to be regenerated (such as after enabling a new plugin, 436 before processing hook_enable). 437 @return 438 An array with the names of the plugins which are implementing this hook. 439 """ 440 php.static(implements, 'implementations', {}) 441 if (refresh): 442 implements.implementations = {} 443 elif (not lib_bootstrap.MAINTENANCE_MODE and \ 444 php.empty(implements.implementations)): 445 cache = lib_cache.get('hooks', 'cache_registry') 446 if (cache): 447 implements.implementations = cache.data; 448 implements.implementations = \ 449 lib_bootstrap.registry_get_hook_implementations_cache() 450 if (not php.isset(implements.implementations, hook)): 451 implements.implementations[hook_] = [] 452 for plugin_ in list_(): 453 if (hook(plugin_, hook_)): 454 implements.implementations[hook_].append( plugin_ ) 455 lib_bootstrap.registry_cache_hook_implementations({'hook' : hook_, \ 456 'plugins' : implements.implementations[hook_]}); 457 # The explicit cast forces a copy to be made. This is needed because 458 # implementations[hook] is only a reference to an element of 459 # implementations and if there are nested foreaches (due to nested node 460 # API calls, for example), they would both manipulate the same array's 461 # references, which causes some plugins' hooks not to be called. 462 # See also http://www.zend.com/zend/art/ref-count.php. 463 return implements.implementations[hook_]
464 465 466
467 -def invoke(plugin_, hook_, *args):
468 """ 469 Invoke a hook in a particular plugin. 470 471 @param plugin 472 The name of the plugin (without the .plugin extension). 473 @param hook 474 The name of the hook to invoke. 475 @param ... 476 Arguments to pass to the hook implementation. 477 @return 478 The return value of the hook implementation. 479 """ 480 if (hook(plugin_, hook_)): 481 function_name = 'hook_' + hook_ 482 function = DrupyImport.getFunction(plugins[plugin_], function_name) 483 return php.call_user_func_array(function, args)
484 485 486
487 -def invoke_all(*args):
488 """ 489 Invoke a hook in all enabled plugins that implement it. 490 491 @param hook 492 The name of the hook to invoke. 493 @param ... 494 Arguments to pass to the hook. 495 @return 496 An array of return values of the hook implementations. If plugins return 497 arrays from their implementations, those are merged into one array. 498 """ 499 global plugins 500 args = list(args) 501 hook = 'hook_%s' % args[0] 502 del(args[0]) 503 return_ = [] 504 for plugin_ in implements(hook): 505 if (lib_bootstrap.drupal_function_exists(hook, plugins[plugin_])): 506 function = DrupyImport.getFunction(\ 507 plugins[plugin_], hook) 508 result = php.call_user_func_array(function, args); 509 if (result is not None and php.is_array(result)): 510 return_ = p.array_merge_recursive(return_, result); 511 elif (result is not None): 512 return_.append( result ); 513 return return_
514 515 516 # 517 # @} End of "defgroup hooks". 518 # 519
520 -def drupal_required_plugins():
521 """ 522 Array of plugins required by core. 523 """ 524 return ('block', 'filter', 'node', 'system', 'user')
525