The Foswiki plugins architecture allows you to hook into the Foswiki flows at a very deep level. To support this, plugins are automatically loaded at startup and "stitched in" to the flow at the points dictated by the handlers they implement. Because of the extra startup processing they require, plugins are distinguished from other forms of extension by existing in their own "Plugins" namespace.
However sometimes you have an existing contrib that
does not need to hook into the flow, but you
do just want to add a REST or macro handler to it. But that's a problem, because unlike plugins, Contrib modules are not automatically loaded when Foswiki starts up.
Consider how Contribs work. Let's say we have a really simple extension that defines a function, 'fnord', that is designed to be called from other extensions. The Contrib module will look something like this:
package Foswiki::Contrib::FnordContrib;
use strict;
our $RELEASE = '2.9.4';
use version 0.77; our $VERSION = version->declare('v2.9.4');
sub fnord {
...
1;
To use this contrib, callers will have something like this in the code:
require Foswiki::Contrib::FnordContrib;
Foswiki::Contrib::FnordContrib::fnord();
So far so good; the caller takes responsibility for ensuring the contrib code is loaded if they want to call the function. However a problem arises when the Contrib
isn't explicitly loaded by any other auto-loaded code, but we want to register a REST handler that will allow
fnord
to be called via an HTTP request, or a macro handler that supports the
%FNORD
macro. We could always implement a companion plugin to perform the relevant calls to
Foswiki::Func::registerRESTHandler
and
Foswiki::Func::registerTagHandler
.
But there's a simpler approach; just explain quietly to Foswiki that the Contrib is also a plugin. That way, Foswiki will call the
initPlugin
method in the Contrib, which can be used to declare the desired handlers.
First, we implement a standard
initPlugin
method in the same package, and use
Foswiki::Func
to add the handlers:
package Foswiki::Contrib::FnordContrib;
use strict;
our $RELEASE = '2.9.5';
use version 0.77; our $VERSION = version->declare('v2.9.5');
sub initPlugin {
Foswiki::Func::registerRestHandler('fnord', \&_restFnord);
Foswiki::Func::registerTagHandler('FNORD', \&_FNORD);
# The details of how to write REST and macro tag handlers are out of scope here
return 1;
}
Now we edit the
Config.spec
for the contrib and add:
# **STRING EXPERT**
# Plugin module path for handler registration
$Foswiki::cfg{Plugins}{FnordContrib}{Module} = 'Foswiki::Contrib::FnordContrib';
# **BOOLEAN EXPERT**
# Enable plugin module for handler registration
$Foswiki::cfg{Plugins}{FnordContrib}{Enabled} = 1;
Next time we run
configure
with this contrib installed, and save something, these settings will be added to
LocalSite.cfg
. Then, when Foswiki runs, it enumerates the
$Foswiki::cfg{Plugins}
hash and tries to call
initPlugin
on each module it finds - which of course includes our contrib.
Of course it's not a
true plugin, and won't get treated as such by support modules such as
configure
and the installers, but for the purposes of adding REST and macro handlers this works just fine. This approach should work with any Foswiki release.
One final note; be warned that the contrib package is treated internally as a plugin, and flow handler functions will be picked up from the it.
But don't assume this is a cheap way to convert your contrib into a plugin! If everyone starts declaring Contribs that are actually plugins, the support tools that rely on being able to distinguish them will fail. If you want to do any more than simple registration of REST and macro handlers, then convert your contrib into the Plugins namespace, or create a companion plugin.
--
CrawfordCurrie