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
Table of Contents
The MDPro system allows for expansion of its functionality through the use of modules. A module is set of files containing functions with pre-defined names and roles that integrate very easily with a standard deployment of MDPro. A module can also include blocks, images, plain HTML files, etc.
There are a number of reasons to write a module. The main reason is because MDPro does not provide a specific function that you would like it to. Examples of modules that have been developed to date for MDPro include bulletin boards, galleries, calendars, address books, and MP3 search utilities.
The MDPro module API is currently in beta. This means that the module functionality may well be increased prior to its first official release. However, creating a module as outlined in this document will work, and continue to work as described for the foreseeable future. Any future versions of the MDG will note where areas and functions have been superceded or deprecated, and developers will have at least 6 months between any major changes in the module design being implemented and backwards compatibility being removed from the core, allowing suitable time for migration.
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.
This document gives a step-by-step guide to creating a module for the MDPro system. It covers all stages from the initial design and registering yourself with the MDPro community to deploying, certifying, and upgrading your module.
Other documents that might be of use in conjunction with this guide are the API Reference Command Reference, the Theme Development guide, and the Output Functions Guide. Note that the Theme and Output guide remain unwritten at this point.
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.
Table of Contents
This chapter describes the basic architecture of MDPro, explains the major parts, and contains information on the design choices made for the system.
TBD: Accepting arguments [$args Vs. pnVarCleanFromInput()] - priority etc. Why you HAVE to pass all variables through pnVarCleanFromInput etc Variable scope, what to store in session vars
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
The user id.
The user name (full spelling).
The user name (short form, 'nick').
The email address of the user.
The user url.
The status of the user (active, inactive, deleted etc).
The autentication module that was last used for this user.
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 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
|
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();
}
|
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;
}
|
Table of Contents
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.
Separation of user and administrator functions allows for a much cleaner module. It speeds up the responsiveness of the module in the most-often used cases (i.e. user actions) as the module only needs to load the code that is required of it. It allows for work one one area of the code (e.g. an admin GUI redesign) to take place without affecting the other areas. And it also gives an extra layer of security to help ensure that privileged functions cannot be executed inadvertently from user areas.
Separation of display and operational functions allows for areas within and without MDPro to use the functionality supplied with a module. This is most obvious in the case of modules with blocks. where the block might display its own information but use the module functions to gather that information. Other modules where this is hugely important are the utility modules; things like comments and rating systems, that have no real use on their own but can be coupled with other modules to provide generic and site-wide functionality at very little cost to the module developer.
Having a single install directory allows for much easier maintenance of large MDPro systems, and far easier install and removal of modules both for the module developer and for the site administrator. Dependencies of the layout on the file system are no longer required, and as such the module designer does not need to worry about on which systems his module might be deployed, and how it needs to interact with the underlying operating system to function correctly.
Allowing access to module functions from external (i.e. non-MDPro) systems is a very desirable thing to do. By allowing this, the MDPro system becomes a content repository, where information can be accessed in ways other than through the standard web interface. An example of this power can be seen through use of the XML-RPC interface that is provided with MDPro and which allows other systems to obtain information directly from modules without going through the web interface, thus allowing things like easy syndication of a site's content.
Table of Contents
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.
All MDPro modules must be placed within their own subdirectory of the 'modules' directory to be recognized Modules placed anywhere else within the file system will not be located correctly.
A module might have administration or user functionality, or both. MDPro works out which functionality each module has by looking for the files 'pnadmin.php' or 'pnadminapi.php' to confirm administration functionality, and 'pnuser.php' or 'pnuserapi.php' to confirm user functionality. Lack of these files results in MDPro assuming that this specific module functionality does not exist.
Initialization of modules is through the modname_init() in the 'pninit.php' file within the module's directory. function. No other function is called when the module is initialized.
Activation and deactivation of modules is done through field settings within the appropriate database table. Unlike earlier versions of MDPro, no physical changes to the module directories is made to infer the activation status of the module.
Module functions are called through the pnModFunc() and pnModAPIFunc() functions. No direct calling of module functions is allowed, even from within the same module.
URLs for new-style modules go through the 'index.php' entry point, and are defined by a number of parameters. The parameters that currently decide which particular module function to call are as follows:
The name of the module. This corresponds to the well-known name of the module, which can be found through the modules administration interface
The type of the module function. This is currently either 'user' for user functions or 'admin' for administrative functions.
The name of the function itself. This is module-dependent.
If any of these parameters are undefined within a URL MDPro will apply defaults to them. Note that both the names of the parameters and their default values might change, and as such it is not recommended to create direct URLs for anything but to either go through the MDPro main page or to use the pnModURL() function to generate URLs that will always be internally consistent for any given version of MDPro.
Table of Contents
There are a number of steps that need to be taken before you can start building your module.
Choosing a name for your module is important, as this is the main way that your module will be known throughout the MDPro community. The name should be related to the functionality that the module provides, but also be specific enough to be able to discern it from separate modules that might offer similar functionality.
Module names are case-sensitive. For this reason, it is highly recommended that all modules names are lower-case only.
There are two broad types of module available in MDPro. Item modules are modules which contain their own content and operate on that content, whereas utility modules are modules which contain additional information or functionality for the content of other modules. Examples of item modules are news, FAQ, and downloads. Examples of utility modules are comments, ratings, and global index. Utility modules can either work in the same way as item modules, or they can operate through the use of hooks, which allow module functions to be acted upon without being explicitly called by other modules. Hooks are normally used for items that are not part of a piece of content but directly related to it
Registering your module is not compulsory, but it is a very good idea. By registering your module you can ensure that no other official MDPro module will take the name that you have chosen for your module. Two modules with the same name will not operate correctly on a single MDPro site, so it is beneficial to both yourself and the MDPro community in general to have a unique name.
Need information on how to register module names and get a module ID
A copy of the MDPro API reference guide is essential when developing a module. This guide covers all of the core functionality that the MDPro system provides and provides example code for every API function available.
Add link to pnAPI download
The section entitled 'Notes on Developing Modules' includes a lot of miscellaneous information that does not fit in other sections of this document. It should be read fully before any attempt to design or develop a module is started.
Understanding the difference between GUI and operational functions is critical when building a good module. Proper separation of these functions will allow other modules to be able to access the functionality of your module and incorporate it into their modules. It will also allow methods of access apart from those that the standard web-based MDPro system.
Understanding the difference between user and administrative functions is very important when building a good module. The separation of these types of actions allows for
The MDPro security model is a very important area to understand before coding a module. Developers should understand which parts of their module need to be protected, and exactly how this is accomplished.
Add description of the security model.
Every well-defined module function must return the appropriate return codes. Return codes are the main way in which a module communicates with the MDPro core, and as such it is vital that the correct return codes are used.
The following return codes should be used when returning control to the MDPro core from any module function:
Returning a text string implies that the module function has finished its work and has output to be displayed in the appropriate place on the MDPro web page. MDPro will take the returned output and display it as appropriate. Note that all output from modules is displayed verbatim, with no escaping of HTML characters. This is to allow for formatted output from the module functions.
Returning boolean true implies that the module function has finished its work and set up an appropriate redirect to send the user to a page that will have display output. The MDPro core will take no further action as far as this module is concerned.
Returning boolean false implies that the module function has finished its work but not set up an appropriate redirect to send the user to a page that will have display output. The MDPro core will set an appropriate redirect for this module.
Note that none of these functions carry any information about the success or failure of the attempted operation that the module function was undertaking.
Modules cover two separate areas of MDPro. The first is administration of core functions, (e.g. users, permissions), and the second is extension of system functionality (e.g. downloads, web links). As each of these areas are not core this implies two things. First is that no module is actually required - the MDPro system would work without anything in its modules directory, although its functionality would be severely limited and there would be no configuration options available. Second is that modules should not remove any core functionality when they are installed, operated, or removed.
An often overlooked point is that the module should be designed before being coded. This will allow for far easier coding later on, and an understanding of how the module fits into the generic MDPro module structure. Some of the points that should be considered are:
What data does the module store? How should the module data best be stored? Is the data hierarchical or flat?
What does the module do with the stored data? How is the data displayed, how much data is displayed at any one time? What options should the user have to view the data in different ways?
How does the module interact with other modules? Does it compete directly with other modules? If so, does it make sense to follow their module API to allow for greater interoperability between similar modules? Can it use other modules for part of its functionality? Is it better written as an extension to a current module rather than starting again from scratch?
There are a number of standard module functions that allow a newly written module to interface with other parts of the MDPro system. These functions have predefined inputs and outputs, allowing external modules and core functions to use them effectively without needing to tailor their operation to each separate module. The best example of these functions is the 'search' function, which passes in a simple text string and requires that an array is passed back about all items within the module that match the string.
Clarify the search function
If your module does not have these functions then it will not integrate fully with the other parts of the MDPro system. It is recommended that these functions are supplied if they make any sense in the context of 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.
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.
main() - the default function to call, normally just presents the user menu
view() - display an overview of all items, normally paged output
display() - display a single item in detail, given an identifier for that item
getall() - get basic information on all items, can take optional parameters to obtain a subset of all items
get() - get detailed information on a specific item
main() - the default function to call, normally just presents the user menu
view() - display an overview of all items, normally paged output, with relevant administrative options. Note that it is possible to combine this function with the user view() function
new() - display a form to obtain enough information from the user to create a new item
create() - take the information from the form displayed by the administration new() function and pass it on to the administration API for creating the item
modify() - display the details of a current item given the item description, and present the relevant fields for modification
update() - take the information from the form displayed by the administration modify() function and pass it on to the administration API for modifying the item
delete() - display confirmation for deletion of an item, and if confirmed pass the relevant information on to the administration API for deleting the item
modifyconfig() - display the details of the module's current configuration, and present the relevant fields for modification
updateconfig() - take the information from the form displayed by the administration modifyconfig() function and update the relevant module configuration variables
There are a number of utility modules available to carry out features that are required by many item modules within MDPro. Examples of available utility modules are comments, ratings, and categorization. Take a look at the modules in downloads at maxdev.come to find out what other utility modules are available and if they can be used in lieu of parts of the code that you would otherwise be writing for your own module.
Point to a repository of utility modules and functions.
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.
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.
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.
|
The top-level directory in MDPro for modules |
|
The directory that contains all of the module code (in this case the module is named 'gallery') |
|
The file that contains all administrative GUI functions for the module |
|
The file that contains all administrative operational functions for the module |
|
The directory that contains all blocks associated with the module |
|
A file that contains a block associated with this module; in this case it displays a random snapshot from the gallery |
|
The directory that contains all images for the module |
|
The image for the administration icon of the module |
|
The file that contains Initialization functions for the module |
|
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 |
Table of Contents
Create the directory to hold the module files. This directory must be created under the 'modules' directory in the MDPro install, and must be created with the name of your module as registered at the MDPro modules site.
Copy over all of the files from the template directory into you newly created module directory. These files set up the basic structure for your module and allow you to get to work creating your module very quickly.
Coding your database tables requires you to edit the pntables.php file in your module directory. This file gives information on the structure of the tables used by this module, although it does not carry out any actions itself. The structure information is wrapped in a function (modname_pntables()) for easy access by the MDPro system. An annotated copy of the template pntables.php file is available in the standard MDPro distribution as part of the Template module.
If your module uses tables specified by another module then you can either remove the pntables.php file completely from your module directory, or have a suitably named function that just returns an empty array.
If you attempt to use the same table name as another module or the MDPro core then your module will fail in unexpected ways. Try to give your tables unique names, preferably based on your module name.
Module Initialization functions are required for three separate actions. These actions are Initialization of the module's tables and configuration, upgrade of the module's tables and configuration, and deletion of the module's tables and configuration. Each of these items are generally only ever called once, although if a site administrator desires they should be able to initialize and delete a module as many times as they wish. It should be assumed that whenever these functions are called the MDPro system has already loaded the relevant information from pntables.php and it is available in the information returned by pnDBGetTables().
An annotated copy of the template pninit.php file is available in the standard MDPro distribution as part of the Template solution.
Once the database structure and Initialization files are in place they should be tested by using the modules administration area of your MDPro system to test Initializing and deleting your module. You should manually check that the database table created is correct, and that deleting a module removes all of the relevant configuration variables and database tables. Once you are happy that the module Initialization functions are working correctly you should carry out an Initialization so that work on the administration and user functions can proceed with suitable database tables in place.
With your database tables in place the next step is to write some administration functions. The administration functions that you will write depend on the nature of your module, however most modules have at least the following items:
add a new item
modify an existing item
delete an existing item
Each of these items is normally broken down into three separate pieces. The first piece is part of the GUI and displays a form with suitable fields for user input. The second piece is part of the API and carries out the requested operation. The third piece is another part of the GUI and gathers information from the form displayed by the first piece and passes it as arguments to the second piece. The interaction between the three pieces is shown in the diagram below.

