Creating Modules for Phorum5
============================

This document describes the Phorum5 module system. It is targeted at
developers who want to do customization and extend the functionality
of Phorum5. Modules are the preferred way to archieve this in
Phorum5.

For much of this document, we will be talking about an example module
"foo". Of course you will not name your module "foo", but something much
more appropriate. If you're not familiar with the terms "foo" and "bar",
you can visit http://en.wikipedia.org/wiki/Metasyntactic_variable

Be sure to read at least the **CAUTIONS AND SECURITY ISSUES** section,
before making your own modules.


Table of contents:

1. Introduction
   1.1 Modules
   1.2 Hacks
   1.3 Hooks
2. Creating your own modules
   2.1 What modules are built of
       2.1.1 Hook functions
       2.1.2 Module information
       2.1.3 Other "stuff"
   2.2 Module structure
       2.2.1 Single file modules
       2.2.2 Multiple file modules
   2.3 Supporting multiple languages
   2.4 Storing message data
       2.4.1 From hooks that are run before saving a message to the database
       2.4.2 From other hooks
   2.5 Storing user data
   2.6 Creating custom URLs
   2.7 Implementing settings for your module
   2.8 Changing the template
   2.9 Example modules
3. **CAUTIONS AND SECURITY ISSUES**
   3.1 Make modules, not hacks
   3.2 Reload your module if you change the module information
   3.3 How to access the $PHORUM array from your hook functions
   3.4 How to access additional files in your multi file module
   3.5 Secure your PHP files agains hackers
   3.6 Secure your pages from XSS
   3.7 Prevent namespace collisions
       3.7.1 (Hook) functions
       3.7.2 Data stored in $PHORUM
       3.7.3 Language strings stored in $PHORUM
       3.7.4 Data stored in messages, users and settings
4. Overview of available Phorum hooks
   4.1 Code hooks
   4.2 Template hooks
5. Support


1. Introduction
-------------------------------------------------------------------------------


 1.1 Modules
 -----------

   Modules are self contained pieces of software, that can be added to
   Phorum to change or extend its functionality. Modules can do this
   without having to change anything in the standard Phorum distribution
   files or database structure. So installing a module means: drop in
   the code, go to the admin "Modules" page, enable the module and it
   works.


 1.2 Hacks
 ---------

   The moment it is necessary to make changes to the standard Phorum
   distribution files or database structure to implement some kind of
   functionality, we're talking about a hack (even if the changes
   that have to be made are accompanied by a drop in module).

   Although there is nothing wrong with writing hacks, the Phorum team
   wants to urge you to try if you can write a module before resorting
   to a hack. Modules are the preferred way of modifying Phorum
   functionality, because that will make both upgrading your distribution
   and having your modification adopted by others easier.


 1.3 Hooks
 ---------

   Phorum uses hooks to run its modules. Hooks are points in the
   application where Phorum stops and runs its data through the modules
   that are configured to handle the hook. The modules can act upon and
   change this data.

   The following image visualizes what happens when Phorum reaches
   a hook point in the application, for which two modules ("foo" and
   "bar") have been configured.


     Phorum
   Application
       (1)                                (1) Phorum is running.
        |                                 (2) Phorum reaches the
        |                                     hook named "some_hook".
        v           Phorum                (3) Phorum sends data to
    some_hook >----- data ------+             the module system.
       (2)            (3)       |         (4) The module "foo" is run.
                                v         (5) The module "bar" is run.
                       (4) module "foo"   (6) The Phorum data (which
                                |             might be modified by the
                                v             modules) is sent back
                       (5) module "bar"       to Phorum.
                                |         (7) Phorum continues running
     Phorum        Modified     |             with the modified data.
   Application <---- data ------+
       (7)            (6)
        |
        |
        v


