MDPro Module Developers Guide

This document is in alpha state and as such the description of the API is likely to change.

12nd May 2002

Revision History
Revision 2.4 11th May 2002 mc
Described error and exception handling
Revision 2.3 3rd May 2002 mc
Extended Variable validation
Revision 2.2 28th April 2002 mc
Added section variable validation
Revision 2.1 27th April 2002 mc
Added section on user variables

Table of Contents

1. Introduction
1.1. What is a Module?
1.2. Why Write a Module?
1.3. Status of the Module API
1.4. On-Going Work
1.5. This Document
1.6. Related Documents
1.7. Suggestions and Updates
2. MDPro Architecture
2.1. Variable handling
2.2. User variables
2.3. Variable validation
2.4. Error handling
3. MDPro Module Design
3.1. Separation of User and Administrator Functions
3.2. Separation of Display and Operational Functions
3.3. Single Directory Installation
3.4. External Access to Module Functions
4. MDPro Module Operations
4.1. Locating Modules
4.2. Working out Module Functionality
4.3. Initializing Modules
4.4. Activating/Deactivating Modules
4.5. Calling Module Functions
4.6. Creating Module URLs
4.7. Direct URLs to functions
5. Before Starting Your Module
5.1. Choose a Name for Your Module
5.2. Decide on the Type of Your Module
5.3. Register Your Module Name
5.4. Obtain a Copy of The MDPro API Reference Guide
5.5. Read the 'Notes on Developing Modules' Section
5.6. Understand the Following Areas
5.6.1. Difference Between GUI and Operational Functions
5.6.2. Difference Between User and Administrative Functions
5.6.3. The MDPro Security Model
5.6.4. Function Return Codes
5.6.5. Where Modules Fit in MDPro
5.7. Design Your Module!
5.8. Consider Including the Standard Module Functions
5.9. Use Standard Function Names
5.9.1. User Display Functions
5.9.2. User API Functions
5.9.3. Administration Display Functions
5.9.4. Administration API Functions
5.10. Find Out What Utility Modules Are Available
6. Module Directory Structure
7. Building Your Module
7.1. Make Your Initial Directory
7.2. Copy the Module Template
7.3. Code your Database Tables
7.4. Write your Initialization Functions
7.5. Test Your Initialization Routines
7.6. Write your Administration Functions
7.7. Test Your Administration Routines
7.8. Write your User Functions
7.9. Test Your User Routines
7.10. Write your Blocks
7.11. Test Your Blocks
7.12. Document Your Module
7.13. Package Your Module
8. Interacting With Other Modules
8.1. Overview
8.2. Hooks
8.2.1. Calling Hooks
8.2.2. Writing Hooks
8.3. Function Calls
9. Upgrading Your Module
10. Notes on Developing Modules
10.1. Use pnAPI
10.2. Security
10.2.1. Variable Handling
10.2.2. Authorization
10.2.3. Reserved Variable Names
10.2.4. Page Path
10.3. Output
10.4. Using OO Code
11. Module Developer's Checklist

MDPro is an alpha product and very much a work in progress. There are a number of areas that are currently still under redesign, and when the redesign and resultant new code is in place module developers will need to change their code to be able to support the latest functions. Any change of these areas will have at least one full release cycle where both old and new style code is supported so the transition period will always be a matter of months. All efforts will be made to keep the changes as simple as possible.

Areas that are still defined as to be upgraded before the 1.0 release are as follows:

  • The multilingual system. The current multilingual system uses defines, which does not lend itself to high levels of flexibility. This will be solved through some sort of functional interface into the multilingual system, although the details are yet to be defined. The change should involve developers replacing their current _LANGUAGE defines with function calls but should be a relatively simple search and replace operation without changing any logic within the modules.

    The theming/templating system. The theming and templating systems within MDPro are currently very basic and nonexistent, respectively. A number of MDPro developers are looking at a much more flexible solution to display content. The change will probably involve developers reworking the display part of their modules, although it is hoped that one of the advantages of the new system is that it will make the entire work of displaying content to users less of a developer task and hence the re-worked code will be a lot simpler than the current display functions.

    The hooks system. The hooks system is very new to MDPro, and it is already recognized that it will require some extra work. The most obvious addition is to allow hooks access to module content, but others might be required as well. This should require minimal changes to development modules, but developers should be aware of this up-coming change both when using and coding hooks.

