PHPGeneral/Connector writing guide

From Linuxnetworks
Jump to: navigation, search

Writing additional modules for general is an easy way to extend its functionality in various ways. Currently the module which is most often used is surely the management module for the Cyrus imap server (lib/modules/user/userimap.class.php). It may also be a good example for coding such a module. More complex examples are the ldap modules (lib/modules/*/*ldap.class.php), which are heart of general, but they are also only modules like the others.

Environment

There are (up to now) four different types of objects, which can be managed by general:

  • nodes
  • users
  • groups
  • devices

For each type exists a directory underneath the lib/module/ directory, which contains the files (classes) representing the different modules. If the administrator wishes to perform an operation on an object, the admin class (lib/module/admin.class.php) selects an intermediate admin class ({node,user,group,device}admin.class.php in the module directory) and delegates the job. This intermediate admin class then instantiates all modules, whose names are included into the configuration file (e.g /etc/general/*/user.conf, "module" array) and calls the member functions of these classes for the requested operation. Simply said, the application logic of general is a big dispatcher.

Basic layout

The file name containing the new module must consist of the type of the managed object (e.g. user, if it is located in lib/modules/user/), the keyword describing the module (e.g. imap) and the extension (e.g. .class.php). The file name must be in lower case.

This file must contain the module class and must be named like the file (without the .class.php), but use the C++ naming convention (first letter of each word is upper case, e.g. UserImap). The class must also extend directly or indirectly (by a base class) the Locale class, which provides the internationalization support. It will also give you the ability (through the PEAR extension) to create a destructor for your module class if you need one. The UserImap class looks like this:

class UserImap extends Locale
{
    function UserImap( $param, &$errmsg ) {}

    function add( &$node, &$errmsg ) {}
    function delete( &$node, &$errmsg ) {}
    function modify( &$node, &$errmsg ) {}
    function show( &$node, &$errmsg ) {}
}

Constructor explained

A typical constructor for an example module located in lib/modules/user/ may look similar to the next few lines:

function UserExample( $param, &$errmsg )
{
    // Do the necessary stuff
}

Parameter description

The constructor is responsible for retrieving all needed parameters and instantiates the used classes for further operations. The first parameter "param" is an associative array and contains up to now three elements:

  • userid (name of the authenticated user)
  • password (password of the authenticated user)
  • realm (path to the configuration files, without trailing slash)

The errmsg parameter is a reference to a variable which should be used to store error messages if an error occurs. Error messages must be appended by the ".=" operator and should not be overwritten by "=", because only then the user is able to trace an error. The errmsg variable is also used by the function, which instantiated the class to detect, if there was an error. The execution of the script is then aborted by the function at the highest level and the error message stack is displayed. The error messages must begin with the name of the class and the current function (e.g. "UserExample::UserExample()\n" for the constructor) and must be followed by a descriptive error message. All error message lines must end with a newline ("\n"), but the newline must not be included into the call to the localization function ($this->i18n( "string" )). 3.2 Localization support

One of the first things in the constructor which should be done is the call of the Locale constructor by

Locale::Locale( "localearea", "../lib/locale" );

This ensures, that all (error) messages included into $this->i18n( "string" ) are correctly translated into the language selected by the user. The string "localearea" must be replaced by the appropriate string for this module. All classes related to ldap - the ldap modules in lib/modules/*/ and the support classes in lib/support/ldap/ for example - are in the same locale area "ldap". Thus all messages, which should be translated are in the same file used by gettext for its lookup. The second parameter is the directory, which contains the subdirectories for the available languages. The path has to be relative to the location of the file called by the web browser. For a module this is always "../lib/locale".

Get configuration options

The module constructors often use the getref() function provided by lib/include/common.inc.php to instantiate the config class to retrieve configuration parameters stored in support.conf. getref() does the real work when a class from the support directory should be instantiated. It also contains some optimizations, particularly the reference counter for classes. The module class itself must not create neither any class by itself nor access anything (like a file) directly. It only relies on the classes provided by the support directory. Imagine, if you have to open a file on the local machine. Someone might extend your and all other modules to be able to access files on other machines. By a strict separation of logic (the module) and the low level functionality (file operations) it reduces the amount of work, if only one class in the support directory must be rewritten.

getref() needs at least two parameters to be able to instantiate the config class or any other class. Often more parameters are retrieved from /etc/general/*/support.conf and are used by the classes in the support directory to connect to a service.

