DEBIAN GFORGE PLUGINS HOWTO
--------------------------------

Here is a short HOWTO explaining how plugins work in Debian
GForge, and how to make a new one.

  It was written by Roland Mas <lolando@debian.org>.

WHAT PLUGINS ARE, AND WHY THEY ARE USEFUL
-----------------------------------------

  Plugins are extensions to the "core" of GForge, providing extra
functionality without being tightly integrated within Sourceforge
proper.  They are useful because they allow for independent
development of third-party functionality, and they add flexibility to
Sourceforge as to what features are available on a particular
installation.

  As an example, it's been suggested to integrate a shared calendar
application in Sourceforge.  It's a good idea and an interesting
feature, but not one that everybody wants.  Thus, including it in the
GForge code would piss off someone.  Additionnally, there might
be several competing implementations for such a calnedar application.
Choosing one among them would also piss off people.  So it is made
possible to have a system so that different implementations can exist
and be installed separately.

HOW PLUGINS WORK
----------------

  It is expected that a plugin is just some new feature added to
GForge, and not a change in the behaviour of existing features.
A plug-in should therefore only add files, not change existing ones.
Whether these files be web pages, offline scripts, static
documentation or else is not relevant.

  Of course, *some* changes will have to be made to the "core" files,
if only to add links to new web pages, for instance.  These changes
are acceptable, and will be discussed below.  Here come the details
about how the plugin system is implemented.

- A plugin will be identified primarily by a string handle, which will
be static across all installations of this plugin.  It should be
composed of lowercase letters only, because it's going to be used in
table names and we don't want namespace conflicts.  For instance, if
the ACME company writes a time tracking tool plugin, the handle for
that plugin could be "acmetimetracker".  When installed, the plugin
will be assigned an integer identifier.  This id might vary from site
to site, and should not be depended upon.

  We [the GForge-proper maintainers team] will maintain some sort
of list of allocated plugin names so that different plugins get
different allocated identifiers, see below.