The MDPro module system is a work-in-progress. There are no doubt many good ideas out there that have not been incorporated into the MDPro module system, and if a developer has a request for a particular set of functionality then they can submit it to the MDPro features request list on SourceForge at the MDPro Homepage. If you have found a bug within the current module system then you can submit it to the bug list at the same address.

Please note that the main requirement for the MDPro module design is stability. Due to this it is possible that your request for new or updated functionality will get refused on the grounds that it is too specific, can easily be built from core API functions, or carries out work that should rightly be done by a module. In such situations the MDPro team will always try to provide a simple alternative, but please remember that submission of a new or updated addition to the module design does not guarantee inclusion.

This chapter describes the basic architecture of MDPro, explains the major parts, and contains information on the design choices made for the system.

An user variable is an entity identified by a name that stores a value owned by exactly one user. MDPro offers two API functions to manipulate user variables, they are pnUserGetVar() and pnUserSetVar(). The purpose of pnUserGetVar() is to allow read access to one user variable. In contrast to that, pnUserSetVar() allows write access to one user variable. The $name parameter is checked against metadata to make sure the variable is registered. MDPro keeps some metadata about every user variable, so you can't access the $name user variable if its metadata is not registered. A module can register a new user variable by providing its metadata only if it has the right permissions (permissions are checked by the registration function). Usually the registration process should take place at initialization time for a module that wants to use the $name user variable during its life cycle.

MDPro doesn't impose any restriction on the value of $name except for duplicate and reserved names. As of this writing, the list of reserved names consists of

uid

The user id.

name

The user name (full spelling).

uname

The user name (short form, 'nick').

email

The email address of the user.

url

The user url.

status

The status of the user (active, inactive, deleted etc).

auth_module

The autentication module that was last used for this user.

You are advised (even for performance reasons) to use the following naming convention: $name := $module_name . '_' . $real_name

To register the $name user variable you have to use the module API function register_user_var() exported by the Modules module. Here is an example:

$module_name = 'MyModule';
$variable_name = 'MaxLinesPerPage';
$metadata['label'] = $module_name . '_' . $variable_name;
$metadata['dtype'] = _UDCONST_INTEGER; //one of the values defined for dynamic user data variable types
$metadata['default'] = 20;
$metadata['validation'] = 'num:>=:10&num:<=:100';

pnModAPILoad('Modules', 'admin');

$result = pnModAPIFunc('Modules', 'admin', 'register_user_var', $metadata);
if (!isset($result)) {
  // pnModAPIFunc() failed
} elseif ($result == false)
  // registration failed
} else {
  // registration succeeded
}
         
As you can see in this example, a descriptive array for the new user variable is created first, and later register_user_var is called with that array as parameter. Meaningful keys for the array are: label, dtype, default and validation. The label field is mandatory; it specifies the user variable name as you'll refer later in pnUserGetVar() and pnUserSetVar() $name parameter. The dtype field is mandatory; it can take one of the following values: _UDCONST_STRING, _UDCONST_TEXT, _UDCONST_FLOAT, _UDCONST_INTEGER. You should obviously choose the right value for the data type that the new user variable will contain. The default field is optional; it's used when the user has not yet set a value for the new user variable. The validation field is optional; refer to the next section to get an overview of variable validation. To unregister an user variable you have to call the unregister_user_var(), which is located in the users module admin API. You should call that API only at uninstallation time for your modules. Keep in mind that by calling unregister_user_var() all the existing values for that user variable will be deleted from user data.

As described in this document, MDPro offers support for module variables too. If you get confused from that, and can't see the distinction between these different things, here is a little explanation to cover that issue. Module variables are system-wide variables, shared between each module user, like configuration variables. They are not owned by any particular user, and even if they are often protected by permissions for write access, they are typically administrative-side variables. You are encouraged to use them when you have a need to give administrators the possibility to choose some behaviors of your module. But when those behaviors are more related to user preferences you should avoid using module variables and register a new user variable to be used in your code. As example you can consider the above code listing, where a new user variable is registered to allow every single module user to choose his own MaxLinePerPage setting. Now it's reasonable to have done this choice, but here we could have chosen a unique shared module var as well. On the other hand there are some cases in which you don't have this kind of freedom, for example consider the authldap module. It needs to access a LDAP server, so it needs a variable that contains the LDAP server hostname. Obviously this variable should be a module variable, and access to it should be granted only to administrators with the right permissions. We invite you to ponder this issue for a while before you settle on module vars or user vars.

