SquirrelMail English Site Danish Site French Site Russian Site Japanese Site 
News
About
Support
Screen shots
Download
Plugins
Documentation
Sponsors



SquirrelMail Developer's Manual: Developing plugins Next Previous Contents

4. Developing plugins

Due to the large number and wide range of functions plugins can cover it is impractical to add all but the most popular to the SquirrelMail core project. There is a separate SM Plugins project registered at SourceForge to house plugin code, developers should consider joining this project.

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

4.1 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 password changing, displaying ads and calendars should be possible to add as plugins.

The idea

The idea is to be able to run random code at given places in the SquirrelMail code. This random code should then be able to do whatever needed to enhance the functionality of SquirrelMail. The places where code can be executed are called "hooks".

There are some limitations in what these hooks can do. It is difficult to use them to change the layout and to change functionality that already is in SquirrelMail.

Some way for the plugins to interact with the help subsystem and translations will be provided.

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.

To start using a plugin, its 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 included 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.

4.2 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 down 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.

Any other files used by the plugin should also be placed in the plugin directory (or subdirectory thereof) and should contain the bulk of the plugin logic.

The function squirrelmail_plugin_init_<plugin_name>() is called to initalize a plugin. This function could look something like this (if the plugin was named "demo" and resided in the directory plugins/demo/):

function squirrelmail_plugin_init_demo() {
    global $squirrelmail_plugin_hooks;

    $squirrelmail_plugin_hooks['generic_header']['demo'] = 'plugin_demo_header';
    $squirrelmail_plugin_hooks['menuline']['demo'] = 'plugin_demo_menuline';
}

Please note that as of SquirrelMail 1.5.2, this function is no longer called at run time and is instead called (actually, just parsed) only once at configuration time. Thus, the inclusion of any dynamic code (anything except hook registration) here is strongly discouraged.

Adding functionality

In this example, the "demo" plugin should also have two other functions in its setup.php file called plugin_demo_header() and plugin_demo_menuline(). The first of these might look something like this:

function plugin_demo_header() {
    include_once(SM_PATH . 'plugins/demo/functions.php');
    plugin_demo_header_do();
}

The function called plugin_demo_header_do() in the file plugins/demo/functions.php contains the plugin's core logic for the generic_header hook.

4.3 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. As of SqruirrelMail 1.5.2, 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.

  • plugin name
  • plugin version
  • minimum SquirrelMail
  • version
  • summary
  • details
  • requires_configuration
  • requires_source_patch

Depending on the plugin, other information such as required PHP extensions or Pear packages should be included. Currently, SquirrelMail understands the following values:

array(
   'english_name' => 'My Plugin Name',   // different than directory name
   'version' => '1.0.0',
   'required_sm_version' => '1.5.2',
   'summary' => 'This is the short plugin description',
   'details' => 'This is the long, detailed plugin description',
   'requires_configuration' => 1,  // can be 0 if no config file required
   'requires_source_patch' => 0,   // set to 2 if depends on SM version
   'required_plugins' => array(    // plugin name and version sub-array
                                      'compatibility' => '2.0.3',
                               ),
   'required_php_modules' => array(
                                      'recode'
                                 ),
   'required_pear_packages' => array(
                                      'Cache_Lite',
                                      'MDB2',
                                   ),
   'required_functions' => array(
                                      'recode_string',
                               ),
   'required_php_version' => '4.1.0',
   'other_requirements' => 'Courier Maildrop LDA',
   'external_project_uri' => 'http://example.org/my_plugin'
);

The more of these that are returned to SquirrelMail, the more useful and cooperative a plugin will be.

