Handling common coding tasks
From MGWiki
This chapter has reusable code for common tasks one needs to code when working on a module. The code is not only reusable, but shows best-practice examples for using the moregroupware framework and the libraries we use.
Note: Coding the way described herein reduces your work, avoids errors and makes the code easier to read afterwards!
Contents |
Secure programming
Never use include($var/myinclude.php)! Please use never an include that way, because this way you open a big security hole. Always use something like this: include(ROOTPATH . "/folder/myinclude.php") if possible (constants cannot be ovrridden from the outside).
You should always use $_GET, $_POST, $_SESSION to access (input) data! Never rely on register_globals being on! It is deprecated, insecure and clumsy (so turn it off while developing)!
Always escape data before using it in a database query, to avoid SQL injection problems. ADOdb has some woderful quote methods for this.
The definition.xml file
Modules have some important information associated with them. This information is stored in a file named defintion.xml in the modules root directory.
The type defines whether the module is a core module (type 1, always installed, not shown for selection), currently there is only one core module, the 'general' module. Modules with type 2 are base modules, and are shown on the module selection screen, but are always installed. Type 3 denotes user modules, they are selectable at the user's will.
Example: The defintion.xml of the notes module.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mgw PUBLIC "-//MGW//DTD Moregroupware DTD V1.0//EN" "http://130.89.166.186/mgw/mgw.dtd"> <mgw> <moduleDefinition> <module type="3"> <name>notes</name> <version>0.8</version> <date>2003-02-28</date> <author>Karsten Dambekalns</author> <email>k.dambekalns@fishfarm.de</email> <description>A module for managing notes.</description> <compatibility> <version>0.6.6</version> </compatibility> <updates> </updates> <dependancies> </dependancies> <cache> </cache> </module> </moduleDefinition> </mgw>
Table Definitions
Below is a sample table definition, the "notes_categories" table from the "notes" module.
<table name="notes_categories">
<field name="id" type="I">
<KEY/>
<NOTNULL/>
</field>
<field name="name" type="C" size="100">
<NOTNULL/>
</field>
<field name="description" type="C" size="255">
</field>
<field name="lu_update" type="T">
</field>
<field name="lu_user" type="I">
<NOTNULL/>
<DEFAULT value="0"/>
</field>
<index name="name">
<UNIQUE/>
<col>name</col>
</index>
</table>
table
The only parameter to this is the table name. Note that moregroupware will automatically prepend "mgw_" to the table name, but does not prepend the module name. It is highly recommended to have all table names begin with the name of the module.
field
The name and type are required. For a field type of char, you may also include a maximum size. The valid field types are (from http://phplens.com/lens/adodb/docs-datadict.htm )
C: varchar X: Largest varchar size XL: For Oracle, returns CLOB, otherwise same as 'X' above C2: Multibyte varchar X2: Multibyte varchar (largest size) B: BLOB (binary large object) D: Date (some databases do not support this, and we return a datetime type) T: Datetime or Timestamp L: Integer field suitable for storing booleans (0 or 1) I: Integer (mapped to I4) I1: 1-byte integer I2: 2-byte integer I4: 4-byte integer I8: 8-byte integer F: Floating point number N: Numeric or decimal number
Additional options may be set. These are single-value tags, terminated with / instead of a closing tag. Available options follow. There are more options listed in the adodb data-dict, but those may not be portable.
KEY Primary key field. Sets NOTNULL also. Compound keys are supported. PRIMARY Same as KEY. NOTNULL If field is not null.
You may also add indexes to your tables. Index definitions consist of a list of "col" attributes, and optionally a UNIQUE attribute.
It is also possible to have the schema execute SQL when it is run. However, it is better to include the sql in setup/install.sql
Logging functions
A module author will sooner or later want to log what the module is doing. Most helpful is logging when the module is not doing what it should do, though...
Note: This is not intended to notify the end user of errors or tell them what happens. Have a look at the notification functions for this!
Using the core logging in more.groupware
In the settings module, in the general section, the are a bunch of logging settings. The first one, 'enable_logging' is a systemwide on/off switch for logging. When it is enabled a 'log' link is placed next to 'help' in the mgw submenu. This link opens a window displaying all log data for the page you just requested.
There are also 'log level' settings for each module (except general, which always has all levels enabled). You can turn these all on/off independantly. If a level is off, all log calls for that level are discarded.
Only the admin user can change the logging settings. During normal use, it is probably a good idea to disable logging (disabled by default after setup) to reduce overhead.
You can always log messages with one simple function call. Those messages can have one of five logging levels: MGW_FATAL, MGW_ERROR, MGW_WARNING, MGW_NOTICE, MGW_INFO. Those are predefined constants you can use right away.
In appconfig the logging class is initiated and can be reached anywhere using $GLOBALS['log']. To log stuff, use the function $GLOBALS['log']->message(). It returns a boolean value to indicate the call result.
Example: Firing a log message.
$GLOBALS['log']->message(MGW_INFO, 'This is a info message.', __LINE__, __FILE__);
If you want to force logging even if a particular log level is turned off, you can specify a boolean as fifth parameter. If you set this to true, the message is logged forcibly. But: if logging is disabled generally, the message will be discarded regardless of this setting!
Finally you can specify another optional parameter (a string), to overwrite the module name associated with the message. Ususally this is taken from $myEnv, so you generally shouldn't need this.
Example: Firing an extended log message.
$GLOBALS['log']->message(MGW_NOTICE, 'Forced logging under a manually set module name.',
__LINE__, __FILE__, true, 'test');
Use those calls wherever you see fit, and try to distribute them logically throughout the code. The main purpose is tracking what the code really does to aid debugging, so you might want to log error messages, checkpoints reached, ...
Notification functions
A user usually expects some feedback from a computer. So if the user created a new entry, deleted something or executed some other function, you might want to output a message indicating success or notifying the user about an error that occurred. This is where the notification comes into play, plus it offers a few more nifty features.
Note: This is not intended to create a debugging log of what is happening internally to the module. Have a look at the logging functions for this!
Using notification in more.groupware
In the settings module, in the general section, are a few notification settings. Some are accessible for all users, some are for admin users only. Those are explained elsewhere in the manual.
You can always trigger notification with one simple function call. The framework knows about several types of messages, namely NOTIFY_SCREEN, NOTIFY_MIM and NOTIFY_MAIL. Those are predefined constants you can use right away.
In appconfig the notification class is initiated and can be reached anywhere using $GLOBALS['notify']. Use the function $GLOBALS['notify']->message() to make use of it. (If you noticed the similarity to the logging function - yes, this is intentional.)
The types of message available are the following: NOTIFY_SCREEN spools a message for direct display on the screen, either below the sub menu or as a JavaScript alert() window (depending on user settings). Those messages will be shown upon next page load, for users not currently logged in those messages are stored and displayed at the next login. If used for the current user, this is perfect for displaying results of user actions.
NOTIFY_MIM sends an instant message to the target user's MIM. This can be used to notify users of new items that have been created or, as in todo and calendar, to inform about delegated items and new appointments.
NOTIFY_MAIL can be used to send a message through email. This does not use the webmail2 module, but uses PHP's mail() function. This may cause problems on some systems, and will eventually be changed.
Example: Displaying a success message.
$GLOBALS['notify']->message('Item creation succeeded.', NOTIFY_SCREEN);
The example above is the most simple way of using notification. The message will be displayed for the current user upon next page load (in most cases 'right after the call', when $smarty->display() is called).
to be continued...
Database handling
Handling database connections, fetching results, preparing input values... Remember to read the ADOdb manual as well!
Don't use your own database connections: If you see parameters like $appconf["dbuser"] or other database-related parameters, please don't use them to create your own database connections! Use the auto-generated $conn ADOdb object instead, more on this later.
SQL portability
We want to support more than just MySQL. That's why we use ADOdb. But to make more.groupware portable you need to do more than just a database abstraction layer. You need to write portable SQL statements as well. We learned that the hard way, and you can profit from that experience...
INSERT statements
If you are familiar with MySQL, you may be used to statements such as
INSERT INTO table SET field1=value1, field2=value2
This is not portable and will not work for other databases, such as postgresql. The proper format is
INSERT INTO table VALUES (value1, value2) INSERT INTO table (field1, field2) VALUES (value1, value2)
Both of the above are identical, if table contains two fields which appear in the order indicated by the second example. As this may not necessary be the case, the second version is preferred.
Even better than this is using adodb to generate INSERT statements for you. It requires a couple of extra statements, to prepare the blank record which will be used, but this method guarentees a compatible insert statement. See Generating Update and Insert SQL in the ADOdb manual.
table and field names in lowercase
Some databases allow you to store mixed-case table and field names, while others force these names to be lowercase. This in itself is not a problem, as databases tend to be case insensitive. However, if you expect a field name with uppercase letters and the field name is forced to lowercase, you will have problems accessing the data once it is retrieved.
For this reason, always use all lowercase table and field names.
Date and time handling
Date and time handling. You need to make sure that date and time values are formatted according to the user's preference settings, you need the user to input date and time values, and need to parse input submitted by the user? This section is for you.
Templates with Smarty
Smarty is the template engine used in more.groupware. Read this to get a head start on it and the templates and techniques used in more.groupware.
Session usage
We heavily rely on PHP's session management. This discusses the standard ways of using session variables in moregroupware and introduces you to some predefined variables and objects.
Using session standards in more.groupware
Important: Since we want more.groupware to work with register_globals being set to off, always use $_SESSION to access session variables!
Note: When using $_SESSION (as we do), the use of functions like session_register() or session_is_registered() is forbidden (see PHP manual). Instead one should assign values to the $_SESSION array directly, and use isset() instead of session_is_registered().
There are a few things always available in the $_SESSION array for use in your module. One of those things is $_SESSION['MGW'], which is an instance of the MGW class, and holds information about the current user (e.g. $_SESSION['MGW']->userid) and some more interesting things. There is $_SESSION['MGW']->settings, which holds all of the user settings, there are the users rights, and more.
Note: You do not need to worry about starting the session usually. If you work on a regular module, the session will be started by the more.groupware code for you.
Example: Checking for the ID and rights of the current user.
Here we do two things together. We check for the ID of the current user and access the user rights information.Be aware that those will hopefully change ASAP!
if(($_SESSION["MGW"]->rights["contact"]->modify and $row["owner"] == $_SESSION["MGW"]->userid)
OR ($_SESSION["MGW"]->rights["contact"]->modify_others and $row["owner"] <> $_SESSION["MGW"]->userid)) {
$editlink = "<a href=\"".$_SERVER['PHP_SELF']."?mod=contact&obj=person&view=editform&id=$myid&act=$action&searchterm=$searchterm&list=$list&npage=$curr_page&".SID."\" class=\"edit\">".$icons["edit"]."</a>";
}
else {
$editlink = "";
}
Example: Checking for user settings.
Note the third line, there we check the use_webmail setting.
function linkEmail($email){
if($email!=""){
if($_SESSION["MGW"]->settings["use_webmail"] == 1) $email="<a href=\"../webmail2/index.php?rightframe=".rawurlencode("sendmail.php?to=".$email)."&".SID."\">".$email."</a>";
else $email = "<a href=\"mailto:".$email."\">".$email."</a>";
}
return $email;
}
Passing sessions around in more.groupware
There was a problem with conflicting session names, caused relying on the default session name provided by PHP (see bug #550122 and feature request #564819) when using more than one application using sessions simultaneously.
So we want this to be something other then PHPSESSID, it defaults to MGWSESSID, and is configurable in config.inc.php. To make this work, a few things are needed:
- Before every session_start(), a call to session_name() has to be done. There are only a few places where this is needed. If you are writing a module there should be no need to worry about this.
- The must not use $PHPSESSID (or $MGWSESSID). If this is used, links will stop to work after changing the session name. Use the constant SID instead!
- Templates must use {$SESSION} when creating links directly, and use {$SESSIONNAME} and {$SESSIONID} when creating hidden input fields in forms. There are three Smarty assignments in appconfig.inc, these populate the variables explained in above.
The following examples show typical uses of the variables explained above. The first shows how to create a hidden form field for passing the session. The second show a line from a template creating a simple link. The third shows how to create HTML links in PHP.
Example: Creating a hidden form field for session passing.
<form name="f1" action="{$phpself}" method="POST">
<input type="hidden" name="{$SESSIONNAME}" value="{$SESSIONID}" />
...
</form>
Example: Creating a regular link for session passing.
<a href="index.php?mod=todo&obj=item&view=newform&{$SESSION}" class="new">{$new}</a>
Note the use of the predefined (uppercase) variables in Smarty. Those get defined in appconfig.inc. The following example uses the standard PHP constant SID.
Example: Creating a regular link for session passing in PHP.
// build action urls $detlink = "<a href=\"".$_SERVER['PHP_SELF']."?mod=todo&obj=item&view=details&id=$myid&ty=dto&".SID."\" CLASS=\"edit\">".$icons["details"]."</a>"; $editlink = "<a href=\"".$_SERVER['PHP_SELF']."?mod=todo&obj=item&view=editform&id=$myid&".SID."\" CLASS=\"edit\">".$icons["edit"]."</a>";
Storing per-module information in the session
The todo module uses $_SESSION["todo"][...] to store the state of viewing (current items/archive) and the searchterm used in searching. This makes those persistent, without having to worry about each and every link, and whether it adds those to the URL, or not. Other modules might store more, e.g. the current page being viewed, if paged result sets are used.
Just use the name of your module as the first array index, to avoid name clashes and overwriting other information.
Using the settings module
As soon as you want the users to be able to change the behaviour of a module you need a possibility to manage user preferences. The moregroupware settings module makes this a lot easier. Read on for the gory details...