- Tables in the database schema: special tables have been added to the
database schema to keep track of installed plugins.  They are
described below (simplified descriptions):
,----
| CREATE TABLE plugins (plugin_id integer,
|                       plugin_name character(32),
|                       plugin_desc text,
|                       CONSTRAINT plugins_pkey PRIMARY KEY (plugin_id)
|                      )
| CREATE TABLE group_plugin (group_plugin_id integer,
|                            group_id integer,
|                            plugin_id integer,
|                            CONSTRAINT PRIMARY KEY (plugin_id),
|                            CONSTRAINT FOREIGN KEY (group_id) REFERENCES groups(group_id)
|                           )
| CREATE TABLE user_plugin (user_plugin_id integer,
|                           user_id integer,
|                           plugin_id integer,
|                           CONSTRAINT PRIMARY KEY (plugin_id),
|                           CONSTRAINT FOREIGN KEY (user_id) REFERENCES users(user_id)
|                          )
`----

  "plugins" lists the installed plugins, with the numeric id, the
string handle (say, "acmetimetracker") and a description.

  "group_plugin" is a way to store the fact that a group "uses" a
plugin without needing to add a "uses_acmetimetracker" to the groups
table for each known plugin.

  "user_plugin" is the same, for users.

- A plugin may create its own tables in the same database.  These
tables must be named plugin_foo_* if the plugin's string identifier is
"foo".  One suggested table is plugin_foo_meta_data, which should be
used to store the plugin meta-data, such as the installed version.
The plugin can then use some code like db-upgrade.pl if its database
schema changes over time.

[TODO: Standardise the command/script/something below]
  These tables may have foreign key referential integrity constraints
going from them to standard tables, but not the other way round.  If
they have, then a command/script/something must be provided so that
the main db-upgrade.pl can disable the constraints and re-enable them
afterwards in case some database schema changes are needed.

  Similarly, a plugin may create sequences, indexes, views, etc,
provided that their names are prefixed with plugin_foo_ too.

  A plugin should not modify the data in tables that do not belong to
it.  If it really needs to, then please discuss it with us first,
there might be cases where it's needed/useful.  Reading those data is
okay, but it must be careful not to leak any info to places/users
which normally wouldn't have had access to it.

- Functions in Group.class and User.class: the Group and User classes
now have a usesPlugin() method.  It takes a single parameter, the
"acmetimetracker" identifier for the module, and returns a boolean if
the particular user/group has turned on the use of that module.  Also
provided are setPluginUsage() methods, taking a plugin name and a
boolean value as arguments and returning true on success and false on
failure.

- A plugin should not change the existing files.  Of course, it will
need a way to adds links to its own web pages.  This is done by a
"hook" system.  Each plugin can hook itself up to a number of hook
points in the main code, and execute arbitrary code when that point is
reached.  Basically, the plugin registers itself to a global object
(of the PluginManager class, if you want to know).  You have to call
the register_plugin() function, providing it an object of a subclass
of the Plugin class that is provided by the main code.  That object
must provide a GetHooks() method, which returns a list of hook names.
Whenever one of these hooks is encountered, the object's CallHook()
method is called with the hook name and extra parameters that depend
on the hook.  Adding a link to your page in some place is just a
matter of "subscribing" yourself to the hook that is in the
appropriate place, and printing the appropriate link whenever your
CallHook() method is called from that place.

  Registering your plugin is done by providing a
/usr/lib/gforge/plugins/<pluginname>/include/<pluginname>-init.php.
It will be parsed by the PluginManager object.  That file should
contain a call to register_plugin(), passing it an object of the
appropriate class.  See the helloworld plugin for an example.

  The hooks are managed centrally by the GForge code maintainers.
If you need one, please ask, we'll add it.  At the time of writing,
there's only one hook implemented (for the helloworld plugin).  It's
called "usermenu" and occurs in the user_menu() method of the Layout
class.  I rely on you plugins developers to provide more ideas :-)

- Plugin-specific web pages should reside either in the /plugin/*/
URL-space (that is, plugin "foo" will probably put its files in
/usr/share/gforge/www/plugins/foo/) or (if the web interface is not
written in PHP) in /plugin/*/cgi-bin/ URL-space (files in
/usr/lib/sourceforge/cgi-bin/plugins/foo/).

  If possible, and as much as possible, a plugin should use the layout
functions defined by GForge (Layout.class, HTML.class, or
whatever they're called), to ensure a consistent look and themability.

  Of course, the previous point only applies to plugins written in
PHP.  Plugins written in other languages are not excluded by this
proposal, and there is no need to restrict them.  Should they appear,
they might need to recode some of GForge's functions in Perl or
another language.  I see no need to restrict that either.  Only thing:
it would be better if the porting were as straightforward as possible.
Do not reimplement, please.  Just translate from PHP to Perl or
whatever.  If you do, please submit your translation to us, so that it
can be provided by GForge proper and maintained in common.

[TODO: Think about that, design, implement]
  Speaking of languages...  There should be some way to have
plugin-specific language files, so that the plugins can use the
standard methods used elsewhere in Sourceforge.  I haven't thought
about that very deeply yet, but I think it will evolve into a
recommendation that the "handles" in the language files are
plugin_foo_page / item (as compared to the current page / item model
used for "core" GForge i18n strings).

- A plugin should register itself into the database using the provided
register-plugin script on its installation, and unregister itself
using unregister-plugin on removal.  When unregistering, be careful
to delete all the rows in tables that contain a reference to your
plugin_id, so that the unregistration process (which deletes your row
in the plugins table) does not fail due to referential integrity errors.

HOW DO I MAKE A PLUGIN?
-----------------------

  Your best bet would be to start with the sample "helloworld" plugin
and change parts of it.  It shows an example of most of the things you
should need to make your plugin: PHP pages, configuration files, bits
of Apache configuration, cron jobs, etc.  If you need something else,
please ask, we'll discuss it, (hopefully) reach an agreement on how to
Do It Right, and implement a sample in helloworld.

HOW TO NAME MY PLUGIN
---------------------

If you plan on distributing your plugin to the public, please contact
the GForge maintainer with your proposed name, and we'll add it
to the list below.  This ensures that no other plugin will use the
same name, so as to reduce the risk of name conflicts.

  If you only intend to keep your plugin for yourself, you might still
contact us with your plugin name.  If you're really nice, we might
consider adding it here too, so that other people who want to
distribute their plugin do not reuse the same name.

  For reference, this is the list of currently used plugin names:

- helloworld, the plugin provided as an example.

  -- Roland Mas <lolando@debian.org>
