3. An Introduction to
Module Development
Or: How To Create Modules Without Becoming a
Bitter, Angry Little Man
4. What is a module?
⢠adds functions or capabilities to CMSMS
⢠allows more complexity than a plug-in or
user-deďŹned tag (UDT)
5. Why a module instead
of a plug-in or UDT?
⢠multi-language
⢠install / uninstall / upgrade
⢠trigger / handle events
⢠provide infrastructure and functions for
other modules
6. What else does the
module API offer?
⢠database abstraction (ADODB)
⢠templating system (Smarty)
⢠user-input sanitizing
⢠persistent preferences
⢠access to CMS conďŹgurations
⢠dependencies management
7. Module API, cont.
⢠admin-side permission system
⢠admin-side tools for tabbed interfaces
⢠form utilities
⢠... and more ...
9. Types of modules
⢠plug-ins (e.g., News, Search, FormBuilder)
⢠content-type modules (e.g., Cataloger)
⢠infrastructure module support (e.g.,
CMSMailer, nuSOAP)
⢠administrative (e.g., ModuleManager)
⢠event handlers (e.g., UserWarning*)
⢠ďŹlters (e.g., RegTM*)
* modules written for this presentation, source in appendix
10. Ready? Letâs go!
⢠âHello Worldâ module
⢠create the directory:
$CMSROOT/modules/HelloWorld
⢠create the initial module ďŹle:
HelloWorld.module.php
11. HelloWorld.module.php
<?php
class HelloWorld extends CMSModule
{
function GetName()
{
return 'HelloWorld';
}
function IsPluginModule()
{
return true;
}
function DoAction($action, $id, $params, $returnid=-1)
{
echo 'Hello World!';
}
}
?>
12.
13.
14.
15. Congratulations!
⢠youâre done!
⢠next steps:
⢠publish Hello World on the Developerâs
Forge
⢠check in to svn or git
⢠mention the new module in the Forum
⢠wait for fame and fortune to roll in
16. Uh-oh!
⢠our ďŹrst Bitter Angry Little Man alert!
⢠3 people ďŹled bug reports / feature requests:
⢠âwhy English only?â
⢠âCapitalizing âWorldâ is grammatically
incorrect.â
⢠âI want to use this module in German!â
17. Solving problems
⢠reject issue about `Worldâ capitalization
(âworks for me!â)
⢠solve other problem by using language ďŹles:
⢠create directory HelloWorld/lang
⢠create initial language ďŹle: en_US.php
19. HelloWorld.module.php
<?php
class HelloWorld extends CMSModule
{
function GetName()
{
return 'HelloWorld';
}
function IsPluginModule()
{
return true;
}
function DoAction($action, $id, $params, $returnid=-1)
{
echo $this->Lang('hello_world_string');
}
}
?>
20. Translation
⢠by adding module to Translation Center, it
can be translated into any language
⢠add by setting up externals in svn
⢠instructions at
http://forum.cmsmadesimple.org/index.php/topic,2639.0.html
⢠results in lang/ext directory containing
translation ďŹles
22. Success!
⢠localized version of the module is available
⢠checked into the repository and published
on the Forge
⢠downloaded all over the world
⢠surely, fame and fortune must follow
23. Uh-Oh!
⢠another Bitter Angry Little Man alert!
⢠more bug reports / feature requests:
⢠âI want the message in <h2> tagsâ
⢠âtext should be in <p> tags!â
⢠âI also want âgoodbye worldâ as an
option!â
24. Solutions
⢠output should use a template to allow
different formatting
⢠module should use the DoAction method to
provide alternate functionality
25. Templates
⢠create
CMSROOT/modules/HelloWorld/templates
directory
⢠create your template: hello.tpl
⢠templates use the Smarty markup language
⢠you will learn to read and love the Smarty
manual at http://www.smarty.net/manual/en/
27. Changes to HelloWorld.module.php
function DoAction($action, $id, $params, $returnid=-1)
{
$this->smarty->assign_by_ref('mod',$this);
$this->ProcessTemplate('hello.tpl');
}
28. Where weâre at
⢠output is now templated. Site developer
could edit the hello.tpl to make it output the
message in any way they wanted
⢠we still have only one action - the module
will only display the âhello worldâ string
29. Multiple actions
⢠build out the DoAction method
⢠initially, weâll implement it in-line in the
module
30. DoAction in HelloWorld.module.php
function DoAction($action, $id, $params, $returnid=-1)
{
switch ($action)
{
case 'default':
case 'hello':
{
$this->_do_hello($id, $params, $returnid);
break;
}
case 'goodbye':
{
$this->_do_goodbye($id, $params, $returnid);
break;
}
}
}
31. DoAction
⢠special action: default
⢠weâll see another special action later
⢠other parameters will be explained later
32. Adds to HelloWorld.module.php
function _do_hello($id, $params, $returnid)
{
$this->smarty->assign_by_ref('mod',$this);
echo $this->ProcessTemplate('hello.tpl');
}
function _do_goodbye($id, $params, $returnid)
{
$this->smarty->assign_by_ref('mod',$this);
echo $this->ProcessTemplate('goodbye.tpl');
}
36. Update lang ďŹle
<?php
/* CMSROOT/modules/HelloWorld/lang/en_US.php */
$lang['hello_world_string'] = 'Hello World!';
$lang['goodbye_world_string'] = 'Goodbye you cruel old planet.';
?>
37.
38. Take a breath
⢠what weâve covered so far:
⢠basic module ďŹle
⢠localization
⢠templates
⢠multiple actions
39. Oh Noes!
⢠another Bitter Angry Little Man alert!
⢠12 people ďŹled bug reports:
⢠âI have one installation that accepts the
âhelloâ and âgoodbyeâ actions, and another
thatâs the older version, and Iâm confused.â
⢠âI have different versions on my sites and
canât tell them apart easily.â
40. Solutions
⢠we should have the module report its
version
⢠this opens up a can of worms: module
installation, upgrades, uninstallation
⢠which opens up another can of worms:
separating ďŹles
42. Adds to module
function GetVersion()
{
return '1.0';
}
function Install()
{
$this->Audit( 0,$this->GetName(),$this->Lang('installed',
$this->GetVersion()) );
}
function Uninstall()
{
$this->Audit( 0,$this->GetName(),$this->Lang('uninstalled') );
}
function Upgrade($oldversion, $newversion)
{
$this->Audit( 0,$this->GetName(),$this->Lang('upgraded',
$newversion) );
}
43. update lang ďŹle
<?php
/* CMSROOT/modules/HelloWorld/lang/en_US.php */
$lang['hello_world_string'] = 'Hello World!';
$lang['goodbye_world_string'] = 'Goodbye you cruel old planet.';
$lang['installed'] = 'Hello World version %s installed.';
$lang['uninstalled'] = 'Hello World has been uninstalled.';
$lang['upgraded'] = 'Hello World upgraded to version %s.';
?>
45. Separate ďŹle features
⢠global handle to CMS is deďŹned: $gCms
⢠security tip: test for the global, so people
canât call the ďŹle directly and cause trouble
⢠other variables pre-deďŹned based on
method thatâs been split out
50. Separate actions
⢠DoAction can also be split into multiple ďŹles
⢠this keeps memory footprint smaller, code
organization more logical
⢠each ďŹle is called action.action_name.php
⢠these ďŹles also have special pre-set variables
55. Wha??
⢠whereâs our âHello World?â
⢠action âhelloâ is not the same as action
âdefaultâ
⢠we can either implement action âhelloâ by
creating action.hello.php, or change our
model such that this is the default.
56. Another deep breath
⢠what weâve accomplished:
⢠keeping track of module versions
⢠providing a clean approach to module
upgrades and uninstalls
⢠breaking module into separate ďŹles for
better memory management
⢠set preferences, logged to the admin log
57. Uh-Oh!
⢠Bitter Angry Little Man alert!
⢠16 people ďŹled feature requests:
⢠âI should be able to load in multiple
messages, not just âhelloâ and âgoodbyeâ!â
⢠âYeah, and they should have the option of
displaying randomly according to that new
preference you created!â
58. The database
⢠handle to database available via GetDb()
method
⢠supports ADODB functionality
⢠hides a lot of the complexity for table
creation. see
http://phplens.com/lens/adodb/docs-datadict.htm
59. Adds to method.install.php
// get database handle
$db = &$this->GetDb();
// mysql-speciďŹc, but ignored by other database
$taboptarray = array( 'mysql' => 'TYPE=MyISAM' );
// database-independent table creation
$dict = NewDataDictionary( $db );
$ďŹds = "phrase_id I KEY AUTO,
phrase C(255)";
$sqlarray = $dict->CreateTableSQL( cms_db_preďŹx().
'module_hello', $ďŹds, $taboptarray);
$dict->ExecuteSQLArray($sqlarray);
$db->Execute('insert into '.cms_db_preďŹx().
'module_hello (phrase) values (?)',
array($this->Lang('hello_world_string') ));
60. Donât forget!
⢠implement similar code in the
method.upgrade.php
⢠bump the moduleâs version number to 1.1
⢠and implement code that uses the phrases
from the database
61. Revised function
function _do_hello($id, $params, $returnid)
{
$db = &$this->GetDb();
if ($this->GetPreference('use_random_phrase','y') == 'y')
{
$count = $db->GetOne('select count(phrase) from '.
cms_db_preďŹx().'module_hello');
$rand_line = rand(1,$count) - 1;
$res = $db->SelectLimit('select phrase from '.
cms_db_preďŹx().'module_hello',1,$rand_line);
if ($res && $row=$res->FetchRow())
{
$phrase = $row['phrase'];
}
}
else
{
$phrase = $db->GetOne('select phrase from '.cms_db_preďŹx().
'module_hello where phrase_id=1');
}
$this->smarty->assign('phrase',$phrase);
echo $this->ProcessTemplate('hello.tpl');
}
64. Fear & loathing
⢠Angry bitter little man alert!
⢠23 users complained about not being able
to change the preference
⢠47 users complained about the lack of a
form to add phrases
65. Solution
⢠need to create an admin area for the module
⢠and need to create an input form
66. Creating an admin
⢠HasAdmin() method returns true
⢠our other magic action: defaultadmin
⢠separated ďŹle: action.defaultadmin.php
77. Resources
⢠the Wiki
http://wiki.cmsmadesimple.org/index.php/Developers
⢠Skeleton module
http://dev.cmsmadesimple.org/projects/skeleton
⢠IRC
freenode.net #cms
78. Bitter, angry little man
⢠hitting a moving target; keeping up to date
⢠lots of complaints about the ďŹavors of free
ice cream available
⢠give an inch, theyâll ask for a mile
⢠using module for what it was never meant to
do
79. Preparing for CMSMS 2.0
⢠beware of direct access to API class
variables! e.g., use $this->GetDb(), donât use
$this->db
⢠no more PHP 4.x-isms
⢠callbacks are going away! use the event
system.
⢠lots more â ORM, etc, so beware!
80. Calguyâs cardinal rules
⢠Do not use members of the module object,
use accessors
⢠Split everything up into logical classes
⢠No separate entry points, use actions
⢠Use the permissions model liberally
⢠Use separate ďŹles for actions, tabs, install/
upgrade/uninstall actions
81. Calguyâs rules, cont.
⢠Cache data where practical data may be read
(if it's feasible/expected that the
more than one time in a request).
⢠Use DEFINES or class constants rather than
hardcoded strings
⢠Separate logic from display... use Smarty
⢠Provide the data to Smarty, let Smarty
display it.
82. Calguyâs rules, cont.
⢠Use ADODBâs parameter cleansing stuff,
don't build in all the params yourself.
⢠Keep your code clean.
⢠Get off my lawn, you kids!
88. RegTM Module source
<?php
# extremely simple ďŹlter module; adds registered trademark symbol to your company's name
class RegTM extends CMSModule
{
var $company_name = 'CMS Made Simple';
function GetName()
{
return 'RegTM';
}
function GetFriendlyName()
{
return 'RegTM';
}
function GetVersion()
{
return '0.1';
}
function GetAuthor()
{
return 'SjG';
}
function GetAuthorEmail()
89. RegTM Module source, cont.
function MinimumCMSVersion()
{
return "1.6";
}
function SetParameters()
{
$this->AddEventHandler( 'Core', 'ContentPostRender', true );
}
function DoEvent( $originator, $eventname, &$params )
{
if ($originator == 'Core' && $eventname == 'ContentPostRender')
{
$params['content'] = str_replace($this->company_name,
$this->company_name.'<sup>®</sup>',
$params['content']);
}
}
}
// end
?>
90. User Warning Module source
<?php
# extremely simple event-handler module; sends email to admins giving them warning when a user is deleted.
class UserWarning extends CMSModule
{
function GetName()
{
return 'UserWarning';
}
function GetFriendlyName()
{
return 'UserWarning';
}
function GetVersion()
{
return '0.1';
}
function GetAuthor()
{
return 'SjG';
}
function GetAuthorEmail()
{
return 'sjg@cmsmodules.com';
91. User Warning Module source, cont.
function MinimumCMSVersion()
{
return "1.6";
}
function GetDependencies()
{
return array('CMSMailer'=>'1.73');
}
function SetParameters()
{
$this->AddEventHandler( 'Core', 'DeleteUserPre', true );
}
92. User Warning Module source, cont.
function DoEvent( $originator, $eventname, &$params )
{
if ($originator == 'Core' && $eventname == 'DeleteUserPre')
{
$db = $this->GetDB();
$user = $params['user'];
$result = $db->Execute('select * from '.cms_db_preďŹx().'users where user_id <>?',
array($user->id));
if ($result)
{
$mail =& $this->GetModuleInstance('CMSMailer');
if ($mail != FALSE)
{
$mail->reset();
$mail->SetSubject('Admin User Deleted');
$mail->SetBody('FYI: CMS admin user "'.$user->ďŹrstname.' '.$user->lastname.
'" ('.$user->username.') has been terminated.');
}
while ($row = $result->FetchRow())
{
$mail->AddAddress($row['email'],$row['ďŹrst_name'].' '.$row['last_name']);
}
$sent = $mail->Send();
}
}
}
}
// end