SquirrelMail  
Donations
News
About
Support
Screen shots
Download
Plugins
Documentation
Sponsors
Bounties



SquirrelMail Developer's Manual: Developing plugins Next Previous Contents

4. Developing plugins

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.

4.1 Before embarking

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.

4.2 The plugin architecture

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.

4.3 The starting point

Plugin initialization

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.

4.4 Hooks

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.

Read more about the do_hook() function in the 1.5 API documentation.

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.

Read more about the concat_hook_function() function in the 1.5 API documentation.

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.

Read more about the boolean_hook_function() function in the 1.5 API documentation.

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!

Read more about the do_hook() function in the 1.4 API documentation

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.

Read more about the do_hook_function() function in the 1.4 API documentation

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.

Read more about the concat_hook_function() function in the 1.4 API documentation

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).

Read more about the boolean_hook_function() function in the 1.4 API documentation

List of hooks

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 nameFound inCalled withNotes
abook_initfunctions/addressbook.phpdo_hook() 
abook_add_classfunctions/addressbook.phpdo_hook() 
loading_constantsfunctions/constants.phpdo_hook() 
logout_errorfunctions/display_messages.phpdo_hook() 
error_boxfunctions/display_messages.phpconcat_hook_function() 
get_pref_overridefunctions/file_prefs.phpdo_hook_function() 
get_preffunctions/file_prefs.phpdo_hook_function() 
options_identities_processfunctions/identity.phpdo_hook()&
options_identities_renumberfunctions/identity.phpdo_hook()&%
special_mailboxfunctions/imap_mailbox.phpdo_hook_function() 
rename_or_delete_folderfunctions/imap_mailbox.phpdo_hook_function()%
folder_statusfunctions/imap_general.php (functions/imap_mailbox.php since 1.5.1)do_hook_function() 
mailbox_index_beforefunctions/mailbox_display.phpdo_hook() 
mailbox_form_beforefunctions/mailbox_display.phpdo_hook() 
mailbox_index_afterfunctions/mailbox_display.phpdo_hook() 
check_handleAsSent_resultfunctions/mailbox_display.phpdo_hook() 
subject_linkfunctions/mailbox_display.phpconcat_hook_function() 
mailbox_display_buttonsfunctions/mailbox_display.phpdo_hook() 
mailbox_display_button_actionfunctions/mailbox_display.phpdo_hook_function() 
message_bodyfunctions/mime.phpdo_hook() 
attachment $type0/$type1functions/mime.phpdo_hook()^
attachment $type0/*functions/mime.phpdo_hook()^
attachment */*functions/mime.phpdo_hook()^
attachments_bottomfunctions/mime.phpdo_hook_function() 
decode_bodyfunctions/mime.phpdo_hook_function() 
generic_headerfunctions/page_header.phpdo_hook() 
menulinefunctions/page_header.phpdo_hook() 
prefs_backendfunctions/prefs.phpdo_hook_function() 
config_override (since 1.5.2)include/init.phpdo_hook() 
loading_prefsinclude/load_prefs.phpdo_hook() 
addrbook_html_search_belowsrc/addrbook_search_html.phpdo_hook() 
addressbook_bottomsrc/addressbook.phpdo_hook() 
compose_formsrc/compose.phpdo_hook()!
compose_bottomsrc/compose.phpdo_hook() 
compose_button_rowsrc/compose.phpdo_hook() 
compose_sendsrc/compose.phpdo_hook() 
compose_send_aftersrc/compose.phpdo_hook() 
configtest (since 1.5.2)src/configtest.phpboolean_hook_function() 
folders_bottomsrc/folders.phpdo_hook() 
help_topsrc/help.phpdo_hook() 
help_chaptersrc/help.phpdo_hook() 
help_bottomsrc/help.phpdo_hook() 
left_main_after_each_foldersrc/left_main.phpconcat_hook_function() 
left_main_beforesrc/left_main.phpdo_hook() 
left_main_aftersrc/left_main.phpdo_hook() 
login_cookiesrc/login.phpdo_hook() 
login_topsrc/login.phpdo_hook() 
login_formsrc/login.phpdo_hook() (concat_hook_function() since 1.5.1) 
login_bottomsrc/login.phpdo_hook() 
optpage_set_loadinfosrc/options.phpdo_hook()*
optpage_loadhook_personalsrc/options.phpdo_hook()*
optpage_loadhook_displaysrc/options.phpdo_hook()*
optpage_loadhook_highlightsrc/options.phpdo_hook()*
optpage_loadhook_foldersrc/options.phpdo_hook()*
optpage_loadhook_ordersrc/options.phpdo_hook()*
options_personal_savesrc/options.phpdo_hook()*
options_display_savesrc/options.phpdo_hook()*
options_folder_savesrc/options.phpdo_hook()*
options_savesrc/options.phpdo_hook()*
optpage_register_blocksrc/options.phpdo_hook()*
options_link_and_descriptionsrc/options.phpdo_hook()*
options_personal_insidesrc/options.phpdo_hook()*
options_display_insidesrc/options.phpdo_hook()*
options_highlight_insidesrc/options.phpdo_hook()*
options_folder_insidesrc/options.phpdo_hook()*
options_order_insidesrc/options.phpdo_hook()*
options_personal_bottomsrc/options.phpdo_hook()*
options_display_bottomsrc/options.phpdo_hook()*
options_highlight_bottomsrc/options.phpdo_hook()*
options_folder_bottomsrc/options.phpdo_hook()*
options_order_bottomsrc/options.phpdo_hook()*
options_highlight_bottomsrc/options_highlight.phpdo_hook()*
options_identities_topsrc/options_identities.phpdo_hook()&
options_identities_tablesrc/options_identities.phpconcat_hook_function()&
options_identities_buttonssrc/options_identities.phpconcat_hook_function()&
message_bodysrc/printer_friendly_bottom.phpdo_hook() 
read_body_headersrc/read_body.phpdo_hook() 
read_body_menu_topsrc/read_body.phpdo_hook_function() 
read_body_menu_bottomsrc/read_body.phpdo_hook() 
read_body_header_rightsrc/read_body.phpdo_hook() 
read_body_topsrc/read_body.phpdo_hook() 
attachments_topsrc/read_body.phpconcat_hook_function() 
read_body_bottomsrc/read_body.phpdo_hook() 
login_beforesrc/redirect.phpdo_hook() 
login_verifiedsrc/redirect.phpdo_hook() 
right_main_after_headersrc/right_main.phpdo_hook() 
right_main_bottomsrc/right_main.phpdo_hook() 
search_index_beforesrc/search.phpdo_hook() 
search_before_formsrc/search.phpdo_hook() 
search_after_formsrc/search.phpdo_hook() 
search_bottomsrc/search.phpdo_hook() 
logoutsrc/signout.phpdo_hook() 
message_body (since 1.5.2)src/view_html.phpdo_hook() 
message_body (since 1.5.2)src/view_text.phpdo_hook() 
webmail_topsrc/webmail.phpdo_hook() 
webmail_bottomsrc/webmail.phpconcat_hook_function() 
logout_above_textsrc/signout.phpconcat_hook_function() 
info_bottomplugins/info/options.phpdo_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"):

