<chapter id="modules">
  <title>Modules</title>

  <section id="modules.introduction">
    <title>Introduction</title>
    <para>
      This section describes Phorum's module system. It is targeted at
      developers who want to do customization and extend the functionality
      of Phorum. Modules are the preferred way to archieve this.
    </para>
    <para>
      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 <ulink url="http://en.wikipedia.org/wiki/Metasyntactic_variable">Wikipedia</ulink> to see why we chose them.
    </para>
    <para>
      <remark>TODO: Link to section once it's ready</remark>
      Be sure to read at least the CAUTIONS AND SECURITY ISSUES section,
      before making your own modules.
    </para>
  </section>

  <section id="modules.terminology">
    <title>Terminology</title>

    <section id="modules.modules">
      <title>Modules</title>
      <para>
        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.
      </para>
      <para>
        The big advantage of modules this is that upgrading the Phorum code
        is easy (no file changes to redo after upgrading) and that modules
        can be easily uninstalled when needed.
      </para>
      <para>
        Installing a module means: drop the code in the Phorum mods directory,
        go to the admin "Modules" page, enable the module and enjoy!
        One additional thing that might be needed, is editing one or
        more template files to display data that is generated by the module.
      </para>
    </section>

    <section id="modules.hacks">
      <title>Hacks</title>
      <para>
        The moment it is necessary to make changes to the standard Phorum
        distribution files or database structure to implement some kind of
        functionality, we are talking about a hack (even if the changes
        that have to be made are accompanied by a drop in module).
      </para>
      <para>
        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. Especially if you are going to publish your changes to
        the public. 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.
      </para>
    </section>

    <section id="modules.addons">
      <title>Add-ons</title>
      <para>
        If you add functionality to Phorum by providing extra scripts
        that go in the Phorum install directory and/or extra templates
        that need to be added to the main template directory
        (<filename>templates/templatename/...</filename>), then we
        talk about an add-on.
      </para>
      <para>
        For Phorum 5.0, this was a pretty common thing to do. For Phorum
        5.1 and up, a special script was added for being able to implement
        addon code fully through a module. Therefore, there is no real
        need anymore for writing add-ons: <filename>addon.php</filename>.
      </para>
    </section>

    <section id="modules.hooks">
      <title>Hooks</title>
      <para>
        The Phorum core and Phorum modules are interconnected through hooks.
        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.
      </para>
      <para>
        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.
      <literallayout><literal><![CDATA[
     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
]]></literal></literallayout>
      </para>
    </section>

    <section id="modules.hookfunctions">
      <title>Hook functions</title>
      <para>
        A module contains PHP functions that act as hook functions.
        Hook functions will receive some data from Phorum
        through their arguments 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 <xref endterm="the hooks" linkend="modules.hooks"/>).
        Based on this, the most basic (and useless) hook function you could
        write would look somewhat like this (see XXX for an explanation
        of the naming scheme that was used for the function):
        <programlisting><![CDATA[
function phorum_mod_foo_some_hook ($data) {
  return $data;
}]]></programlisting>
      </para>
      <para>
        The exact nature of the data that is sent to the hook functions
        depends solely on the hook that is run. See <xref linkend="hooks"/>
        for a description of all supported hooks, including a specification
        of the type of data that is sent.
      </para>
    </section>

  </section>

  <section id="modules.writingyourown">
    <title>Writing your own modules</title>

    <section id="module.writingyourownintro">
      <title>Introduction</title>
      This section will explain to you how to roll your own Phorum modules.
      We will start out by explaining some of the
      <xref linkend="modules.terminology" xrefstyle="template:terminology"/>
      that relates to modules. After that, we will explain a very important
      part modules: the <xref linkend="modules.moduleinfo"
      xrefstyle="template:module information"/>. This contains information
      for both Phorum (what hooks to run in what order, version dependancies)
      and module users (title, description and other interesting facts).
      From there on we will walk you through all the possibilities that
      modules have.
    </section>

    <section id="modules.moduleinfo">
      <title>Module information</title>
      <para>
        Module information is the glue between your module and Phorum.
        It provides information to Phorum about your module.
        Before we explain how to add this module information to your
        module, we will first explain what data can be put in there
        and how that data is formatted.
      </para>
      <para>
        Module information is formatted using lines of plain text. Each line
        contains a piece of information about the module. The general format
        for each of the lines in the module information is:
        <programlisting><![CDATA[ <key>: <value> ]]></programlisting>
        Empty lines are allowed between these key/value pairs.
        Below, you can find a list of the keys and values that can be
        used in the module information.
        <table id="modules.moduleinfokeys">
          <title>Keys and values in module information</title>
          <tgroup cols="2">
          <thead>
            <row>
              <entry>&lt;key&gt;</entry>
              <entry>&lt;value&gt;</entry>
            </row>
          </thead>
          <tbody>

            <row>
              <entry>
                <emphasis role="bold">title</emphasis>
              </entry>
              <entry>
                <para>
                  This is the title for the module that is displayed in the
                  "Modules" page of the admin interface.
                </para>
                <para>
                  Example:<sbr/>
                  <literal>title: Foo</literal>
                </para>
              </entry>
            </row>

            <row>
              <entry>
                <emphasis role="bold">desc</emphasis>
              </entry>
              <entry>
                <para>
                  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
                  &lt;value&gt; part is allowed.
                </para>
                <para>
                  Example:<sbr/>
                  <literal>desc: This is a very cool module to do stuff.</literal>
                </para>
              </entry>
            </row>

            <row>
              <entry>
                <emphasis role="bold">hook</emphasis>
              </entry>
              <entry>
                <para>
                  This describes which
                  <xref linkend="modules.hookfunctions"
                        xrefstyle="template:hook functions"/>
                  are called for which Phorum hooks. The value 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.
                </para>
                <para>
                  Example:<sbr/>
                  <literal>hook: some_hook|phorum_mod_foo_some_hook</literal>
                </para>
              </entry>
            </row>

            <row>
              <entry>
                <emphasis role="bold">priority</emphasis>
              </entry>
              <entry>
                <para>
                  This can be used for changing priorities and dependancies
                  for modules and hooks. Possible values are (in order in
                  which they are processed):
                  <itemizedlist>
                    <listitem><literal>
                      run module before|after *
                    </literal></listitem>
                    <listitem><literal>
                      run module before|after &lt;other module name&gt;
                    </literal></listitem>
                    <listitem><literal>
                      run hook &lt;hook name&gt; before|after *
                    </literal></listitem>
                    <listitem><literal>
                      run hook &lt;hook name&gt; before|after &lt;other module name&gt;
                    </literal></listitem>
                  </itemizedlist>
                </para>
                <para>
                  Examples:<sbr/>

                  <para>
                    Run this module before all other modules:<sbr/>
                    <literal>
                      priority: run module before *
                    </literal>
                  </para>

                  <para>
                    Run this module before the bbcode module.<sbr/>
                    <literal>
                      priority: run module before bbcode
                    </literal>
                  </para>

                  <para>
                    Run the "format" hook for this module before the
                    "format" hook of the smileys module.<sbr/>
                    <literal>
                      priority: run hook format before smileys
                    </literal>
                  </para>

                  <para>
                    Run the "after_header" hook for this module after all
                    other "after_header" hooks.<sbr/>
                    <literal>
                      priority: run hook after_header after *
                    </literal>
                  </para>
                </para>

                <para>
                  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:
                </para>
                <para>
                  <literal>
                  priority: run module before *<sbr/>
                  priority: run hook foo after *<sbr/>
                  priority: run hook bar before smileys
                  </literal>
                </para>
              </entry>
            </row>

            <row>
              <entry>
                <emphasis role="bold">require_version</emphasis>
              </entry>
              <entry>
                <para>
                  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. This feature was added in Phorum 5.2.
                </para>
                <para>
                  Example:<sbr/>
                  <literal>require_version: 5.2.2</literal>
                </para>
              </entry>
            </row>

            <row>
              <entry>
                <emphasis role="bold">
                  author<sbr/>
                  url<sbr/>
                  version<sbr/>
                  release_date
                </emphasis>
              </entry>
              <entry>
                <para>
                  These fields are all informational. The information
                  from these fields will be shown on the Modules admin
                  page. You are allowed to omit these fields from the
                  module information, but we advice you to fill them to
                  provide as much useful info to the users of your module.
                </para>
                <para>
                  The fields are free form. The "url" field should contain
                  a URL to a page where more information about the module
                  can be found. This can for example be a dedicated page
                  about your module or a discussion page in the phorum.org
                  support forums.
                </para>
                <para>
                  Example:<sbr/>
                  <literal>
                    author: John Doe, Phorum hacker<sbr/>
                    url: http://example.com/phorum/mod_foo/<sbr/>
                    version: 0.9.1-alpha<sbr/>
                    release_date: May 17th, 2007
                  </literal>
                </para>
              </entry>
            </row>

            <row>
              <entry>
                <emphasis role="bold">category</emphasis>
              </entry>
              <entry>
                <para>
                  The category field is used for categorizing the module.
                  You can place your module inside one or more categories by
                  adding one or more category lines to the module information.
                  The category is used for automatically generating a
                  categorized list of available modules on the phorum.org
                  web site.
                </para>
                <para>
                  For consistency and for preventing wild growth of
                  categories, we use a fixed set of categories from which
                  you can choose. Please select your category / categories
                  carefully and do not put your module in too many
                  categories. Available categories to choose from are:
                  <itemizedlist>
                    <listitem>
                      <emphasis role="bold">admin</emphasis><sbr/>
                      Modules that are useful for performing administrative
                      tasks and for handling advanced Phorum configuration.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">bbcode</emphasis><sbr/>
                      Modules that implement extra BBcode tags.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">embed_content</emphasis><sbr/>
                      Modules for embedding content (e.g. images, audio,
                      video, flash, etc.) in message bodies. These
                      modules implement viewers that are shown inside the
                      message bodies when reading the message and which take
                      away the need to launch some external viewer to open
                      attachments and/or linked files.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">phorum3conversion</emphasis><sbr/>
                      Modules that can help sites to convert from the
                      old Phorum 3 system to Phorum 5.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">email</emphasis><sbr/>
                      Modules that extend or enhance the email system.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">integration</emphasis><sbr/>
                      Modules that can be used for integrating Phorum with
                      other systems.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">user_management</emphasis><sbr/>
                      These modules deal with user registration, logging in
                      and out, login status and user management tasks.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">moderator</emphasis><sbr/>
                      Modules that extend or enhance the moderation system.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">posting</emphasis><sbr/>
                      Modules that extend or enhance posting and/or
                      editing messages.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">search</emphasis><sbr/>
                      Modules that provide search features
                      or that replace the full message search backend.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">seo</emphasis><sbr/>
                      Modules that apply search engine optimization
                      measures to Phorum.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">spam</emphasis><sbr/>
                      Modules that help protecting Phorum against spam.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">user_features</emphasis><sbr/>
                      Modules that give the users new options and enhance
                      their experience.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">viewlayout</emphasis><sbr/>
                      Modules that add information to or alter the view /
                      layout of Phorum pages.
                    </listitem>
                    <listitem>
                      <emphasis role="bold">uncategorized</emphasis><sbr/>
                      Modules that do not have a category assigned. This one
                      is mainly mentioned for completeness. In the online
                      module list, this category will be assigned to modules
                      that don't have a category set in their info.txt.
                    </listitem>
                  </itemizedlist>
                </para>
                <para>
                  Example:<sbr/>
                  <literal>category: user_features</literal><sbr/>
                  <literal>category: posting</literal>
                </para>
              </entry>
            </row>

          </tbody>
          </tgroup>
        </table>
      </para>

      <para>
        It is allowed to use multiple hook lines in your module information,
        so your module can act upon 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).
      </para>
      <para>
        Here is an example of what the module information for our example
        module "foo" might look like:
        <example id="example.moduleinfo">
          <title>Module information</title><programlisting><![CDATA[
title: Foo
desc: This is the Foo module for Phorum. Nothing exciting...
version: 1.0.2
release_date: Jan 1st, 2008
url: http://www.phorum.org
author: John Doe <johndoe@example.com>
require_version: 5.2.2
category: user_features

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]]></programlisting>
        </example>

        What this module info does, is telling Phorum that when it gets to
        "some_other_hook", it will have to call the function
        <literal>phorum_mod_foo_some_other_hook()</literal> 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".
      </para>

    </section> <!-- end of module info -->

    <section id="modules.structure">
      <title>Module file structure</title>

        <section id="modules.structureintro">
          <title>Introduction</title>
          <para>
            This section describes the file structure of Phorum modules.
            This structure contains things like the
            <xref linkend="modules.moduleinfo"
            xrefstyle="template:module information"/>,
            <xref linkend="modules.hookfunctions"
            xrefstyle="template:hook functions"/> and possibly additional
            stuff like templates, translations, modules settings, images,
            scripts, classes, etc.
          </para>
          <para>
            If your module only needs module information and hook functions
            to function, it is possible to use the
            <xref linkend="modules.singlefile"
            xrefstyle="template:single file structure"/>. If you need more
            than that, then use the <xref linkend="modules.multifile"
            xrefstyle="template:multiple file structure"/>.
          </para>
        </section>

        <section id="modules.singlefile">
          <title>Single file modules</title>
          <para>
            Single file modules are useful in case case no additional files
            have to be distributed with your module. Because the module
            consists of only one single file, it is very easy to distribute.
            Beware though that the moment that 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.
            Switching does mean some extra work for your users. So only use
            this format for modules for which you are sure that you do not
            need additional files in the future.
          </para>
          <para>
            Single file modules consist of one single PHP file. The name
            of this file is not restricted. We advice you to use
            <filename>mod_&lt;module name&gt;.php</filename> though
            for clarity and consitency with other module
            (e.g. <filename>mod_foo.php</filename>).
            This file contains both the module information and the hook
            function definitions. For storing the module informaton, a
            special PHP comment is used. This comment must look like the
            following:
            <programlisting><![CDATA[
/* phorum module info
<module information lines go here>
*/]]></programlisting>
            Using the example module info from
            <xref linkend="example.moduleinfo"/>, the complete single
            file module would look like this (see XXX why we use the
            check on PHORUM at the start of this file):
            <example id="example.singlefilemodule">
              <title>Single file module</title>
