July 06, 2008 | 11:14 AM  
Welcome

Don't have an account yet? You can create one, it is free, just click here

as a registered user you have some advantages like free downloads, comments and posting on our forums, depending upon this site's configuration and options.

 • •  Control Panel - Register - Login  • • 
Current Stable MDPro Lite 1.0821 Download
   19-Oct-2005  Print current page  Show map

Module Developers Guide

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

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.