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 neccessary 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: : Here is a list of the types that can be used: +--------+-----------------------------------------------------------+ | | | +--------+-----------------------------------------------------------+ | 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 | | | 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. | +--------+-----------------------------------------------------------+ 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 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. 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 */ 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): 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: 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: 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 "pre_post" and "pre_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_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_user_save($userdata); When using a single custom field "mod_foo" for this module: $user = phorum_user_get($user_id); $userdata = array( "user_id" => $user_id, "mod_foo" => array ( "foodata" => "Some user data", "bardata" => "Some more user data" ) ); phorum_user_save($user); 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__ 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// 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: 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 "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__ So if you are writing functions for a module named "foo", all function names will look like: phorum_mod_foo_ You can use whatever you like for the 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 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_. 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_. 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 neccessary, 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. ---------------------------------------------------------------------------- admin_file_purge Where : admin interface, option "Purge Stale Files" When : Right before stale files are deleted from the database. Input : An array, containing a description of all stale files. Return : Same as Input The primary use of this hook would be to cleanup stale files, created by an alternate storage system for attachments (see after_attach and after_detach as well). The array that is passed on to the hook function contains arrays, which contain the following fields: file_id : Internal id to reference the file. filename : Name of the file. filesize : Filesize in KB. add_datetime : Epoch timestamp for the time the file was created. reason : A description why this file is considered to be stale. ---------------------------------------------------------------------------- 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_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_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 : Same as Input 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_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 pre_edit and/or pre_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 : post.php When : Right after performing preliminary posting checks, unless these checks have returned something bad. Input : Array containing: 0 => the $_POST array with form data 1 => $error, to return errors in Return : Same as Input This hook can be used for modifying data in the $_POST array 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. ---------------------------------------------------------------------------- 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. ---------------------------------------------------------------------------- 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. The location field specifies where the login failure occurred and its value can be either "forum" or "admin". Return : Same as Input 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 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. ---------------------------------------------------------------------------- post_edit Where : include/moderation_functions.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. ---------------------------------------------------------------------------- post_post Where : 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 (read-only). Return : none This hook can be used for performing actions based on what the message contained. ---------------------------------------------------------------------------- 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_permission 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! ---------------------------------------------------------------------------- pre_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. ---------------------------------------------------------------------------- pre_post Where : 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 Return : The quoted body to use in the post form. 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 two elements: the author and the body 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. 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, then remind 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. ---------------------------------------------------------------------------- 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. ---------------------------------------------------------------------------- user_check_login Where : include/users.php When : Whenever phorum_user_check_login() is called. Input : Array containing: username => the username to check password => the password to check user_id => empty value 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 hook wants to let Phorum use its standard authentication mechanism, then it can set user_id to FALSE. Here is an example (not too useful) hook function: function phorum_mod_foo_user_check_login($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"] = FALSE; } 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: => Where is an array containing: username => the username for the user displayname => 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"] : ""; ?> Foo field Shoe size