2. Creating your own modules
-------------------------------------------------------------------------------


 2.1 What modules are built of
 -----------------------------


  2.1.1 Hook functions
  --------------------

   A module contains one or more PHP functions that act as hook
   functions. Hook functions will receive some data in a variable
   from Phorum and have to return the (possibly modified) data, which
   will then go either back to Phorum or to the input of another module
   which also handles the same hook (see 1.3). So the most basic (and
   useless :-) hook function you could write would look somewhat like this
   (see 3.7 for an explanation of the naming of the function):

      function phorum_mod_foo_some_hook ($data) {
          return $data;
      }

   The exact nature of the data that is sent to the hook functions
   depends solely on the hook that is run. In chapter 4 of this document
   you will find a description of all supported hooks, including a
   specification of the type of data that is sent.


  2.1.2 Module information
  ------------------------

   For each hook that you want to handle in your module, you will have
   to point the module system to the function in your module that will
   handle the hook. Together with some other information, used for
   describing the module, this is stored in the module information.
   The module information acts as the glue between Phorum and your
   module.

   Module information is formatted using lines of plain text. Each line
   contains a bit of information about the module. The general format
   of the lines in the module information is:

      <type>: <data>

   Here is a list of the types that can be used:

   +----------+-----------------------------------------------------------+
   | <type>   | <data>                                                    |
   +----------+-----------------------------------------------------------+
   | title    | This is the title for the module that is displayed in the |
   |          | "Modules" page of the admin interface.                    |
   +----------+-----------------------------------------------------------+
   | desc     | This is the description that is displayed along with the  |
   |          | title in the admin interface, to give a little more       |
   |          | information about the module. Using HTML in the <data>    |
   |          | part is allowed.                                          |
   +----------+-----------------------------------------------------------+
   | hook     | This describes which hook functions are called for which  |
   |          | Phorum hooks. The data consists of two fields, separated  |
   |          | by a pipe "|" symbol. The first field contains the name   |
   |          | of the hook that this module is hooking into. The second  |
   |          | field contains the name of the hook function that will be |
   |          | called for the hook.                                      |
   +----------+-----------------------------------------------------------+
   | priority | This can be used for changing priorities and dependancies |
   |          | for modules and hooks. Possible values are (in order in   |
   |          | which they are processed):                                |
   |          |                                                           |
   |          | (1) run module before|after *                             |
   |          | (2) run module before|after <other module name>           |
   |          | (3) run hook <hook name> before|after *                   |
   |          | (4) run hook <hook name> bofore|after <other module name> |
   |          |                                                           |
   |          | Examples:                                                 |
   |          |                                                           |
   |          | priority: run module before *                             |
   |          |   Run this module before all other modules.               |
   |          | priority: run module before bbcode                        |
   |          |   Run this module before the bbcode module.               |
   |          | priority: run hook format before smileys                  |
   |          |   Run the "format" hook for this module before the        |
   |          |   "format" hook of the smileys module.                    |
   |          | priority: run hook after_header after *                   |
   |          |   Run the "after_header" hook for this module after all   |
   |          |   other "after_header" hooks.                             |
   |          |                                                           |
   |          | The main difference between "run module" and "run hook"   |
   |          | is that "run module" will affect the priority for each    |
   |          | hook of the module and that "run hooK" is used to         |
   |          | specifically change the priority of a single hook.        |
   |          | So to run all hooks for a module as early as possible,    |
   |          | but run the "foo" hook as late as possible and the        |
   |          | "bar" hook before the smileys mod, one could use the      |
   |          | following priority lines:                                 |
   |          |                                                           |
   |          | priority: run module before *                             |
   |          | priority: run hook foo after *                            |
   |          | priority: run hook bar before smileys                     |
   |          |                                                           |
   +----------+-----------------------------------------------------------+-------+
   | required_version | This describes which phorum version is required to use    |
   |          		  | this module. Modules with a requirement above the current |
   |          		  | phorum version are automatically disabled when going to   |
   |          		  | the modules page.										  |
   |         		  |                                                           |
   |         		  | Example:                                                  |`
   |         		  |                                                           |
   |         		  | required_version: 5.2.0                                   |
   |				  | This will require to be the phorum version at least 5.2.0 |
   +------------------+-----------------------------------------------------------+

   It is allowed to use multiple hook lines in your module information,
   so your module can hook into multiple hooks. When doing this, it
   is also allowed to use the same hook function for handling different
   hooks in your module (asuming the hooks are compatible).

   Here's an example of what the module information for our example
   module "foo" might look like:

      title: Foo example module
      desc: This is the Foo module for Phorum. Nothing exciting...
      hook: some_hook|phorum_mod_foo_some_hook
      hook: some_other_hook|phorum_mod_foo_some_other_hook
      hook: yet_another_hook|phorum_mod_foo_some_other_hook
      priority: run some_hook before some_other_module
      required_version: 5.2.0

   So what this module info for example does, is telling Phorum that
   when it gets to "some_other_hook", it will have to call the function
   phorum_mod_foo_some_other_hook() in your module. It also tells
   that for "yet_another_hook" the same function has to be called.
   It will also take care that the hook "some_hook" is run before the
   same hook in the module "some_other_module".

  2.1.3 Other "stuff"
  -------------------

   Hook functions and the module information are all the parts needed
   for creating a working module. However, your module might need
   extra stuff like template, language and image files. You can
   store these files along with your module when using the multiple
   file module structure (see 2.2.2 below).

   If you do not need to store any other stuff with your module, you
   can also choose to use the single file (see 2.2.1 below) module
   structure.


 2.2 Module structure
 --------------------


  2.2.1 Single file modules
  -------------------------

   Single file modules are useful in case case no additional files have
   to be distributed with your module. Because the module consist of
   only one single file, it is very easy to distribute. Beware that the
   moment you want to support for example a settings screen, multiple
   languages or custom images, you will have to switch to the multiple
   file module structure.

   Single file modules consist of one single PHP file, which contains
   both the module information and the hook functions. For storing the
   module informaton, a special PHP comment is used. This comment must
   look like the following:

      /* phorum module info
      <module information lines go here>
      */

   Using the example module info from 2.1.2, the complete single
   file module would look like this (see 3.5 why we use the
   check on PHORUM at the start of this file):

      <?php

      if(!defined("PHORUM")) return;

      /* phorum module info
      title: Foo example module
      desc: This is the Foo module for Phorum. Nothing exciting...
      hook: some_hook|phorum_mod_foo_some_hook
      hook: some_other_hook|phorum_mod_foo_some_other_hook
      hook: yet_another_hook|phorum_mod_foo_some_other_hook
      */

      function phorum_mod_foo_some_hook ($data) {
          // Do stuff for "some_hook".
          return $data;
      }

      function phorum_mod_foo_some_other_hook ($data) {
          // Do stuff for "some_other_hook" and "yet_another_hook".
          return $data;
      }

      ?>

   Installation of a single file module is done by putting the PHP
   file (e.g. foo.php) directly in the directory {phorum dir}/mods/
   and activating the module from the "Modules" screen in your
   admin interface.


  2.2.2 Multiple file modules
  ---------------------------

   Multiple file modules are useful in case you need additional files
   to be stored with your module, for example a settings screen,
   language files or custom images.

   Multiple file modules are stored in their own subdirectory below
   the directory {phorum dir}/mods/. So if you have a module named
   "foo", you will have to create a directory {phorum dir}/mods/foo/ for
   storing all module files.

   Inside this subdirectory, you will have to create a least two files.
   The first is a file called "info.txt". This file contains the
   module information for your module (see 2.1.2). The second file
   is the PHP file which contains the hook functions for your module.
   The basename of this file should be the same as the name of the
   module subdirectory. So for our example module "foo", you will have
   to create a file named "foo.php".

   Using the example module info from 2.1.2, the complete multiple
   file module would look like this (see 3.5 why we use the
   check on PHORUM at the start of the PHP file):

   info.txt:

      title: Foo example module
      desc: This is the Foo module for Phorum. Nothing exciting...
      hook: some_hook|phorum_mod_foo_some_hook
      hook: some_other_hook|phorum_mod_foo_some_other_hook
      hook: yet_another_hook|phorum_mod_foo_some_other_hook

   foo.php:

      <?php

      if(!defined("PHORUM")) return;

      function phorum_mod_foo_some_hook ($data) {
          // Do stuff for "some_hook".
          return $data;
      }

      function phorum_mod_foo_some_other_hook ($data) {
          // Do stuff for "some_other_hook" and "yet_another_hook".
          return $data;
      }

      ?>

   So far, the module has exactly same functionality as the single
   file module from 2.2.1. From here on, the functionality can be
   extended. Some of the possibilities are:

   - Using custom files for your module (images, classes, libs, etc.);
   - Letting your module support multiple languages;
   - Creating a settings screen for your module;


 2.3 Supporting multiple languages
 ---------------------------------

   (this feature is only available for the multiple file module structure)

   If your module includes text that will be displayed to end users,
   you should strongly consider making it support multiple languages.
   This will allow Phorum installations using another language to display
   output of your module in the same language, instead of the language
   you have written the module in.

   For supporting multiple languages, the first thing to do is add the
   following to your module information file (info.txt):

      hook: lang|

   There is no hook function configured here, because the "lang" hook
   is only used as a marker for Phorum. This only tells Phorum that your
   module supports multiple languages.

   Next, you must provide at least one language file with your module.
   Language files are stored in a subdirectory name "lang" inside your
   module directory. So in our sample module, the full directory would be
   {phorum dir}/foo/lang/. The language files must be named identical
   to the main language files that Phorum uses. So, to include both
   English and French, your module would have the following file
   structure below the Phorum's mods directory:

      foo/info.txt
      foo/foo.php
      foo/lang/english.php
      foo/lang/french.php

   The structure of your language files will be almost identical to that
   of the main Phorum language files. However, for your own language files
   it is advisable to add an extra level in the language variables, to
   avoid conflicts with other modules or Phorum itself. Here is an
   example of how you would do that:

      <?php
      $PHORUM["DATA"]["LANG"]["mod_foo"]["Hello"] = "Hello!";
      ?>

   Here, the extra inserted level is ["mod_foo"]. You can add as many
   lines as you need for your module. To access the above language string,
   from your module code you would use:

      $PHORUM["DATA"]["LANG"]["mod_foo"]["Hello"]

   From a template file, you would use:

      {LANG->mod_foo->Hello}

   In case a Phorum installation is using a language that your module
   does not support, Phorum will automatically attempt to fallback to
   English. So it is highly recommend that you include an english.php
   language file in all your modules. If both the current language and
   english.php are not found, Phorum will be unable to load a language
   for your module and will display empty space instead of language
   strings.

   Try to reuse strings that are already in the main Phorum language
   files itself. Only create custom strings when there is no alternative
   available. Having more text to translate is more work for everybody,
   especially the Phorum translators.


 2.4 Storing message data
 ------------------------

   If your module needs to store data along with a Phorum message,
   you can make use of the meta information array that is attached
   to each message ($message["meta"]). This array is a regular PHP
   array, which is stored in the database as serialized data
   (see http://www.php.net/serialize). Because Phorum and other modules
   make use of this meta data as well, you should not squash it,
   neither access the meta data in the database directly. Instead
   use the methods described in this section.

   Remark: because the meta data is stored as serialized data in the
   database, it is not possible to include data you store in there
   in SQL queries.

   When storing information in the meta data from a hook function, you
   can encounter two different situations, which both need a different
   way of handling.


  2.4.1 From hooks that are run before saving a message to the database
  ---------------------------------------------------------------------

   There are some hooks that send a full message structure to the
   hook functions, so these can change the message data before storing
   the message in the database. Examples are the hooks "before_post"
   and "before_edit". In this case you can simply update the meta
   information directly. Here's an example of how this would look
   in your hook function:

      function phorum_mod_foo_pre_post ($message) {
          $message["meta"]["mod_foo"]["foodata"] = "Some data";
          $message["meta"]["mod_foo"]["bardata"] = "Some more data";
          return $message;
      }

   Phorum will take care of storing the updated meta data in the database.


  2.4.2 From other hooks
  ----------------------

   For other hooks, the proper way to store information in the meta
   data is to retrieve the current meta data using phorum_db_get_message(),
   copy the meta data to a new message structure, make changes as needed
   and use phorum_db_update_message() to update the message in the
   database. Here is an example of how this could look in your hook
   function:

      function phorum_mod_foo_some_hook ($data) {

          // Somehow you get the id for the message. Here we asume
          // that it is stored in the $data parameter.
          $message_id = $data["message_id"];

          // Retrieve the current message data.
          $message = phorum_db_get_message ($message_id);

          // Create updated meta data.
          $new_message = array("meta" => $message["meta"]);
          $new_message["meta"]["mod_foo"]["foodata"] = "Some data";
          $new_message["meta"]["mod_foo"]["bardata"] = "Some more data";

          // Store the updated data in the database.
          phorum_db_update_message($message_id, $new_message);

          return $data;
      }

   Changing meta data for a message this way will ensure that the
   existing meta data is kept intact.


 2.5 Storing user data
 ---------------------

   If your module needs to store data along with a Phorum user,
   you can make use of custom profile fields. In the admin interface,
   under "Custom Profiles", you can add your own profile fields
   (see also docs/creating_custom_userfields.txt).

   The custom profile fields will be accessible from within the user
   data. E.g. if you have created a custom profile field named "foobar",
   the value of that field will be stored in $user["foobar"].

   When using a custom profile field for storing module information,
   you can use a separate field for each piece of data you want to
   store. But instead, you can also create a single field for storing
   a complete array of information. Phorum will automatically take care
   of storing this information (serialized) in the database. You only
   should make sure that the custom profile field is large enough to
   store all the data. When your module needs to store multiple fields,
   this is the preferred way.

   For storing data in the custom profile field, you can make use of the
   phorum_api_user_save() function. This function needs the user_id of the
   user and all fields that need to be updated. Below are two pieces of
   code which show how our example module might store data for a user
   (asuming $user_id is the id of the user that must be changed).

   When using multiple fields "mod_foo_foodata" and "mod_foo_bardata":

      $userdata = array(
          "user_id"         => $user_id,
          "mod_foo_foodata" => "Some user data",
          "mod_foo_bardata" => "Some more user data"
      );
      phorum_api_user_save($userdata);

   When using a single custom field "mod_foo" for this module:

      $userdata = array(
          "user_id" => $user_id,
          "mod_foo" => array (
              "foodata" => "Some user data",
              "bardata" => "Some more user data"
          )
      );
      phorum_api_user_save($userdata);


 2.6 Creating custom URLs
 -------------------------

   Phorum uses the function phorum_get_url() to consistenly build URLs
   that point to parts of Phorum. It is recommended that you use this
   function as well when creating links yourself, so special features
   and future changes will automatically be incorporated in the links
   you use.

   Here's an example of building an URL, which will open the profile
   for the user with user_id = 17:

      $url = phorum_get_url(PHORUM_PROFILE_URL, 17);

   The argument list that this function takes, depends on the first
   argument which tells Phorum what kind of URL has to be built.
   So when building other URLs, other arguments will probably
   be used.

   If you need to build a custom URL to link to your own module, you
   can use phorum_get_url() as well. The way to go is simple. You
   need to use PHORUM_CUSTOM_URL as the first argument and add all
   URL building parameters to it.

   The first parameter needs to be the filename of the file to link
   to, without the (.php) extension. The second parameter needs to
   be 0 or 1. If it is 1, the current forum_id is added to the URL.
   All other parameters are added comma separated to the URL.

   Here's an example of building a custom URL which links to the
   file "myfile.php". The URL has to have the forum_id in it and
   needs to contain the additional parameter "foo=bar":

      $url = phorum_get_url(PHORUM_CUSTOM_URL, "myfile", 1, "foo=bar");


 2.7 Implementing settings for your module
 -----------------------------------------

   (this feature is only available for the multiple file module structure)

   Some modules that you write might need to store settings for later
   use. For those, you can create a settings page which will be used
   from within the admin interface.

   The settings page must be put in your modules's directory by the
   name of "settings.php". So for our example module "foo" the file
   would go in {phorum dir}/mods/foo/settings.php. In the admin
   interface under the option "Modules", a link to the settings.php
   page will automatically be added if the settings.php file is
   available for your module.

   Although you can do anything you want from your settings.php script,
   it is recommended that you use the tools that are handed to you
   by Phorum for building pages and storing settings.

   One of those tools is a PHP object "PhorumInputForm" which
   can be used to create standard input forms and table displays in
   the admin interface. The best example here is to look at one of the
   modules that come with Phorum like "bbcode" or "replace".

   Another tool is the function phorum_db_update_settings() which can
   be used for storing settings in the database. To store settings using
   this function you do something like the following:

      $foo_settings["foodata"] = "Some setting data";
      $foo_settings["bardata"] = "Some more setting data";
      phorum_db_update_settings(array("mod_foo" => $foo_settings));

   $foo_settings can be anything you like: an array, object, string, etc.
   The first request after you have stored your settings, the setting
   data for this example will be available in $PHORUM["mod_foo"].

   To ensure that your settings file is only loaded from the admin
   interface, place this line at the top of your settings.php file
   (see also 3.5):

      if(!defined("PHORUM_ADMIN")) return;


 2.8 Changing the templates using template hooks
 -----------------------------------------------


  2.8.1 When to use a template hook
  ---------------------------------

   Changing the templates should be avoided as much as possible when
   writing a module. This will basically turn your mod into a hack,
   because files have to be edited for it. Inexperienced users might
   find it hard to install your module if they have to modify files
   to get it to work.

   If you cannot avoid changing the template, then consider to use
   template hooks for this. You can use these if your template change
   involves adding extra code to a template. The advantage is that
   there's only little code that has to be added to the templates,
   which makes things less confusing to users that want to install the
   module.


  2.8.2 How to use a template hook
  --------------------------------

   To create a template hook, you do the following:

   * Add "{HOOK tpl_some_hook}" to the template at an appropriate spot;

   * Put "hook: tpl_some_hook|phorum_mod_foo_tpl_some_hook" in your
     module info;

   * Create the hook function phorum_mod_foo_tpl_some_hook() that
     prints out the code that has to be placed at the position of
     the "{HOOK tpl_some_hook}" code the the template.

   If you want to pass on the data from template variables to
   the hook function, you can simply add the variables to the hook
   definition in the template. Example:

      {HOOK tpl_some_hook DATA1 DATA2}

   The hook function will get the contents of these variables passed in
   a single array. This can for example be useful if your template hook
   needs access to loop data. Example:

      {LOOP MESSAGES}
         ...
         {HOOK tpl_some_hook MESSAGES}
         ...
      {/LOOP MESSAGES}


  2.8.3 Preventing collisions in hook names
  -----------------------------------------

   You can use any name for "tpl_some_hook", but beware that your
   name does not collide with an already existing hook name.

   The easiest way to do this is use the techniques from section 3.7.
   As a rule of thumb, you can use the following format:

      tpl_mod_<modulename>_<identifier>

   Example: If a buttonbar is added in one of the templates for a module
   named "foo", the name for the hook could be "tpl_mod_foo_buttonbar".


 2.9 Example modules
 -------------------

   The best way of learning how to write modules is probably looking
   at existing module code. In your Phorum distribution's docs directory,
   you will find the directory example_mods. This directory contains a
   couple of example modules, demonstrating the features described in this
   document. The modules have no real functional purpose, but they might
   be easier to read than the real Phorum modules.


3. **CAUTIONS AND SECURITY ISSUES**
-------------------------------------------------------------------------------


 3.1 Make modules, not hacks
 ---------------------------

   Making modules that require database changes are discouraged and may
   not be accepted as an approved module. We want modules to be as
   transparent as possible for upgrades. Please attempt to store your
   data in the proper place. See chapter 2 for more information on that.


 3.2 Reload your module if you change the module information
 -----------------------------------------------------------

   If you are changing the module info for a module that is already
   activated in your Phorum installation, you must deactivate and
   reactivate it to have Phorum reload the changed information. For
   performance reasons the module information is only read when the
   module is activated.

   If you have added a new hook function to your module and it seems
   not to be run, it probably is because you did not do this.


 3.3 How to access the $PHORUM array from your hook functions
 ------------------------------------------------------------

   The $PHORUM array is in the global scope. From inside a function,
   you can not directly access this array. So you will have to import
   the $PHORUM array into your function scope. The Phorum team
   recommends the following method for doing this (check out the
   faq.txt to see why we do not use the "global" keyword):

      function phorum_mod_foo_some_hook ($data) {
          $PHORUM = $GLOBALS["PHORUM"];

          // Do stuff for "some_hook".

          return $data;
      }


 3.4 How to access additional files in your multi file module
 ------------------------------------------------------------

   All standard Phorum pages are run from the Phorum installation
   directory. The hook functions that you write also work from
   the same directory. So if you want to access files in your module
   directory, you will have to specify the relative path to those
   files. This path looks like:

      ./mods/<module>/<filename>

   So let's say that our module "foo" has a subdirectory "images"
   which contains "bar.gif", then we could display that image
   using the HTML code:

      <img src="./mods/foo/images/bar.gif" />

   Another example: let's say that there is a function library
   named "my_module_functions.php" in the module, which must be
   included from then module code, then this is done using:

      include("./mods/foo/my_module_functions.php");


 3.5 Secure your PHP files agains hackers
 ----------------------------------------

   To prevent hackers from loading your PHP module files directly, you
   should add the following to the start of your PHP files:

      if(!defined("PHORUM")) return;

   This will make sure that the file will only work when loaded from
   the Phorum application. If you are writing pages that are loaded
   from the admin interface (like a settings screen for your module),
   then use the following line instead:

      if(!defined("PHORUM_ADMIN")) return;

   This will make sure that the file will only work when loaded from
   the Phorum admin interface.


 3.6 Secure your pages from XSS
 ------------------------------

   XSS stands for cross site scripting. This means that hackers
   can feed HTML data to your application, which is displayed on
   screen without stripping or escaping the HTML data. This way
   it can be possible for hackers to feed malicous javascript
   code into the browser of users on the forum, causing a security
   risk. If you want to learn more about XSS, please visit
   http://en.wikipedia.org/wiki/XSS

   To prevent XSS security holes, you must take care that all
   user input is properly sanitized before displaying it on screen.
   Sanitizing can be done by either stripping all HTML from
   the data (e.g. using http://www.php.net/strip_tags) or by escaping
   all html characters (using http://www.php.net/htmlspecialchars).

   Example:

   If your module needs to display the username for a user on
   screen, it must not simply do:

       print $user["username"];

   Instead you must use:

       print htmlspecialchars($user["username"]);

   It's not only for security that you have to sanitize data before
   displaying it. You must use htmlspecialchars() to prevent some other
   possible problems as well. Imagine you have a user with the username
   "<b>ob". Without htmlspecialchars() the username would be interpreted
   as HTML code, possibly making the full page bold from the username on.


 3.7 Prevent namespace collisions
 --------------------------------

   When creating modules, you must always be aware that you are
   working in the same namespace as other modules and Phorum itself.
   This means that there is a risk of duplicate use of function
   and variable names. By following a couple of simple rules, you
   can greatly reduce this risk.


  3.7.1 (Hook) functions
  ----------------------

   Always construct names for your module functions like this:

      phorum_mod_<module name>_<identifier>

   So if you are writing functions for a module named "foo", all
   function names will look like:

      phorum_mod_foo_<identifier>

   You can use whatever you like for the <identifier> part. When writing
   a hook function, it is recommended to use the name of the hook for
   which you are writing the function (this will make clear what the
   function does, without having to check the module info). So in case
   you are writing a hook function for the hook "some_hook", the full
   function name would be:

      phorum_mod_foo_some_hook

   If your hook function handles multiple hooks at once, then
   simply use one of the hook's names as the <identifier> or make up
   something yourself.


  3.7.2 Data stored in $PHORUM
  ----------------------------

   When storing data in $PHORUM, always prepend the array key name
   with mod_<module name>. If your module is named "foo", do not use:

      $PHORUM["mydata"]

   but instead:

      $PHORUM["mod_foo_mydata"]


  3.7.3 Language strings stored in $PHORUM
  ----------------------------------------

   When storing your custom language strings, do not put them directly
   in $PHORUM["DATA"]["LANG"] like Phorum does, because that might
   result in conflicting language strings. Instead add an extra data level,
   which makes sure that your module keeps all language strings to itself.

   If your module is named "foo", you should store language strings in:

      $PHORUM["DATA"]["LANG"]["mod_foo"]

   See also section 2.3.


  3.7.4 Data stored in messages, users and settings
  -------------------------------------------------

   When using the Phorum provided ways of storing data in messages,
   users and settings, always prepend the data key with
   mod_<module name>. SO if your module is named "foo", do not use
   things like:

      $new_message["meta"]["foodata"] = "Some data";
      $user["foodata"] = "Some data";
      phorum_db_update_settings(array("settings" => $foo_settings));

   but instead:

      $new_message["meta"]["mod_foo_foodata"] = "Some data";
      $user["mod_foo_foodata"] = "Some data";
      phorum_db_update_settings(array("mod_foo" => $foo_settings));

   See also sections 2.4 (message data), 2.5 (user data) and 2.7 (settings).


4. Overview of available Phorum hooks
-------------------------------------------------------------------------------

   In this chapter you will find an overview of all available Phorum
   hooks and a description of what they do.

   Remarks:

   * Input is what your module function should expect as parameters.

   * Return is what your module function should return. Most hooks
     expect the same data structure as was sent. For those items,
     the Return is listed simply as "Same as Input".

   * Normally, hook functions are allowed to modify the data that was
     sent as input. If this is not allowed, the input data will be
     flagged as read-only.

   * In most cases the hook description will provide only one or more
     of the possible uses for the hook. The full leverage of each hook
     is only limited by the imagination of the module writer (it's
     as much a cliche as it is true).

   * It may be that you need to hook into a spot where there is
     currently no hook available. If that is the case, let the dev
     team know by posting a message in the development forum
     on phorum.org, explaining where and why you need an extra hook.
     Hooks will be added as necessary, especially while Phorum 5
     is young.


 4.1 Code hooks
 --------------

   Code hooks are hooks that are called from within the Phorum core
   code. These hooks are typically used for modifying Phorum's internal
   datastructures.


   ----------------------------------------------------------------------------
   admin_general

   Where  : admin interface
   When   : Right before the PhorumInputForm object is shown.
   Input  : The PhorumInputForm object.
   Return : Same as Input

   This hook can be used for adding items to the form on the
   "General Settings" page of the admin interface.

   ----------------------------------------------------------------------------
   after_approve

   Where  : moderation.php
   When   : Just after approving a message and possibly its replies.
   Input  : An array containing two elements: the message data and the
            type of approval (either PHORUM_APPROVE_MESSAGE or
            PHORUM_APPROVE_MESSAGE_TREE).
   Return : Same as Input.

   This hook can be used for performing extra actions after a message has
   been approved.

   ----------------------------------------------------------------------------
   after_attach

   Where  : include/posting/action_attachments.php
   When   : Just after a file attachment is saved in the database
   Input  : Two part array where the first element is the message array and
            the second element is a file array that contains the name, size,
            and file_id of the newly saved file.
   Return : Same as Input

   The primary use of this hook would be for creating an alternate storage
   system for attachments. You would need to use the before_attach hook to
   remove the file data and in this hook it could be saved properly. You will
   need to use the file hook to retreive the file data later.

   ----------------------------------------------------------------------------
   after_detach

   Where  : include/posting/action_attachments.php
   When   : Just after a file attachment is deleted from the database
   Input  : Two part array where the first element is the message array and
            the second element is a file array that contains the name, size,
            and file_id of the deleted file.
   Return : Same as Input

   The primary use of this hook would be for creating an alternate storage
   system for attachments. Using this hook, you can delete the file from
   your alternate storage.

   ----------------------------------------------------------------------------
   after_edit

   Where  : include/posting/action_edit.php
   When   : Right after storing an edited message in the database.
   Input  : Array containing message data (read-only).
   Return : none

   This hook can be used for sending notifications or for making log entries
   in the database when editing takes place.

   ----------------------------------------------------------------------------
   after_header

   Where  : Every page, except for the admin interface pages
   When   : Right after the header is displayed.
   Input  : none
   Return : none

   This hook can be used for creating content at the end of the header,
   just before the main content is displayed.

   ----------------------------------------------------------------------------
   after_login

   Where  : login.php
   When   : After a successful login, just before redirecting the
            user to a Phorum page.
   Input  : The redirection URL.
   Return : Same as Input

   This hook can be used for performing tasks after a successful user
   login and for changing the page to which the user will be redirected
   (by returning a different redirection URL). If you need to access the
   user data, then you can do this through the global $PHORUM variable.
   The user data will be in $PHORUM["user"].

   ----------------------------------------------------------------------------
   after_logout

   Where  : login.php
   When   : After a logout, just before redirecting the user to
            a Phorum page.
   Input  : The redirection URL.
   Return : Same as Input

   This hook can be used for performing tasks after a successful user
   logout and for changing the page to which the user will be redirected
   (by returning a different redirection URL). The user data will still
   be availbale in $PHORUM["user"] at this point.

   ----------------------------------------------------------------------------
   after_message_save

   Where  : action_post.php
   When   : After storing a new message and all database updates are done.
   Input  : Array containing message data.
   Return : Same as Input

   This hook can be used for performing actions based on what the message
   contained or altering it before it is emailed to the subscribed users.
   It is also useful for adding or removing subscriptions.

   ----------------------------------------------------------------------------
   after_post

   Where  : action_post.php
   When   : After all posting work is done and just before the user is
            redirected back to the list.
   Input  : Array containing message data
   Return : Same as input

   This hook can be used for performing actions based on what the message
   contained.  It is specifically useful for altering the redirect behavior.

   ----------------------------------------------------------------------------
   after_register

   Where  : register.php
   When   : Right after a successful registration of a new user is done
            and all confirmation mails are sent.
   Input  : Array containing the user data of the user (read-only).
   Return : None

   This hook can be used for performing tasks (like logging and
   notification) after a successful user registration.

   ----------------------------------------------------------------------------
   before_attach

   Where  : include/posting/action_attachments.php
   When   : Just before a file attachment is saved in the database
   Input  : Two part array where the first element is the message array and
            the second element is a file array that contains the name, size
            and data.
   Return : Same as Input

   The primary use of this hook would be for creating an alternate storage
   system for attachments. You would need to use the after_attach hook to
   complete the process as you do not yet have the file_id for the file. You
   will need to use the file hook to retreive the file data later.

   ----------------------------------------------------------------------------
   before_delete

   Where  : moderation.php
   When   : Just before deleting the message(s)
   Input  : an array containing the following 5 parameters
            $delete_handled  default = false, set it to true to avoid the
                             real delete afterwards
            $msg_ids         array containing all deleted message ids
            $msgthd_id       msg- or thread-id to be deleted
            $message         array of the message retrieved with msgthd_id
            $delete_mode     mode of deletion, either
                             PHORUM_DELETE_MESSAGE or PHORUM_DELETE_TREE
   Return : Same as Input; $delete_handled and $msg_ids are used as return
            data for the hook.

   The primary use of this hook would be for moving the messages to some
   archive-area instead of really deleting them.

   ----------------------------------------------------------------------------
   before_editor

   Where  : posting.php
   When   : Just before the message editor is displayed.
   Input  : Array containing data for the message that will be shown
            in the editor screen.
   Return : Same as Input

   This hook can be used for changing message data, just before the editor
   is displayed. This is done after escaping message data for XSS prevention
   is done. So in the hook, the module writer will have to be aware that
   data is escaped and that he has to escape data himself if needed.

   This hook is called every time the editor is displayed. If modifying
   the message data does not have to be done on every request (for example
   only on the first request when replying to a message), the module will
   have to check the state the editor is in. Here's some hints on what
   you could do to accomplish this:

   * Check the editor mode: this can be done by looking at the "mode" field
     in the message data. This field can be one of "post", "reply" and "edit".

   * Check if it's the first request: this can be done by looking at the
     $_POST array. If no field "message_id" can be found in there, the
     editor is handing the first request.

   Using this, an example hook function that appends the string "FOO!"
   to the subject when replying to a message (how useful ;-) could look
   like this:

      function phorum_mod_foo_before_editor ($data)
      {
         if ($data["mode"] == "reply" && ! isset($_POST["message_id])) {
             $data["reply"] = $data["reply"] . " FOO!";
         }

         return $data;
      }

   Beware: this hook function only changes message data before it is
   displayed in the editor. From the editor, the user can still change
   the data. Therefore, this hook cannot be used to control the data which
   will be stored in the database. If you need that functionality, then
   use the hooks before_edit and/or before_post instead.

   ----------------------------------------------------------------------------
   before_footer

   Where  : Every page, except for the admin interface pages
   When   : Right before the footer is displayed.
   Input  : none
   Return : none

   This hook can be used for creating content at the end of the main
   content, just before the footer. It can also be used for
   performing tasks that have to be executed at the end of each page.

   ----------------------------------------------------------------------------
   before_register

   Where  : register.php
   When   : Right before a new user is stored in the database.
   Input  : Array containing the user data of the user.
   Return : Same as Input

   This hook can be used for performing tasks before user registration.
   This hook is useful if you want to add some data to or change some
   data in the user data and to check if the user data is correct.

   When checking the registration data, the hook can set the "error" field
   in the returned user data array. When this field is set after running
   the hook, the registration processed will be halted and the error
   will be displayed. If you created a custom form field "foo" and you
   require that field to be filled in, you could create a hook function
   which looks like this:

      function phorum_mod_foo_before_register ($data)
      {
         $myfield = trim($data['your_custom_field']);
         if (empty($myfield)) {
             $data['error'] = 'You need to fill in my custom field';
         }

         return $data;
      }

   The error must be safely HTML escaped, so if you use untrusted data in
   your error, then make sure that it is escaped using htmlspecialchars()
   to prevent XSS (see also paragraph 3.6: Secure your pages from XSS).

   ----------------------------------------------------------------------------
   buddy_add

   Where  : pm.php
   When   : Right after a buddy has been added successfully.
   Input  : The user id of the buddy that has been added.
   Return : Same as Input

   This hook can be used for performing actions after a buddy has been
   added for a user (e.g. sending the new buddy a PM about this event,
   update popularity counters, do logging, synchronizing with other
   databases, etc.).

   ----------------------------------------------------------------------------
   buddy_delete

   Where  : pm.php
   When   : Right after a buddy has been deleted successfully.
   Input  : The user id of the buddy that has been deleted.
   Return : Same as Input

   This hook can be used for performing actions after a buddy has
   been deleted for a user.

   ----------------------------------------------------------------------------
   cc_save_user

   Where  : control.php
   When   : Right before data for a user is saved in the control panel.
   Input  : Array containing the user data to save.
   Return : Same as Input

   This hook works the same way as the before_register hook, so you can
   also use it for changing and checking the user data that will be
   saved in the database. There's one difference. If you want to
   check a custom field, you'll also need to check the panel which
   you are on, because this hook is called from multiple panels.
   The panel that you are on, will be stored in the 'panel' field
   of the user data.

   If you have added a custom field to the template for the option
   "Edit My Profile" in the control panel, your hook function will
   look like this:

      function phorum_mod_foo_cc_save_user ($data)
      {
         // Only check data for the panel "user".
         if ($data['panel'] != "user") return $data;

         $myfield = trim($data['your_custom_field']);
         if (empty($myfield)) {
             $data['error'] = 'You need to fill in my custom field';
         }

         return $data;
      }

   ----------------------------------------------------------------------------
   check_post

   Where  : include/posting/check_integrity.php
   When   : Right after performing preliminary posting checks, unless
            these checks have returned something bad.
   Input  : Array containing:
            0 => an array of the message data
            1 => $error, used to return an error message
   Return : Same as Input

   This hook can be used for modifying the message data and for
   running additional checks on the data. If an error is put in $error,
   Phorum will stop posting the message and show the error to the user
   in the post-form.

   Beware that $error can already contain an error on input, in case
   multiple modules are run for this hook. Therefore you might want to
   return immediately in your hook function in case $error is already
   set.

   Below is an example of how a function for this hook could look.
   This example will disallow the use of the word "bar" in the
   message body.

      function phorum_mod_foo_check_post ($args) {
          list ($message, $error) = $args;
          if (!empty($error)) return $args;

          if (stristr($message["body"], "bar") !== false) {
              return array($message, "The body may not contain 'bar'");
          }

          return $args;
      }

   ----------------------------------------------------------------------------
   close_thread

   Where  : moderation.php
   When   : Right after a thread has been closed by a moderator.
   Input  : The id of the thread that has been closed (read-only).
   Return : Same as Input

   This hook can be used for performing actions like sending notifications
   or making log entries after closing threads.

   ----------------------------------------------------------------------------
   common

   Where  : common.php, so in practice every page
   When   : Right before the end of the common.php include script.
   Input  : none
   Return : none

   This hook can be used for applying custom settings or altering
   Phorum settings based on external parameters.

   ----------------------------------------------------------------------------
   common_no_forum

   Where  : common.php, so in practice every page
   When   : Right after no forum settings were found, before doing the redirect
   Input  : none
   Return : none

   This hook can be used for returning some other message (i.e. a 404-page)
   to the visitor if the requested forum was not found.

   ----------------------------------------------------------------------------
   common_post_user

   Where  : common.php, so in practice every page
   When   : Right after loading the user from the database, but just
            before making descisions on language and template.
   Input  : none
   Return : none

   This hook can be used for applying custom settings or altering
   Phorum settings based on external parameters.

   ----------------------------------------------------------------------------
   common_pre

   Where  : common.php, so in practice every page
   When   : Right after loading the settings from the database, but just
            before making descisions on language, template and user.
   Input  : none
   Return : none

   This hook can be used for applying custom settings or altering
   Phorum settings based on external parameters.

   ----------------------------------------------------------------------------
   database_error

   Where  : common.php
   When   : Right after a database error has occurred.
   Input  : The database error that occurred.
   Return : Same as Input

   This hook can be used for handling database errors differently than the
   standard database error handling (a module can handle the error in its own
   way and then call exit() to finish processing) or for other purposes like
   logging database errors.

   ----------------------------------------------------------------------------
   delete

   Where  : moderation.php
   When   : Right after deleting a message from the database.
   Input  : Array of ids for messages that have been deleted (read-only).
   Return : none

   This hook can be used for cleaning up anything you may have created
   with the post_post hook or any other hook that stored data tied to
   messages.

   ----------------------------------------------------------------------------
   email_user_start

   Where  : include/email_functions.php
   When   : Right at the beginning of the function, before any modification of data
   Input  : array of addresses and data (which are both an array of their own)
   Return : array of addresses and data

   This hook is put at the very beginning of phorum_email_user and is therefore
   called for *every* email that is sent from phorum.
   It is put before every replacement done in that function so that all data
   which is sent to that function can be replaced/changed at will.

   ----------------------------------------------------------------------------
   end_output

   Where  : include/common.php
   When   : called just after footer template is included
   Input  : none
   Return : none

   This hook can be used to do any post output tasks
   It could also be used to read in any buffered output by using
   ob_get_contents() if ob_start() was called in a start_output hook function.

   ----------------------------------------------------------------------------
   external

   The external hook functions are never called from any of the standard
   Phorum pages. These functions are called by invoking script.php on the
   command line with the --module parameter. This can be used to pipe
   output from some arbitrary command to a specific module, which can do
   something with that input. If your module does not need any command
   line input and is meant to be run on a regular basis, you should
   consider using the scheduled hook.

   Mind that for using an external hook, the module in which it is
   handled must be enabled in your admin interface. So if an external
   hook is not running, the containing module might be disabled.

   To run the external hook from the command line, you have to be in
   the phorum installation directory. So running the external hook of
   a module named "external_foo" would be done like this on a UNIX
   system prompt:

      # cd /your/phorum/dir
      # php ./script.php --module=external_foo

   For easy use, you can of course put these commands in a script file.

   ----------------------------------------------------------------------------
   failed_login

   Where  : login.php
   When   : When a user login fails.
   Input  : An array containing three fields: username, password and location
            (read-only).
            The location field specifies where the login failure occurred and
            its value can be either "forum" or "admin".
   Return : None

   This hook can be used for tracking failing login attempts. This can be used
   for things like logging or implementing login failure penalties (like
   temporary denying access after X login attempts).

   ----------------------------------------------------------------------------
   file

   Where  : file.php
   When   : When attachments are requested.
   Input  : Two part array where the first element is the mime type already
            detected by file.php and the second part is the file array that
            contains the filename, file_data, filesize, etc.
   Return : Same as Input

   This hook could be used to count file downloads, or along with after_attach
   an alternate file data storage mechanism could be created.

   ----------------------------------------------------------------------------
   format

   Where  : phorum_format_messages() in include/format_functions.php
   When   : Everytime phorum_format_messages() is called for formatting
            a message, just before it is sent to the templates.
   Input  : Array of messages.
   Return : Same as Input

   This hook can be used for applying custom formatting to messages. The
   message fields that are most applicable for this are "body" and "author".
   When writing a module using this hook, you probably want to format
   those fields. In practice you can apply formatting to all the fields
   you want.

   The changes you make to the messages are for displaying purposes
   only, so the changes are not stored in the database.

   ----------------------------------------------------------------------------
   hide_thread

   Where  : moderation.php
   When   : Right after a message has been hidden by a moderator.
   Input  : The id of the message that has been hidden (read-only).
   Return : Same as Input

   This hook can be used for performing actions like sending notifications
   or making log entries after hiding a message.

   ----------------------------------------------------------------------------
   index

   Where  : include/index_new.php and include/index_classic.php
   When   : Right before the list of forums is displayed.
   Input  : Array of forums.
   Return : Same as Input

   This hook can be used for changing or adding data to the forums
   in the list.

   ----------------------------------------------------------------------------
   lang

   The lang hook is a only a 'marker'. It flags Phorum that your module
   supports multiple languages. It does not take a hook function in
   your module information. If you do define a hook function, it will
   never be called.

   Read section 2.3 for information on the use of multiple languages.

   ----------------------------------------------------------------------------
   list

   Where  : list.php
   When   : Right before the messages are formatted and displayed.
   Input  : Array of threads (or messages in threaded mode).
   Return : Same as Input

   This hook can be used for changing or adding data to the messages
   in the list.

   ----------------------------------------------------------------------------
   moderation

   Where  : moderation.php
   When   : At the start of moderation.php
   Input  : The id of the moderation step which is run (read-only).
   Return : none

   This hook can be used for logging moderator actions. You can
   use the $PHORUM-array to retrieve additional info like the
   moderating user's id and similar.

   The moderation step id is the variable $mod_step that is used in
   moderation.php. Please read that script to see what moderation
   steps are available and for what moderation actions they stand.

   When checking the moderation step id for a certain step, always use
   the contstants that are defined for this in include/constants.php.
   The numerical value of this id can change between Phorum releases.

   ----------------------------------------------------------------------------
   move_thread

   Where  : moderation.php
   When   : Right after a thread has been moved by a moderator.
   Input  : The id of the thread that has been moved (read-only).
   Return : none

   This hook can be used for performing actions like sending notifications
   or for making log entries after moving a thread.

   ----------------------------------------------------------------------------
   pm_sent

   Where  : include/controlcenter/pm.php
   When   : Right after a PM and its email notifications have been sent.
   Input  : Array containing the private message data (read-only).
   Return : none

   This hook can be used for performing actions after sending a PM. Before
   PM notification by email was put in the Phorum core, this hook was
   used to send those notifications.

   ----------------------------------------------------------------------------
   posting_custom_action

   Where  : posting.php
   When   : Right after all the initialization tasks are done and
            just before the posting script starts its own action processing.
   Input  : Array containing message data.
   Return : Same as Input

   This hook can be used by modules to handle (custom) data coming from the
   posting form. The module is allowed to change the data that is in the
   input message. When a module needs to change the meta data for a
   message, then this is the designated hook for that task.

   ----------------------------------------------------------------------------
   posting_init

   Where  : posting.php
   When   : Right after the posting.php script's configuration setup and
            before starting the posting script processing.
   Input  : none
   Return : none

   This hook can be used for doing modifications to the environment
   of the posting scripts at an early stage. One of the intended purposes
   of this hook is to give mods a chance to change the configuration
   of the posting fields in $PHORUM["posting_fields"].

   ----------------------------------------------------------------------------
   posting_permissions

   Where  : posting.php
   When   : Right after Phorum has determined all abilities that apply
            to the logged in user.
   Input  : none
   Ouput  : none

   This hook can be used for setting up custom abilities and permissions
   for users, by updating the applicable fields in $GLOBALS["PHORUM"]["DATA"]
   (e.g. for giving certain users the right to make postings sticky, without
   having to make the full moderator for a forum).

   Read the code in posting.php before this hook is called to find out
   what fields can be used.

   Beware: Only use this hook if you know what you are doing and understand
   Phorum's editor permission code. If used wrong, you can open up security
   holes in your Phorum installation!

   ----------------------------------------------------------------------------
   before_edit

   Where  : include/moderation_functions.php
   When   : Right before storing an edited message in the database.
   Input  : Array containing message data.
   Return : Same as Input

   This hook can be used for changing the message data before storing it
   in the database.

   ----------------------------------------------------------------------------
   before_post

   Where  : include/posting/action_post.php
   When   : Right before storing a new message in the database.
   Input  : Array containing message data.
   Return : Same as Input

   This hook can be used for changing the message data before storing it
   in the database.

   ----------------------------------------------------------------------------
   profile

   Where  : profile.php and include/controlcenter/summary.php
   When   : Right before a user profile is displayed.
   Input  : Array containing user profile data.
   Return : Same as Input

   This hook can be used for making changes to the profile data. This
   is for displaying purposes only, so the changes are not stored in the
   database.

   ----------------------------------------------------------------------------
   quote

   Where  : reply.php, read.php (for inline reply form support)
   When   : Right after the message to reply to has been loaded.
   Input  : Array containing:
            0 => The message author
            1 => The message body
            2 => The message user_id
   Return : The quoted body to use in the post form or the input array
            in case the hook does not handle the quoting.

   When quoting a message for reply, by default Phorum formats quoted
   messages using an old school email style of quoting. By using the quote
   hook, you can implement a different quoting mechanism.

   Your hook function will retrieve an array containing three elements:
   the author, the body and the user_id of the message to be quoted. The
   return value for your hook function must be the quoted body that will
   be pre-filled into the reply form or the original array in case the
   hook function does not handle the quoting.

   The BBCode module that is distributed with Phorum has a quote hook
   function. Because it does not make sense to have more than one quote
   hook active, the BBCode module has an option to disable its quote hook
   function. You need to make sure that its quote hook function is disabled
   when using your own quote hook.

   ----------------------------------------------------------------------------
   read

   Where  : read.php
   When   : Right before messages are formatted for displaying.
   Input  : Array of messages.
   Return : Same as Input

   This hook can be used for making changes to the message data when
   reading messages. This is for displaying purposes only, so the
   changes are not stored in the database.

   ----------------------------------------------------------------------------
   read_user_info

   Where  : read.php post.php include/moderation_functions.php
   When   : Right after retrieving user data.
   Input  : Array of users.
   Return : Same as Input

   This hook can be used for changing information for the users before
   being displayed. For example: add a border around user signatures.
   This is for displaying purposes only, so the changes are not stored
   in the database. This hook modifies the MESSAGES->user (in read.tpl)
   and MESSAGE->user (read_threads.tpl) data that is available in the
   templates.

   ----------------------------------------------------------------------------
   readthreads

   Where  : read.php
   When   : At the start of the threaded read handling, just before
            sorting and displaying the threads.
   Input  : Array of messages.
   Return : Same as Input

   This hook does exactly the same as the read hook, except that this
   one is only applied to messages when viewing the message list in
   threaded mode.

   ----------------------------------------------------------------------------
   reopen_thread

   Where  : moderation.php
   When   : Right after a thread has been reopened by a moderator.
   Input  : The id of the thread that has been reopened (read-only).
   Return : Same as Input

   This hook can be used for performing actions like sending notifications
   or making log entries after reopening threads.

   ----------------------------------------------------------------------------
   report

   Where  : report.php
   When   : Just before a reported message is sent to the moderators.
   Input  : Array with maildata (see report.php for the exact contents).
   Return : Same as Input

   This hook can be used for changing the report data that will be
   sent to the moderators or for performing actions like making log
   entries.

   ----------------------------------------------------------------------------
   sanity_checks

   Where  : include/admin/sanity_checks.php
   When   : Just before the admin interface's sanity checks are run
   Input  : Array with sanity checks. Each sanity check is an array with:
            function    => The function that runs the sanity check
            description => A description to show in the admin interface
   Return : Same as Input

   This hook can be used to add custom sanity checks to the admin
   interface option "System Sanity Checks".

   Each checking function is expected to return an array containing
   two elements:

      [0] A status, which can be one of
          PHORUM_SANITY_OK     No problem found
          PHORUM_SANITY_WARN   Problem found, but no fatal one
          PHORUM_SANITY_CRIT   Critical problem found

      [1] A description of the problem that was found or NULL.

   A general checking function looks like this:

      function check_foo() {
         $check_ok = ...some check...;
         if (!$check_ok) {
            return array(PHORUM_SANITY_CRIT, "Foo went wrong because ...");
         } else {
            return array(PHORUM_SANITY_OK, NULL);
         }
      }

   ----------------------------------------------------------------------------
   scheduled

   Scheduled hook functions are similar to external ones, except these
   functions do not require any input from the command line. The modules
   containing a scheduled hook are invoked by running script.php with
   the --scheduled argument (no module name is taken; this argument
   will run all scheduled hooks for all available modules).

   Like the name of the hook already suggests, this hook can be used for
   creating tasks which have to be executed on a regular basis. To
   archieve this, you can let script.php run from a scheduling
   service (like a cron job on a UNIX system).

   In general, scheduled hooks are used for automating tasks you want
   to execute without having to perform any manual action. Practical
   uses for a scheduled hook could be housekeeping (cleanup of
   stale/old data), daily content generation (like sending daily digests
   containing all posted messages for that day) or forum statistics
   generation.

   Mind that for using a scheduled hook, the module in which it is
   handled must be enabled in your admin interface. So if a scheduled
   hook is not running, the containing module might be disabled.

   To run the scheduled hook from the command line or from a scheduling
   service, you have to be in the phorum installation directory. So
   running the scheduled hooks for your Phorum installation would
   be done like this on a UNIX system prompt:

      # cd /your/phorum/dir
      # php ./script.php --scheduled

   When creating a scheduling service entry for running this
   automatically, remember to change the directory as well.
   You might also have to use the full path to your PHP binary
   (/usr/bin/php or whatever it is on your system), because
   the scheduling service might not know the path to it. An entry
   for the cron system on UNIX could look like this:

   0 0 * * * cd /your/phorum/dir && /usr/bin/php ./script.php --scheduled

   Please refer to your system's documentation to see how to
   use your system's scheduling service.

   ----------------------------------------------------------------------------
   search

   Where  : search.php
   When   : Right before messages are formatted for displaying.
   Input  : Array of messages.
   Return : Same as Input

   This hook can be used for making changes to the message data when
   searching for messages. This is for displaying purposes only, so the
   changes are not stored in the database.

   ----------------------------------------------------------------------------
   search_action

   Where  : search.php
   When   : Right before the search request to the database
   Input  : Array of search-request data and a continue flag
   Return : Same as Input, continue flag can be set to 0 to avoid the db-call

   This hook can be used for using another search application / layer instead
   of the buildin one.

   ----------------------------------------------------------------------------
   search_start

   Where  : search.php
   When   : Right before calling the template on first call to search page
   Input  : Array of input variables
   Return : Same as Input

   This hook can be used for modifying the input variables, e.g. for modifying the
   default values


   ----------------------------------------------------------------------------
   send_mail

   Where  : include/email_functions.php in the function phorum_email_user()
   When   : Right before email is sent using PHP's mail() function.
   Input  : Array with maildata (read-only) containing:
            addresses => Array of e-mail addresses,
            from      => The sender address,
            subject   => The mail subject,
            body      => The mail body,
            bcc       => Whether to use Bcc for mailing multiple recipients
   Return : true/false - see description

   This hook can be used for implementing an alternative mail sending
   system (e.g. like the SMTP module does). The hook should return true if
   Phorum should still send the mails himself. If you do not want to have
   Phorum send the mails also, return false.

   ----------------------------------------------------------------------------
   shutdown

   Where  : include/common.php
   When   : The very last thing that happens in any Phorum page
   Input  : none
   Return : none

   This hook can be used to do clean up like saving data or logging.  Output
   is highly discouraged here as this will happen all templates are included.

   ----------------------------------------------------------------------------
   start_output

   Where  : include/common.php
   When   : called just before header template is included
   Input  : none
   Return : none

   This hook can be used to do any last minute things before output starts.
   It could also be used to buffer output by using ob_start()

   ----------------------------------------------------------------------------
   user_check_login

   Where  : include/users.php
   When   : Whenever phorum_api_user_authenticate() is called.
   Input  : Array containing:
            type        => either PHORUM_FORUM_SESSION or PHORUM_ADMIN_SESSION
            username    => the username to check
            password    => the password to check
            user_id     => NULL
   Return : Same as input

   This hook can be used to check user authentication against an external
   source. If the hook decides that the username and password are okay,
   then it can set the user_id field to the user_id of the authenticated
   user. If the authentication is rejected, it can set user_id to FALSE.
   If the hook wants to let Phorum use its own standard authentication
   mechanism, then it can set user_id to NULL.

   Here is an example (not too useful) hook function:

      function phorum_mod_foo_user_check_login($login)
      {
          // Skip admin password checking.
          if ($login["type"] == PHORUM_ADMIN_SESSION) return $login;

          // Logging in with john / doe will authenticate the user with
          // user_id 1234.
          if ($login["username"] == "john" && $login["password"] == "doe") {
              $login["user_id"] = 1234;
          }
          // For all other users, we let Phorum do the authentication.
          else
          {
              $login["user_id"] = NULL;
          }

          return $login;
      }

   Mind that when returning a user_id using this hook, the user data for
   the user must be available in Phorum's users table. This hook only
   handles the authentication step. So if this hook is used for
   authenticating against an external source, somehow the user data
   has to be put in Phorum's users table. There are multiple ways of
   handling this. Here's two of them:

   1) Synchronize the data from the external system when it has changed
      there. So if a user is created, changed or deleted on the
      external system, that system has to update the Phorum users
      table with the new information. The advantage of this method, is
      that the users table only is updated when changes occur and that
      the users table is always up-to-date with the latest information.

   2) Synchronize the data on-the-fly from the user_check_login hook.
      As long as the user data is available before that hook has
      ended, it will be okay for Phorum. The advantage of this method
      is that updating the user data is done in an easy way at the
      moment a user logs into Phorum. Disadvantages are that the user
      data is only updated at the moment the user logs in and that a
      user in only known to Phorum after the first time logging in.

      Here's an example of how a hook function that uses on-the-fly
      updates could look:

      function phorum_mod_foo_user_check_login($login)
      {
          // Check the authentication against the external system.
          $username = $login["username"];
          $password = $login["password"];
          if (! externalsystem_checkauth($username, $password)) {
              return $login;
          }

          // Synchronize the external system with Phorum's users table.
          $externaluser =  externalsystem_getuser($username);
          ...code to put $externaluser in Phorum's user table ...
          ...and determine the Phorum $user_id to return ...

          // Set the user_id to tell Phorum which user logged in.
          $login["user_id"] = $user_id;

          return $login;
      }


   ----------------------------------------------------------------------------
   user_list

   Where  : include/users.php include/controlcenter/groupmod.php
   When   : Whenever phorum_user_get_list() is called.
   Input  : Array containing:
            <user_id> => <data>
            Where <data> is an array containing:
              username     => the username for the user
              display_name => the way to display the username
   Return : Same as Input

    This hook can be used for reformatting the list of users in some
    way, such as changing the sort order or changing the format of
    the displayed names.


 4.2 Template hooks
 ------------------

   Template hooks are called from within Phorum's template files.
   These hooks can be used to extend the user interface with custom
   elements. From a hook function for one of these template hooks, the
   module writer can print the HTML code that has to be added to the
   interface at the postition of the hook call in the template.

   ----------------------------------------------------------------------------
   tpl_editor_after_subject

   Where  : posting_messageform.tpl
   When   : After the Subject: field in the message editor.
   Input  : none
   Return : none

   This hook can be used to add custom form fields to the message editor.
   In the default template, the hook is run from within a two column table.
   Column one contains the labels and column two the form fields. So your
   hook function for adding a field to the editor could look like this:

      function phorum_mod_foo_tpl_editor_after_subject()
      {
         $value = isset($_POST["mod_foo"]) ? $_POST["mod_foo"] : "";
         ?>
         <tr>
           <td>
             Foo field
           </td>
           <td>
             <input type="text" name="mod_foo"
              value="<?php print htmlspecialchars($value) ?>"/>
           </td>
         </tr>
         <?php
      }

   ----------------------------------------------------------------------------
   tpl_cc_usersettings

   Where  : cc_usersettings.tpl
   When   : After the built-in usersettings fields.
   Input  : Array containing the user's current profile.
   Return : Same as Input

   This hook can be used to add extra fields to the settings pages in the
   user's control center. Here's an example hook function that will add
   an extra field to the "Edit My Profile" page.

      function phorum_mod_foo_tpl_cc_usersettings($profile)
      {
          // Check if we're on the userprofile page of cc_usersettings.tpl.
          if (! isset($profile["USERPROFILE"]) || ! $profile["USERPROFILE"])
             return;

          $value = isset($profile["shoesize"])
                 ? htmlspecialchars($profile["shoesize"]) : "";
          ?>
          <tr>
            <td>Shoe size</td>
            <td><input name="shoesize" type="text" value="<?=$value?>"></td>
          </tr>
          <?php
      }

   ----------------------------------------------------------------------------
   tpl_editor_attachment_buttons

   Where  : posting_attachments_list.tpl
   When   : Before the delete button for an attachment.
   Input  : Array containing attachment information.
   Return : Same as Input

   This hook can be used to add extra buttons to each attachment in the
   editor, so you can do custom actions for attachments.

   To make your buttons look the same as Phorum's buttons, use the
   CSS class "PhorumSubmit".

   Here's an example hook function that will add a button to the attachments,
   which will send a javascript alert to the user when clicked.

      function phorum_mod_foo_tpl_editor_attachment_buttons($data)
      {
         $id = $data["file_id"];
         ?>
         <input type="submit" class="PhorumSubmit" value="Say it!"
          onclick="alert('You clicked attachment id <?php print $id ?>')" />
         <?php
      }

   ----------------------------------------------------------------------------
   tpl_editor_before_textarea

   Where  : posting_messageform.tpl
   When   : Before the textarea.
   Input  : none
   Return : none

   This hook can be used to add custom user interface elements before the
   textarea in the message editor.

   ----------------------------------------------------------------------------
   tpl_editor_buttons

   Where  : posting_buttons.tpl
   When   : Before the main editor buttons (like Preview, Post and Cancel).
   Input  : none
   Return : none

   This hook can be used to add extra buttons to the editor buttons.

   To make your buttons look the same as Phorum's buttons, use the
   CSS class "PhorumSubmit".

   Here's an example hook function that will add a button to the editor,
   which will send a javascript alert to the user when clicked.

      function phorum_mod_foo_tpl_editor_buttons()
      { ?>
         <input type="submit" class="PhorumSubmit" value="Say it!"
          onclick="alert('Hello, foo!')" />
        <?php
      }


6. Support
-------------------------------------------------------------------------------

   If you have questions about creating modules for Phorum, please visit
   the website http://phorum.org/ and ask the development team for help in
   the Development forum.