<programlisting>
<filename>{phorum dir}/mods/mod_foo.php</filename><![CDATA[
<?php

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

/* phorum module info
title: Foo
desc: This is the Foo module for Phorum. Nothing exciting...
version: 1.0.2
release_date: Jan 1st, 2008
url: http://www.phorum.org
author: John Doe <johndoe@example.com>
require_version: 5.2.2
category: user_features

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
*/

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

?>]]></programlisting>
            </example>

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

        </section> <!-- end of single file modules -->

        <section id="modules.multifile">
          <title>Multiple file modules</title>

          <para>
            These 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.
          </para>
          <para>
            They are stored in their own subdirectory below
            the directory <filename>{phorum dir}/mods/</filename>.
            If you have a module named "foo", you will have to create a
            directory <filename>{phorum dir}/mods/foo/</filename> for storing
            all module files.
          </para>
          <para>
            Inside this subdirectory, you will have to create a least
            two files:
            <itemizedlist>
              <listitem>
                A file called <filename>info.txt</filename>.
                This file contains the module information for your module
                (see <xref linkend="modules.moduleinfo"/>).
              </listitem>
              <listitem>
                The PHP file which contains the hook function definitions
                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
                <filename>foo.php</filename>.
              </listitem>
            </itemizedlist>
          </para>
          <para>
            Using the example module info from
            <xref linkend="example.moduleinfo"/>, the complete multiple
            file module would look like this (see XXX why we use the
            check on PHORUM at the start of the PHP file):
            <example id="example.multifilemodule">
              <title>Multi file module</title>