MDPro includes a transparent mechanism for variable validation. It's currently used by two API functions: pnUserValidateVar() and pnUserSetVar(). The validation works thanks to the Dynamic User Data architecture. As you saw in the above section, with Dynamic User Data you can register new user variables simply by providing its metadata. That metadata can also contain a special field denoted by the validation key. A powerful syntax has been invented for this field. All what you need to do is to follow the right syntax and write your own validator(s), later you register it with user variable metadata and now you can get rid of validation in your module functions. Simply when you call pnUserSetVar(), MDPro will automatically apply your validator(s) and if the check fails you will be notified of that by the return value. To compensate for all this loss of control on validation, a new API function has been created. You can validate an user variable value with the pnUserValidateVar() function. That gives you the possibility to first validate all variables from user input and second update them if all validation checks have succeeded.

Here is the grammar for the validation string:

         validation_string := validator_list
         validator_list := validator [ + '&' + validator_list ]
         validator := ['!' +] type + ':' + operator + ':' + param
	 
Reserved characters to be escaped with a preceding '\' are: ':' and '&'

type can be one of these values: 'num', 'string', 'stringlen', 'func'

operator is type-sensitive:

valid operators for num type are: ==, !=, <, >, <=, >=

valid operators for string type are: is, contains, starts, ends, regex

valid operators for stringlen are the same as num type.

there's only one valid operator for func type: it's a string composed from ModName + ',' + FuncName. FuncName MUST be exported as an user API function from ModName module.

param is the second parameter to be used with operator, except for the func type: here param is the second parameter that will be passed to FuncName function.

You can create complex validators simply by concatenating them with the logic & (AND) operator.

Here are some examples:

// validation string = "string:starts:foo bar"
// validation will succeed
pnUserSetVar("myVar", "foo bar is better than bar foo");
// validation will fail
pnUserSetVar("myVar", "bar foo is ugly");

// validation string = "string:starts:foo\\: bar&stringlen:<=:16"
// NOTE: if you need to use the ':' character you have to
//       escape it with a preceding '\'
// validation will succeed
pnUserSetVar("myVar", "foo: bar is good");
// validation will fail, the string is too long
pnUserSetVar("myVar", "foo: bar is better");

// validation string = "!string:regex:/(censored1|censored2)/"
// NOTE: the negation operator before the string type
// validation will succeed
pnUserSetVar("myVar", "i'm a good boy, i'm not posting something bad");
// validation will fail
pnUserSetVar("myVar", "i'm a bad boy, you are a censored1");

// validation string = "num:>=:1&num:<=:10"
// validation will succeed
pnUserSetVar("myVar", "5");
// validation will fail
pnUserSetVar("myVar", "12");

// validation string = "func:MyModule,MyFunc:none"
// IMPORTANT: if your validation function works only with the
//            variable value you must specify that param has not
//            to be passed to function.
//            You achieve that by simply setting it to 'none'
// validation will succeed
pnUserSetVar("myVar", "Homer Simpson");
// validation will fail
pnUserSetVar("myVar", "Marco Canini");

// MyModule user API

function MyModule_userapi_MyFunc($args)
{
    extract($args);  // $value
    $ssconn = StarShip::openConnection();
    return !$ssconn->isAlienLifeForm($value);
}
         

