SquirrelMail is built with an ingenious and powerful system that allows
add-ons known as plugins to extend its feature set in almost infinite
directions. This is one of the main reasons cited when users and
administrators explain SquirrelMail as their webmail of choice, and the
technology behind it has also been borrowed for other projects.
It is also thanks to the many plugin authors that have contributed large
amounts of code and a wide array of functionality ideas that SquirrelMail
is as popular as it is. The SquirrelMail team would like to encourage
authors to contribute in the best way possible, which is what the following
information will enable them to do. If you are considering adding a feature
to SquirrelMail, this is the best place to start.
One of the most enjoyable parts of programming is seeing an idea turn into
a working product - the transition from "what if SquirrelMail could do..."
to "SquirrelMail can do..." The SquirrelMail team can appreciate and
relate to that kind of enthusiasm, but we also don't want to see it go to
waste. Therefore, the first thing to be done before considering the development
of a new plugin (for public consumption at least) is to ask the SquirrelMail
team if anyone has already attempted to implement the idea. It is also
extremely helpful to announce your intentions in public so that the
SquirrelMail community can provide feedback and help refine the idea before
it is implemented.
Additionally, in keeping with the SquirrelMail theme of providing a product
that is simple to install and maintain, the team's philosophy regarding
plugins is to avoid the "Firefox Syndrome", wherein it can take hours for
an administrator to find the right plugin to suit a single need. Plugins
that contain similar feature sets should be merged and authors should work
collaboratively instead of duplicating each others' efforts. If there is a
plugin on the SquirrelMail web site that almost does what you want to
do, please inquire with its author and/or the SquirrelMail team about
enhancing it with your ideas.
The plugin architecture of SquirrelMail is designed to make it possible to add
new features without having to patch SquirrelMail itself. Functionality like
changing user passwords, displaying ads or calendars, and managing spam
settings should be possible to add as plugins. In this scheme, either package
(SquirrelMail or a plugin) may be upgraded independently without risk of one
breaking the other.
The idea
The idea is to be able to run plugin code at given places in the SquirrelMail
core. The plugin code should then be able to do whatever is needed to enhance
the functionality of SquirrelMail. The places where plugin code can be executed
are called "hooks".
There are some limitations with what these hooks can do. It is difficult to use
them to change the layout and to change some functionalities that are hard-coded
in SquirrelMail.
The implementation
The plugin start point in the main SquirrelMail code is in the file
functions/plugin.php. In places where hooks are made available, they
are executed by calling one of the hook functions. The hook function then
traverses the array $squirrelmail_plugin_hooks['hookname'] and executes all
the functions that are named in that array. Those functions are placed there
when plugins register themselves with SquirrelMail as discussed below. A plugin
may add its own internal functions to this array under any hook name provided by
the SquirrelMail developers.
A plugin must reside in a subdirectory of the plugins/ directory. The
name of the subdirectory is considered to be the name of the plugin. Note that
the plugin will not function correctly if this is not the case.
A plugin is registered with SquirrelMail by using the configuration utility
or by manually editing the SquirrelMail configuration file. When adding a
plugin manually, its name (its directory name) must be added to the
$plugins array in config.php like this:
$plugins[] = 'plugin_name';
When a plugin is registered, the file
plugins/plugin_name/setup.php is loaded and the function
squirrelmail_plugin_init_<plugin name>() is called with no parameters.
That function is where the plugin may register itself against any hooks it
wishes to take advantage of.
As of SquirrelMail 1.5.2, the plugins/plugin_name/setup.php file is
parsed at configuration time instead of at run time as a performance
enhancement. The resultant $squirrelmail_plugin_hooks['hookname'] array
is then stored statically in the SquirrelMail config directory in a file
called plugin_hooks.php. Plugin authors should keep in mind that when
making changes to the hook structure of their plugins, they will need to
re-run the SquirrelMail configuraion utility or manually edit
plugin_hooks.php before the changes will take effect.
TODO: Add a description on how to manually create the file plugin_hooks.php, i.e. without using the Perl script. Perl is not a requisite.
All plugins must contain a file called setup.php and must include a
function called squirrelmail_plugin_init_<plugin name>() therein.
Since including numerous plugins can slow SquirrelMail's performance
considerably, the setup.php file should contain little else. Any functions
that are registered against plugin hooks should do little more than call another
function in a different file. No other files should be included in the global
scope of setup.php, as this defeats the purpose of keeping it streamlined.
Any other files used by the plugin should also be placed in the plugin directory
(or a subdirectory thereof) and should contain the bulk of the plugin logic.
The function squirrelmail_plugin_init_<plugin name>() is called to
initalize a plugin as explained
above. This
function could look something like this (if the plugin was named "demo" and
resided in the directory plugins/demo/):
/**
* Register this plugin with SquirrelMail
*/
function squirrelmail_plugin_init_demo() {
global $squirrelmail_plugin_hooks;
$squirrelmail_plugin_hooks['optpage_register_block']['demo']
= 'plugin_demo_options_stub';
$squirrelmail_plugin_hooks['login_cookie']['demo']
= 'plugin_demo_login_stub';
}
Again, please note that as of SquirrelMail 1.5.2, this function is no longer
called at run time and is instead called (actually, it is only parsed, never
executed) only once at configuration time. Thus, the inclusion of any dynamic
code (anything except hook registration) here is strongly discouraged.
Adding functionality
In the example
above, the "demo" plugin
should also have two other functions in its setup.php file called
plugin_demo_options_stub() and plugin_demo_login_stub(). The first
of these might look something like this:
/**
* Add link to the Demo options page on the SquirrelMail
* main options page listing
*/
function plugin_demo_options_stub() {
include_once(SM_PATH . 'plugins/demo/functions.php');
plugin_demo_options();
}
The function called plugin_demo_options() in the file
plugins/demo/functions.php contains the plugin's core logic for the
optpage_register_block hook. Aside from plugin_demo_options_stub()
and plugin_demo_login_stub() and the
version reporting information explained below,
there should be little-to-nothing else in setup.php.
In the example
above, the "demo" plugin
uses two "hooks" called "optpage_register_block" and "login_cookie". Which
hooks a plugin uses depends on what kind of functionality is being
implemented - where in the SquirrelMail source code does the plugin need to
execute its own code?
Hooks themselves are functions executed by the SquirrelMail core using one of
just a few hook types which in turn pass execution to the plugins that have
registered themselves against the current hook as explained
above.
When executed, hooks are called with differing parameters and may or may not
take return values, all depending on the type of hook being called and the
context in which it is being used. On the source side (where the hook call
originates), all hooks have at least one parameter, which is the name of the
hook. After that, things (sometimes) get complicated.
Hook types, parameters and return values
The design of each of the hook types has changed somewhat starting with
SquirrelMail version 1.5.2, so we provide a description of the hook types for
both the 1.4 release series and versions starting with 1.5.2. Despite the
differences noted here, in most cases, plugins will run on the same hook
without the need for version-specific code in either environment (however,
there are other differences that do require version-specific code, which are
explained elsewhere).
SquirrelMail 1.5.2+ uses three hook functions: do_hook(),
concat_hook_function(), and boolean_hook_function().
The do_hook() function is the most common do-all hook in the SquirrelMail
core. It provides all plugins a single parameter which is passed through the
hook by reference, so, if a plugin also accepts it by reference, it can be
changed as needed (and it is often an array of multiple values of interest to
plugins using a particular hook: take a look in the core - the second argument
to the do_hook call is what is passed along to the plugins). There is a
second parameter given to plugins, but it is not normally used: it is the
current value of the ultimate hook return value. All plugins are responsible
for sharing the creation/modification of whatever do_hook returns. The
ultimate return value is whatever the last plugin returns from its hook
implementation, but if it does return something, it should be sure to check
if there was already a return value created by other plugins, which is what is
given as the second parameter to the plugin. As with all hooks, the usage of
the hook parameters and/or its return value are determined based on the
context in which the hook is used - you have to look at it in the SquirrelMail
core. Please remember NEVER to output any HTML directly during hook execution.
The concat_hook_function() is used in places where the SquirrelMail
core needs to aggregate return data from all plugins on the same hook.
Plugins are provided with the same (modifiable) parameter as with do_hook.
In some places, the core will expect all plugins to return strings, which will
be concatenated together and returned to the SquirrelMail core. In other cases,
arbitrary values are used by the core, which will be placed into an array of
return values. What kind of return value is expected is best determined by
inspecting the core where the hook is called. Please remember NEVER to output
any HTML directly during hook execution.
The boolean_hook_function() allows plugins to vote for a specified action.
What that action is is entirely dependent on how the hook is used in the
SquirrelMail core (look for yourself). Plugins make their vote by returning a
boolean TRUE or FALSE value. This hook may be configured to tally
votes in one of three ways. This configuration is done with the third parameter
in the hook call in the core:
> 0 -- At least one TRUE will override all FALSE.
< 0 -- At least one FALSE will override all TRUE.
= 0 -- Majority wins. Any ties are broken with the last parameter in the
hook call.
Plugins using boolean_hook_function are provided with the same
(modifiable) parameter as with do_hook. Please remember NEVER to output
any HTML directly during hook execution.
SquirrelMail 1.4 uses four hook functions: do_hook(), do_hook_function(),
concat_hook_function(), and boolean_hook_function().
The do_hook() function is a simple function that allows injecting of
custom output or override of default interface data. It doesn't pass any
data and doesn't ask for anything back. A limited number of do_hook()
calls do pass some extra parameters, in which case your plugin may modify
the given data if you do so by reference. It is not necessary to return
anything from your function in such a case; modifying the parameter data
by reference is what does the job (although the hook call itself (in the
SquirrelMail core) must grab the return value for this to work). Note that
in this case, the one and only parameter to your hook function will be an
array, the first element simply being the hook name, followed by any other
parameters that may have been included in the actual hook call in the core.
Modify parameters with care!
The do_hook_function() was intended to be the main hook type used when the
source needs to get something back from your plugin. It is somewhat limited in
that it will only use the value returned from the last plugin registered
against the hook. The SquirrelMail core might use the return value for this hook
type for internal purposes, or might expect you to provide text or HTML to be
sent to the client browser (you'll have to look at its use in context to
understand how you should return values here). The parameters that your hook
function gets will be anything you see after the hook name in the actual
hook call in the source. These cannot be changed in the same way that the
do_hook() parameters can be.
The concat_hook_function() fixes some shortcomings of the
do_hook_function() in that it uses the return values of all plugins
registered against the hook. In order to do so, the return value is assumed to
be a string, which is just piled (concatenated) on top of whatever it got from
the other plugins working on the same hook. Again, you'll have to inspect the
source code to see how such data is put to use, but most of the time, it is
used to create a string of HTML to be inserted into the output page. The
parameters that your hook function will get are the same as for the
do_hook_function; they are anything after the hook name in the
actual hook call in the source.
The boolean_hook_function() allows plugins to vote for a specified action.
What that action is is entirely dependent on how the hook is used in the
SquirrelMail core (look for yourself). Plugins make their vote by returning a
boolean TRUE or FALSE value. This hook may be configured to tally
votes in one of three ways. This configuration is done with the third parameter
in the hook call in the core:
> 0 -- At least one TRUE will override all FALSE.
< 0 -- At least one FALSE will override all TRUE.
= 0 -- Majority wins. Any ties are broken with the last parameter in the
hook call.
Each plugin registered on such a hook will receive the second paramter in the
hook call in the core as its one and only parameter (this might be an array if
multiple values need to be passed).
This is a list of all hooks currently available in SquirrelMail, ordered by
file.
TODO: Update this list. (especially in 1.5.2, we are removing a great number of hooks to be replaced with the template_construct_<template> hook)
TODO: For 1.5.2, the template_construct_<template> hook deserves special attention (explanation) below - briefly, plugins can add output in templates by hooking into the template_construct_<template file> hook -- the "template file" is the template file name that is to be modified by the plugin, and the plugin points of modification in that file are found by looking for "plugin_output" in the template code - e.g.: php if (!empty($plugin_output['menuline'])) echo $plugin_output['menuline']; ? The plugin can then add its output at the desired array index by returning it from the hook in a one-element array with the key being the desired "plugin_output" index in the template file and the value being the output: return array('menuline' => $output);
TODO: Reduce list of hooks by grouping them into separate chapters.
TODO: List is very out-of-synch with newest 1.5.2 and all its changes, as is the rest of the plugin documentation... much needs to be re-written
TODO: Even for 1.4.x, this list may no longer accurate
TODO: The hook explanations below this list are also out of date! Need to update!
Hook name
Found in
Called with
Notes
abook_init
functions/addressbook.php
do_hook()
abook_add_class
functions/addressbook.php
do_hook()
loading_constants
functions/constants.php
do_hook()
logout_error
functions/display_messages.php
do_hook()
error_box
functions/display_messages.php
concat_hook_function()
get_pref_override
functions/file_prefs.php
do_hook_function()
get_pref
functions/file_prefs.php
do_hook_function()
options_identities_process
functions/identity.php
do_hook()
&
options_identities_renumber
functions/identity.php
do_hook()
&%
special_mailbox
functions/imap_mailbox.php
do_hook_function()
rename_or_delete_folder
functions/imap_mailbox.php
do_hook_function()
%
folder_status
functions/imap_general.php (functions/imap_mailbox.php since 1.5.1)
do_hook_function()
mailbox_index_before
functions/mailbox_display.php
do_hook()
mailbox_form_before
functions/mailbox_display.php
do_hook()
mailbox_index_after
functions/mailbox_display.php
do_hook()
check_handleAsSent_result
functions/mailbox_display.php
do_hook()
subject_link
functions/mailbox_display.php
concat_hook_function()
mailbox_display_buttons
functions/mailbox_display.php
do_hook()
mailbox_display_button_action
functions/mailbox_display.php
do_hook_function()
message_body
functions/mime.php
do_hook()
attachment $type0/$type1
functions/mime.php
do_hook()
^
attachment $type0/*
functions/mime.php
do_hook()
^
attachment */*
functions/mime.php
do_hook()
^
attachments_bottom
functions/mime.php
do_hook_function()
decode_body
functions/mime.php
do_hook_function()
generic_header
functions/page_header.php
do_hook()
menuline
functions/page_header.php
do_hook()
prefs_backend
functions/prefs.php
do_hook_function()
config_override (since 1.5.2)
include/init.php
do_hook()
loading_prefs
include/load_prefs.php
do_hook()
addrbook_html_search_below
src/addrbook_search_html.php
do_hook()
addressbook_bottom
src/addressbook.php
do_hook()
compose_form
src/compose.php
do_hook()
!
compose_bottom
src/compose.php
do_hook()
compose_button_row
src/compose.php
do_hook()
compose_send
src/compose.php
do_hook()
compose_send_after
src/compose.php
do_hook()
configtest (since 1.5.2)
src/configtest.php
boolean_hook_function()
folders_bottom
src/folders.php
do_hook()
help_top
src/help.php
do_hook()
help_chapter
src/help.php
do_hook()
help_bottom
src/help.php
do_hook()
left_main_after_each_folder
src/left_main.php
concat_hook_function()
left_main_before
src/left_main.php
do_hook()
left_main_after
src/left_main.php
do_hook()
login_cookie
src/login.php
do_hook()
login_top
src/login.php
do_hook()
login_form
src/login.php
do_hook() (concat_hook_function() since 1.5.1)
login_bottom
src/login.php
do_hook()
optpage_set_loadinfo
src/options.php
do_hook()
*
optpage_loadhook_personal
src/options.php
do_hook()
*
optpage_loadhook_display
src/options.php
do_hook()
*
optpage_loadhook_highlight
src/options.php
do_hook()
*
optpage_loadhook_folder
src/options.php
do_hook()
*
optpage_loadhook_order
src/options.php
do_hook()
*
options_personal_save
src/options.php
do_hook()
*
options_display_save
src/options.php
do_hook()
*
options_folder_save
src/options.php
do_hook()
*
options_save
src/options.php
do_hook()
*
optpage_register_block
src/options.php
do_hook()
*
options_link_and_description
src/options.php
do_hook()
*
options_personal_inside
src/options.php
do_hook()
*
options_display_inside
src/options.php
do_hook()
*
options_highlight_inside
src/options.php
do_hook()
*
options_folder_inside
src/options.php
do_hook()
*
options_order_inside
src/options.php
do_hook()
*
options_personal_bottom
src/options.php
do_hook()
*
options_display_bottom
src/options.php
do_hook()
*
options_highlight_bottom
src/options.php
do_hook()
*
options_folder_bottom
src/options.php
do_hook()
*
options_order_bottom
src/options.php
do_hook()
*
options_highlight_bottom
src/options_highlight.php
do_hook()
*
options_identities_top
src/options_identities.php
do_hook()
&
options_identities_table
src/options_identities.php
concat_hook_function()
&
options_identities_buttons
src/options_identities.php
concat_hook_function()
&
message_body
src/printer_friendly_bottom.php
do_hook()
read_body_header
src/read_body.php
do_hook()
read_body_menu_top
src/read_body.php
do_hook_function()
read_body_menu_bottom
src/read_body.php
do_hook()
read_body_header_right
src/read_body.php
do_hook()
read_body_top
src/read_body.php
do_hook()
attachments_top
src/read_body.php
concat_hook_function()
read_body_bottom
src/read_body.php
do_hook()
login_before
src/redirect.php
do_hook()
login_verified
src/redirect.php
do_hook()
right_main_after_header
src/right_main.php
do_hook()
right_main_bottom
src/right_main.php
do_hook()
search_index_before
src/search.php
do_hook()
search_before_form
src/search.php
do_hook()
search_after_form
src/search.php
do_hook()
search_bottom
src/search.php
do_hook()
logout
src/signout.php
do_hook()
message_body (since 1.5.2)
src/view_html.php
do_hook()
message_body (since 1.5.2)
src/view_text.php
do_hook()
webmail_top
src/webmail.php
do_hook()
webmail_bottom
src/webmail.php
concat_hook_function()
logout_above_text
src/signout.php
concat_hook_function()
info_bottom
plugins/info/options.php
do_hook()
O
% = This hook is used in multiple places in the given file
O = Optional hook provided by a particular plugin
& = See "Identity hooks" below
^ = See "Attachment hooks" below
* = See "Options" below
! = See "Compose form hook" below
The address book hooks
SquirrelMail 1.4.5 and 1.5.1 introduced two hooks that allow custom address book
backends. These hooks are placed in functions/addressbook.php file.
The abook_add_class hook is a simple hook designed to load custom address
book classes before any other code is loaded. The abook_init hook allows to
modify the $abook object that represents the configured address books. The
hook is executed after the initiation of the local address book backends (file
and DB based ones) and before the remote (LDAP) backend init. The second
abook_init argument stores the address book object, and the third argument
stores the return value of the $abook->add_backend() method.
The attachment hooks
When a message has attachments, this hook is called for each attachment according
to its MIME type. For instance, a ZIP file will trigger a hook call to a hook
named attachment application/x-zip. A plugin registered on such a hook typically
will show a link to do a specific action, such as "Verify" or "View" for a ZIP
file. Thus, to register your plugin for ZIP attachments, you'd do this in
setup.php (assuming your plugin is called "demo"):
This is a breakdown of the data passed in the array to the hook that is called:
[0] = An array of links for actions (see below) (alterable).
[1] = The message index of the first message on the message list page within
which the current message is found (startMessage). Can be used herein
for building URLs that point to the correct message list page.
[2] = The ID of the current message (id). Can be used herein to build URLs
that point to the correct message.
[3] = The mailbox name, encoded with urlencode() (urlMailbox). Can be used
herein to build URLs that point to the correct message list page.
[4] = The entity ID of the attachment inside the mail message (ent). Can
be used herein to build URLs that point to the correct attachment.
[5] = The default URL to go to when the filename is clicked upon (alterable,
but only one URL is allowed, thus, the last plugin to execute for
the current attachment wins out - usually it's better to just add a
new action to array element 1 above).
[6] = The filename that is displayed for the attachment.
[7] = The "where" criteria that was used to find the current message (where)
(only applicable when the message was in fact found using a search).
Can be used herein to build URLs that point to the correct message list
page.
[8] = The "what" criteria that was used to find the current message (what)
(only applicable when the message was in fact found using a search).
Can be used herein to build URLs that point to the correct message list
page.
To set up links for actions, add them to the array data passed in to the hook
like this:
$args[0]['<plugin name>']['href'] = 'link URL';
$args[0]['<plugin name>']['text'] = _("Text to display");
$args[0]['<plugin name>']['extra'] =
'extra stuff, such as an <img ...> tag (will be part of the clickable link)';
Note: The syntax of _("Text to display") is explained in the section about
internationalization.
You can leave the text empty and put an image tag in extra to show an
image-only link for the attachment, or do the opposite (leave extra empty)
to display a text-only link.
It's also possible to specify a hook as "attachment type0/*", for example
"attachment text/*". This hook will be executed whenever there's no more
specific rule available for that type.
There is also an "attachment */*" hook for plugins that want to handle any
and all attachments, regardless of their mime types. Please use conservatively.
Putting all this together, the demo_handle_zip_attachment() function should
look like this (note how the argument is being passed):
function demo_handle_zip_attachment(&$args) {
include_once(SM_PATH . 'plugins/demo/functions.php');
demo_handle_zip_attachment_do($args);
}
And the demo_handle_zip_attachment_do() function in the
plugins/demo/functions.php file would typically (but not necessarily)
display a custom link:
Note that the text domain has to be changed to properly translate the link text.
The file plugins/demo/zip_handler.php can now do whatever it needs with
the attachment (note that the link will hand information about how to retrieve the
source message from the IMAP server as GET varibles).
The compose form hook
The compose_form hook allows plugins to insert their own code into the form
tag for the main message composition HTML form. Usually plugins will want to
insert some kind of code in an onsubmit event handler. In order to allow
more than one plugin to do so, all plugins using this hook to add some
onsubmit code need to add that code (without the enclosing attribute name
and quotes) as a new array entry to the global $compose_onsubmit array. The
code should use "return false" if the plugin has found a reason to stop form
submission, otherwise it should do nothing (that is, please do not use
"return true", as that will prevent other plugins from using the onsubmit
handler). SquirrelMail itself will insert a final "return true". All
onsubmit code will be enclosed in double quotes by SquirrelMail, so plugins
need to quote accordingly if needed. For example:
global $compose_onsubmit;
$compose_onsubmit[] = ' if (somevar == \'no\') return false; ';
Note the escaped single quotes. If you use double quotes, they would have to be
escaped as such:
global $compose_onsubmit;
$compose_onsubmit[] = ' if (somevar == \'no\') { alert(\\"Sorry\\"); return false; }';
Also, plugin authors should try to retain compatibility with the Compose Extras
plugin by resetting its compose submit counter when preventing form submit. Use
this JavaScript code:
global $compose_onsubmit;
$compose_onsubmit[] = ' if (your-code-goes-here) { submit_count = 0; return false; }';
Any other form tag additions by a plugin (beside onsubmit event code) can
currently be echoed directly to the browser.
The configuration testing hook
SquirrelMail has a script called configtest.php which can be used to test
the SquirrelMail configuration. Since SquirrelMail 1.5.2, the configtest script
includes the configtest hook. The hook uses the boolean hook function call.
Plugins that are attached to this hook should return a boolean TRUE if
they detect any errors in the plugin's configuration. Verbose messages can be
printed with a do_err('error message', FALSE) function call or with any
standard PHP inline output function.
The configtest script is executed in an anonymous, unauthenticated environment, so
username and password information isn't available as it would be in all other
SquirrelMail scripts. Only a limited set of functions are loaded. The
do_err() function is a special configtest script
function, which is used to print error messages. If a plugin uses the
do_err() function, it is recommended to set the second function argument to
FALSE even on fatal errors. The configuration testing will stop on a fatal
error, that is, if the hook call returns a boolean TRUE, and it's best to
let the script continue checking for other system configuration problems.
The identity hooks
This set of hooks allows plugins to add options to the SquirrelMail identity
preferences.
This set of hooks is passed special information in the array of arguments:
options_identities_process
This hook is called at the top of the "Identities" page, which is most useful
when the user has changed any identity settings. This is where you'll want to
save any custom information you are keeping for each identity or catch any
custom submit buttons that you may have added to the identities page. In
SquirrelMail 1.4.4 or older, and in 1.5.0, the arguments to this hook are:
[0] = hook name (always "options_identities_process")
[1] = should I run the <tt/SaveUpdateFunction()/ (alterable)
By default, SaveUpdateFunction() isn't called. If you want to trigger it,
you have to set the second array element to 1 or TRUE.
Since SquirrelMail 1.4.6 and 1.5.1, the arguments to this hook are:
[0] = hook name (always "options_identities_process")
[1] = action (hook is used only in 'update' action and any custom
action added to form with option_identities_table and
option_identities_buttons hooks)
[2] = processed identity number
This hook isn't available in SquirrelMail 1.4.5.
options_identities_renumber
This hook is called when one of the identities is being renumbered. If a user
has three identities and deletes the second, this hook would be called with an
array that looks like this: ('options_identities_renumber', 2, 1). The
arguments to this hook are:
[0] = hook name (always "options_identities_renumber")
[1] = being renumbered from ('default' or 1 through (# idents) - 1)
[2] = being renumbered to ('default' or 1 through (# idents) - 1)
This hook isn't available in SquirrelMail 1.4.5, and the renumbering order
differs since 1.4.5 and 1.5.1.
options_identities_table
This hook allows you to insert additional rows into the table that holds each
identity. The arguments to this hook are:
[0] = additional HTML attributes applied to table row.
use it like this in your plugin:
<tr "<?php echo $args[0]; ?>">
[1] = is this an empty section (the one at the end of the list)?
[2] = what is the 'post' value? (ident # or empty string if default)
You need to return any HTML you would like to add to the table. You could add a
table row with code similar to this:
The first hook argument was modified in 1.4.5 and 1.5.1. In SquirrelMail
1.4.1--1.4.4 and 1.5.0, the argument only contains the background color. You
should use <tr bgcolor="<?php echo $args[0]; ?>"> in these
SquirrelMail versions.
options_identities_buttons
This hook allows you to add a button (or other HTML) to the row of buttons under
each identity. The arguments to this hook are:
[0] = is this an empty section (the one at the end of the list)?
[1] = what is the 'post' value? (ident # or empty string if default)
You need to return any HTML you would like to add here. You could add a button
with code similar to this:
function demo_identities_button(&$args) {
return '<input type="submit" name="demo_button_' . $args[1] .
'" value="Press Me" />';
}
Since SquirrelMail 1.4.6 and 1.5.1, the input element should use a
smaction[action_name][identity_no] value in the name attribute if you
want to process your button actions in the options_identity_process hook.
See sample implementation of identity hooks in the SquirrelMail demo plugin.
svn co https://squirrelmail.svn.sourceforge.net/svnroot/squirrelmail/trunk/squirrelmail/plugins/demo
The preference hooks
These hooks are used when you want to add preferences to existing preference
pages. See
Preferences.
Requesting new hooks
It's impossible for the SquirrelMail team to foresee all of the places where
hooks might be useful, so you might need to negotiate the insertion of a new
hook to make your plugin work. Start by writing a patch which will insert the
hook you want to add, and mail your request (with the patch attached) to the
SquirrelMail development mailing list. Don't forget to explain the need for the
new hook in your message.
Before you start adding user preferences to your plugin, please take a moment to
think about it: in some cases, more preferences may not be a good thing. Having
too many preferences can be confusing. Thinking from the user's perspective,
will the proposed preferences actually be used? Will users understand what these
preferences are for? Consider this carefully.
If you decide that more preferences are needed for your plugin, there are two
ways to add them. When there's only a few preferences that don't merit an
entirely new preferences page, you can incorporate them as a section in an
existing SquirrelMail preference section ("Personal Information", "Display
Preferences", "Message Highlighting", "Folder Preferences", or "Index Order";
in 1.5.2 and above, there is also a section called "Compose Preferences").
If there's an extensive number of preferences, a separate page might be needed.
There may also be other reasons to create a separate page for the user to
interact with.
Adding preferences to an existing page
There are two ways to accomplish the integration of your plugin's settings into
another preferences page. The first, and preferred, way to add preferences to
an existing preference page is to use one of the "optpage_loadhook_<pref
page> hooks. The sent_subfolders plugin has an excellent example of
this method (although beware that several other aspects of it are not currently
conformant to plugin specifications). Briefly, this way of adding preferences
consists of adding some plugin-specific information to a predefined data
structure which SquirrelMail then uses to build the HTML input forms for you.
As an example, we'll use the optpage_loadhook_display hook to add a new
group of preferences to the display preferences page. First, we inform
SquirrelMail that we want to use this hook by adding a line to the
squirrelmail_plugin_init_demo() function in
plugins/demo/setup.php:
Make sure that the function demo_options_stub() calls another function,
located elsewhere, called demo_options(). The demo_options() function
needs to add a new key to two arrays: $optpage_data['grps'] and
$optpage_data['vals']. The value associated with the key in
$optpage_data['grps'] should simply be the plugin's section heading on the
preferences page, and the value associated with the key in
$optpage_data['vals'] an array with all the plugin's preferences. (Yes,
that's four levels of nested arrays.) The specified preference attributes are
used by SquirrelMail to build the HTML input elements automatically. This
example includes just one input element, a select (drop-down) list:
The array used to specify each plugin preference has the following possible
attributes:
name
The preference name, used both as a key when saving the preferences and as the
input element name.
caption
The text that prefaces this preference.
trailing_text (optional)
A text that follows a text input or a select list. This is useful for
indicating units, meanings of special values, etc.
type
The type of input element, which should be one of:
SMOPT_TYPE_STRING for a string/text.
SMOPT_TYPE_STRLIST for a select list.
SMOPT_TYPE_TEXTAREA for a text area.
SMOPT_TYPE_INTEGER for an integer.
SMOPT_TYPE_FLOAT for a floating point number.
SMOPT_TYPE_BOOLEAN for a boolean (off/on) checkbox.
SMOPT_TYPE_HIDDEN for a hidden input (not actually shown on preferences page).
SMOPT_TYPE_COMMENT for a showing the text specified by the comment attribute, but no user input is needed.
SMOPT_TYPE_FLDRLIST for a select list of IMAP folders.
SMOPT_TYPE_FLDRLIST_MULTI for a select list of IMAP folders where the user can select more than one folder.
TODO: Version 1.5.2+ have some new option types: SMOPT_TYPE_EDIT_LIST, SMOPT_TYPE_STRLIST_MULTI, SMOPT_TYPE_BOOLEAN_CHECKBOX, SMOPT_TYPE_BOOLEAN_RADIO, SMOPT_TYPE_STRLIST_RADIO, SMOPT_TYPE_SUBMIT, SMOPT_TYPE_INFO, SMOPT_TYPE_PASSWORD
refresh (optional)
Indicates if a link should be shown to refresh part or all of the window after
saving. Possible values are:
SMOPT_REFRESH_NONE doesn't show a refresh link.
SMOPT_REFRESH_FOLDERLIST shows a link for refreshing the folder list.
SMOPT_REFRESH_ALL shows a link for refreshing the entire window.
initial_value
The value that should initially be placed in this input element.
posvals
For select lists, this should be an associative array where each key is an
actual input value and the corresponding value is what is displayed to the user
for that list item in the drop-down list.
save
You may indicate that special functionality needs to be used instead of just
saving this setting by giving the name of a function to call when this value
would otherwise just be saved in the user's preferences.
size
Specifies the size of certain input elements (typically textual inputs).
Possible values are:
SMOPT_SIZE_TINY
SMOPT_SIZE_SMALL
SMOPT_SIZE_MEDIUM
SMOPT_SIZE_LARGE
SMOPT_SIZE_HUGE
SMOPT_SIZE_NORMAL
comment
For SMOPT_TYPE_COMMENT type preferences, this is the text displayed to the
user.
extra_attributes
This is where you may add any additional JavaScript or other attributes to the
preference element. The value of this setting should be an array, where the
keys of the array are the attribute names as you expect them to show up in the
resulting HTML, and the values are the corresponding attribute values. For
example, you could add an "onchange" JavaScript handler to a drop-down list:
$optpage_data['vals'][1][] = array(
'extra_attributes' => array('onchange' => 'alert("Thank you for changing the drop-down list!");'),
...
post_script
You may specify some script (usually JavaScript) that will be placed after
(outside of) the input element.
htmlencoded
Disables HTML sanitizing. Don't disable the sanitizing if user input is
possible, unless your plugin uses its own sanitizing functions. Currently only
works with SMOPT_TYPE_STRLIST.
folder_filter
Controls folder list limits in SMOPT_TYPE_FLDRLIST widget. See the
$flag argument in the sqimap_mailbox_option_list() function.
Available since 1.5.1.
Note that you do not have to create a whole new section on the preferences page
if you merely want to add a simple input item or two to a preferences section
that already exists. For example, the "Display Options" page has these groups:
If you indicated a save attribute for any of your preferences, you must
create that function (you'll only need to do this if you need to do some special
processing for one of your settings). The function gets one parameter, which is
an object with mostly the same attributes you defined when you made the
preference above. The new_value (and possibly value, which is the
current value for this setting) is the most useful attribute in this context:
function save_plugin_demo_favorite_color($option) {
// if user chose orange, do something special
if ($option->new_value == 3) {
// more code here as needed
}
// don't even save this setting if user chose green (old setting will remain)
if ($option->new_value == 2) {
return;
}
// for all other colors, save as normal
save_option($option);
}
The second, deprecated, legacy method is to add the HTML code for your
preferences directly to the preferences page of your choice. Although currently
very popular, please avoid it if you can. The SquirrelMail team may refuse
to publish plugins using such code, but we provide this information for the
rare case where the method above may not work. Look for any of the hooks named
as options_<pref page>_inside, where <pref page> is "display",
"personal", etc. For this example, we'll use options_display_inside and,
as above, "demo" as our plugin name:
Inform SquirrelMail that we want to use this hook by adding a line to the
squirrelmail_plugin_init_demo() function in
plugins/demo/setup.php:
Note that there are also hooks such as options_display_bottom, however,
they place your preferences at the bottom of the preferences page, which is
usually not desirable (mostly because they also come after the closure of
the HTML form element). It is possible to use these hooks if you want to
create your own form with custom submission logic.
The function demo_show_options() should have output similar to this
(note that you will be inserting code into a table that is already defined with
two columns, so please be sure to keep this framework in your plugin):
Of course, you can place any text where "OPTION NAME" is and any input tags
where "OPTION INPUT" is.
You will want to use the options_<pref page>_save hook (in this case,
options_display_save) to save the user's settings after they have pressed
the "Submit" button. Again, inform SquirrelMail that we want to use this hook by
adding a line to the squirrelmail_plugin_init_demo() function in
plugins/demo/setup.php:
The function demo_save_options() should put the user's settings into
permanent storage (see
Saving and retrieving preferences below for more information). This example assumes that in the
preferences page, the input element's name attribute was set to
"demo_option":
function demo_save_options() {
global $data_dir, $username;
sqgetGlobalVar('demo_option', $demo_option);
setPref($data_dir, $username, 'demo_option', $demo_option);
}
Creating a custom preference page
It is also possible to create a custom preference page for a plugin. This is
particularly useful when the plugin has numerous preferences or needs to offer
special interaction with the user (for things such as changing password, etc.).
Here is an outline of how to do so (again, using the "demo" plugin name):
First of all, you have to add a new listing to the main "Options" page. Always
use the optpage_register_block hook where you create a simple array that
lets SquirrelMail build the HTML to add the plugin preferences entry
automatically. Inform SquirrelMail that we want to use this hook by adding a
line to the squirrelmail_plugin_init_demo() function in
plugins/demo/setup.php:
Assuming the function demo_options_block_stub() calls another function
elsewhere called demo_options_block(), that function only needs to create
a simple array and add it to the $optpage_blocks array:
function demo_options_block() {
global $optpage_blocks;
sq_change_text_domain('demo');
$optpage_blocks[] = array(
'name' => _("Favorite Color Settings"),
'url' => sqm_baseuri() . 'plugins/demo/options.php',
'desc' => _("Change your favorite color and find new exciting colors"),
'js' => FALSE
);
sq_change_text_domain('squirrelmail');
}
The array must have four elements:
name
The title of the plugin's preference page as it will be displayed at the Options
page. Note that the text domain has to be changed to properly translate this text.
url
The URI that points to your plugin's custom preferences page.
desc
A description of what the preferences page offers the user. This is displayed at
the Options page below the title. Note that the text domain has to be changed to
properly translate this text.
js
Indicates if this preference page requires the client browser to be
JavaScript-capable. Should be TRUE or FALSE. If given as TRUE
and a user accesses SquirrelMail with JavaScript disabled in their browser
(or turns it off in their SquirrelMail user preferences), this preferences
"block" will not be shown on the "Options" page.
TODO: It is both possible AND RECOMMENDED for a plugin to have its custom preferences page auto-generated by SquirrelMail using the same code explained above plus some extra magic. We need to explain how to do that. For now, see the Server Settings plugin as a reference.
Saving and retrieving preferences
TODO: Complete this section. (What happened to the text for this section from plugin.txt?)
There are a few places in a plugin, such as when hooking into the "menuline" or
"optpage_register_block" hooks, where you can provide a link to a file that is
called directly by the client browser. No matter what that page does, it should
always validate that the calling client has a current login session. Thus, all
such pages should start with almost identical code as shown in the example below.
Note that the code that loads the SquirrelMail framework shown below works for
SquirrelMail versions from 1.2.x to 1.5.x; if your plugin does not support the
1.2.x series, for example, that part can be left out.
// Load the SquirrelMail framework
//
// include/init.php contains the SquirelMail startup code starting
// with SquirrelMail 1.5.2
//
if (file_exists('../../include/init.php')) {
include_once('../../include/init.php');
// include/validate.php contains the SquirelMail startup code from
// SquirrelMail 1.4.0 up to SquirrelMail 1.5.1
//
} elseif (file_exists('../../include/validate.php')) {
define('SM_PATH', '../../');
include_once(SM_PATH . 'include/validate.php');
// src/validate.php contains the SquirelMail startup code for the
// SquirrelMail 1.2.x series
//
} else {
chdir('..');
define('SM_PATH', '../');
include_once(SM_PATH . 'src/validate.php');
}
// Make sure this plugin is activated
//
global $plugins;
if (!in_array('demo', $plugins))
exit;
Although this document may only be available in English, we sure hope that you
are thinking about making your plugin useful to the thousands of non-English
speaking SquirrelMail users out there! It isn't much trouble, so failing to do
so could be considered almost rude.
This document will only describe how you can accomplish the
internationalization of a plugin. For more general information about PHP and
SquirrelMail translation facilities, see the
SquirrelMail language translation resources.
The steps to fully internationalizing your plugin are quite easy, especially
if you follow them from the beginning of the plugin's development:
Text Domains
Switch to what's called a "text domain" for your plugin before any text
output. After the output, you will want to switch back to the SquirrelMail
text domain. You switch text domains by using sq_change_text_domain().
Note that if, in the middle of a translated function in your plugin, you use
any SquirrelMail functions that generate output, you'll need to temporarily
switch back to the SquirrelMail domain as well.
Output Syntax
ALL textual output must be echoed using standard gettext syntax. This
includes hyperlink text, page titles, etc. Including the use of
sq_change_text_domain(), here is the needed syntax:
You must use double quotes around your strings - not single quotes.
Also, please do not break up sentences (including punctuation such as colons)
into fragments that might cause problems when translating into languages with
different syntactic structure. That is, DO NOT do this:
echo _("I want to eat a ") . $fruitName . _(" before noon.");
Instead, do this:
echo sprintf(_("I want to eat a %s before noon."), $fruitName);
Translation Template
Now you just need to provide a file that contains all the strings in your
plugin that need to be translated into any given language. There is a tool
that is available on most *nix systems that can build this file for you:
Many plugin authors keep a special script file on hand in a "locale" directory
called "getpot" that does this automatically for them. You can borrow one such
file for your plugin. Please also store the resultant .pot file in the "locale"
directory, with a slightly modified header as follows:
# <your language> SquirrelMail Demo Plugin Translation File
# Copyright (c) 2005-2020 The Squirrelmail Development Team
# This file is distributed under the same license as the SquirrelMail package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: demo\n"
"Report-Msgid-Bugs-To: SquirrelMail Internationalization <[email protected]>\n"
"POT-Creation-Date: 2008-12-19 16:09-0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: SquirrelMail Language Team <[email protected]>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Hello!"
msgstr ""
msgid "Favorite Color"
msgstr ""
When your plugin is accepted and published on the SquirrelMail web site,
this template file will be added to the main SquirrelMail repository and
any translations of it should be submitted to the SquirrelMail
Internationalization team for inclusion in future language packs.
It is common to need a configuration file that holds some variables that are
set up at install time. For ease of installation, maintenance and upgrades, all
behavioral settings should be placed in a configuration file that is isolated
from the rest of the plugin code. A typical file name to use is
config.php. The plugin should allow the administrator to store this file
in either the plugin directory or in the main SquirrelMail configuration
directory, giving precedence to the latter.
Plugins that make use of configuration files should not include a file
called config.php in distribution packages; instead, a copy of that file
named config_example.php is desirable, since customized config.php
files will not be lost upon upgrading the plugin. If the plugin does not need
environment-specific configuration settings, you may also consider including
a default configuration file so that the administrator does not need to
configure the plugin unless she wants to change some particular setting.
The usual default configuration file name is config_default.php.
A plugin can try to be smart about where to find the needed configuration
file by doing something such as this:
if (!@include_once(SM_PATH . 'config/config_demo.php'))
if (!@include_once(SM_PATH . 'plugins/demo/config.php'))
@include_once(SM_PATH . 'plugins/demo/config_default.php');
Here, the plugin's name would be "demo". If the plugin must be configured
specifically for the system upon which it is installed, remove the third
line in this example.
In order to facilitate easier (and possibly automated) plugin management by
system administrators as well as in-SquirrelMail functionality that enables
cross-plugin compatibility and communication, plugin versioning should remain
consistent with that of SquirrelMail itself. While SquirrelMail does its
best to work with non-standard version strings, versioning such as that
explained in
Version numbering, with the
possible addition of an applicable SquirrelMail version, is the best way
to track your plugin's development (i.e., "1.0.4-1.4.5" or just "1.0.4").
Versions with only two digits ("1.0-1.4.5" or just "1.0") are also acceptable
(a third digit of zero is implied).
Version reporting
The way any automated process gets its hands on the plugin version string
depends on the SquirrelMail version being used. All
plugins should include an administrative function in setup.php that
returns an array of details that describe the plugin, including its version
number. The function must be named <plugin name>_info() and
should return an associative array of version and requirements information.
Currently, at least the following elements are required, but the more
information returned, the better.
Element Name
Explanation
Example Values & Notes
english_name
Prettified plugin display name
"Demo Future Prediction" (can be different than the plugin's directory name)
version
Plugin version
"1.0", "2.5.1", etc.
authors
Plugin authors
An array of authors, where each author is described by a sub-array that is keyed by "email" and "sm_site_username" (the latter to be used at a later date). See below for an example.
required_sm_version
Minimum required SquirrelMail version
"1.2.8", "1.4.15", "1.5.2", etc.
summary
Functionality summary
This should be a brief sentence or two at the most.
details
Functionality details
This can be much more verbose compared to the summary.
requires_configuration
Whether or not the plugin requires a configuration file to be prepared by the administrator
boolean 0 or 1.
requires_source_patch
Whether or not the plugin requires patching of the SquirrelMail source code
boolean 0 or 1 (or 2 to indicate that patching is optional (enables optional functionality) or is only required for some SquirrelMail versions, although the latter should be done in more detail using per_version_requirements).
Other optional elements include:
Element Name
Explanation
Example Values & Notes
required_php_version
Minimum required PHP version
"4.1.0", "4.4.8", "5.2.6", etc.
required_php_modules
List of required PHP extensions
An array of PHP extension names needed by the plugin: array("recode", "gd", )
required_functions
List of required PHP functions
An array of function names needed by the plugin: array("recode_string", "ngettext", )
required_pear_packages
List of required Pear packages
An array of Pear package names needed by the plugin: array("MDB2", "Cache_Lite", )
required_plugins
List of other plugins that are required
An array of required plugins, where each plugin is described by a sub-array that is keyed by "version" (the minimum version of the plugin that is required) and "activate" (whether or not the plugin needs to be activated, or merely present in the plugins directory (as a code library)). Instead of the sub-array, the special constant SQ_INCOMPATIBLE may be used to indicate that this plugin is not compatible with the other. See below for an example.
rpc
List of RPC commands that this plugin answers to
A two-element array keyed by "commands" and "errors". Each of these contains a sub-array. The "commands" array is keyed by RPC command name, where corresponding values are the English explanations for what each command does. The "errors" array is keyed by each possible error number that the plugin's RPC interface might produce, where corresponding values are English explanations of what each error number means. array("commands" => array("demo_get_one_prediction" => "Peer into the crystal ball once"), "errors" => array(720 => "demo_get_one_prediction failed"),)
other_requirements
Some other non-SquirrelMail or non-PHP requirements
"Courier Maildrop LDA"
external_project_uri
Address of the project page or source code repository for the plugin
"http://example.org/my_plugin"
per_version_requirements
List of requirements this plugin has that are different based on the SquirrelMail version being used
An array which is keyed by SquirrelMail version numbers. Any version numbers skipped receive the same requirements laid out for the last version number that is listed below the skipped one. Each value is then a sub-array that may contain any of the requirement entries listed here, such as requires_source_patch, required_plugins, etc., or a special constant called SQ_INCOMPATIBLE (set the associated value to boolean 1), which indicates that the plugin is not compatible with the corresponding version of SquirrelMail. SQ_INCOMPATIBLE may also replace the entire sub-array for the incompatible SquirrelMail version, which is easier to understand. See below for an example.
A typical implementation of this function is as follows. Note that
this example shows a conditional requirement for the Compatibility
plugin that is due to the (required) use of sq_change_text_domain()
(explained in the
plugin internationalization section below), which
was added to the SquirrelMail core as of version 1.4.10 and 1.5.2.
/**
* Returns info about this plugin
*/
function demo_info() {
return array(
'english_name' => 'Demo Future Prediction',
'authors' => array(
'Emma Goldman' => array(
'email' => '[email protected]',
'sm_site_username' => 'emma',
),
),
'summary' => 'Adds important information above the folder list.',
'details' => 'This plugin automatically sees into the user\'s '
. 'future and places important details about upcoming '
. 'events above the folder list.',
'version' => '2.5.1',
'required_sm_version' => '1.2.9',
'requires_configuration' => 0,
'requires_source_patch' => 0,
'per_version_requirements' => array(
'1.5.2' => array(
'required_plugins' => array(),
),
'1.5.0' => array(
'required_plugins' => array(
'compatibility' => array(
'version' => '2.0.7',
'activate' => FALSE,
)
)
),
'1.4.10' => array(
'required_plugins' => array(),
),
'1.2.9' => array(
'required_plugins' => array(
'compatibility' => array(
'version' => '2.0.7',
'activate' => FALSE,
)
)
),
),
);
}
A function called <plugin name>_version() is required in all
versions of SquirrelMail prior to SquirrelMail 1.5.2. This function provides a
subset of the information in <plugin name>_info() (the version
number), so the easiest thing to do is to piggy-back it on top of the
<plugin name>_info() function like this:
/**
* Returns version info about this plugin
*/
function demo_version() {
$info = demo_info();
return $info['version'];
}
There is also a legacy version reporting mechanism that we would like plugin
authors to implement, since we are still in a transition period between the
older and newer reporting usages. The now deprecated reporting mechanism is to
create a file called version in the plugin directory. That file should
have only two lines: the first line should have the name of the plugin as
named on the SquirrelMail web site (this is often a prettified version of the plugin
directory name), and the second line must have the version and nothing more. So
for our "demo" plugin, whose name on the web site might be something like "Demo
Future Prediction", the file plugins/demo/version should have these two
lines:
All plugin authors should consider the security implications of their plugin.
Great care must always be taken if the plugin calls external programs, especially
ones that require set-uid permissions.
FIXME: more here about how to secure exec() calls with escapeshellarg() and named pipes method vs exec()
Storing sensitive data
If a plugin needs to store sensitive user configuration files or other such data,
please consider extra steps to secure such files. One very easy way to do so is
to wrap all configuration files in PHP tags (and C-style block comments if the
configuration data is not PHP code itself):
<?php /*
# below is example non-PHP configuration
# data that is secured from prying eyes
username_1 = admin
username_2 = super_admin
*/ ?>
Another approach is to store sensitive configuration data in SquirrelMail's
own $data directory, since most system administrators (at least those who
have read the installation instructions) know that the $data directory
needs to be protected and (hopefully) have made sure that it has been.
Never store unsecured configuration data that contains any user or system-specific
information in your plugin directory. Even the above suggestions
may not be sufficient depending on how sensitive the data is that you are
storing. In such a situation, you might think about a more complex encryption
scheme such as the one provided by the Vadmin plugin.
Note that just shipping unsecured configuration files along with a
configuration file for Apache (.htaccess) is not sufficient because
not every system uses Apache as its web server.
Disallowing access for diabled plugins
In some cases, it is also prudent to make sure that the plugin doesn't perform
its function when it is not enabled. Any functions or files that contain PHP
code that is not wrapped inside a function can be protected from being
executed when the plugin is not activated by adding code similar to this:
For SquirrelMail 1.5.1 and up:
if (!is_plugin_enabled('my_plugin_name')) {
echo 'Security Violation';
exit;
}
For SquirrelMail versions 1.4.0 to 1.5.0:
global $plugins;
if (!in_array('mypluginname', $plugins)) {
echo 'Security Violation';
exit;
}
Note, however, that sometimes other plugins can legitimately access a
disabled plugin, so don't shoot yourself in the foot with too much protection.
Listed below are several useful tips to keep in mind while coding plugins.
Some of these items will be scrutinized and possibly treated as mandatory by
the plugin team when evaluating new plugins.
Knowing what hook is running
Some plugins may register the same function against multiple hooks or otherwise
run the same library code for more than one hook. If that code needs to make a
hook-specific decision, it can look at the global variable called
$currentHookName. However, this variable is only available in SquirrelMail
versions 1.5.1 and above, so it is more portable to use the function
get_current_hook_name(), which is provided by the Compatibility plugin
(see elsewhere for more information about using the Compatibility
plugin[FIXME: provide a link for this]).
No global code
All plugin code should be contained in functions that are only executed when
needed. Even constant declarations and other setup code should not be
executed unconditionally. The only two exceptions to this rule are
configuration files (where only configuration variables should be set) and
pages that are accessed directly by the browser (e.g., options pages).
Whenever new versions of SquirrelMail are released, there is always a
considerable lag time before it is widely adopted. During that transitional
time, especially when the new SquirrelMail version contains any architectural
and/or functional changes, plugin developers are put in a unique and very
difficult position. That is, there will be people running both the old and new
versions of SquirrelMail who want to use your plugin, and you will probably want
to accommodate them both.
One way to keep both sides happy is to keep two different versions of
your plugin up to date, one that runs under the older SquirrelMail, and one that
requires the newest SquirrelMail. This is inconvenient, however, especially if
you are continuing to develop the plugin.
Depending on the changes implemented in the new SquirrelMail version, another
approach you may be able to use is to include code that can auto-sense the
SquirrelMail version and make adjustments on the fly. There is a function called
check_sm_version() available which does that. Read more about it in the
stable and
development API documentation.
Finally, there is a plugin called "Compatibility" that is intended to solve
this problem without requiring any special coding on the part of plugin authors.
Authors can develop one version of their plugin and seamlessly support any
SquirrelMail version. Plugin code is typically developed against the newest
SquirrelMail release, and users running older versions of SquirrelMail can
use said plugin as long as they also have the "Compatibility" plugin. For
more inforamtion, please download "Compatibility" and read its README file.
Two more files must be included with your plugin. It is recommended that they
be placed in a subdirectory called "docs". One file shall describe your plugin
and offer detailed instructions for configuration or help with
troubleshooting, etc. This file is usually entitled README. Some useful
sections to include might be:
Plugin name and author
Current version
Plugin features
Plugin requirements
Detailed plugin description
How-to for plugin configuration
Change log
Future ideas/enhancements/to do list
Another file shall explain how to install your plugin. This file is typically
called INSTALL. If you do not require any special installation actions, you
can probably copy one from another plugin or use this as a template:
Installing the Demo plugin
==========================
1) Start with untaring the file into the plugins directory. Here
is an example for the 1.0 version of the Demo plugin.
$ cd plugins
$ tar -zxvf demo-1.0-1.4.0.tar.gz
2) Decide if you want to store the plugin configuration file in
the plugin directory or in the main SquirrelMail config directory.
A) To store the configuration file in the plugin directory,
change into the demo directory, copy config_example.php
to config.php and edit config.php, making adjustments as
you deem necessary.
$ cd demo
$ cp config_example.php config.php
$ vi config.php
B) To store the configuration file in the main SquirrelMail
config directory, change into the demo directory,
copy config_example.php to ../../config/config_demo.php
and edit ../../config/config_demo.php, making
adjustments as you deem necessary.
$ cd demo
$ cp config_example.php ../../config/config_demo.php
$ vi ../../config/config_demo.php
3) Then go to your config directory and run "conf.pl". Choose
option 8 and move the plugin from the "Available Plugins"
category to the "Installed Plugins" category. Save and exit.
$ cd ../../config/
$ ./conf.pl
Upgrading the Demo plugin
=========================
1) Start with untaring the file into the plugins directory. Here
is a example for the 1.0 version of the Demo plugin.
$ cd plugins
$ tar -zxvf demo-1.0-1.4.0.tar.gz
2) Change into the demo directory and check your config.php file
against the new version to see if there are any new settings
that you must add to your config.php file.
$ diff -u config.php config_example.php
If you store your configuration file in the main SquirrelMail
config directory, adjust this command as follows:
$ diff -u ../../config/config_demo.php config_example.php
Or simply replace your configuration file with the provided
example and reconfigure the plugin from scratch (see step two
under the installation procedure above).
As long as you've consulted the list of plugin standards and done your best to
follow them, there's little standing in the way of great fame as an official
SquirrelMail plugin developer.
First you have to make a distribution file. The file should be named
demo-1.0-1.4.0.tar.gz, where "demo" is the name of your plugin, "1.0" is
the version of your plugin, and "1.4.0" is the version of SquirrelMail required
to use your plugin.
You can create the distribution file in most *nix environments by running this
command from the plugins directory (NOT your plugin directory):
tar -czvf demo-1.0-1.4.0.tar.gz demo
Requirements Checklist
Make sure the plugin meets the SquirrelMail plugin requirements explained throughout this document. Here is a quick checklist to help summarize the various requirements.
TODO: some of these items need more explanation (the old plugin.txt file has some useful text for some of this)
Plugins must be developed in an environment with PHP error reporting
set to E_ALL and PHP register_globals turned off. PHP
notices in plugin code are not acceptable.
Do not access superglobals directly. Always use sqgetGlobalVar()
and sqsession_*() functions. Code that accesses $_GET,
$_POST, $_SESSION or $_SERVER directly will be rejected.
TODO: provide more info or a link to how to use sqgetGlobalVar()
No PHP files may end with the closing PHP tag ?>.
Fallbacks must be provided for all JavaScript-based functionality
(check using the global $javascript_on value).
Use include_once() and not include(), require(), or
require_once().
All plugin directories should have index.php files copied from the
SquirrelMail "plugins" directory itself.
All plugin directories should have .htaccess files with
"Deny from All" in them, the only exception being a directory that
contains a file accessed directly from the client browser (an option
page, for example).
README file that points to the "docs" directory
COPYING, README and INSTALL files
"locale" directory with translation tools and .pot template file
displayPageHeader() with no applicable mailbox must NOT use
"None" - instead use an empty string:
displayPageHeader($color, '');
SM_PATH only for use when referencing other files, such as in
include_once() calls.
sqm_baseuri() only for use when constructing relative hyperlinks
given to the client browser
get_location() only for use when constructing absolute hyperlinks
given to the client browser, such as for header('Location...')
redirects.
Plugins that create user preference settings that are sensitive
from a security standpoint and should not be controllable by
users directly should be specified in a hook called "reset_user_prefs".
Please see the README file for the Reset User Preferences plugin.
TODO: there's more here I'm sure
Submitting the plugin
When the plugin is ready to be reviewed by the SquirrelMail plugin team, mail it
to the SquirrelMail plugin team members (who may be found by consulting the
SquirrelMail team list).
When the plugin is approved and you're granted access to upload it, all you have
to do is the actual uploading along with filling out some general information
about the plugin and what it does.