Tutorial - Creating a New Module in SyntaxCMS

Tutorial - Creating a New Module in SyntaxCMS

19 Jul 2005

Summary

Creating a module in SyntaxCMS is quite a simple process. Currently there are no restrictions to the internal logic, class architecture, etc. of your module; however, you will find it advantageous to use the core modules as a guide — and use the SyntaxCMS content classes as a foundation. This loose system is designed to maximize ease of porting 3rd party software and existing SyntaxCMS tools.

Details

Getting Started

The SyntaxCMS architecture is alerted to the presence of your module in the config/user.conf.php file. Based on this definition, the SyntaxCMS system will expect to find:

  • A directory that corresponds to the 'name' attribute of the module that you register. So if you create a module name document, the system will pass all requests for that module to scripts within the modules/document/ directory.
  • A PHP script for each of the actions that can be performed on your module. So, e.g., if your module document registers the following capabilities (actions):
    Modules::addCapability('document', 'list');
    Modules::addCapability('document', 'detail');
    
    The SyntaxCMS architecture will expect to be able to locate a list.php and detail.php file inside the module/document/ directory.
  • A templates/ directory is used by the generic module to include list, list (featured), and detail templates for the datatypes used by your module (if any). Specifically the generic module will use the listitem.tpl template for displaying content from your module in a list, listitem-featured.tpl for displaying in the highlights section, and the detail.tpl file for displaying the detail page. If any of these files are missing the generic versions will be substituted.
  • An init/ directory containing register.php which will include_once() any libraries, classes, etc. that are used in the listitem.tpl, listitem-featured.tpl, and detail.tpl. When using include_once() (as opposed to require_once()) the contents of included files is not parsed until requested, so overhead for including all content classes and libraries should be minimal.

Key Concepts

In building modules it is important to bear the following factors in mind.

The $Request variable will contain all the variables in the request for your module. This will include any variables passed via GET or POST.

You should always use the $Request->getVar() method to get variables that are in the $_GET or $_POST arrays. You cannot rely on $_GET or $_POST because your module may be included within a section using the F1CMS::module() mechanism. (See architecture document).

The $Request variable is also where the breadcrumb trail and page title can be customized. See the API Docs for more information about what can be configured by calling methods of the Request object. (Also see examples of other modules)

There are a number of system-wide constants that you will also want to use. In particular, note the SCRIPT_URI (relative URL to current REQUEST — i.e. works with the rewrite rules) and SCRIPT_URL (full URL to current REQUEST — for redirects) constants. You will probably want to set form actions to SCRIPT_URI — if they need to post to the same page, or else you can specify relative paths.