MDPro is capable of error handling through a powerful exception handling system. Since the PHP language doesn't support language-level exceptions, MDPro provides an artificial mechanism to deal with exceptions. MDPro divides exceptions into two types: system exceptions and user exceptions. System exceptions are used by MDPro API functions, but you can use them if it's meaningful in that such situation; for example consider the DATABASE_ERROR exception, you are strongly encouraged to use this exception when a database error occurs and not to use your own exception. As another example consider the BAD_PARAM exception, you should choose to use that exception in your module functions and API functions when passed parameters are considered wrong. Finally system exceptions are well known exceptions for which MDPro can undertake particular actions like logging or emailing, on the other and user exceptions are not known by MDPro, and since they are indistinguishable, MDPro will treat them as they were all the same thing. Another good point in distinction between system and user exceptions is the fact that you should not leave uncaught user exceptions as you can do for system exceptions. Hence you should catch all user exceptions instead of throwing back them to MDPro, this because user exceptions can be seen as soft exceptions, so you could be in the position of doing other actions and/or returning a properly formatted error message that will look better than the default MDPro exception caught error message. However it's not illegal to throw back user exceptions to MDPro, so fell free to do that if it's the case. On the other hand you should avoid to catch system exceptions, except particular cases. A system exception is an hard exception, this means that something very wrong happened and MDPro should be noticed of that. You achieve this simply by throwing back system exceptions. Also here there are particular circumstances in which you could and perhaps should catch system exceptions. For example consider the pnUserGetVar() API function: it raises a NO_PERMISSION system exception in the case you don't have right permission, however you weren't in the position to get access level for user variables, so it's perfectly acceptable here to catch this exception and go ahead when it's meaningful to go ahead.

Now it's the moment to explore how MDPro permits to deal with exceptions. Here we begin by exposing how to catch exceptions. When a function, that potentially can raise exceptions, outcomes with a void value you MUST check if some exception was raised. You can do that by calling the pnExceptionMajor() function and comparing its return value with the PN_NO_EXCEPTION constant. If they are different you know that an exception was raised. The pnExceptioMajor() return value can assume one of these values: PN_NO_EXCEPTION, PN_USER_EXCEPTION, PN_SYSTEM_EXCEPTION. Obviously the value PN_NO_EXCEPTION indicates that no exception was raised, and PN_USER_EXCEPTION stays for user exception was raised and PN_SYSTEM_EXCEPTION stays for system exception was raised. When you see that an exception was raised you have two possibilities: throw it back or handle it. To throw back an exception you have only to return with a void value. To handle an exception you have to check for the exception type, id and value if one.

Consider the following example:

$res = pnModFunc('MyModule', 'user', 'MyFunc');
if (!isset($res) && pnExceptionMajor() != PN_NO_EXCEPTION) {
    // Got an exception
    if (pnExcepionMajor() == PN_SYSTEM_EXCEPTION) {
        return; // throw back
    }
    // Got a user exception
    if (pnExceptionId() == 'MyException1') {
        $value = pnExceptionValue();
        $output->Text("Syntax error at line: ".$value->lineNumber);
    } elseif (pnExceptionId() == 'MyException2') {
        /* Do something useful */
    } else { // MyException3
        /* Do something useful */
    }
    // reset exception status
    // NOTE: it's of vital importance to call this function
    //       before returning
    pnExceptionFree();
    return $output->GetOutput();
}
      
To throw exception you use a unique function: pnExceptionSet(). You simply call it by passing the exception major, id and value if one; and after this call you return void.

Consider the following example:

class MyException1
{
    var $lineNumber;
}

/* ... */

MyModule_user_MyFunc()
{
    /* ... */
    if ($syntax == false) {
        // Syntax error
        $exc = new MyException1;
        $exc->lineNumber = $line;
        pnExceptionSet(PN_USER_EXCEPTION, 'MyException1', $exc);return;
    }
    /* ... */
    pnExceptionSet(PN_USER_EXCEPTION, 'MyException2');
    /* ... */
    pnExceptionSet(PN_USER_EXCEPTION, 'MyException3');
    /* ... */
    return true;
}
      
Note that no value is associated to MyException2 and MyException3, so there is no need to create a class for exception value. As you can see exception handling is very powerful but also boring and tedious. However you can always choose to not use user exceptions and always throw back system exceptions. But keep in mind that good error handling is not something that should be left for last. It should be part of the development process. Note that is wrong to not check exception status after a call to a function that can potentially raise something. And note also that if you choose to handle one or more exceptions you MUST call pnExceptionFree() before exiting, otherwise the trust relationship on which the exception handling mechanism is based won't work and you will produce very bad things. An ulterior thing for who of you aims to code an official MDPro module: you MUST always check for possibly raised exceptions and not code with the thought that something will never happen; you MUST also raise DATABASE_ERROR in every function that do queries. To get a better understanding of exception handling functions you should now look at MDPro API Command Reference.