<programlisting>
<filename>{phorum dir}/mods/foo/info.txt</filename><![CDATA[
title: Foo
desc: This is the Foo module for Phorum. Nothing exciting...
version: 1.0.2
release_date: Jan 1st, 2008
url: http://www.phorum.org
author: John Doe <johndoe@example.com>
require_version: 5.2.2
category: user_features

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]]></programlisting>

<programlisting>
<filename>{phorum dir}/mods/foo/foo.php</filename><![CDATA[
<?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;
}

?>]]></programlisting>

            </example>

            So far, the module has exactly same functionality as the single
            file module from <xref linkend="modules.singlefile"/>.
            From here on, the functionality can be extended. Some of the
            possibilities are:
            <itemizedlist>
              <listitem>
                Adding custom files to your module tree
                (images, classes, libs, etc.);
              </listitem>
              <listitem>
                <xref linkend="modules.languages" xrefstyle="template:Letting your module support multiple languages"/>;
              </listitem>
              <listitem>
                <xref linkend="modules.settings" xrefstyle="template:Implementing a settings screen for your module"/>;
              </listitem>
              <listitem>
                Adding template files for your module;
                (See XXX about module template files)
              </listitem>
            </itemizedlist>
          </para>

        </section> <!-- end of multi file modules -->

    </section> <!-- end of module structure -->

    <section id="modules.languages">
      <title>Supporting multiple languages</title>

      <para>
        This feature is supported by the <xref linkend="modules.multifile"
        xrefstyle="template:multiple file structure"/>.
      </para>
      <para>
        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 that use a different language(s)
        to display output of your module in the same language(s),
        instead of the language you have written the module in.
      </para>
      <para>
        For supporting multiple languages, the first thing to do is add the
        following line to your module information file
        <filename>info.txt</filename>:

        <programlisting>hook: lang|</programlisting>

        There is no actual hook function configured here, because the
        "lang" hook is only used as a marker for Phorum. It tells Phorum
        that your module supports multiple languages.
      </para>
      <para>
        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. In our sample module, the full directory would be
        <filename>{phorum dir}/mods/foo/lang/</filename>. The language files
        must be named identical to the main language files that Phorum uses.
        To include both English and French, your module would require the
        following file structure:
        <example>
          <title>Tree structure for a module that supports languages</title>
          <programlisting>
{phorum dir}/
     |
     +-- mods/
          |
          +-- foo/
               |
               +-- info.txt
               |
               +-- foo.php
               |
               +-- lang/
                    |
                    +-- english.php
                    |
                    +-- french.php </programlisting>
        </example>

        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 language string from
        other modules or Phorum itself. Here is an example of how you
        could do that:

        <example>
          <title>Custom language file for a module</title>
          <programlisting><![CDATA[
<?php
$PHORUM["DATA"]["LANG"]["mod_foo"] = array(
    "Hello"   => "Hello!",
    "Bye"     => "Good bye!"
);
?> ]]></programlisting>
        </example>

        Here, the extra inserted level is <literal>["mod_foo"]</literal>.
        To access the "Hello" string from your module code you would
        use the PHP variable:
        <programlisting>