As mentioned earlier in the document, it is vital that the separation between the GUI and API functions is clear. If you are unsure about whether part of a function should be in the GUI or the API, take a look at what it does. If it is directly involved with user interaction (gathering information from the user or displaying information to the user) then it is a GUI function. If it is involved with obtaining or updating information in the MDPro system itself (normally in a database table) then it is an API function.
Annotated copies of the template pnadmin.php and pnadminapi.php files are available in the standard MDPro distribution in the Template module.
Once the administration functions are in place they should be tested by using the administration area of your module to carry out the basic functionality that you have created. The operation of the module functions should be checked against the information in the database to ensure that they are storing and displaying the data correctly.
Once the administration functions are in place to manipulate your module's data then you can write the user functions to display the data. As with the administration functions the user functions that you will write depend on the nature of your module, however most modules have at least the following items:
overview of a number of items
detailed view of a single item
Each of these items is normally broken down into two separate pieces. The first piece is part of the GUI and gathers information from the user as to which item they wish to view, passes it on to the API piece, and displays the resultant information. The second piece is part of the API and obtains the required information for the display piece. The interaction between the three pieces is shown in the diagram below.

Annotated copies of the template pnuser.php and pnuserapi.php files are available in the standard MDPro distribution as part of the Template module.
Once the user functions are in place they should be tested by operating the module in the same way that a normal user would. The operation of the module functions should be checked against the information in the database to ensure that they are displaying the data correctly.
You might want your module to include blocks. Blocks are smaller functional units of a module that display specific information, and generally show up down the left and right hand sides of a page. Blocks are relatively simplistic items, and can either use their module's API functions to obtain information or use their own direct SQL query. Although they are packaged as part of the module they are not directly related to it except that they use the same database tables, and as such they might have to load the module's database table information directly through the use of the pnModDBInfoLoad() function if they intend to access the module's tables directly.
An annotated copy of the template first.php block file is available in the standard MDPro distribution as part of the Template module.