The MDPro module system design has been carried out by the MDPro development team to allow for the maximum flexibility to developers whilst ensuring that the module can be accessed in a generic fashion by the MDPro core, other modules, and remote systems given access through other interfaces such as XML-RPC. The main design characteristics of the module system are listed below.

This chapter covers how modules interact with MDPro. The information in this chapter is correct for the 0.71 release of MDPro, for other releases please get the most recent copy of the Module Developers Guide.

There are a number of steps that need to be taken before you can start building your module.

There are a number of function names that are considered standard i.e. they have well-known meanings and are used in a number of modules. Using the standard function names makes it easier for other module developers to use your module. Some of the standard functions are shown below.

Note

The below list is subject to addition as more standard functions are introduced - the template module supplied with your copy of MDPro should have the most up-to-date set of standard functions available.

MDPro modules have a very specific directory structure. This allows the MDPro system to use a generic system to access all modules without needing to know specific information about each separate module that is built. Following the directory structure as laid out below is an absolute requirement of any MDPro-compliant module.

Note

Extra files and directories in addition to those shown below are allowed. Also, if any of the files below are not required (e.g. the module does not have database tables of its own so it does not require the pntables.php file) then they do not need to exist. However, files that perform the functions outlined below must comply with the file naming convention to allow the MDPro system to load the suitable files at the appropriate times to ensure correct operation of the module.

Note

This shows the example layout that a gallery module might have. Other modules will have different names for their top-level directory and blocks as appropriate for their specific functionality.

        modules/                                1
                gallery/                        2
                        pnadmin.php             3
                        pnadminapi.php          4
                        pnblocks/               5
                                 snapshot.php   6
                        pnimages/               7
                                 admin.png      8
                        pninit.php              9
                        pnlang/                 10
                               deu/             (11)
                                   admin.php    (12)
                                   init.php     (13)
                                   manual.html  (14)
                                   snapshot.php (15)
                                   user.php     (16)
                               eng/             (17)
                                   admin.php    (18)
                                   init.php     (19)
                                   manual.html  (20)
                                   snapshot.php (21)
                                   user.php     (22)
                               ...
                        pntables.php            (23)
                        pnuser.php              (24)
                        pnuserapi.php           (25)
                        pnversion.php           (26)
      
1

The top-level directory in MDPro for modules

2

The directory that contains all of the module code (in this case the module is named 'gallery')

3

The file that contains all administrative GUI functions for the module

4

The file that contains all administrative operational functions for the module

5

The directory that contains all blocks associated with the module

6

A file that contains a block associated with this module; in this case it displays a random snapshot from the gallery

7

The directory that contains all images for the module

8

The image for the administration icon of the module

9

The file that contains Initialization functions for the module

10

The directory that contains all language translation files for the module

(11)

The directory that contains all German language translation files for the module

(12)

The file that contains German language translations for the administrative GUI functions for the module (i.e. pnadmin.php)

(13)

The file that contains German language translations for the Initialization functions for the module (i.e. pninit.php)

(14)

The file that contains the German language translation of the manual for the module

(15)

The file that contains German language translations for the snapshot block

(16)

The file that contains German language translations for the user GUI functions for the module (i.e. pnuser.php)

(17)

The directory that contains all English language translation files for the module

(18)

The file that contains English language translations for the administrative GUI functions for the module (i.e. pnadmin.php)

(19)

The file that contains English language translations for the Initialization functions for the module (i.e. pninit.php)

(20)

The file that contains the English language translation of the manual for the module

(21)

The file that contains English language translations for the snapshot block

(22)

The file that contains English language translations for the user GUI functions for the module (i.e. pnuser.php)

(23)

The file that contains all information on database tables for the module.

(24)

The file that contains all user GUI functions for the module

(25)

The file that contains all user operational functions for the module

(26)

The file that contains all version and credit information for the module

Hooks are a way of adding functionality to modules without the modules themselves knowing what the functions might be. The operation of hooks are controlled by the site administrator, so the decision as to which pieces of extra functionality to use and which not is in their hands rather than the module developer.