Important: the URL for accessing modules is /content/{module_name}/. That means that if you want to link (or set a form action) to a script in your module you need to do it like this: /content/{module_name}/{action_name/ — where action-name will correspond to the actual PHP script (w/o the .php extension) in your module directory. E.g. I have a list.php script which shows a list of records which can be clicked to show the detail view; a line in my list template might look like this:

<a href="/content/document/detail?id=<?=$Record->getID()?>"><?=$Record->getTitle()?></a>

Creating the Module

Here's an example of how to create a new document module using PHlexDB entity classes.

Adding Module Files

Create the module directory and sub-directories:

$> cd $SITE_ROOT/modules
$> mkdir document 
$> mkdir document/init
$> mkdir document/templates
$> mkdir document/lib

Set Up DB Object

Use DBasis to set up the document object, giving it appropriate attributes. As you specify the 'unique name' for each field bear in mind that this will be used to generate the method names in the next step.

Get PHlexDB Entity Classes

Using the DBasis tool, generate the entity classes.

The header for these classes (that contains datatype constant definition) will become the contents of init/register.php.


<?php
// Include class dependencies
pxdb_import('content.output.pxdb_collection');
pxdb_import('content.output.pxdb_record');
pxdb_import('content.output.pxdb_search');
 
// Set up constants to use in place of datatype numbers
define('DATATYPE_DOCUMENT', 5);    
    
// Register the Document class as the pxdb_record class to use for datatype DATATYPE_DOCUMENT
pxdb::register_class('Document', 'pxdb_record', DATATYPE_DOCUMENT);
 
// Include the classes
$_module_lib_path = realpath(dirname(__FILE__).'/../lib');    
include_once($_module_lib_path . '/DocumentCollection.class.php');
include_once($_module_lib_path . '/Document.class.php');
unset($_module_lib_path);
?>

Then, for this example, copy and paste the records class into a new file lib/Document.class.php:

<?php
class Document extends pxdb_record {
    function Document($data=null)
    {
        parent::pxdb_record($data);
    }
    // other class methods
}
?>

Then, do the same for the DocumentCollection class, lib/DocumentCollection.class.php:


<?php
class DocumentCollection extends pxdb_collection {
    function DocumentCollection()
    {
        parent::pxdb_collection(DATATYPE_DOCUMENT);
    }
    // other class methods
}
?>

Create the Action Scripts

For each "action" or "capability" registered for your module you will need to provide a PHP file. If you make a request for an action that is not registered to your module the system will try to find a fallback provider or use the generic module to handle your request.

Here is a sample list.php which simply returns the 10 most recent documents:

<?php

$MODULE_PATH = dirname(__FILE__);
include($MODULE_PATH . '/init/register.php');

$Records = &new DocumentCollection();
$Records->set_order();
$Records->find();

$Records->execute_limit(10);

while ($Record = &$Records->fetchRecord())
{
    include($MODULE_PATH . '/templates/listitem.tpl');
}

?>

Or more likely, you will have methods of the DocumentCollection class which will do more complex searching:

<?php

$MODULE_PATH = dirname(__FILE__);
include($MODULE_PATH . '/init/register.php');

$Records = &new DocumentCollection();
$Records->findByKeyword($Request->getVar('search_terms'));

while($Record = &$Records->fetchRecord()) {
    include($MODULE_PATH . '/templates/listitem.tpl');
}
?>

Creating the Templates

In the example above we're only using one template, listitem.tpl . That template might well look like this:

<h2><?=$Record->getTitle()?></h2>
<div class="desc"><?=$Record->getDescription()?></div>

The important thing to note here is that it is expected (if you want the generic module to be able to use your templates) that $Record is the current object — and it will be an instance of the Document class in this case.

This template will be included by the generic content module when ever it is attempting to list content of this datatype (document). The pxdb::register_class() call in register.php is what informs the pxdb_collection class what object type to return for records of a given datatype.

Registering the Module

Once the files are in place you can register this module with the system — by adding a section to the system-wide /private/config/user.conf.php file:

Modules::addModule('document', 'Document Library');

What actions can be performed on this module?

Modules::addCapability('document', 'list');

The datatype(s) handled by this module:

Modules::setAttribute('document', 'datatype', DATATYPE_DOCUMENT);

Should content from this module be included in sections grouped by datatype?

Modules::setAttribute('document', 'public_exposure', true);

What label should be used for items of this content type when grouping by module?

Modules::setAttribute('document', 'content_label', 'Documents');

Should content from this module be cached?

Modules::setAttribute('document', 'cache', true);

Try it Out!

Now that your module has been setup and registered with SyntaxCMS, you can give it a whirl. The easiest way to do this is just type the URL into your browser's location field:

http://www.hypotheticalsyntaxsite.com/content/document/list

You may want to pass additional paramters:

http://www.hypotheticalsyntaxsite.com/content/document/list?search_terms=employee+handbook

The other way to do this is to make a request from within a section template:

<?=F1CMS::module(array('module'=>'document', 'action'=>'list', 'search_terms'=>'employee handbook'))?>

Summary

The example above should go along way in demonstrating the basic steps in building a module. Of course this is only one particular type of module — namely a module that represents data of one datatype.

More than likely your module will be more complex than this, but this should illustrate the basic concepts of module building. Use the other core modules as examples of more complex modules.