Minimum parameters:

  • url
  • type

The url must be in the format "service://host:port/path/". For the config file class e.g. "conf://localhost:0/realm/conffile", where realm is the path to the config files provided by $param["realm"]. Type differentiates between different types of the same service, e.g. "phparray" if the configuration is stored as php array or (fictitious) "xml" for an XML file. The config class is able to extract configuration options from the specified file by invoking the member function

bool $confref->getParameter( $path, $confparam, $errmsg );

This method requires the path to the option (array["my"]["option"] will be "my/option") and returns the result as reference in the second variable. This result is then often used to instantiate the support class by calling getref() with is parameters retrieved from the config file and the user id and the password.

Member functions

Every module must implement exactly four functions, which will be called by the admin class respectively the intermediate admin classes if the user selected to perform one of those operations:

  • bool add( &$node, &$errmsg )
  • bool delete( &$node, &$errmsg )
  • bool modify( &$node, &$errmsg )
  • bool show( &$node, &$errmsg )

Function and parameter description

The function names are rather self explanatory. The &$errmsg parameter is like the one used in the constructor to store the messages in case of an error. All messages can be translated by calling $this->i18n( "message") after the constructor called the Locale constructor and therefore set the locale area and the locale directory. The format of the error messages must be the same as described in the constructor section: At first append the name of the function (e.g. "UserExample::add()\n") and the the descriptive error message included into the $this->i18n() call. All error message lines must end with a newline ("\n"), but the newline must not be included into the localization function call.

&$node is more interesting: it is a class for exchanging data between the logic layer (admin class, intermediate admin classes and the modules) and the presentation layer (the .php files in the admin/user/etc. directory called by the web browser). The node class (lib/include/node.class.php) contains the attributes, the location (path) and the type of an object and it is also able to generate the html code for its tree node. The node instance is only referenced (&) and not copied, because then the functions can add additional attributes to the node. These attributes can then be used by subsequent modules for their operations.

If you need additional functions only relevant for your class, write private member functions. PHP doesn't make any difference between public and private functions, so the only convention is to name them like pMyPrivateFunction(). These function must not be used by any other class or function outside the class.

Accessing the attributes

The most important member functions for the modules are the functions for retrieving the attributes from the node instance and storing the new or modified attributes in the node instance:

  • array getAttributes()
  • bool setAttributes( $attributes, $errmsg )

They return or accept an array of arrays which contains the attributes. The first array (array["storage"]) contains the attributes which should be stored in e.g. a ldap directory. It can hold various attributes accepted by the backend responsible for saving the attributes, but they are highly dependent of the type of the object.

Furthermore there can be more arrays for specific things. They are created by the modules and you are free to add a new one, if you really need it. Currently the imap module creates such an array (array["imap"]) for the attributes of a user mailbox (quota and acls). The presentation of the attributes to the user is done automagically by the presentation layer. The only things to do for you is to add a new array in the attribute array, store your values there and create an array with the same name in the correct config file (node, user, group or device), which must include the used attribute names.

Using the support classes

After your constructor instantiated the required classes containing the low level functionality and you have (hopefully) stored the reference in a member variable of your class, the member function will be able to access the functions provided by support class. The functions of the support classes are not specified in the way the module member functions are. Instead they encapsulate the functions which are often provided by a library and make them available inside a class container to the modules. Thus your module have to know, which member functions the support class provides, which parameter they accept and what they will return.

That's it! ;-)



Back to PHPGeneral