$PHORUM["DATA"]["LANG"]["mod_foo"]["Hello"]</programlisting>

        When you want to use the language string from a template file,
        the you would use the following <xref linkend="templates.templatevars"
        xrefstyle="template:template variable"/>:
        <programlisting>
{LANG->mod_foo->Hello}</programlisting>

      </para>
      <para>
        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
        <filename>english.php</filename> language file in all your modules.
        If both the current language and English are not found, Phorum will
        be unable to load a language for your module and will display empty
        space instead of your language strings.
      </para>
      <para>
        Always 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 translators and using core language strings helps in
        keeping the used terminology consistent.
      </para>

    </section> <!-- end of module languages -->

    <section id="modules.datastorage">
      <title>Module data storage</title>

      <section id="modules.datastorageintro">
        <title>Introduction</title>
        <para>
          Sometimes, modules will have to store some data in the Phorum
          system. For example an avatar module would have to store what
          avatar a user want to show and a poll module would have to add
          the question, answers and voting results for a poll to messages
          in which a poll is added.
        </para>
        <para>
          This section description what standard methods are available for
          letting modules store their data in the Phorum system. Of course,
          as a module writer, you can divert from this and use any kind
          of storage that you like. You are in no way limited to only use
          Phorum specific methods here.
        </para>
      </section>

      <section id="modules.storemessagedata">
        <title>Storing data for messages</title>

        <para>
          If your module needs to store data along with a Phorum message,
          the easiest way is to make use of the meta information array that
          is attached to each message array
          (<literal>$message["meta"]</literal>).
          This array is a regular PHP array, which is stored in the
          database as serialized data (see <ulink url="http://www.php.net/serialize">PHP's serialize manual</ulink>).
          Because Phorum and other modules make use of this meta data as
          well, you should never squash it, neither access the meta data
          in the database directly. Instead use the methods described in
          this section.
        </para>
        <para>
          To prevent name space collissions with other modules or Phorum,
          it is good practice to create only one key in the meta data
          array named <literal>mod_&lt;yourmodule&gt;</literal> (in our
          example: <literal>mod_foo</literal>). If your module needs to
          store only one single value, then put it directly under this key:
          <programlisting>
$message["meta"]["mod_foo"] = "the single value";</programlisting>
          If multiple values need to be stored, then put an array under
          the key. This array can be as complicated as you like:
          <programlisting><![CDATA[
$message["meta"]["mod_foo"] = array(
    "key1"    => "value1",
    "key2"    => "value2",
    "complex" => array(
        0 => "what",
        1 => "a",
        2 => "cool",
        3 => "module"
    )
); ]]></programlisting>
        </para>
        <para>
          <important>because the meta data is stored as serialized data in the
          database, it is not possible to handle the data you store in there
          through SQL queries.</important>
        </para>
        <para>
          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: hooks that get an editable message array as their
          argument and hooks that don't.
        </para>

        <section id="modules.storemessagedata.type1">
          <title>
            From hooks that get an editable message array as their argument
          </title>
          <para>
            <remark>
              If you see ??? below at the places where you are supposed
              to see hook docs, then it is because the hook docs for
              "before_post" and "before_edit" have not yet been written.
            </remark>
            There are some hooks that send a full message structure to the
            hook functions. These can change the message structure before
            returning it to Phorum. Examples are the hooks
            "hook.before_post" and "before_edit". For these kind of
            hooks, you can update the meta information in the message
            structure and be done with it. Here's an example of what this
            could look like in your hook function:
            <programlisting><![CDATA[
function phorum_mod_foo_before_post ($message)
{
    // Make sure that we have an array for mod_foo in the meta data.
    if (!isset($message["meta"]["mod_foo"]) ||
        !is_array($message["meta"]["mod_foo"])) {
        $message["meta"]["mod_foo"]["foodata"] = array();
    }

    // Add some fields to the mod_foo data.
    $message["meta"]["mod_foo"]["foodata"] = "Some data";
    $message["meta"]["mod_foo"]["bardata"] = "Some more data";

    // Return the updated message. Phorum will take care of
    // storing the "mod_foo" array in the database.
    return $message;
} ]]></programlisting>
          </para>
        </section> <!-- end of storing message data from message hooks -->

        <section id="modules.storemessagedata.type2">
          <title>From other hooks</title>
          <para>
            For other hooks, the proper way to store information in the meta
            data is to first retrieve the current message data (including the
            current meta data) using the
            <literal>phorum_db_get_message()</literal> function.
            After this, merge the information for your module with the
            existing meta data and store the updated data in the database
            using the <literal>phorum_db_update_message()</literal> function.
            Here is an example of what this could look like in your hook
            function:
            <programlisting><![CDATA[
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 hook parameter.
    $message_id = $data["message_id"];

    // Retrieve the message from the database.
    $message = phorum_db_get_message ($message_id);

    // Extract the current meta data.
    $meta = $message['meta'];

    // Make sure that we have an array for mod_foo in the meta data.
    if (!isset($meta["mod_foo"]) || !is_array($meta["mod_foo"])) {
        $meta["mod_foo"]["foodata"] = array();
    }

    // Add some fields to the mod_foo data.
    $meta["mod_foo"]["foodata"] = "Some data";
    $meta["mod_foo"]["bardata"] = "Some more data";

    // Store the updated meta data in the database.
    phorum_db_update_message($message_id, array("meta" => $meta));

    // Return the data that we got as input for this hook function.
    return $data;
} ]]></programlisting>

           Changing meta data for a message this way will ensure that the
           existing meta data is kept intact.
          </para>
        </section> <!-- end of storing message data from other hooks -->

      </section> <!-- end of storing message data -->

      <section id="modules.storeuserdata">
        <title>Storing data for users</title>

        <section id="modules.customuserfields">
          <title>Custom profile fields for users</title>
          <para>
            If your module needs to store data along with a Phorum user,
            you can make use of custom profile fields. These fields will
            be accessible from within the user data. E.g. if you create
            a custom profile field named "foobar", the value of that field
            will be stored in $user["foobar"] (so right next to the standard
            fields like $user["username"] and $user["email"]).
          </para>
          <para>
            Creating custom profile fields can be done from the admin
            interface, under "Custom Profiles". It is also possible to
            let your module create the custom profile field fully
            automatical, by using the <ulink url="../api/PhorumAPI/CustomProfileFieldAPI/_custom_profile_fields.php.html">Custom Profile Fields API</ulink>.
            If you choose to let the user of your module create the field by
            hand, then please include a thorough description of what
            configuration the user has to do. A lot of problems with modules
            that require manual configuration come from using wrong
            options for a custom profile field.
          </para>
          <para>
            <emphasis role="bold">
              Using a separate field for each piece of data
            </emphasis>
          </para>
          <para>
            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. The advantage of doing this, is that you can then use the
            option "Disable HTML" for the fields that you will be sending to
            the user's browser. In fields with this option enabled, characters
            that have a special meaning in HTML will be escaped after loading
            the user from the database. This prevents the field from being
            vulnerable to XSS attacks. Recommended settings for storing a single
            value in a profile field are:
            <itemizedlist>
              <listitem>
                <emphasis role="bold">Field Name:</emphasis><sbr/>
                Name your field <literal>mod_&lt;module name&gt;</literal>
                if you only need to store one single value. If you need to
                store more values, then use the format
                <literal>mod_&lt;module name&gt;_&lt;field name&gt;</literal>.
                This prevents the risk of clashing with standard Phorum user
                fields or custom fields that are added for other modules.
                For example, the "foo" module could use the field names
                <literal>mod_foo_size</literal> and
                <literal>mod_foo_name</literal>.
              </listitem>
              <listitem>
                <emphasis role="bold">Field Length:</emphasis><sbr/>
                If you want some field to contain a predefined maximum number
                of characters, then fill in that number of characters in this
                field. Before storing the field data to the database, Phorum
                will trim the data down if it is longer than the defined number
                of characters. If you need no limit, you can also use
                <literal>65000</literal> here.
              </listitem>
              <listitem>
                <emphasis role="bold">Disable HTML:</emphasis><sbr/>
                Enable this option, unless you are absolutely sure that the
                data for this field is either not shown in the browser or
                escaped by your module before showing it.
              </listitem>
              <listitem>
                <emphasis role="bold">Show in user admin:</emphasis><sbr/>
                If you want the field to be visible along with the user data
                for a user in the admin interface, then enable this option.
              </listitem>
            </itemizedlist>
          </para>
          <para>
            <emphasis role="bold">
              Using a single field for storing a complex data structure
            </emphasis>
          </para>
          <para>
            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 and that HTML is allowed for the field, so the
            special PHP serialization code will not be broken by escaping special
            characters. When your module needs to store multiple fields,
            this is the preferred way. Recommended settings for storing a full
            array in a profile field are:
            <itemizedlist>
              <listitem>
                <emphasis role="bold">Field Name:</emphasis><sbr/>
                Name your field <literal>mod_&lt;module name&gt;</literal>,
                so you will not risk clashes with standard Phorum user fields
                or custom fields that are added for other modules. For example,
                the "foo" module would use the field name
                <literal>mod_foo</literal>.
              </listitem>
              <listitem>
                <emphasis role="bold">Field Length:</emphasis><sbr/>
                Use <literal>65000</literal> here. Using smaller values will
                not make the database storage smaller. This value is only used
                to trim down the data to the provided length. So for storing
                serialized data, it is best to set this value as high as
                possible.
              </listitem>
              <listitem>
                <emphasis role="bold">Disable HTML:</emphasis><sbr/>
                Disable this option, so the serialize data will not be
                broken by escaping special characters.
              </listitem>
              <listitem>
                <emphasis role="bold">Show in user admin:</emphasis><sbr/>
                Disable this option. There is currently no support for showing
                serialized fields in the user admin pages in a readable way.
              </listitem>
            </itemizedlist>
          </para>
        </section> <!-- end of custom user fields -->

        <section id="modules.storeuserdata.type1">
          <title>
            From hooks that get an editable user array as their argument
          </title>
          <para>
            There are some hooks that send a full message structure to the
            hook functions. These can change the message structure before
            returning it to Phorum. An example is the hook
            "<xref endterm="user_save" linkend="hook.user_save"/>".
            For these kind of hooks, you can update the custom profile
            field data in the user structure and be done with it. Here's
            an example of what this could look like in your hook function:
            <programlisting><![CDATA[
function phorum_mod_foo_user_save ($user)
{
    // Some data to store in the "mod_foo" custom field.
    $data = array(
        "user_id" => $user_id,
        "mod_foo" => array (
            "foodata" => "Some user data",
            "bardata" => "Some more user data"
        )
    );

    // Put the data in the user sructure.
    $user["mod_foo"] = $data;

    // Return the updated user. Phorum will take care of
    // storing the "mod_foo" array in the database.
    return $user;
} ]]></programlisting>
          </para>
        </section>

        <section id="modules.storeuserdata.type2">
          <title>From other hooks</title>
          <para>
            For storing data in the custom profile field, you can make use of
            the <literal>phorum_api_user_save()</literal> 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).
            <example>
              <title>Filling custom profile fields for a user with data</title>
              <programlisting><![CDATA[
  // 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);]]></programlisting>
            </example>

          </para>
        </section>

      </section> <!-- end of storing user data -->

    </section> <!-- end of storing data for a module -->

    <section id="modules.buildingurls">
      <title>Building URLs for Phorum</title>

      <section id="modules.buildingurlsintro">
        <title>Introduction</title>
        <para>
          You might have noticed that all URLs that are used by Phorum
          are full absolute URLs. Phorum does not use relative URLs anywhere.
          and all of the URLs that point to Phorum PHP scripts are generated
          by the function <literal>phorum_get_url()</literal>. This was done
          for several reasons. Here are some of them, for the curious
          developer:
          <itemizedlist>
            <listitem>
              By generating full URLs, we guarantee ourselves that the user
              is always opening pages within the same domain. With relative
              URLs, the user might end up at a different domain because of
              some webserver redirect (e.g. from
              <literal>http://example.com</literal> to
              <literal>http://www.example.com</literal>), causing possible
              loss of cookies as a result (since cookies bind to domains).
              Loss of cookies result in the user being logged out.
            </listitem>
            <listitem>
              Integrating Phorum in a website is a lot easier when using
              absolute URLs. When editing the header template for example,
              it should be okay to add a
              <literal>&lt;base href="..."/&gt;</literal> in there, pointing
              at the URL where the original site exists. After doing so, the
              header template can use the same constructions and paths as
              the main site's header template.
            </listitem>
            <listitem>
              This becomes even more important when running Phorum in portable
              or embedded setups. There, Phorum will be run from some
              script at a random location and no longer from a script in the
              Phorum directory. By using absolute URLs, the linked resources
              can still be found.
            </listitem>
            <listitem>
              Code for generating absolute URLs is needed for generating
              URLs that can be put in mail messages. It's a logical choice
              to use the same code for generating the other URLs in Phorum,
              so only one URL generating function has to be maintained.
            </listitem>
            <listitem>
              By letting <literal>phorum_get_url()</literal> generate all
              URLs, we are prepared for future changes and new features.
              If changes are needed in the URL schema, we only have to
              update this function and nothing further. All core code and
              modules that use this function will automatically follow
              the changes.
            </listitem>
          </itemizedlist>
        </para>
      </section> <!-- end of generating urls intro -->

      <section id="modules.scripturls">
        <title>Build URLs for Phorum PHP scripts: phorum_get_url()</title>
        <para>
          Phorum uses the function <literal>phorum_get_url()</literal> to
          consistenly build URLs that point to Phorum PHP scripts. It is
          recommended that you use this function as well when creating
          URLs to scripts yourself, so special features and future changes
          will automatically be incorporated in the links you use.
        </para>
        <para>
          Here is an example of building an URL, which will open the
          profile page for the user with user_id = 17:
          <example>
            <title>
              Generating a profile URL using phorum_get_url()
            </title>
            <programlisting><![CDATA[
$url = phorum_get_url(PHORUM_PROFILE_URL, 17); ]]></programlisting>
          </example>
          The argument list that this function takes, depends on the first
          argument which tells Phorum what URL type has to be built.
          When building other URLs, other arguments will be used.
        </para>
        <para>
          About all URL types that <literal>phorum_get_url()</literal>
          supports are used for building URLs that point to the scripts
          that are bundled with Phorum. Sometimes, you might want to add
          an extra script of your own to the Phorum tree (see
          <xref linkend="modules.addons"/>). For those, you can use
          <literal>phorum_get_url()</literal> as well. The way to go is
          simple. You need to use <literal>PHORUM_CUSTOM_URL</literal> as
          the first argument and add the following parameters to it:
          <itemizedlist>
            <listitem>
              The first parameter needs to be the filename of the file to link
              to, without the (.php) extension.
            </listitem>
            <listitem>
              The second parameter needs to be FALSE or TRUE. If it is TRUE,
              then the current forum_id is added to the URL.
            </listitem>
            <listitem>
              All other parameters are added directly to the URL.
            </listitem>
          </itemizedlist>
        </para>
        <para>
          Here is an example of building a URL which links to the
          add-on file <filename>myfile.php</filename> in the Phorum
          installation directory. Lets asume that the URL has to have the
          forum_id in it and that it needs to contain the additional
          parameters <filename>foo=bar</filename> and
          <filename>baz=foo</filename>:
          <example>
            <title>
              Generating a custom script URL using phorum_get_url()
            </title>
            <programlisting><![CDATA[
// Build the URL for the add-on script.
$url = phorum_get_url(PHORUM_CUSTOM_URL, "myfile", 1, "foo=bar", "baz=foo");

// After this, you could store the URL in the template data, so you
// can use it from the templates to link to the add-on script.
// Here an example for filling the template variable {URL->MYFILE}:
$PHORUM["DATA"]["URL"]["MYFILE"] = $url; ]]></programlisting>
          </example>
        </para>
      </section> <!-- end of build urls to Phorum PHP scripts -->

      <section id="modules.scripturls">
        <title>Build URLs to files in the Phorum tree</title>
        <para>
          If you have some non-script files in your module that you need
          to access through a URL, then make sure that you are generating
          absolute URLs for these. You can make use of the Phorum
          setting variable <literal>$PHORUM['http_path']</literal>
          to build these. This setting relates to the "HTTP Path"
          option under "General Settings" in the admin interface.
        </para>
        <para>
          Let's asume you have a file named <filename>foobar.gif</filename>
          in your "foo" module tree, then you could generate the URL for
          that file like this:
          <example>
            <title>
              Generating an absolute URL for a file in a module directory
            </title>
            <programlisting><![CDATA[
// Since the code probably runs inside a function scope,
// the global $PHORUM variable needs to be imported.
global $PHORUM;

// Build the URL for the foobar.gif.
$url = $PHORUM['http_path'] . "/mods/foo/foobar.gif";

// After this, you could store the URL in the template data, so you
// can use it from the templates. Here an example for filling the
// template variable {MOD_FOO->IMAGE_URL}:
$PHORUM["DATA"]["MOD_FOO"]["IMAGE_URL"] = $url; ]]></programlisting>
          </example>

        </para>
      </section>

    </section> <!-- end of generating custom urls -->

    <section id="modules.settings">
      <title>Implementing a settings screen for your module</title>

      <para>
        Note: this feature is only available for modules that use the
        <xref linkend="modules.multifile"
              xrefstyle="template:multiple file module structure"/>
      </para>
      <para>
        Some modules that you write might need to store settings for later
        use. For those, you can create a settings page that can be
        accessed from the &quot;Modules&quot; page in the admin interface.
      </para>
      <para>
        The settings page must be put in your module's directory by the
        name of &quot;<filename>settings.php</filename>&quot;.
        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.
      </para>
      <para>
        Although you can do anything you want in 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.<sbr/>
        If the standard tools are not enough for building your settings
        page, then it is of course fine to do things differently
        (e.g. let your module create and use database tables or build the
        full settings interface using your own form code.)
      </para>
      <section id="modules.phoruminputform">
        <title>Building input forms</title>
        <para>
          The Phorum PHP object "PhorumInputForm" can be used to build
          input forms and table displays in the admin interface.
          The best thing you can do for learning the possibilities of
          this class is to look at other Phorum modules (like "bbcode"
          or "replace") or the core admin scripts from the
          <filename>{phorum dir}/include/admin</filename> directory.
        </para>
      </section>
      <section id="modules.feedback">
        <title>Error and success feedback messages</title>
        <para>
          For displaying error and success messages, make use of the functions
          <literal>phorum_admin_error()</literal> and
          <literal> phorum_admin_okmsg()</literal>. Both functions take
          the message to display as their argument. By using those functions,
          the messages are shown using the standard Phorum admin formatting.
        </para>
      </section>
      <section id="modules.savesettings">
        <title>Saving module settings to the database</title>
        <para>
          Another tool is the function
          <literal>phorum_db_update_settings()</literal> that can
          be used for storing settings in the database. To store settings using
          this function, you can use code like this:
          <example>
            <title>
              Storing settings for a module in the database
            </title>
            <programlisting><![CDATA[
  // It is possible to store either scalars or arrays in a settings field.
  // Phorum will automatically take care of correct handling.
  $foo_settings = array();
  $foo_settings["foodata"] = "Some setting data";
  $foo_settings["bardata"] = "Some more setting data";

  // Store the data in the database.
  phorum_db_update_settings(array("mod_foo" => $foo_settings));

  // In the next request, the settings data can be found in
  // the variable $PHORUM['mod_foot'], for example:
  print $PHORUM['mod_foo']['foodata'];]]></programlisting>
          </example>
        </para>
      </section>
      <section id="modules.security">
        <title>Prevent settings.php from being loaded directly</title>
        <para>
          To ensure that your settings.php file is only loaded from the admin
          interface, place this line at the top of your settings.php file
          (see also XXX: secure against hackers):
          <programlisting>if (!defined("PHORUM_ADMIN")) return;</programlisting>
        </para>
      </section>
      <section id="modules.example">
        <title>Full module settings page example</title>
        <para>
          Here is a full example settings page, using the tools from above.
          A real settings page will often be much larger than this, but the
          basics are the same.
          <example>
            <title>
              An example module settings.php script
            </title>
            <programlisting><![CDATA[
  <?php

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

  // If data is posted, then store the posted settings in the database.
  if (count($_POST))
  {
      $PHORUM['mod_foo']['field1'] = empty($_POST['field1']) ? 0 : 1;
      $PHORUM['mod_foo']['field2'] = (int) $_POST['field2'];

      // Do some error checking.
      if ($PHORUM['mod_foo']['field2'] > 1000) {
          phorum_admin_error("The value for field 2 is too high!");
      }
      // The data was okay. Store the settings.
      else {
          phorum_db_update_settings(array("mod_foo" => $PHORUM["mod_foo"]));
          phorum_admin_okmsg('The settings were saved successfully');
      }
  }

  // This block is standard for every settings page. The "mod" field
  // must be set to the name of the module for which the settings
  // page is written.
  include_once "./include/admin/PhorumInputForm.php";
  $frm = new PhorumInputForm ("", "post", "Submit this form");
  $frm->hidden("module", "modsettings");
  $frm->hidden("mod", "foo");

  // Add a header row to the form.
  $frm->addbreak("Foo module settings");

  // Add a checkbox to the form.
  $row = $frm->addrow(
      "Field 1",
      $frm->checkbox("field1", "1", "Yes", $PHORUM['mod_foo']['field1'])
  );

  // Add a help balloon to Field 1.
  $frm->addhelp(
      $row, "Field 1",
      "This is a help balloon text for Field 1."
  );

  // Add a text field to the form.
  $frm->addrow(
      "Field 2",
      $frm->text_box("field2", $PHORUM['mod_foo']['field2'], 50)
  );

  // Display the form.
  $frm->show();

  ?>
  ]]></programlisting></example>
        </para>
      </section>
    </section> <!-- end of implementing settings for your module -->

  </section> <!-- end of writing your own modules -->

</chapter>