$squirrelmail_plugin_hooks['attachment application/x-zip']['demo'] =
    'demo_handle_zip_attachment';

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:

function demo_handle_zip_attachment_do(&$args) {
    bindtextdomain('demo', SM_PATH . 'locale');
    textdomain('demo');

    $args[0]['demo']['href'] = sqm_baseuri() . 'plugins/demo/zip_handler.php?' .
        'passed_id=' . $args[2] . '&mailbox=' . $args[3] .
        '&passed_ent_id=' . $args[4];
    $args[0]['demo']['text'] = _("Show zip contents");

    bindtextdomain('squirrelmail', sqm_baseuri() . 'locale');
    textdomain('squirrelmail');
}

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:

function demo_identities_table(&$args) {
    return '<tr bgcolor="' . $args[0] . '"><td>&nbsp;</td><td>' .
        'YOUR CODE HERE' . '</td></tr>' . "\n";
}

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.

4.5 Preferences

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:

$squirrelmail_plugin_hooks['optpage_loadhook_display']['demo'] = 'demo_options_stub';

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:

function demo_options() {
    global $optpage_data;
    sq_change_text_domain('demo');
    $optpage_data['grps']['demo'] = _("Demo Options");
    $optionValues = array();
    $optionValues[] = array(
        'name'    => 'plugin_demo_favorite_color',
        'caption' => _("Please choose your favorite color"),
        'type'    => SMOPT_TYPE_STRLIST,
        'refresh' => SMOPT_REFRESH_ALL,
        'posvals' => array(0 => _("red"),
                           1 => _("blue"),
                           2 => _("green"),
                           3 => _("orange")),
        'save'    => 'save_plugin_demo_favorite_color'
    );
    $optpage_data['vals']['demo'] = $optionValues;
    sq_change_text_domain('squirrelmail');
}

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:

0 - "General Display Options"
1 - "Mailbox Display Options"
2 - "Message Display and Composition"

To add our previous input drop-down to the "Mailbox Display Options", don't create your own group - just add it to the existing one.

function demo_options() {
    global $optpage_data;
    sq_change_text_domain('demo');
    $optpage_data['vals'][1][] = array(
        'name'    => 'plugin_demo_favorite_color',
        'caption' => _("Please choose your favorite color"),
        'type'    => SMOPT_TYPE_STRLIST,
        'refresh' => SMOPT_REFRESH_ALL,
        'posvals' => array(0 => _("red"),
                           1 => _("blue"),
                           2 => _("green"),
                           3 => _("orange")),
        'save'    => 'save_plugin_demo_favorite_color'
    );
    sq_change_text_domain('squirrelmail');
}

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:

$squirrelmail_plugin_hooks['options_display_inside']['demo'] = 'demo_show_options_stub';

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):

     ------cut here-------
     <tr>
        <td>
           OPTION NAME
        </td>
        <td>
           OPTION INPUT
        </td>
     </tr>
     ------cut here-------

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:

$squirrelmail_plugin_hooks['options_display_save']['demo'] = 'demo_save_options_stub';

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:

$squirrelmail_plugin_hooks['optpage_register_block']['demo'] = 'demo_options_block_stub';

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.

The next step is to create your custom options page, keeping in mind the guidelines in pages called directly and possibly using the techniques described in Saving and retrieving preferences below.

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?)

4.6 Plugin pages called directly by the client browser

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;

Here, the plugin's name would be "demo". There is more information about the files and functionality that are loaded using the code above in the "including files" section of the SquirrelMail coding guidelines.

4.7 Internationalization

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:

   sq_change_text_domain('demo');
   echo _("Hello!");
   sq_change_text_domain('squirrelmail');

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:

   $ xgettext --keyword=_ -L php --default-domain=demo *.php --output=demo.pot

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.

4.8 Configuration

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.

4.9 Versioning

Version numbering

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 NameExplanationExample Values & Notes
english_namePrettified plugin display name"Demo Future Prediction" (can be different than the plugin's directory name)
versionPlugin version"1.0", "2.5.1", etc.
authorsPlugin authorsAn 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_versionMinimum required SquirrelMail version"1.2.8", "1.4.15", "1.5.2", etc.
summaryFunctionality summaryThis should be a brief sentence or two at the most.
detailsFunctionality detailsThis can be much more verbose compared to the summary.
requires_configurationWhether or not the plugin requires a configuration file to be prepared by the administratorboolean 0 or 1.
requires_source_patchWhether or not the plugin requires patching of the SquirrelMail source codeboolean 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 NameExplanationExample Values & Notes
required_php_versionMinimum required PHP version"4.1.0", "4.4.8", "5.2.6", etc.
required_php_modulesList of required PHP extensionsAn array of PHP extension names needed by the plugin: array("recode", "gd", )
required_functionsList of required PHP functionsAn array of function names needed by the plugin: array("recode_string", "ngettext", )
required_pear_packagesList of required Pear packagesAn array of Pear package names needed by the plugin: array("MDB2", "Cache_Lite", )
required_pluginsList of other plugins that are requiredAn 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.
rpcList of RPC commands that this plugin answers toA 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_requirementsSome other non-SquirrelMail or non-PHP requirements"Courier Maildrop LDA"
external_project_uriAddress of the project page or source code repository for the plugin"http://example.org/my_plugin"
per_version_requirementsList of requirements this plugin has that are different based on the SquirrelMail version being usedAn 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:

Demo Future Prediction
2.5.1

4.10 Security considerations

Calling external programs

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.

4.11 Coding tips

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).

4.12 Compatibility with older versions of SquirrelMail

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.

4.13 Documentation files

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).

4.14 Storing code

[FIXME: expand this section]

4.15 Additional Resources

In addition to this document, help writing plugins is easily obtained by posting to the SquirrelMail plugins mailing list.

4.16 How to release your plugin

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().
  • Keep setup.php small and efficient
  • Internationalize all output
  • 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
  • Plugin "info" function and "version" function in setup.php
  • Example (and possibly default) configuration files.
  • No global code
  • Correct syntax for pages accessed directly by client browser.
  • 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.


Next Previous Contents
© 1999-2016 by The SquirrelMail Project Team