1
2
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
46
47
48
49
50 plugins = {}
51
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
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
130 files = drupal_system_listing('\.plugin$', 'plugins', 'name', 0)
131
132 system_get_files_database(files, 'plugin')
133 ksort(files)
134
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
144 file.info = drupal_parse_info_file(php.dirname(file.filename) + '/' + \
145 file.name + '.info')
146
147 if (php.empty(file.info)):
148 del(files[filename])
149 continue
150
151 files[filename].info = file.info + defaults
152
153
154 drupal_alter('system_info', files[filename].info, files[filename])
155
156 bootstrap = 0
157 for hook in bootstrap_hooks():
158 if (plugin_hook(file.name, hook)):
159 bootstrap = 1
160 break
161
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
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
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
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
211 if (dependency_name == '-circular-' or \
212 not php.isset(files[dependency_name])):
213 continue
214
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
220 for candidate in dependency.info['dependencies']:
221 if (array_search(candidate, file.info['dependencies']) == False):
222
223 if (candidate == filename):
224
225
226 candidate = '-circular-'
227
228
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
239
240
241 new_dependency = True
242 file.info['dependencies'].append( candidate )
243
244 del(file)
245 if (not new_dependency):
246 break
247 return files
248
249
250
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
265 """
266 Load a plugin's installation hooks.
267 """
268
269 plugin_load_include('install', plugin_)
270
271
272
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
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
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
327 plugin_list(True, False)
328
329 registry_rebuild()
330 for plugin_ in invoke_plugins:
331 plugin_invoke(plugin_, 'enable')
332
333
334
335
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
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
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
365 plugin_list(True, False)
366
367 registry_rebuild()
368
369
370 if (node_access_needs_rebuild() and \
371 php.count(plugin_implements('node_grants')) == 0):
372 node_access_rebuild()
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
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
458
459
460
461
462
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
514
515
516
517
518
519
521 """
522 Array of plugins required by core.
523 """
524 return ('block', 'filter', 'node', 'system', 'user')
525