1 FUSIONFORGE PLUGINS HOWTO
2 --------------------------------
4 Here is a short HOWTO explaining how plugins work, and how to make
7 It was written by Roland Mas <lolando@debian.org>.
9 WHAT PLUGINS ARE, AND WHY THEY ARE USEFUL
10 -----------------------------------------
12 Plugins are extensions to the "core" of GForge, providing extra
13 functionality without being tightly integrated within Sourceforge
14 proper. They are useful because they allow for independent
15 development of third-party functionality, and they add flexibility to
16 Sourceforge as to what features are available on a particular
19 As an example, it's been suggested to integrate a shared calendar
20 application in Sourceforge. It's a good idea and an interesting
21 feature, but not one that everybody wants. Thus, including it in the
22 GForge code would piss off someone. Additionnally, there might
23 be several competing implementations for such a calnedar application.
24 Choosing one among them would also piss off people. So it is made
25 possible to have a system so that different implementations can exist
26 and be installed separately.
31 It is expected that a plugin is just some new feature added to
32 GForge, and not a change in the behaviour of existing features.
33 A plug-in should therefore only add files, not change existing ones.
34 Whether these files be web pages, offline scripts, static
35 documentation or else is not relevant.
37 Of course, *some* changes will have to be made to the "core" files,
38 if only to add links to new web pages, for instance. These changes
39 are acceptable, and will be discussed below. Here come the details
40 about how the plugin system is implemented.
42 - A plugin will be identified primarily by a string handle, which will
43 be static across all installations of this plugin. It should be
44 composed of lowercase letters only, because it's going to be used in
45 table names and we don't want namespace conflicts. For instance, if
46 the ACME company writes a time tracking tool plugin, the handle for
47 that plugin could be "acmetimetracker". When installed, the plugin
48 will be assigned an integer identifier. This id might vary from site
49 to site, and should not be depended upon.
51 We [the GForge-proper maintainers team] will maintain some sort
52 of list of allocated plugin names so that different plugins get
53 different allocated identifiers, see below.
55 - Tables in the database schema: special tables have been added to the
56 database schema to keep track of installed plugins. They are
57 described below (simplified descriptions):
59 | CREATE TABLE plugins (plugin_id integer,
60 | plugin_name character(32),
62 | CONSTRAINT plugins_pkey PRIMARY KEY (plugin_id)
64 | CREATE TABLE group_plugin (group_plugin_id integer,
67 | CONSTRAINT PRIMARY KEY (plugin_id),
68 | CONSTRAINT FOREIGN KEY (group_id) REFERENCES groups(group_id)
70 | CREATE TABLE user_plugin (user_plugin_id integer,
73 | CONSTRAINT PRIMARY KEY (plugin_id),
74 | CONSTRAINT FOREIGN KEY (user_id) REFERENCES users(user_id)
78 TODO: add plugins_persistence, group_plugin_persistence, user_plugin_persistence
80 "plugins" lists the installed plugins, with the numeric id, the
81 string handle (say, "acmetimetracker") and a description.
83 "group_plugin" is a way to store the fact that a group "uses" a
84 plugin without needing to add a "uses_acmetimetracker" to the groups
85 table for each known plugin.
87 "user_plugin" is the same, for users.
89 - A plugin may create its own tables in the same database. These
90 tables must be named plugin_foo_* if the plugin's string identifier is
91 "foo". One suggested table is plugin_foo_meta_data, which should be
92 used to store the plugin meta-data, such as the installed version.
93 The plugin can then use some code like db-upgrade.pl if its database
94 schema changes over time.
96 [TODO: Standardise the command/script/something below]
97 These tables may have foreign key referential integrity constraints
98 going from them to standard tables, but not the other way round. If
99 they have, then a command/script/something must be provided so that
100 the main db-upgrade.pl can disable the constraints and re-enable them
101 afterwards in case some database schema changes are needed.
103 Similarly, a plugin may create sequences, indexes, views, etc,
104 provided that their names are prefixed with plugin_foo_ too.
106 A plugin should not modify the data in tables that do not belong to
107 it. If it really needs to, then please discuss it with us first,
108 there might be cases where it's needed/useful. Reading those data is
109 okay, but it must be careful not to leak any info to places/users
110 which normally wouldn't have had access to it.
112 - Functions in Group.class.php and User.class.php: the Group and User classes
113 now have a usesPlugin() method. It takes a single parameter, the
114 "acmetimetracker" identifier for the module, and returns a boolean if
115 the particular user/group has turned on the use of that module. Also
116 provided are setPluginUsage() methods, taking a plugin name and a
117 boolean value as arguments and returning true on success and false on
120 - A plugin should not change the existing files. Of course, it will
121 need a way to adds links to its own web pages. This is done by a
122 "hook" system. Each plugin can hook itself up to a number of hook
123 points in the main code, and execute arbitrary code when that point is
124 reached. Basically, the plugin registers itself to a global object
125 (of the PluginManager class, if you want to know). You have to call
126 the register_plugin() function, providing it an object of a subclass
127 of the Plugin class that is provided by the main code. That object
128 must provide a GetHooks() method, which returns a list of hook names.
129 Whenever one of these hooks is encountered, the object's CallHook()
130 method is called with the hook name and extra parameters that depend
131 on the hook. Adding a link to your page in some place is just a
132 matter of "subscribing" yourself to the hook that is in the
133 appropriate place, and printing the appropriate link whenever your
134 CallHook() method is called from that place.
136 Registering your plugin is done by providing a
137 @PLUGINS_PATH@/<pluginname>/include/<pluginname>-init.php.
138 It will be parsed by the PluginManager object. That file should
139 contain a call to register_plugin(), passing it an object of the
140 appropriate class. See the helloworld plugin for an example.
142 The hooks are managed centrally by the GForge code maintainers.
143 If you need one, please ask, we'll add it. The current list of
144 hooks is provided at the end of this document.
145 I rely on you plugins developers to provide more ideas :-)
147 - Plugin-specific web pages should reside either in the /plugin/*/
148 URL-space (that is, plugin "foo" will probably put its files in
149 @SOURCE_PATH@/www/plugins/foo/) or (if the web interface is not
150 written in PHP) in /plugin/*/cgi-bin/ URL-space (files in
151 /usr/lib/sourceforge/cgi-bin/plugins/foo/).
153 If possible, and as much as possible, a plugin should use the layout
154 functions defined by GForge (Layout.class.php, HTML.class.php, or
155 whatever they're called), to ensure a consistent look and themability.
157 Of course, the previous point only applies to plugins written in
158 PHP. Plugins written in other languages are not excluded by this
159 proposal, and there is no need to restrict them. Should they appear,
160 they might need to recode some of GForge's functions in Perl or
161 another language. I see no need to restrict that either. Only thing:
162 it would be better if the porting were as straightforward as possible.
163 Do not reimplement, please. Just translate from PHP to Perl or
164 whatever. If you do, please submit your translation to us, so that it
165 can be provided by GForge proper and maintained in common.
167 [TODO: Think about that, design, implement]
168 Speaking of languages... There should be some way to have
169 plugin-specific language files, so that the plugins can use the
170 standard methods used elsewhere in Sourceforge. I haven't thought
171 about that very deeply yet, but I think it will evolve into a
172 recommendation that the "handles" in the language files are
173 plugin_foo_page / item (as compared to the current page / item model
174 used for "core" GForge i18n strings).
176 - A plugin should register itself into the database using the provided
177 register-plugin script on its installation, and unregister itself
178 using unregister-plugin on removal. When unregistering, be careful
179 to delete all the rows in tables that contain a reference to your
180 plugin_id, so that the unregistration process (which deletes your row
181 in the plugins table) does not fail due to referential integrity errors.
183 HOW DO I MAKE A PLUGIN?
184 -----------------------
186 Your best bet would be to start with the sample "helloworld" plugin
187 and change parts of it. It shows an example of most of the things you
188 should need to make your plugin: PHP pages, configuration files, bits
189 of Apache configuration, cron jobs, etc. If you need something else,
190 please ask, we'll discuss it, (hopefully) reach an agreement on how to
191 Do It Right, and implement a sample in helloworld.
193 HOW TO NAME MY PLUGIN
194 ---------------------
196 If you plan on distributing your plugin to the public, please contact
197 the GForge maintainer with your proposed name, and we'll add it
198 to the list below. This ensures that no other plugin will use the
199 same name, so as to reduce the risk of name conflicts.
201 If you only intend to keep your plugin for yourself, you might still
202 contact us with your plugin name. If you're really nice, we might
203 consider adding it here too, so that other people who want to
204 distribute their plugin do not reuse the same name.
206 For reference, this is the list of currently used plugin names:
208 - helloworld, the plugin provided as an example.
209 - extldapauth, a plugin allowing on-the-fly account creation from an
210 existing LDAP directory;
212 CURRENT LIST OF PLUGIN HOOKS
213 ----------------------------
215 The following is a list of hooks available in GForge for plugins to utilise.
216 Each hook is listed with its name, locations in the source code where the
217 hook is called, and parameters that are passed into the hook and a brief
218 description. There may be other hooks available, added after this section
221 Hook Name : session_set_entry
222 Locations : common/include/session.php
223 Description: Called before checking if the user is logged in by
224 reading session cookie.
225 You can use this hook to handle session setup specific
228 Hook Name : session_set_return
229 Locations : common/include/session.php
230 Description: Called after checking if the user is logged in by reading
232 You can use this hook to handle session setup specific
235 Hook Name : artifact_extra_detail
236 Parameters : artifact_id - The ID of a tracker item
237 Locations : www/tracker/detail.php
238 www/tracker/mod-limited.php
240 Description: Use this hook to provide additional information about a
241 tracker item based upon its ID.
243 Hook Name : before_logout_redirect
244 Locations : www/account/logout.php
245 Description: Called once the GForge user has had their session logged
246 out, and before the user is redirected to the homepage of
250 Locations : www/include/Layout.class.php
251 Description: Used to include a CSS link element to include in the page
252 layout. The hook should return a complete <link> element.
255 Locations : www/include/Layout.class.php
256 Description: Used to include inline CSS into the page layout. The
257 hook should return pure CSS, without surrounding
260 Hook Name : group_approved
261 Parameters : group_id - The numeric ID of the group
262 Locations : www/admin/approve-pending.php
263 Description: When a group is approved by a site admin, this hook is called.
265 Hook Name : groupisactivecheckboxpost
266 Parameters : group_id - The numeric ID of the group
267 Locations : www/project/admin/index.php
268 Description: Called when a plugin is activated for a specific group from
269 the group's Edit Public Info page. Use this to perform
270 actions to initialise the plugin for a specific group.
272 Hook Name : groupisactivecheckbox
273 Parameters : group_id - The numeric ID of the group
274 Locations : www/project/admin/index.php
275 Description: Used to display a portion of a form on a group's Edit
276 Public Info page. It should return a HTML <tr> line containing
279 Hook Name : groupmenu
280 Parameters : DIRS - A reference to the array of tab URLs
281 TITLES - A reference to the array of tab titles
282 toptab - A reference to a string containing the name of
283 the GForge tab menu in use (eg. admin, tracker)
284 selected - A reference to an array index of the tabs.
285 group - The numeric ID of the current group
286 Locations : www/include/Layout.class.php
287 Description: Used to provide a plugin specific tab in when viewing
289 [TODO: The use of the 'group' name as a parameter is inconsistent
290 with most other group plugin hooks - which use group_id.]
292 Hook Name : group_plugin_use
293 Parameters : group_id - The numeric ID of the current group
294 Locations : common/include/Group.class.php
295 Description: When a plugin is activated for a specific group, this
298 Hook Name : groupmenu_scm
299 Parameters : DIRS - A reference to the array of tab URLs
300 TITLES - A reference to the array of tab titles
301 toptab - A reference to a string containing the name of
302 the GForge tab menu in use (eg. admin, tracker)
303 selected - A reference to an array index of the tabs.
304 group_id - The numeric ID of the current group
305 Locations : www/include/Layout.class.php
306 Description: Provides a tab for the SCM system in the group pages.
308 Hook Name : headermenu
309 Parameters : toptab - A reference to a string containing the name of
310 the GForge tab menu in use (eg. admin, tracker)
311 template - An HTML template giving how to add the menu.
312 Locations : www/include/Layout.class.php
313 Description: Used to provide a plugin specific menu entry in the header
314 top menu (after: Login, Logout, My Account).
315 See plugin online_help for example of use.
318 Locations : www/include/Layout.class.php (& derived classes)
319 Description: Used to allow plugins to include code in the <head>.
320 See plugin message for example of use.
322 Hook Name : javascript
323 Locations : www/include/Layout.class.php
324 www/include/LayoutSF.class.php
325 Description: Provides a place to add inline Javascript into the page.
326 The output (in $params['return']) of the hook should be pure Javascript, as it will
327 be placed within an existing <script> block.
328 [TODO: The output of the hook appears after the closing SGML comment marker
329 and before the closing </script> element. Is this what is really indended?]
331 Hook Name : javascript_file
332 Locations : www/include/Layout.class.php (function headerJS)
333 Description: Add JS file in header. No params.
334 Offer easy use ot use_javascript('/path/to/yourfile.js') or declare
335 a specific call of html_use_jquery if needed.
336 See plugin headermenu of example of use.
338 Hook Name : project_admin_plugins
339 Parameters : group_id - The numeric ID of the group
340 Locations : www/project/admin/index.php
341 Description: Provides a place for plugin authors to add a link on the
342 group summary page to the admin page for a plugin.
344 Hook Name : project_after_description
345 Parameters : group_id - The numeric ID of the group
346 Locations : www/include/project_home.php
347 Description: Provides some space for plugin specific text on a group's
350 Hook Name : project_public_area
351 Parameters : group_id - The numeric ID of the group
352 Locations : www/include/project_home.php
353 Description: Used to provide plugin specific infos on a group's
356 Hook Name : scm_admin_update
357 Parameters : group_id - The numeric ID of the group
358 Parameters : scmradio
359 A number of scm specific values generated from the
360 form fields created by the scm_admin_page hook.
361 Locations : www/scm/admin/index.php
362 Description: When the SCM admin page is submitted, this hook is called.
363 [TODO: Is scmradio actually used anywhere or is it legacy? A grep through
364 the source code seems to indicate its never used!]
366 Hook Name : scm_admin_page
367 Parameters : group_id - The numeric ID of the group
368 Locations : www/scm/admin/index.php
369 Description: Used to generate an administrative form for the SCM
370 plugin. All the form fields generated by this hook should
371 be named [pluginname]_[fieldname] where [pluginname] is the
372 SCM plugin name and [fieldname] is a field name for the
373 form element. Using this naming scheme ensures the fields
374 are properly passed as parameters to the scm_admin_update hook.
377 Parameters : group_id - The numeric ID of the group
378 Locations : www/scm/index.php
379 Description: Show a page for the SCM in use by a group.
381 Hook Name : scm_plugin
382 Parameters : scm_plugins - A reference to an array of plugins providing
383 SCM features. Each element is a plugin string name.
384 Locations : common/scm/SCMFactory.class.php
385 Description: This is used by GForge to identify SCM plugins. Any plugin that
386 provides SCM features should add itself to the scm_plugins array
387 when this hook is called.
389 Hook Name : scm_stats
390 Parameters : group_id - The numeric ID of the group
391 Locations : www/include/project_home.php
392 Description: Shows SCM specific statistics on the group's summary page.
394 Hook Name : search_engines
395 Locations : www/search/include/SearchManager.class.php
397 Hook Name : session_before_login
398 Parameters : loginname - The login as passed in from the user
399 passwd - The password as passed in from the user
400 Locations : common/include/session.php
401 Description: Authentication plugins can use this hook to authenticate
402 a user before GForge passes the authentication details on
403 to its own database. The hook should return true if the
404 authentication succeeds.
406 Hook Name : site_admin_option_hook
407 Locations : www/admin/index.php
408 Description: Use this to provide a link to the site wide administrative
409 pages for your plugin. The hook should return HTML within
410 a <li> block and will appear on the Site Admin page in the
413 Hook Name : site_admin_project_maintenance_hook
414 Locations : www/admin/index.php
415 Description: Use this to provide a link to the project maintenance pages
416 for your plugin. The hook should obey the plugin_hook_by_reference()
417 protocol and concatenate a <li> HTML block to params['result'] so
418 that it can appear in the "Plugins Project Maintenance" subsection
420 Hook Name : site_admin_user_maintenance_hook
421 Locations : www/admin/index.php
422 Description: Use this to provide a link to the user maintenance pages
423 for your plugin. The hook should obey the plugin_hook_by_reference()
424 protocol and concatenate a <li> HTML block to params['result'] so
425 that it can appear in the "Plugins User Maintenance" subsection
427 Hook Name : task_extra_detail
428 Parameters : task_id - The numeric ID for a task
429 Locations : www/pm/detail_task.php
431 Description: Provides a place to include extra information about a
432 task. The hook should return a <tr> row containing 2 cells
433 (or colspan'ed to 2).
436 Locations : common/include/html.php
437 Description: Prints out a tab to show when displaying user pages.
438 Unlike the groupmenu hook, this hook should use the PrintSubMenu
439 method to display the tab itself.
442 Locations : common/include/Role.class.php
443 Description: Provides a place to read role from another subsystem (LDAP, DB,
446 Hook Name : role_update
447 Locations : common/include/Role.class.php
448 Description: Triggered when new a role is updated
450 Hook Name : role_setuser
451 Locations : common/include/Role.class.php
452 Description: Provides a way to extend the way user information are stored
454 hook Name : outermenu
455 Parameters : DIRS - A reference to the array of tab URLs
456 TITLES - A reference to the array of tab titles
457 Location : common/include/Navigation.class.php
458 Description: Used to provide a plugin specific tab in main menu.
460 Hook Name : user_logo
461 Parameters : user_id, size, content (return value)
462 Locations : common/include/utils.php
463 Description: plugin_hook_by_reference hook used to provide a user
464 picture in params['content'] (see gravatar plugin for instance)
466 Hook Name : user_link_with_tooltip
467 Parameters : username, user_id, user_link (return value)
468 Locations : common/include/utils.php
469 Description: plugin_hook_by_reference hook used to replace util_display_user()
470 default behaviour, returns value in params['user_link']
471 (see oslc plugin for instance)
473 TODO : document Auth plugins :
475 Hook Name : display_auth_form
476 Parameters : return_to
477 Locations : www/include/login-form.php
478 Description: returns a login dialog and/or a redirect URL :
479 it should return an HTML dialog appened to passed $params['html_snippets']
480 it may return a redirection URL appened to $params['transparent_redirect_urls']
482 Hook Name : check_auth_session
483 Parameters : auth_token ??
484 Locations : common/include/session.php
485 Description: is there a valid session?
486 it returns a ??? appened to passed $params['results'] :FORGE_AUTH_AUTHORITATIVE_ACCEPT, FORGE_AUTH_AUTHORITATIVE_REJECT
488 Hook Name : fetch_authenticated_user
490 Locations : common/include/session.php
491 Description: what FFUser is logged in?
492 it returns a user object in passed $params['results']
495 Parameters : $params['owner_type'] == WidgetLayoutManager::OWNER_TYPE_GROUP or WidgetLayoutManager::OWNER_TYPE_USER
496 Description: appends to $params['fusionforge_widgets'] (or 'codendi_widgets') names
497 of widgets it provides depending on the project home or user home context
499 Hook Name : widget_instance
500 Parameters : $params['widget'] provides the widget name
501 Description: returns in $params['instance'] an instance of the new Widget subclass
503 Hook Name : script_accepted_types
504 Parameters : $params['script'] == 'project_home' or 'user_home' depending on whether providing alternate accept types for such pages
505 Description: appends to $params['accepted_types'] alternate HTTP Accept Content-types supported by a plugin for the
506 /projects or /users/ pages rendered through content-negociation
508 Hook Name : content_negociated_project_home
509 Parameters : $params['accept'] provides the content-type to be rendered, $params['groupname'] the project name, $params['group_id'] the project ID
510 Description: returns in $params['content'] an alternate content for /projects/ page and
511 in $params['content_type'] the actual content-type to return
513 Hook Name : content_negociated_user_home
514 Parameters : $params['accept'] provides the content-type to be rendered, $params['username'] the user name
515 Description: returns in $params['content'] an alternate content for /users/ page and
516 in $params['content_type'] the actual content-type to return
518 Hook Name : project_rdf_metadata
519 Parameters : $params['prefixes'] : already used RDF prefixes in the form URL => shortname,
520 $hook_params['group'] : group_id of the project,
521 $hook_params['in_Resource'] : (read-only) ARC2 resource already available,
522 $hook_params['details'] : 'full' or 'minimal' whether requesting a detailed or minimal description
523 $hook_params['out_Resources'] : (write) to be returned the new ARC2 resource
524 Description: returns in $params['prefixes'] and $params['out_Resources'] added prefixes and ARC2 RDF resource to be included in the project's DOAP description
526 Hook Name : role_adduser
527 Parameters : $params['user'] : user added to the role, $params['role'] : role added to the user
528 Locations : common/include/RBAC.php
529 Description: Called when a role is added to an user.
531 Hook Name : role_removeuser
532 Parameters : $params['user'] : user removed to the role, $params['role'] : role removed to the user
533 Locations : common/include/RBAC.php
534 Description: Called when a role is removed to an user.
536 Hook Name : alt_representations
537 Parameters : $params['script_name'] contains the SCRIPT_NAME (filtered to work only on /projects or /users for the moment)
538 Description: returns alternate representations for a particular page in $params['results'] which is populated by the hook users
540 Hook Name : content_negociated_projects_list
541 Parameters : $params['accept'] provides the content-type to be rendered
542 Description: returns in $params['content'] an alternate content for /projects page and
543 in $params['content_type'] the actual content-type to return
545 Hook Name : softwaremap_links
546 Parameters : TITLES - A reference to the array of submenu titles
547 URLS - A reference to the array of submenu URLs
548 ATTRS - A reference to the array of submenu attributes
549 Locations : www/include/Layout.class.php
550 Description: Used to provide plugin specific softwaremap submenu link(s)
552 Hook Name : content_negociated_trove_list
553 Parameters : $params['accept'] provides the content-type to be rendered
554 Description: returns in $params['content'] an alternate content for /softwaremap/trove_list.php page and
555 in $params['content_type'] the actual content-type to return
557 Hook Name : content_negociated_frs_index
558 Parameters : $params['accept'] provides the content-type to be rendered
559 Description: returns in $params['content'] an alternate content for /frs/
560 in $params['content_type'] the actual content-type to return
562 Hook Name : content_negociated_frs_download_file
563 Parameters : $params['accept'] provides the content-type to be rendered
564 Description: returns in $params['content'] an alternate content for /frs/download.php
565 in $params['content_type'] the actual content-type to return
567 Hook name : admin_tracker_add_actions
568 Parameters : $params['group_id'] provides the group id for which actions should be added
569 $params['atid'] provides the artifact id for which actions should be added
570 Description: return an array in $params['result'] to add additional actions to the tracker
571 admin menu. The returned array consists of a list of the following structure:
573 'text' => 'text for the displayed link',
574 'description' => 'description of the action',
575 'page' => 'path to the php file for displaying the menu page'),
579 TODO (nerville) : document display_hierarchy
580 TODO (lolando ?) : document role_normalize, role_translate_strings, role_has_permission, role_get_setting, list_roles_by_permission
581 TODO : document project_link_with_tooltip
583 -- Roland Mas <lolando@debian.org>