Hooks are called for specific actions that take place in a module. At current, the actions that hooks are enabled for are as follows:

  • Addition of a category

  • Deletion of a category

  • Transformation of category data into a standard MDPro format

  • Display of a category

  • Addition of an item

  • Deletion of an item

  • Transformation of item data into a standard MDPro format

  • Display of an item

Note

The terms category and item are quite broad. Category is used to define any database entity that contains other categories or items, whilst item is used to define any database entity that holds content. Due to this definition it is possible for an item to be a category as well, although this is an unlikely state of affairs and it should be obvious to a module developer which parts of the system deal with categories and which with items.

Hooks are the recommended way of extending the functionality of your module, and use of the appropriate pnAPI hook functions as described below is considered mandatory for a compliant module.

If you are developing an item module then you should allow utility modules to add functionality to the item module. This is carried out through use of the pnModCallHooks() function. This function should be placed wherever a specific action is carried out by the item module, where the current specific actions that the hooks system is able to operate on are:

The hook calls should be made at the appropriate level depending on the action that is being taken. With the current hooks, addition and deletion hooks should be called at the API level, and display hooks should be called at the GUI level.

The pnModCallHooks() function takes a number of parameters, which are explained below:

hookobject

The object for which the hooks are to be called - currently either category, or item, as described above

hookaction

The action for which the hooks are to be called - currently one of create, delete, transform, or display

obid

An ID that, within the scope of the module and object, uniquely defines the entity for which the hook is being called

extrainfo

This is extra information that is required by the hook function, and is dependent on the hook action being called. Information on the information required by each hook is covered below.

For create hooks a string that can be used in conjunction with the obid as part of a URL to access the object. For example, if your gallery_user_display() function uses a variable picid to define the particular picture that a user wishes to look at then the URL would be something like 'index.php?module=gallery&func=display&picid=4' and the identification part of the URL would be something like 'picid=4' so you would pass 'picid' to this hook.

For display() hooks a URL that can be used by the hooks to return to a suitable page once they have finished any work that they might have to do. This is normally just the standard display URL for this function.

For transform() hooks an array of items that contain text-based content that can be transformed. This is normally all text-based items.

The pnModCallHooks() function returns different information depending on value of hookaction. If hookaction is display then the hook will return extra output that should be displayed directly following the display for the item itself. If hookaction is create or delete then the function will return either true or false depending on the success or failure of the hooks. If hookaction is transform then the hook will return an equivalent array to that which was passed in, with the items suitable transformed.

As an example of calling hooks, if you were developing the 'Gallery' module and were displaying a picture, after the display of the picture you would want to call the hooks to add any other functionality available and required by the site administrator. To do this you would use the following lines:

$output->SetInputMode(_PNH_VERBATIMINPUT);
$output->Text(pnModCallHooks('item',
                             'display',
                             $pictureid,
                             pnModURL('gallery',
                                      'user',
                                      'display',
                                      array('pictureid' => $pictureid))));
$output->SetInputMode(_PNH_PARSEINPUT);
        

which would add the verbatim output of the hooks to the current output. It is worth noting again here the from this code it can be seen that the module itself needs no information on what hooks, if any, exist, it just calls the function and lets the MDPro core deal with what extra output should be added to this item.

One important area to understand is where exactly in your code to call hooks. For example, if you were displaying a thumbnail view of 100 pictures from your Gallery module, should you call an item display hook for each picture? The answer to this is somewhat dependent on the nature of your module, but in general you should only call display hooks when you are displaying the details of a single item rather than an overview of a large number of items (of course, if all of those items are in a single category then you should call a display hook for that category). However, the transform hook should be called whenever you are displaying content regardless of it if is just an overview, as the overview information could require transformation before display.

The annotated Template module in the standard MDPro distribution contains notes on calling hooks within an item module.

If you are developing a utility module then you probably want to allow your module to be called as a hook. This requires the module functions to be able to be called as hooks, and the module to register and unregister its hooks as required.

