A plug-in is an add-on code module that can perform customized tasks that are beyond the scope of the core system. Plug-ins can:
Plug-ins are Perl modules that are kept in the cgi/Modules
directory of your system. Installation can be as simple as copying your module into that directory; un-installation can be as simple as removing it.
In practice, complex plug-ins can have many other files as well, including:
But the basic Perl module is the minimum requirement.
The simplest illustration of how a plug-in works is to create a "Hello World" plug-in. This plug-in module does nothing except insert the text "Hello World!" into a web page. Here is a bare-bones version of this plug-in, written in Perl:
package Modules::HelloWorld;
sub read {
my ($this,$options) = @_;
# do nothing
}
sub write {
my ($this,$options) = @_;
return "Hello World!";
}
sub ioctl {
my ($this,$options) = @_;
# do nothing
}
1;
To install this plug-in, save this code to a file named HelloWorld.pm
, and place the file in the cgi/Modules
directory of your system.
What this code does:
read()
method manages all input to the plug-in. It should scan the various sources of input (the query string, the post data, path info, and others) and configure the plug-in appropriately. In this case, we don't need to deal with any input, so the method is empty.write()
method manages all output from the plug-in. Based on the inputs and any other context that is important, it will assemble a block of HTML to be inserted into a web page. In this case, we are only outputting a simple string, "Hello World!", which is the return value of this method.ioctl()
method is used for all other system interactions with the plug-in. The system will query the plug-in from time to time to find out what features it supports, by passing a simple text message to ioctl()
. If the plug-in responds with something appropriate, then the feature is supported, otherwise, the feature is disabled. In this case, we don't support any other features at all, so the method is empty.Linux system programmers might recognize these methods as being similar to unix device drivers. That is because the basic idea is similar: a plug-in handles input (reads) and output (writes) to a special subsystem, which may or may not support extra functions (ioctls) above and beyond basic reads and writes. Other that that rough architectural similarity, you do not need to know anything about Linux system programming to develop your own plug-ins.
The system provides two generic plug-in modules that can serve as base classes for your plug-ins to inherit from.
cgi/Modules/BaseDCD.pm
- generic plug-in module, including methods for automatically loading configuration files, reading standard input sources, generating self-links, and communicating basic module information to the base system.cgi/Modules/Content.pm
- generic plug-in module for a specialized Content type. Includes standardized methods for editing, configuring, copying, ordering, and deleting content, tools for managing workflow states, and administrator screens for managing schedules, translations, prices, images, and contact information.It is very helpful to inherit from one of these generic modules when starting your own plug-in. It ensures that your plug-in is following good practices and has access to some functions that are very useful in plug-in development.
Here is our Hello World plug-in, re-coded to inherit from BaseDCD.pm
:
package Modules::HelloWorld; use strict; use Modules::BaseDCD; use vars qw(@ISA); @ISA = qw(Modules::BaseDCD); sub write { my ($this,$options) = @_; return "Hello World!"; } 1;
Now our Hello World plug-in only has to define its own specific write()
method, since it will inherit anything unspecific from BaseDCD.pm
.
Some plug-ins are simply glue layers to interface to an external code package that is not part of ExSite. Others are tightly integrated into ExSite, making use of the system database and ExSite's internal packages and libraries. In the latter case, there is some additional boilerplate code you will want to include to integrate your plug-in module with the surrounding system.
package Modules::HelloWorld; use strict; use ExSite::Config; use ExSite::Input; use ExSite::Misc; use ExSite::Util; use ExSite::ML; use ExSite::UI; use ExSite::FormBuilder; use ExSite::ReportBuilder; use ExSite::Form; use Modules::BaseDCD; use vars qw(@ISA); @ISA = qw(Modules::BaseDCD); sub write { my ($this,$options) = @_; # # get some standard objects that might be useful # my $db = $share{DB}; # current database handle, probably an ExSite::Form object my $page = $share{Page}; # current page being built, some variation on an ExSite::Content object my $ml = &get_obj("ML"); # ExSite::ML object # # build our HTML # my $out; $out .= $ml->h1("Hello world"); return $out; } 1;
This version of our Hello World plug-in includes a bunch of ExSite packages that may come in handy in the course of writing the plug-in code. Here is a summary of packages that may be useful:
Package | Description |
---|---|
ExSite::Config | covers system setup and configuration, and gives you access to global variables |
ExSite::DB | reads and writes to the database |
ExSite::Report | same as DB, but with extra tools for reporting (ie. displaying) the data you have obtained from the database |
ExSite::Form | same as Report, but with extra tools for generating forms and inputs for data you intend to write to the database |
ExSite::ReportBuilder | tools for converting datasets to visual formats including HTML tables, spreadsheets, and charts |
ExSite::FormBuilder | tools for constructing HTML forms, with various advanced features such as input validation, captchas, and templating |
ExSite::Misc | miscellaneous utilities for text processing and web data handling |
ExSite::Input | tools for processing web inputs such as query strings, posted form data, uploaded files, and path info |
ExSite::ML | markup language generation, useful for generating well-formed HTML and XML documents |
ExSite::UI | tools for generating complex HTML structures and Bootstrap widgets, such as boxes and panels, toolbars, blindboxes, tabs, and popups; very useful when building control panels |
ExSite::Content | base class for the CMS, very useful if interacting directly with the content tables |
ExSite::Mail | tools for sending email notifications |
ExSite::Module | utilities for interacting with other plug-ins |
ExSite::Chart | tools for generating graphs |
There are many others; this is not a complete list, simply a concise overview of the more common ones from the ExSite kernel.
If your plug-in requires any web assets that are not already included in the main system, it can keep those in a reserved directory, html/_Modules/PluginName
, where PluginName
is the same as your module name, less the .pm
suffix. The system will automatically find and make use of the following files in that directory, if they are present:
File/Asset | Description |
---|---|
icon.png or icon.gif | an icon to represent your plug-in in certain contexts |
help.html | help documentation for admins using your plug-in |
PluginName.css | a stylesheet that should be loaded on your plug-in's administrator control panels |
PluginName.js | javascript code that should be loaded on your plug-in's administrator control panels |
Any other files you need can also be included in this same directory, to keep them compartmentalized from other system components.
If your plug-in adds tables to the system database, you can place your extensions' map files (see Database Maps) into its own directory in cgi/dbmap/PluginName/
. These files may include
DBmap file | Description |
---|---|
.table | tables that your plug-in is adding to the database |
.datatype | datatypes that your plug-in is declaring |
tablename | column mappings for each table that you add |
Just as the write()
method generates content for insertion into web pages, you can optionally support a back-end control panel to provide an administrative interface. To find out whether your plug-in supports control panels, the system will query your ioctl()
method, passing the "ControlPanel" keyword. Your ioctl()
method should respond with a CODE reference to a method in your plug-in that will generate the control panel HTML.
If the HTML received back from this call is a complete HTML document (beginning with a doctype declaration or html tag), then the system will presume that your code is generating the complete control panel and will display it to the administrator unaltered.
The HTML received back is a partial HTML document, then it will be merged into the standard system control panel template, automatically incorporating any web assets you have defined (see above).
sub ioctl {
my $this = shift;
$_ = shift; # $_ is the ioctl request
if (/ControlPanel/) {
return \&ctrl_panel;
}
}
sub ctrl_panel {
my $this = shift;
$ml = &get_obj("ML"); # markup generator
my $out; # output buffer
$out .= $ml->h1("This is your control panel");
# ...
return $out;
}
If your plug-in needs to execute off-line tasks at certain intervals, or at preset times, it can make itself available to the system's task manager. To find out whether your plug-in supports scheduled tasks, the system will query your ioctl()
method, passing the "Cron" keyword. Your ioctl()
method should respond with a CODE reference to a method in your plug-in that will handle task management.
All task management calls receive 3 parameters: firstly an action, which is presumed to be a command word, and a type + ID, which can (but does not necessarily have to) refer to some object in the system. For example, a task with the parameters ("publish", "page", 567) could be construed to be a command to publish page ID 567. In practice, you are free to interpret these three parameters in whatever manner suits you. They are only structured this way for convenience, and the system does not otherwise impose any semantic requirements on how they must be used.
sub ioctl { my $this = shift; $_ = shift; # $_ is the ioctl request if (/Cron/) { return \&task_manager; } } sub task_manager { my ($this,$action,$type,$id) = @_; # ... }
The system dashboard is a visual overview of the entire system. Plug-ins that have something offer for the system dashboard can make it available by providing a Dashboard hook, in a similar manner to the above.
sub ioctl { my $this = shift; $_ = shift; # $_ is the ioctl request if (/Dashboard/) { return \&dashboard; } } sub dashboard { my ($this) = @_; # ... }
The dashboard hook takes no parameters, and should restrict its output to a compact overview that occupies a rectangle approximately 500 pixels wide by 300 tall. Many plug-ins opt to use a chart for this purpose, but in principle you can output text, a table, or anything at all.
Typically a plug-in will generate a starting view (which may depend on the options string that is passed to the write()
method) and the user will that navigate around inside the plugin to complete their task. Since you may not know where exactly the plug-in has been "plugged-in" to the website, the URLs that you are linking to are unpredictable.
If you use either of the base classes mentioned above, you can easily generate recursive self-links using the built-in link()
method, which generates a link back to the current URL. This method takes a hash of keys=>values that are used as query string parameters to modify the URL. If you set a key to undef
, that parameter will be cleared from the URL, otherwise it will be added/changed.
For example, if your plug-in accepts two commands, stop and go, you can use the following calls to generate the appropriate links, no matter where the plug-in has been embedded on your site:
$stop_url = $this->link(cmd=>"stop"); $go_url = $this->link(cmd=>"go"); $reset_url = $this->link(cmd=>undef); # clear the command
Sometimes you do need strict control over the page where the plug-in does its work. If so, you can give the plug-in its own home page, where it will automatically redirect to when you interact with it. This not only gives you a predictable URL for your plug-in, but also more control over the page layout.
The plug-in page is called a service page, so-called because it services all links to the plug-in. Define your service page by adding a record to the service table. You can only have one service page per plug-in, although you are allowed to setup alternate services pages for other languages on multilingual sites.
Sometimes you want to make use of other system plug-ins, so that you can take advantage of their functionality without having to redevelop all of those features.
If you simply need to call another plug-in and allow it to assume control, you can embed the other plug-in into your write() method's output. The format is
This basically just calls the other plug-in's write() method, passing the options string as a parameter.
For example, to add an item to the shopping cart, and allow the cart to take control of the workflow so that the user can check out and pay, you can embed the Pay module into your output with the purchased item parameters, like so:
and the cart will embed itself into the page at that spot.
To call another plug-in but not give it control over the workflow, you can call the plug-in from your Perl code. For example, you can make the same shopping cart call as above, but trap the shopping cart output so that it does not display in your page, like so:
use ExSite::Module qw(&get_module); my $Pay = &get_module("Pay"); my $Pay_output = $Pay->write("item=Donation&amount=50.00&acctcode=6");
If you will actually be writing code that interacts with other modules' data and logic, you may need to load up the configurations and other settings so that you are both on the same page, so to speak.
The simplest way to do this is to declare a dependency; in other words, that your module depends on a separate module, and when your module gets set up, the system should also set up the separate module as well to make sure they are both operating under the same conditions.
The system checks for dependencies by calling the ioctl()
method, passing the "Dependencies" parameter. Your ioctl()
method should respond with an array of module names that it depends on. The system will automatically load the configuration settings and dbmaps of those modules, so that they are available in the current context.
The example plug-in below illustrates some useful features and methods. You can use it as a template for writing your own plug-ins.
package Modules::Example; use strict; use ExSite::Config; use ExSite::Input; use ExSite::Misc; use Modules::BaseDCD; use vars qw(@ISA $ml $ui); @ISA = qw(Modules::BaseDCD); sub read { my ($this,$options) = @_; my $in = new ExSite::Input; $this->{input} = $in->combine; $this->{post} = $in->post; $this->{query} = $in->query; $this->{is_post} = $in->is_post; #scalar keys %{$this->{post}}; } sub write { my ($this,$options) = @_; my $ml = &get_obj("ML"); my $out = $ml->h1("Test Plug-in"); # display information about the request $out .= $ml->h2("Request"); $out .= $ml->p("URL: ".$config{server}{server}.$ENV{REQUEST_URI}); $out .= $ml->p("Request Type: ".$ENV{REQUEST_METHOD}); if ($share{Page}) { $out .= $ml->p("Page: ID=".$share{Page}->id." Name=".$share{Page}->name); $out .= $ml->p("Section: ID=".$share{Page}->my_section->id." Title=".$share{Page}->my_section->title); } # display input data $ml->h2("Input"); if ($options) { my %opt = &DecodeString($options); $out .= $ml->h3("Options").&ShowHash(%opt); } if (scalar keys %{$this->{query}}) { $out .= $ml->h3("Query Data").&ShowHash($this->{query}); } if ($this->{is_post}) { $out .= $ml->h3("Post Data").&ShowHash($this->{post}); } # mess around with the query data $out .= $ml->h2("Test Links"); $out .= $ml->p( $ml->a("Here",{href=>$this->link(test=>123)}). " are a few ". $ml->a("test links",{href=>$this->link(test=>undef,cmd=>"go")}). " you can ". $ml->a("try",{href=>$this->link(id=>555,cmd=>undef)}). "." ); # mess around with the post data $out .= $ml->h2("Test Form"); my $f = new ExSite::FormBuilder(); $f->input(prompt=>"Name",name=>"name",type=>"text",size=>40,required=>1,value=>$this->{input}{name}); $f->input(prompt=>"E-mail",name=>"email",type=>"email",size=>25,required=>1,value=>$this->{input}{email}); $f->input(prompt=>"Age",name=>"age",type=>"number",min=>19,max=>85,value=>$this->{input}{age}||30); $f->buttons(submit=>1); $out .= $f->make(); return $out; } sub ioctl { my $this = shift; $_ = shift; # $_ is the ioctl request if (/isRestricted/) { # not restricted, any website can make use of this plug-in return 0; } elsif (/isService/) { # does not use a service page return 0; } elsif (/ControlPanel/) { return \&ctrl_panel; } elsif (/ModuleName/) { return "Example Plug-in"; } elsif (/ModuleInfo/) { return "Sample code for developers to learn how plug-ins work."; } # the following functions are not supported by this example plug-in # elsif (/Cron/) { # return \&task_manager; # } # elsif (/Dependencides/) { # return [ "Plugin1", "Plugin2" ]; # } } sub ctrl_panel { my $this = shift; my $db = $share{DB}; $ml = &get_obj("ML"); $ui = &get_obj("UI"); my $out; $out .= $ui->HelpMsg("This control panel simply reproduces the functionality from write()"); $out .= $this->write(); return $out; } 1;