TODO: might be nice to see something like this for templates too (version/language(HTML,XML,XHTML,CHTML,WML,etc)/engine(Smarty,PHP,etc)/intended interface...

Prior to SquirrelMail 1.5.2, a function called <plugin name>_version() was required. For the sake of compatibility, it can be piggy-backed on top of the <plugin name>_info() function, such that setup.php looks like:

/**
  * Returns info about this plugin
  *
  */
function demo_info()
{

   return array(
                  'english_name' => 'Favorite Colors Demo',
                  'version' => '1.0',
                  'required_sm_version' => '1.4.0', 
                  'summary' => 'Shows user\'s favorite color in folder list.',
                  'details' => 'This plugin will show the user\'s favorite color above the folder list.',
                  'requires_configuration' => 0,
                  'requires_source_patch' => 0,
               );

}


/**
  * 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 Favorite Colors", the file plugins/demo/version should have these two lines:

Favorite Colors Demo
1.0

4.4 Configuration

It is common to need a configuration file that holds some variables that are set up at install time. For ease of installation and maintenance, you should place all behavioral settings in a config file, isolated from the rest of your plugin code. A typical file name to use is config.php. If you are using such a file, you should not include a file called config.php in your plugin distribution, but instead a copy of that file called config.sample.php. This helps systems administrators avoid overwriting the config.php files and losing all of their setup information when they upgrade your plugin.

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

if ( !is_plugin_enabled('my_plugin_name') ) {
    exit('The plugin my_plugin_name isn't enabled in the SquirrelMail configuration.');
}

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.6 Hooks

Hooks, when executed, 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 get complicated.

Hook types: parameters and return values

SquirrelMail 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 custom HTML or override 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 source) must grab the return value for this to work). Note that in this case, the 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 source. Modify parameters with care!

Read more about the do_hook() function in the stable and development 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 source for this hook might use the return value 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 stable and development API documentation.

The concat_hook_function() fixes some shortcomings of the do_hook_function(). 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 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 stable and development 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 source (look for yourself). Plugins make their vote by returning a boolean TRUE or FALSE values. 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 source:

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

The plugin's hook function will get the second paramter in the hook call in the source as its parameter (this might be an array if multiple values need to be passed).

Read more about the boolean_hook_function() function in the stable and development 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: 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

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() 
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_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
# = Called with hook type (see below)
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'] = SM_PATH . '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', SM_PATH . '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; }';

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.0i, 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.7 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 really are needed for your plugin, there are two ways to add them. When there's only a few preferences that doesn'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"). 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. 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';

Make sure that the function demo_options() calls another function, located elsewhere, called demo_options_do(). 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 at 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_do() {
    global $optpage_data;
    $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;
}

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 (yes/no) radio buttons.
  • 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.

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 at 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_do() {
    global $optpage_data;
    $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'
    );
}

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, make note that they are really dumb
    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, avoid it if you can. That said, here is how it works. 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';

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_do() 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';

The function demo_save_options_do() 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":

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';

Assuming the function demo_options_block() calls another function elsewhere called demo_options_block_do(), that function only needs to create a simple array and add it to the $optpage_blocks array:

global $optpage_blocks;
bindtextdomain('demo', SM_PATH . 'locale');
textdomain('demo');
$optpage_blocks[] = array(
    'name' => _("Favorite Color Settings"),
    'url'  => SM_PATH . 'plugins/demo/options.php',
    'desc' => _("Change your favorite color and find new exciting colors"),
    'js'   => FALSE
);
bindtextdomain('squirrelmail', SM_PATH . 'locale');
textdomain('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.

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.

Saving and retrieving preferences

TODO: Complete this section. (What happened to the text for this section from plugin.txt?)

4.8 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 the following code:

/*
 * Pages that are called directly by the client browser need to load the
 * SquirrelMail framework. This differs between various SquirrelMail versions.
 * Plugins that don't support all SquirrelMail versions can strip some of this
 * code away.
 */
if (file_exists('../../include/init.php')) {
    /*
     * "include/init.php" is used since SquirrelMail 1.5.2. This isn't needed
     * for plugins that don't support SquirrelMail 1.5.2 and later.
     */
    include_once('../../include/init.php');
} elseif (file_exists('../../include/validate.php')) {
    /*
     * "include/validate.php" is used since SquirrelMail 1.4.0 until version
     * 1.5.1.
     */
    define('SM_PATH', '../../');
    include_once(SM_PATH . 'include/validate.php');
} else {
    /*
     * "src/validate.php" is used in SquirrelMail 1.2. This isn't needed for
     * plugins that don't support SquirrelMail 1.2.
     */
    chdir('..');
    define('SM_PATH', '../');
    include_once(SM_PATH . 'src/validate.php');
}

TODO: Possibly provide link to the include hierarchy provided by init.php/validate.php

4.9 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.10 Documentation files

Two more files are needed in your plugin directory. One file shall describes your plugin and offers detailed instructions for configuration or help with troubleshooting, etc. This file is usually entitled README. Some useful sections to include might be:

There are a few files that you should make sure to include when you build your final plugin distribution.

  • Plugin name and author
  • Current version
  • Plugin features
  • Detailed plugin description
  • How-to for plugin configuration
  • Change log
  • Future ideas/enhancements/to do list

The other files 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) Change into the demo directory, copy "config.php.sample" to "config.php" and
   edit "config.php", making adjustments as you deem necessary. For more
   detailed explanations about each of these parameters, consult the README
   file.

      cd demo
      cp config.php.sample config.php
      vi config.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 3.1 version of the demo plugin.

  cd plugins
  tar -zxvf demo-3.1-1.4.0.tar.gz

2) Change into the demo directory, 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 -Nau config.php config.php.sample

   Or simply replace your "config.php" file with the provided sample and
   reconfigure the plugin from scratch (see step 2 under the installation
   procedure above).

4.11 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

When the plugin is ready to be reviewed by the SquirrelMail plugin team, mail it to the SquirrelMail plugins team members. Also consider asking on the SquirrelMail plugins mailing list for access to upload your plugin to the official third party plugin list after your plugin is approved.

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-2008 by The SquirrelMail Project Team