Once your module has hook-capable functions in place they need to be registered on Initialization of the module so that the administrator can configure their applicability, and other modules can access them through the pnModCallHooks() function. This is carried out through use of the pnModRegisterHook() function. This function should be placed within the modname_init() function of your module and given appropriate parameters to register the relevant hook-capable module functions within your module as hooks.

The pnModRegisterHook() function takes a number of parameters, which are explained below:

hookobject

The object for which the hook is to be registered - currently either category, or item, as described above

hookaction

The action for which the hook is to be registered - currently one of create, delete, transform, or display

hookarea

The area that the hook function covers - currently either GUI (for functions that are in pnuser.php and pnadmin.php) or API (for functions that are in pnuserapi.php and pnadminapi.php)

hookmodule

The name of the module in which the hook function exists - normally the name of the module calling this function

hooktype

The type of the hook function - currently either user or admin

hookfunc

The name of the hook function

The pnModRegisterHook() function returns true if the registration was successful, and false if the registration is unsuccessful.

As an example of registering hooks, if you were developing the 'globalid' utility module (which gives every piece of content in MDPro a separate ID) and had a globalid_admin_create() function which created an entry in the global ID table for this particular piece of content then you would register this as a creation hook. To do this you would use the following lines within globalid_init():

if (!pnModRegisterHook('item',
                       'create',
                       'API',
                       'globalid',
                       'admin',
                       'create)) {
    return false;
}
          

which would register this hook to be called every time a hook-enabled module someone creates an item (a similar but separate call would be needed to register this hook for the creation of categories as well).

The Ratings module that comes with the core MDPro distribution has detailed comments on registering hooks within a utility module.

Another way of accessing the functionality of other modules is by calling their functions directly with the pnModFunc() function. Doing this allows a number of advantages over hooks, but also a number of disadvantages. In general, calling functions directly is more flexible as the module developer understands exactly which functions they are calling and can also pass additional arguments to the function to customize its abilities. The disadvantages are that the module named in the function call needs to be installed and active on the system for the calls to work, and if this is replace by a different module providing similar functionality it will not work correctly.

Using direct function calls to other modules is fine within a module, but the developer should consider the implications of this on systems that might not have the modules that they are using installed. Also, even if direct function calls are used then the module developer should still call hooks at the appropriate places in the code to allow for other extended functionality to be added to the module.

An example of where direct function calls might be used within the Gallery module would be if the module developer wanted users to be able to rate various aspects of the picture displayed such as 'use of color' and 'originality'. In this case a simple hook would not be able to accommodate this requirement, so the developer would instead make explicit calls to the 'Ratings' utility module to display a number of separate ratings, each with its own identifier. The hook call would still be made, which might also add a rating to the picture, but in this case the value could be considered as the overall rating for the picture rather than that just for a specific part.

Note

Add information how module upgrades interact with the installer

Security is a very important part of MDPro. All modules should subscribe to the MDPro Security model to ensure that they operate correctly within all environments. For full information on security refer to the MDPro Security Model documentation, however the main points as regards modules are covered briefly below.

Note

Add information on the MDPro security model.

The following checklist presents a number of items that need to be checked throughout the process of designing, building, and releasing a module.

Initial  
· Decide on module type
· Choose name for module
· Register name for module
· Obtain and read MDG documentation
· Obtain and read API documentation
Module Design  
· Separate User and Administration Functions
· Separate GUI and API Functions
· Design data tables
· Note which utility modules are of use
· Note which standard module functions apply
· Create module security schema
Module Build  
· Copy template module directory
· Create database tables
· Create database Initialization routines
· Test database Initialization routines
· Write administration functions
· Test administration functions
· Test administration functions
· Write user functions
· Test user functions
· Write blocks
· Test blocks
· Document module API
· Package module
Module Checks  
· No global variables used
· No MDPro reserved variable names used
· No echo() or print() statements used
· All operations protected by pnSecAuthAction()
· All forms results protected by pnSecConfirmAuthKey()
· All form variables obtained by pnVarCleanFromInput()
· All output parsed through pnVarPrepForDisplay() or pnVarPrepHTMLDisplay()
· All suitable output censored by pnVarCensor()
· All variables in SQL queries protected by pnVarPrepForStore()
· All variables in filesystem access protected by pnVarPrepForOS()
· Calls to pnModCallHooks() in all appropriate locations