a7015d7ee3be791c87f4c3913a1fbf3bc9525697
[atutor.git] / docs / documentation / developer / modules.html
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2 <html lang="en">
3 <head>
4         <title>ATutor Module Documentation</title>
5         <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
6         <meta name="author" content="Joel Kronenberg/Greg Gay" />
7         <meta name="description" content="ATutor module developer documentation" />
8 <style type="text/css">
9
10 h1 {
11         background-color: #CCCCCC;
12         padding-left: 20px;
13         padding-right: 20px;
14         margin-bottom: 10px;
15         text-align: right;
16 }
17
18 h2 {
19         background-color: #DDDDDD;
20         padding-left: 10px;
21         margin-bottom: 0px;
22 }
23 h3 {
24         background-color: #EFEFEF;
25         padding-left: 20px;
26         margin-bottom: 0px;
27 }
28 h4  { margin-bottom: 0px; }
29 p   { margin-top: 0px;    }
30 dl  { margin-top: 0px;    }
31 kbd {
32         padding: 0px 1px 0px 1px;
33         border-width: 1px 2px 2px 1px;
34         border-style: solid;
35         border-color: #edd #baa #baa #eed;
36         white-space: pre;
37 }
38 kbd em {
39         font-weight: bold;
40         background-color: #efefef;
41 }
42 blockquote { font-style: italic; }
43 pre.code {
44         background-color: #EEEEFF;
45         padding: 5px;
46         margin-left: 20px;
47         color:#761596;
48     margin-top: 0px;
49 }
50 .top {
51         float: right;
52         color: green;
53         padding-top: 2px;
54         padding-right: 5px;
55 }
56 pre {
57         background-color: #EEEEFF;
58         font-family: Courier, monospace;
59         border-left: 1px solid #761596;
60         padding: 0px 0px 0px 10px;
61         color: #333;
62         margin: 0px 0px 10px 20px;
63         font-size: 88%;
64         line-height: 1.2em;
65 }
66 @media print {
67         h2 {
68                 page-break-after: avoid;
69                 border-bottom: solid 1px black;
70         }
71         h3 {
72                 page-break-after: avoid;
73                 border-bottom: solid 1px black;
74                 width: 75%;
75         }
76         .top {
77                 display: none;
78         }
79
80         pre.code {
81                 page-break-inside: avoid;
82         }
83 }
84 }
85 acronym {
86         cursor: help;
87 }
88 </style>
89
90 </style>
91 </head>
92 <body><h1>Module Development Documentation</h1>
93 <ul>
94 <li><a href="#intro">Introduction</a></li>
95 <li><a href="#structure">Structure</a></li>
96         <ul>
97         <li><a href="#dirname">Directory Name</a></li>
98         <li><a href="#files">Files</a>
99         <ul>
100         <li><a href="#module.xml">module.xml</a></li>
101         <li><a href="#module.php">module.php</a></li>
102         <li><a href="#module.sql">module.sql</a></li>
103         </ul></li>
104         </ul>
105 <li><a href="#installation">Installation</a></li>
106         <ul>
107         <li><a href="#module_install">module_install.php</a></li>
108         <li><a href="#privs">Specifying Privileges</a></li>
109         <li><a href="#data_dir">Creating a Data Directory</a></li>
110         <li><a href="#execute">Executing an SQL FILE</a></li>
111         <li><a href="#errors">Generating Error Messages</a></li>
112         <li><a href="#uninstall">Uninstalling a Module</a></li>
113         </ul>
114
115 <li><a href="#auth">Authentication &amp; Privileges</a></li>
116 <li><a href="#localisation">Localisation</a></li>
117 <li><a href="#config">Configuration Options</a></li>
118 <li><a href="#styles">Custom Style Sheets</a></li>
119 <li><a href="#sidemenu">Side Menu Boxes</a></li>
120 <li><a href="#sublinks">Sublinks for Details View</a></li>
121 <li><a href="#tools">Student Tools</a></li>
122 <li><a href="#groups">Group Tools</a></li>
123 <li><a href="#navigation">Navigation &amp; Hierarchy</a></li>
124 <li><a href="#delete">Course Deletion</a></li>
125 <li><a href="#news">Module News</a></li>
126 <li><a href="#content_format">Custom Content Manipulation</a></li>
127 <li><a href="#backup">Backing-Up and Restoring</a></li>
128 <ul>
129 <li><a href="#directories">Directories</a></li>
130 <li><a href="#database">Database Tables</a></li>
131 </ul>
132 <li><a href="#cron">Running Cron/Scheduling</a></li>
133 </ul>
134
135 <a name="intro"></a>
136 <h2>Introduction</h2>
137         <p>ATutor 1.5.2 introduced the concept of modules, providing developers with a framework to implement additional functionality in a coherent and loosely coupled way.</p>
138
139         <p>The framework defines methods for assigning privileges, backing-up and restoring content, deleting course specific content, and adding side menu blocks,  student tools, course management and administrative tools, as well as public tools and other types of added functionality . </p>
140
141         <p>The intent is to allow for the development and distribution of modules independent of the ongoing development and release of ATutor. The module structure does allow for the creation of modules that run software that is not distributed under the <acronym title="GNU Not Unix">GNU</acronym> General Public License, but distributed separately under their own, perhaps commercial licenses.</p>
142
143         <p>The <em>Hello World</em> example module is included with each ATutor distribution for developers who want to investigate how modules work. The module is found in the <kbd>mods/hello_world</kbd> directory. A copy of the Hello World module works well as a starting point for creating a new module, since it implements (in a simple way) just about all the features found in modules. Also see the files from <a href="http://www.atutor.ca/atutor/modules.php">other modules</a> that operate like you expect your module to operate.</p>
144
145 <a name="structure"></a>
146 <h2>Structure</h2>
147         <p>Modules are stored under ATutor's <kbd>mods</kbd> directory. <em>Core</em> modules are stored in the <kbd>mods/_core</kbd> subdirectory and are made available with every release of ATutor. These modules cannot be disabled by the administrator as they are vital to ATutor's functionality. <em>Standard</em> modules are stored in the <kbd>mods/_standard</kbd> subdirectory and are also made available with every release of ATutor. Standard modules can be disabled by the administrator. <em>Extra</em> modules are stored in the <kbd>mods</kbd> directory and are installed and distributed independently of ATutor. Although the process of developing modules is the same for each type of module, only <em>extra</em> modules can be distributed separately, while <em>core</em> and <em>standard</em> modules are added to the ATutor code repository (i.e. <acronym title="Subversion">SVN</acronym> trunk).</p>
148
149         <p>Whenever a module identifier is needed within code, it should appear in lowercase with spaces converted to underscores.</p>
150
151         <p>The module name, and hence the directory and function names (see below for additional details), must be unique across all possible modules. A module should not be made available if an existing module is already being distributed under that same name. It is up to the module developer to ensure that their module name is unique.</p>
152         <a name="dirname"></a>
153         <h3>Directory Name</h3>
154                 <p>The name given to the directory must be chosen carefully. The name is used to namespace the module's function by prefixing required functions with that directory name. For example, a module named <em>Example Maker</em> should be placed in a directory named <kbd>example_maker</kbd> and the delete function would be named <kbd><em>example_maker</em>_delete()</kbd>.</p>
155         <a name="files"></a>
156         <h3>Files</h3>
157                 <p>The following files should exist under the module's top level directory: <kbd>mods/<em>module_name</em></kbd>.</p>
158
159                 <dl>
160                         <dt><kbd>module.php</kbd></dt>
161                         <dd>This is the main module file which gets included whenever a page is loaded. <em>Required</em>.</dd>
162
163                         <dt><kbd>module.xml</kbd></dt>
164                         <dd>This file is used only for identifying the module for distribution and is only used when viewing a module's details. <em>Required</em>.</dd>
165
166                         <dt><kbd>module_install.php</kbd></dt>
167                         <dd>This file is used when installing the module. <em>Required</em>.</dd>
168
169                         <dt><kbd>module_uninstall.php</kbd></dt>
170                         <dd>This file is used to remove the module from the system. <em>Required for ATutor  1.6.2+</em>.</dd>
171
172                         <dt><kbd>module_backup.php</kbd></dt>
173                         <dd>This file is used when backing-up and restoring course content. <em>Optional</em>.</dd>
174
175                         <dt><kbd>module_delete.php</kbd></dt>
176                         <dd>This file is used when deleting course specific content. <em>Optional</em>.</dd>
177
178
179                         <dt><kbd>module_cron.php</kbd></dt>
180                         <dd>This file is used to run module related commands at specified intervals. <em>Optional</em>.</dd>
181
182                         <dt><kbd>module.sql</kbd></dt>
183                         <dd>This file is used to add tables and/or modify data in the ATutor database. Also used to insert language into the <kbd>language_text</kbd> table. <em>Optional</em>.</dd>
184                 </dl>
185 <a name="module.xml"></a>
186         <h3>The <kbd>module.xml</kbd> File</h3>
187                 <p>The <kbd>module.xml</kbd> file is used for displaying information about the module before it is installed and is useful when distributing the module.</p>
188 <pre>
189 &lt;?xml version="1.0" encoding="ISO-8859-1"?> 
190 &lt;module version="0.1"> 
191     &lt;name lang="en">Example Maker&lt;/name> 
192     &lt;description lang="en">This is an example module that makes examples.&lt;/description> 
193     &lt;maintainers>
194         &lt;maintainer> 
195             &lt;name>ATutor Team&lt;/name> 
196             &lt;email>info@atutor.ca&lt;/email> 
197         &lt;/maintainer>
198         &lt;maintainer> 
199             &lt;name>John Doe&lt;/name> 
200             &lt;email>jd@example.com&lt;/email> 
201         &lt;/maintainer>
202     &lt;/maintainers> 
203     &lt;url>http://www.example.com&lt;/url> 
204     &lt;license>BSD&lt;/license> 
205     &lt;release> 
206         &lt;version>0.2&lt;/version> 
207         &lt;date>2005-08-22&lt;/date> 
208         &lt;state>stable&lt;/state> 
209         &lt;notes>Fixes several bugs in previous version.&lt;/notes> 
210     &lt;/release> 
211 &lt;/module>
212 </pre>
213
214 <a name="module.php"></a>
215         <h3>The <kbd>module.php</kbd> File</h3>
216 <p>The <kbd>module.php</kbd> file is typically used to set permissions, and to link module components into the ATutor navigation elements, as tool icons, navigation tabs, sub navigation menus, or side menu blocks, as administrator, course management, and student tools. It is also used to link tools to  My Start Page, or to various public pages that appear where a user is browsing the system without being logged in. The <kbd>module.php</kbd> file can also be used to run module specific functionality. It runs everytime a module screen is viewed, loading whatever settings it may contain, or running any scripts that may be required by the module.</p>
217 <pre>
218 &lt;?php
219 /*******
220  * doesn't allow this file to be loaded with a browser.
221  */
222 if (!defined('AT_INCLUDE_PATH')) { exit; }
223
224 /******
225  * this file must only be included within a Module obj
226  */
227 if (!isset($this) || (isset($this) && (strtolower(get_class($this)) != 'module'))) { exit(__FILE__ . ' is not a Module'); }
228
229
230 /******
231 * modules sub-content to display on course home detailed view
232 */
233 $this->_list['hello_world'] = array('title_var'=>'hello_world','file'=>'mods/hello_world/sublinks.php');
234
235 /*******
236  * assign the instructor and admin privileges to the constants.
237  */
238 define('AT_PRIV_EXAMPLE_MAKER',       $this->getPrivilege());
239 define('AT_ADMIN_PRIV_EXAMPLE_MAKER', $this->getAdminPrivilege());
240
241 /*******
242  * create a side menu box/stack.
243  */
244 $this->_stacks['example_maker'] = array('title_var'=>'example_maker', 'file'=>'mods/example_maker/side_menu.inc.php');
245 // ** possible alternative: **
246 // $this->addStack('example_maker', array('title_var' => 'example_maker', 'file' => './side_menu.inc.php');
247
248 /*******
249  * if this module is to be made available to students on the Home or Main Navigation.
250  */
251 $_student_tool = 'mods/example_maker/index.php';
252 // ** possible alternative: **
253 // $this->addTool('./index.php');
254
255 /*******
256  * add the admin pages when needed.
257  */
258 if (admin_authenticate(AT_ADMIN_PRIV_EXAMPLE_MAKER, TRUE) || admin_authenticate(AT_ADMIN_PRIV_ADMIN, TRUE)) {
259         $this->_pages[AT_NAV_ADMIN] = array('mods/example_maker/index_admin.php');
260         $this->_pages['mods/example_maker/index_admin.php']['title_var'] = 'example_maker';
261         $this->_pages['mods/example_maker/index_admin.php']['parent']    = AT_NAV_ADMIN;
262 }
263
264 /*******
265  * instructor Manage section:
266  */
267 $this->_pages['mods/example_maker/index_instructor.php']['title_var'] = 'example_maker';
268 $this->_pages['mods/example_maker/index_instructor.php']['parent']   = 'tools/index.php';
269 // ** possible alternative: **
270 // $this->pages['./index_instructor.php']['title_var'] = 'example_maker';
271 // $this->pages['./index_instructor.php']['parent']    = 'tools/index.php';
272
273 /*******
274  * student page.
275  */
276 $this->_pages['mods/example_maker/index.php']['title_var'] = 'example_maker';
277 $this->_pages['mods/example_maker/index.php']['img']       = 'mods/example_maker/example_maker.jpg';
278
279
280 /* public pages */
281 $this->_pages[AT_NAV_PUBLIC] = array('mods/example_maker/index_public.php');
282 $this->_pages['mods/example_maker/index_public.php']['title_var'] = 'example_maker';
283 $this->_pages['mods/example_maker/index_public.php']['parent'] = 'login.php';
284 $this->_pages['login.php']['children'] = array('mods/example_maker/index_public.php');
285
286 /* my start page pages */
287 $this->_pages[AT_NAV_START]  = array('mods/example_maker/index_mystart.php');
288 $this->_pages['mods/example_maker/index_mystart.php']['title_var'] = 'example_maker';
289 $this->_pages['mods/example_maker/index_mystart.php']['parent'] = 'users/index.php';
290 $this->_pages['users/index.php']['children'] = array('mods/example_maker/index_mystart.php');
291 ?>
292 </pre>
293 <a name="module.sql"></a>
294         <h3>The <kbd>module.sql</kbd> File</h3>
295 <p>A very simple <kbd>module.sql</kbd> file such as the following, creates a table for the module, and inserts it's language into the ATutor <kbd>language_text</kbd> table. Module language can then be managed from the ATutor Language Manager. The <kbd>module.sql</kbd> file can contain any number of SQL statements used to add tables or insert data into the ATutor database. </p>
296
297 <pre>
298 # sql file for example maker module
299
300 CREATE TABLE example_maker (
301    `course_id` mediumint(8) unsigned NOT NULL,
302    `value` VARCHAR( 30 ) NOT NULL ,
303    PRIMARY KEY ( `course_id` )
304 );
305
306 INSERT INTO `language_text` VALUES ('en', '_module','example_maker','Example Maker',NOW(),'');
307 INSERT INTO `language_text` VALUES ('en', '_module','AT_ERROR_GOES_HERE','Example Maker Error Message',NOW(),'');
308
309 </pre>
310 <a name="installation"></a>
311 <h2>Installation</h2>
312         <p>The <kbd>module_install.php</kbd> script gets executed during the installation process, using the administrator's Install Module feature. If the script's execution results in <kbd>$msg->containsErrors()</kbd> evaluating to <kbd>TRUE</kbd>, then the errors are displayed and the user is prompted to correct them. The process is then repeated until errors are no longer being generated and the module is installed successfully. Ultimately, it is up to the module to determine the logical steps involved in its installation. For example, it might be better to create the data directories before trying to create any database tables since creating the directory may require several attempts. Typically the flow we describe here should be suitable in most cases.</p>
313
314         <p>Theoretically, the install script's execution is wide-open and does not have to adhere to the process outlined below or make use of any special privileges, provided it generates errors as appropriate.</p>
315
316 <pre># pseudo-code for installing a module:
317 while (there are errors)
318     print the error message
319
320     # inside module_install.php:
321     define the privileges used
322
323     if (create database tables is unsuccessful) then
324         generate an error message
325
326     if (there are no errors AND there is an SQL file) then
327         execute the SQL file
328 end while
329
330 add the module to the system using the defined privileges
331 </pre>
332
333 <a name="module_install"></a>
334         <h3>The <kbd>module_install.php</kbd> File</h3>
335 <p>The <kbd>module_install.php</kbd> file is typically used to run any installation related files, such as an sql file that sets up a database table, or installs the language for the module. The following is an example module_install.php file</p>
336 <pre>
337 &lt;?php
338 /*******
339  * the line below safe-guards this file from being accessed directly from
340  * a web browser. It will only execute if required from within an ATutor script,
341  * in our case the Module::install() method.
342  */
343 if (!defined('AT_INCLUDE_PATH')) { exit; }
344
345 /*******
346  * Note: the many options for these variables are used to decrease confusion.
347  *       TRUE | FALSE | 1 will be the convention.
348  *
349  * $_course_privilege
350  *     specifies the type of instructor privilege this module uses.
351  *     set to empty | FALSE | 0   to disable any privileges.
352  *     set to 1 | AT_PRIV_ADMIN   to use the instructor only privilege.
353  *     set to TRUE | 'new'        to create a privilege specifically for this module:
354  *                                will make this module available as a student privilege.
355  *
356  * $_admin_privilege
357  *    specifies the type of ATutor administrator privilege this module uses.
358  *    set to FALSE | AT_ADMIN_PRIV_ADMIN   to use the super administrator only privilege.
359  *    set to TRUE | 'new'                  to create a privilege specifically for this module:
360  *                                         will make this module available as an administrator privilege.
361  *
362  *
363  * $_cron_interval
364  *    if non-zero specifies in minutes how often the module's cron job should be run.
365  *    set to 0 or not set to disable.
366  */
367 $_course_privilege = TRUE; // possible values: FALSE | AT_PRIV_ADMIN | TRUE
368 $_admin_privilege  = TRUE; // possible values: FALSE | TRUE
369 $_cron_interval    = 35; // run every 30 minutes
370
371
372 /********
373  * the following code is used for creating a module-specific directory.
374  * it generates appropriate error messages to aid in its creation.
375  */
376 $directory = AT_CONTENT_DIR .'example_maker';
377
378 // check if the directory is writeable
379 if (!is_dir($directory) && !@mkdir($directory)) {
380         $msg->addError(array('MODULE_INSTALL', '&lt;li>'.$directory.' does not exist. Please create it.&lt;/li>'));
381 } else if (!is_writable($directory) && @chmod($directory, 0666)) {
382         $msg->addError(array('MODULE_INSTALL', '&lt;li>'.$directory.' is not writeable. On Unix issue the command &lt;kbd>chmod a+rw&lt;/kbd>.&lt;/li>'));
383 }
384
385
386 /******
387  * the following code checks if there are any errors (generated previously)
388  * then uses the SqlUtility to run any database queries it needs, ie. to create
389  * its own tables.
390  */
391 if (!$msg->containsErrors() && file_exists(dirname(__FILE__) . '/module.sql')) {
392         // deal with the SQL file:
393         require(AT_INCLUDE_PATH . 'classes/sqlutility.class.php');
394         $sqlUtility = new SqlUtility();
395
396         /*
397          * the SQL file could be stored anywhere, and named anything, "module.sql" is simply
398          * a convention we're using.
399          */
400         $sqlUtility->queryFromFile(dirname(__FILE__) . '/module.sql', TABLE_PREFIX);
401 }
402
403 ?>
404 </pre>
405
406
407 <a name="privs"></a>
408         <h3>Specifying Privileges</h3>
409                 <p>Privileges control who has access to the course management and administrative sections.</p>
410                 
411                 <p>See the Authentication &amp; Privileges section for additional details using the privileges.</p>
412
413                 <dl>
414                         <dt><kbd>$_course_privilege</kbd></dt>
415                         <dd><p>This variable controls access to a course's management section and can take one of the following values:</p>
416                                 <ul>
417                                         <li><kbd>TRUE</kbd>: To use a custom assignable privilege level. If a custom privilege is created, the module will appear as an option when assigning privileges to enrolled students.</li>
418                                         <li><kbd>AT_PRIV_ADMIN</kbd>: To use the instructor privilege. Only the instructor will be given access to the module's management section.</li>
419                                         <li><kbd>FALSE</kbd>: To disable the privilege if there is no management section for the module.</li>
420                                 </ul>
421                         </dd>
422
423                         <dt><kbd>$_admin_privilege</kbd></dt>
424                         <dd><p>This variable can take one of the following values:</p>
425                                 <ul>
426                                         <li><kbd>TRUE</kbd>: To use a custom assignable privilege level. If a custom privilege is created then the module will appear as an option when assigning privileges to administrators</li>
427                                         <li><kbd>AT_ADMIN_PRIV_ADMIN</kbd>: To use the super administrator privilege. Only the super administrator will be given access to the module's administration section.</li>
428                                 </ul>
429                         </dd>
430                 </dl>
431
432                 <p>Note that creating a privilege is not in itself enough to make the module appear in the Manage section! The hierarchy and navigation path to the management page must be set correctly. See the Navigation &amp; Hierarchy section for additional details.</p>
433 <a name="data_dir"></a>
434         <h3>Creating a Data Directory</h3>
435                 <p>It is best to keep the directory within the <kbd>AT_CONTENT_DIR</kbd> directory as it should already allow the creation of files and directories by the web server. It is then up to the module to create individual course directories as needed.</p>
436 <pre>
437 $directory = AT_CONTENT_DIR .'example_maker';
438
439 // check if the directory is writable
440 if (!is_dir($directory) && !@mkdir($directory)) {
441     $msg->addError(array('MODULE_INSTALL', '&lt;li>'
442                          .$directory
443                          .' does not exist. Please create it.&lt;/li>'));
444 } else if (!is_writable($directory) && @chmod($directory, 0666)) {
445     $msg->addError(array('MODULE_INSTALL', '&lt;li>'
446                          . $directory
447                          .' is not writable.&lt;/li>'));
448 } // else: it was created successfully.
449 </pre>
450 <a name="execute"></a>
451         <h3>Executing an <acronym title="Structured Query Language">SQL</acronym> File</h3>
452                 <p>If the module requires its own database tables or custom language, then it will have to create them itself. The <acronym title="Structured Query Language">SQL</acronym> can either be executed inline using <acronym title="PHP Hypertext Processor">PHP</acronym> database execution directly, or using the <kbd>SQLUtility</kbd> class to execute an external <acronym title="Structured Query Language">SQL</acronym> file. Also see <a href="#module.sql">module.sql</a> for more about creating an SQL file.</p>
453 <pre>
454 if (!$msg->containsErrors() && file_exists(dirname(__FILE__) . '/module.sql')) {
455     // deal with the SQL file:
456     require(AT_INCLUDE_PATH . 'classes/sqlutility.class.php');
457     $sqlUtility = new SqlUtility();
458     $sqlUtility->queryFromFile(dirname(__FILE__) . '/module.sql', TABLE_PREFIX);
459 }
460 </pre>
461 <a name="errors"></a>
462         <h3>Generating Errors Messages</h3>
463                 <p>It is up to the module to generate and check for any errors that occur during the installation. An error message can be generated using <kbd>$msg->addError(array('MODULE_INSTALL', '&lt;li>your error msg goes here&lt;/li>'));</kbd>. Note that the text supplied to the error message is not translated in this case. If the language should be localised, then the appropriate language vairable should replace the text. Something like <kbd>$msg->addError(array('MODULE_INSTALL', 'AT_ERROR_GOES_HERE'));</kbd> . The corresponding SQL INSERT statement should then be found in the <a href="#module.sql">module.sql </a>file, so the language gets added to the <kbd>language_text</kbd> table in ATutor. </p>
464
465                 <p>To check if any errors have been generated, use <kbd>$msg->containsErrors()</kbd> which evaluates to <kbd>TRUE</kbd> if a previous error has been generated.</p>
466                 
467                 <p>See the <a href="guidelines.html#error-feedback-messages">Error and Feedback </a>section of the Developer Documentation for more details about displaying messages.</p>
468
469 <a name="uninstall"></a>
470         <h3>Uninstalling a Module</h3>
471 <p>As of ATutor 1.6.2 a <kbd>module_uninstall.php</kbd> file is required with each module. This file uses the original module.sql file to deletes any database tables, and any language that may have been installed with <kbd>module.sql</kbd>, and removes any directories used by the module, and deletes the module directory itself.</p>
472
473 <pre>
474
475 /*******
476  * module_uninstall.php performs reversion of module_install.php
477  */
478
479 /*******
480  * the line below safe-guards this file from being accessed directly from
481  * a web browser. It will only execute if required from within an ATutor script,
482  * in our case the Module::uninstall() method.
483  */
484 if (!defined('AT_INCLUDE_PATH')) { exit; }
485
486 /********
487  * the following code is used for removing a module-specific directory created in module_install.php.
488  * it generates appropriate error messages to aid in its creation.
489  */
490 $directory = AT_CONTENT_DIR .'example_maker';
491
492 // check if the directory exists
493 if (is_dir($directory)) {
494         require(AT_INCLUDE_PATH.'lib/filemanager.inc.php');
495
496         if (!clr_dir($directory))
497                 $msg->addError(array('MODULE_UNINSTALL', '<li>'.$directory.' can not be removed. Please manually remove it.</li>'));
498 }
499
500 /******
501  * the following code checks if there are any errors (generated previously)
502  * then uses the SqlUtility to run reverted database queries of module.sql, 
503  * ie. "create table" statement in module.sql is run as drop according table.
504  */
505 if (!$msg->containsErrors() && file_exists(dirname(__FILE__) . '/module.sql')) {
506         // deal with the SQL file:
507         require(AT_INCLUDE_PATH . 'classes/sqlutility.class.php');
508         $sqlUtility = new SqlUtility();
509
510         /*
511          * the SQL file could be stored anywhere, and named anything, "module.sql" is simply
512          * a convention we're using.
513          */
514         $sqlUtility->revertQueryFromFile(dirname(__FILE__) . '/module.sql', TABLE_PREFIX);
515 }
516
517 </pre>
518
519 <a name="auth"></a>
520 <h2>Authentication &amp; Privileges</h2>
521         <p>See the <em>Installation: Specifying Privileges</em> section on creating privileges during the installation process.</p>
522
523         <p>Authentication uses constants for the privilege levels. The privileges should be declared in the <kbd>module.php</kbd> file using the <kbd>$this->getPrivilege()</kbd> and <kbd>$this->getAdminPrivilege()</kbd> methods, respectively.</p>
524
525 <pre>
526 define('AT_PRIV_FORUMS',       $this->getPrivilege()      );
527 define('AT_ADMIN_PRIV_FORUMS', $this->getAdminPrivilege() );
528 </pre>
529
530         <p>Once declared, a page can then authenticate against those privileges using either the <kbd>authentication()</kbd> or the <kbd>admin_authenticate()</kbd> functions.</p>
531
532 <pre>
533 define('AT_INCLUDE_PATH', '../include/');
534 require(AT_INCLUDE_PATH.'vitals.inc.php');
535 // authenticate the administrator forums section:
536 admin_authenticate(AT_ADMIN_PRIV_FORUMS);
537 </pre>
538 <a name="localisation"></a>
539 <h2>Localisation</h2>
540         <p>Although a module can be created with all hard-coded language, we recommended you use ATutor's localisation functions. All of ATutor's language is stored in the database, which is then retrieved using the <kbd>_AT()</kbd> function for simple terms and the <kbd>$msg</kbd> object for feedback and error messages.</p>
541
542         <p>Additional details on localising ATutor can be found on the <a href="http://atutor.ca/atutor/docs/translate.php">Thing You Should Know Before Translating</a> and in the <a href="guidelines.html#fn-at">ATutor Developer Documentation</a>.</p>
543
544         <p>Module-specific language should be inserted into the <kbd>language_text</kbd> table during the installation process. The fields in the table are as follows:</p>
545                                           
546         <dl>
547                 <dt><kbd>language_code</kbd></dt>
548                 <dd>The ISO-639-1 language code plus locale.</dd>
549
550                 <dt><kbd>variable</kbd></dt>
551                 <dd>Set to <kbd>_module</kbd> for modules.</dd>
552
553                 <dt><kbd>term</kbd></dt>
554                 <dd>The variable used for retrieving the language.</dd>
555
556                 <dt><kbd>text</kbd></dt>
557                 <dd>The language text.</dd>
558
559                 <dt><kbd>revised_date</kbd></dt>
560                 <dd>Set to <kbd>NOW()</kbd> for modules.</dd>
561
562                 <dt><kbd>context</kbd></dt>
563                 <dd>Short description of the language text.</dd>
564         </dl>
565
566 <p>Each language item should have a corresponding SQL INSERT line in the module.sql file, that gets inserted into the ATutor <kbd>language_text</kbd> table during installation. Do not include a prefix (e.g. "AT_") on the <kbd>language_text</kbd> table name. The installer will detect the right prefix, and automatically prepend it to tables names. Also see the section <a href="#module.sql">module.sql</a> for information about creating a file to install the module's language.</p>
567 <pre>
568 # Insert module specific language:
569 INSERT INTO `language_text` VALUES ('en',
570                                     '_module',
571                                     'example_maker',
572                                     'Example Maker',
573                                      NOW(),
574                                     'the module title');
575 </pre>
576 <p>(Introduced in ATutor 1.5.4) On occassion it may be necessary to modify existing ATutor language to accommodate module functionality that alters the way ATutor itself functions by default. For example, when the payments module is installed the message displayed when a student enrolls in a course that requires a payment, needs to include mention of how to make a payment. In the SQL statement below, the second value (i.e. "variable" in the language_text table) is prefixed with "_c" for custom language. Possible custom language variables are _c_msgs, _c_template, and _c_module.</p>
577
578 <pre>
579 # Insert custom language:
580 INSERT INTO `language_text` VALUES ('en', 
581                                         '_c_msgs',
582                                         'AT_INFOS_EC_PAYMENTS_TURNED_OFF','
583                                         Your request has been made. You will be notifed when your request has been approved. If course fees are pending, they will be listed under the <a href="mods/ecomm/index_mystart.php">Payments</a> tab above, where they can be paid.',
584                                         NOW(),'');
585 </pre>
586
587 <a name="config"></a>
588 <h2>Configuration Options</h2>
589 <p>Module configuration options can be stored in the ATutor <kbd>config</kbd> table, and retrieved using a <kbd>$_config[]</kbd> array variable. All module configuration key/value pairs are automatically loaded from the table into global memory while ATutor is running, and can be accessed from any of the module scripts (or from anywhere is ATutor for that matter). The value for a particular configuration option is retrieved by entering its key into the array. To retrieve a URL for the Example Maker module for example,  you might use <kbd>$_config['example_maker']</kbd>. </p>
590 <p>In the following example of an <kbd>index_admin.php</kbd> file, the form below accepts a URL to an external application used by the Example Maker module (though it could be any value). In this case imagine a third party application has been installed, and the URL to the application is being stored as a configuration option for the example_maker module. When the form is submitted, <kbd>$_POST['uri']</kbd> is inserted into the config table as the value for the <kbd>example_maker</kbd> key. The following is an example module administrator script used to add/edit a config option for the example_maker module.</p>
591
592 <h3>The <kbd>index_admin.php</kbd> File</h3>
593 <pre>
594
595 &lt;?php
596 // make sure user is allowed to see this page (admins only)
597
598 admin_authenticate(AT_ADMIN_PRIV_EXAMPLE_MAKER);
599         
600 if (isset($_POST['submit'])) {
601         // trim whitespace from the value submitted
602         $_POST['uri'] = trim($_POST['uri']);
603
604         // display an error message if the value is empty
605         if (!$_POST['uri']){
606                 $msg->addError('EXAMPLE_MAKER_ADD_EMPTY');
607         }
608         
609         // if no errors, insert the key "example_maker" and value "$_POST['uri']" into the config table 
610         if (!$msg->containsErrors()) {
611                 $_POST['uri'] = $addslashes($_POST['uri']);
612                 $sql = "REPLACE INTO ".TABLE_PREFIX."config VALUES ('example_maker', '$_POST[uri]')";
613                 mysql_query($sql, $db);
614                 $msg->addFeedback('EXAMPLE_MAKER_URL_SAVED');
615
616                 header('Location: '.$_SERVER['PHP_SELF']);
617                 exit;
618         }
619 }
620
621 require (AT_INCLUDE_PATH.'header.inc.php');
622
623 /*******
624  *  First check to see if there is a value for the example_maker key $_config['example_maker']
625  *  If there isn't a value then a missing value message is displayed
626  *  The form below that has a single field for submitting a value, in this case a URL
627  *  If the value exists in the config table, then display it in the text field using  $_config['example_maker']
628  */
629         
630 ?>
631
632 &lt;?php if ($_config['example_maker']): ?>
633         &lt;div class="input-form">
634                 &lt;div class="row">
635                         &lt;p>&lt;?php echo _AT('example_maker_text'); ?>&lt;/p>
636                 &lt;/div>
637         &lt;/div>
638 &lt;?php else: ?>
639         &lt;div class="input-form">
640                 &lt;div class="row">
641                         &lt;p>&lt;?php echo _AT('example_maker_missing_url');  ?>&lt;/p>
642                 &lt;/div>
643         &lt;/div>
644 &lt;?php endif; ?>
645
646 &lt;form action="&lt;?php  $_SERVER['PHP_SELF']; ?>" method="post">
647         &lt;div class="input-form">
648                 &lt;div class="row">
649                         &lt;p>&lt;label for="uri">&lt;?php echo _AT('example_maker_url'); ?>&lt;/label>&lt;/p>
650         
651                         &lt;input type="text" name="uri" value="&lt;?php echo $_config['example_maker']; ?>" id="uri" size="60" style="min-width: 65%;" />
652                 &lt;/div>
653                 &lt;div class="row buttons">
654                         &lt;input type="submit" name="submit" value="&lt;?php echo _AT('save'); ?>"  />
655                 &lt;/div>
656         &lt;/div>
657 &lt;/form>
658 </pre>
659
660 <a name="styles"></a>
661 <h2>Custom Style Sheets</h2>
662         <p>A custom style sheet can be linked into pages by setting <kbd>$_custom_css</kbd> to be the absolute path to the style sheet. This variable must be set on every page that requires that style sheet.</p>
663
664 <pre>
665 define('AT_INCLUDE_PATH', '../../include/');
666 require(AT_INCLUDE_PATH.'vitals.inc.php');
667 // using a custom style sheet:
668 $_custom_css = $_base_path . 'mods/example_maker/module.css';
669 </pre>
670
671 <a name="sidemenu"></a>
672 <h2>Side Menu Boxes</h2>
673         <p>Side menu boxes generally appear in a column at the side of a course (though this layout can be altered by a theme). A module may implement one or more side menu boxes.</p>
674
675         <p>Side menus are specified using the <kbd>$_module_stacks</kbd> array in <kbd>module.php</kbd>. <kbd>$_module_stacks</kbd> have the attributes <kbd>title_var</kbd> (or <kbd>title</kbd>) and <kbd>file</kbd>. The <kbd>title_var</kbd> value is the language key used for that box; the title will be generated by executing <kbd>_AT($title_var)</kbd>. If <kbd>title</kbd> is set instead, a hard-coded title will be used. The <kbd>file</kbd> attribute specifies the absolute path to the side menu's include file.</p>
676
677         <p>The key to the <kbd>$_module_stacks</kbd> should be the name of the module.</p>
678
679 <pre>
680 $_module_stacks['example_maker'] = array('title_var' => 'example_maker', 
681                                          'file' => dirname(__FILE__).'\side_menu.inc.php');
682 </pre>
683
684         <p>Creating a side menu box involves using the <kbd>$savant</kbd> template object and assigning the output of the box to the <kbd>dropdown_contents</kbd> variable.</p>
685
686 <pre>
687 &lt;?php global $savant;
688
689 $box_content = 'This is my side menu box';
690
691 $savant->assign('dropdown_contents', $box_content);
692
693 $savant->assign('title', _AT('example_maker'));
694 $savant->display('include/box.tmpl.php');
695 ?>
696 </pre>
697 <a name="sublinks"></a>
698 <h2>Course tool details using <kbd>sublinks.php</kbd></h2>
699
700 <p>When viewing the detailed course home page, it is possible to display with each module icon, the latest changes, current information for the module, or just a text description of the module. This information is contained in a module sublinks.php file. The following example sublinks.php is free form PHP that gets a list of the three current tests so they can be accessed directly from the course home page along with the link to the Tests & Survey Tool.</p>
701
702 <pre>
703
704 if (!defined('AT_INCLUDE_PATH')) { exit; }
705 global $_base_path, $include_all, $include_one;
706 global $savant;
707 global $db;
708
709 $tests_limit = 3;               //Maximum number of items to display on the course homepage
710
711 $sql = "SELECT test_id, title, UNIX_TIMESTAMP(start_date) AS sd, UNIX_TIMESTAMP(end_date) AS ed FROM ".TABLE_PREFIX."tests WHERE course_id=$_SESSION[course_id] ORDER BY end_date DESC LIMIT $tests_limit";
712 $result = mysql_query($sql, $db);
713
714 if (mysql_num_rows($result) > 0) {
715         while ($row = mysql_fetch_assoc($result)) {
716                 if ( ($row['sd'] <= time()) && ($row['ed'] >= time() ))         
717                         $tests_list[] = array('sub_url' => $_base_path.url_rewrite('tools/test_intro.php?tid=' . $row['test_id']) , 'sub_text' => $row['title']); 
718         }
719         return $tests_list;     
720 } else {
721         return 0;
722 }
723
724 </pre>
725 <a name="tools"></a>
726 <h2>Student Tools</h2>
727         <p>Student tools are pages linked from the home page or the main navigation of courses. A module can only implement one student tool. An instructor controls which student tools are available to a course using the <em>Student Tools</em> section found under <em>Manage</em>.</p>
728
729         <p>The tool main page must be specified using the <kbd>$_student_tool</kbd> variable in the <kbd>module.php</kbd> file. The value of that variable must be the relative path to the file from the ATutor base directory (not the module directory). Example: <kbd>$_student_tool = 'mods/example_maker/index.php';</kbd>.</p>
730
731         <p>For the tool to correctly appear its Navigation &amp; Hierarchy must be defined correctly. If the tool is to have an instructor management section then the <em>parent</em> must be specified as being <kbd>tools/index.php</kbd> and the module must have a non-zero privilege level.</p>
732
733 <a name="groups"></a>
734 <h2>Group Tools</h2>
735         <p>Group tools are student tools that can also be group specific. A module can only implement one group tool. An instructor controls which group tools are available to a each group during the group creation process. Only group tools that are made available to students via the home page or main navigation are available for selection.</p>
736
737         <p>The group tool main page must be specified using the <kbd>$_group_tool</kbd> variable in the <kbd>module.php</kbd> file. The value of that variable must be the relative path to the file from the ATutor base directory (not the module directory). Example: <kbd>$_group_tool = 'mods/example_maker/index.php';</kbd>.</p>
738
739 <a name="navigation"></a>
740 <h2>Navigation &amp; Hierarchy</h2>
741         <p><em>Every</em> page in ATutor must have an entry in the global <kbd>$_pages</kbd> array where the key to the array is the relative path to the file from ATutor's base directory. Module pages are specified using the <kbd>$_module_pages</kbd> array, which are then merged into the <kbd>$_pages</kbd> array when the <kbd>module.php</kbd> file is loaded. The array supports the following attributes:</p>
742
743         <dl>
744                 <dt><kbd>title_var</kbd></dt>
745                 <dd>The language variable to be used with <kbd>_AT()</kbd>.</dd>
746
747                 <dt><kbd>title</kbd></dt>
748                 <dd>The hard-coded version of the language title. If set, overrides the usage of <kbd>_AT(title_var)</kbd>. This version is not language independent.</dd>
749
750                 <dt><kbd>parent</kbd></dt>
751                 <dd>The relative ATutor path to the parent page. Omit for Student Tools.</dd>
752
753                 <dt><kbd>img</kbd></dt>
754                 <dd>The relative ATutor path to the icon to use. Only for Student Tools.</dd>
755
756                 <dt><kbd>children</kbd></dt>
757                 <dd>An array whose values are relative ATutor paths to sub pages.</dd>
758
759                 <dt><kbd>guide</kbd></dt>
760                 <dd>The the section of the handbook that the module page should link to. Not used for modules at this time.</dd>
761         </dl>
762
763         <p>For pages to appear in the instructor Manage section, their <kbd>parent</kbd> field must be set to <kbd>tools/index.php</kbd>.</p>
764
765 <pre>
766 $path = 'mods/example_maker/';
767
768 // the student tool:
769 $_module_pages[$path.'index.php']['title_var'] = 'example_maker';
770 $_module_pages[$path.'index.php']['img']       = $path.'icon.gif';
771 $_module_pages[$path.'index.php']['children']  = array($path.'sub.php', $path.'more.php');
772
773     $_module_pages[$path.'sub.php']['title_var'] = 'sub_page';
774     $_module_pages[$path.'sub.php']['parent']    = $path.'index.php';
775
776     $_module_pages[$path.'more.php']['title_var'] = 'more_page';
777     $_module_pages[$path.'more.php']['parent']    = $path.'index.php';
778
779 // the instructor page:
780 $_module_pages[$path . 'inst_index.php']['title_var'] = 'example_maker';
781 $_module_pages[$path . 'inst_index.php']['parent']    = 'tools/index.php';
782 </pre>
783 <a name="delete"></a>
784 <h2>Course Deletion</h2>
785         <p>When a course is being deleted, or when a back-up is being restored by overriding (i.e. deleting) existing content, a module has to ensure that the content for that course is also deleted. If the module maintains course data directories, then those directories have to either be emptied or deleted. If the module uses database tables for course content, then it has to delete the appropriate entries for that course.</p>
786
787         <p>The function used to delete the course content for that module must be stored in the <kbd>module_delete.php</kbd> file and named <kbd><em>module_name</em>_delete()</kbd>. The delete function takes a single argument which is the ID of the course to delete.</p>
788
789 <pre>
790 &lt;?php
791 function example_maker_delete($course) {
792     global $db;
793
794     // delete directory
795     $path = AT_CONTENT_DIR . 'example_maker/' . $course . '/';
796     clr_dir($path);
797
798     // delete from database
799     $sql = "DELETE 
800             FROM ".TABLE_PREFIX."example_content 
801             WHERE course_id=$course";
802     mysql_query($sql, $db);
803 }
804 ?>
805 </pre>
806
807 <a name="news"></a>
808
809 <h2>Module News <kbd>module_news.php</kbd></h2>
810 <p>In ATutor 2.0 the <strong>Things Current</strong> area of My Start Page was added. It collects changing information from modules and displays it for users so it is one of the first things they see when they login, allowing them to quickly access things like recent forum messages, new news items, or recently added files for download. Things Current scans through all the <kbd>module_news.php</kbd> files, and outputs current activity relevant to each user, and each course. The example below is typical of the type of information that might be output to Things Current, but it can be virtually any data generated by a module. See the Forum module <a href="http://svn.atutor.ca/repos/atutor/trunk/docs/mods/_standard/forums/module_news.php">module_news.php</a> for another more complex example where the latest forum messages are output. </p>
811
812 <pre>
813 &lt;?php
814 /* Typical module_news.php file
815 * Rename the function to match the name of the module. Names of all news functions must be unique
816 * across all modules installed on a system.
817 */
818
819 function mymod_news() {
820         $sql = "SELECT something FROM a table WHERE date &lt; NOW() LIMIT 3";
821         if($result = mysql_query($sql, $db){
822             while($row = mysql_fetch_assoc($result)){
823                 $news[] = $row['something'];
824              }
825         }
826         return $news;
827 }
828
829
830 ?&gt;
831 </pre>
832
833
834
835
836
837
838
839
840
841
842 <a name="content_format"></a>
843 <h2>Custom content manipulation using <kbd>module_format_content.php</kbd></h2>
844
845 <p>ATutor uses the include/lib/output.inc.php file to maniputae how content gets rendered when it is displayed. For example, the [media][/media] get replaced by an appropriate object element inorder to display a given media type. It is possible to extend the output.inc.php rendering of content by creating a <kbd>module_format_content.php</kbd> file containing PHP string replacement functions. </p>
846
847 <p> For example, replace a special tag "[black]The font is black.[/black]" with html "&lt;span style="color: black;"&gt;The font is black.&lt;/span&gt;". Also see the <kbd>module_format_content.php</kbd> for the <a href="http://svn.atutor.ca/repos/atutor/trunk/docs/mods/_standard/flowplayer/module_format_content.php">flowplayer module </a>for a more detailed example of content manipulation.</p>
848
849 <pre>
850 &lt;?php
851 /*******
852  * This file extends the content string manipulation. 
853  * Input parameter: global variable $_input. This variable contains the text input 
854  * of the content being displayed.
855  * Output: $_input. Please make sure to assign the manipulated string back to $_input.
856  */
857
858 /*******
859  * Global input string. DO NOT CHANGE.
860  */
861 global $_input;
862
863 /*******
864  * Example, replace special tag "[black][/black]" with html
865  */
866 $_input = str_replace('[black]','&lt;span style="color: black;"&gt;',$_input);
867 $_input = str_replace('[/black]','&lt;/span&gt;',$_input);
868 ?>
869 </pre>
870 <a name="backup"></a>
871 <h2>Backing-Up and Restoring</h2>
872         <p>It is possible for a module to include its content when a course backup is being created or restored. Backups support database tables with foreign-key constraints as well as course specific directories.</p>
873 <a name="directories"></a>
874         <h3>Directories</h3>
875                 <p>A module can backup as many directories as it requires, all specified using the <kbd>$dirs</kbd> array variable.</p>
876
877                 <p>The example below uses the special <kbd>?</kbd> token as the place holder for the course ID. When the course is backed-up, the question mark will be replaced with the correct course ID. The key to the array is the unique name of the directory to be used inside the backup archive file. The same information to create the backup is also used to restore it, so no additional details are required.</p>
878 <pre>
879 $dirs = array();
880 $dirs['example_maker/'] = AT_CONTENT_DIR . 'example_maker/?/';
881 </pre>
882
883 <a name="database"></a>
884         <h3>Database Tables</h3>
885                 <p>There are two parts to backing-up and restoring a module's database tables. First, the <acronym title="Structured Query Language">SQL</acronym> queries must be specified using the <kbd>$sql</kbd> array variable and then the restore functions must convert the rows so that they can be inserted into the database tables.</p>
886
887                 <p>The example below uses the special <kbd>?</kbd> token as the place holder for the course ID. When the course is backed-up the question-mark will be replaced with the correct course ID. The key to the array is the unique name of the <acronym title="Comma Separated Values">CSV</acronym> file to save in the backup archive, without the extension. The <acronym title="Structured Query Language">SQL</acronym> query itself must only select the fields that will be backed up. If there are foreign key constraints to preserve then the key will have to be retrieved as well so that it can be used when restoring the tables.</p>
888
889 <pre>
890 $sql = array();
891 $sql['example']  = 'SELECT title FROM '.TABLE_PREFIX.'example WHERE course_id=?';
892 </pre>
893
894                 <p>For each key in the <kbd>$sql</kbd> array there must be a function with the same name, but suffixed with <kbd>_convert</kbd>. The <kbd><em>tbl_name</em>_convert()</kbd> function must return the newly transformed row with respect to the version of ATutor that was used to generate the <acronym title="Comma Separated Values">CSV</acronym> file. The function accepts the following arguments:</p>
895
896                 <dl>
897                         <dt><kbd>$row</kbd></dt>
898                         <dd>An array which represents a single row in the <acronym title="Comma Separated Values">CSV</acronym> file.</dd>
899
900                         <dt><kbd>$course_id</kbd></dt>
901                         <dd>The course ID which this content should be associated with.</dd>
902                         
903                         <dt><kbd>$table_id_map</kbd></dt>
904                         <dd>An associative array representing previously restored tables and their new keys. Used to preserve foreign key constraints.</dd>
905
906                         <dt><kbd>$version</kbd></dt>
907                         <dd>The version of ATutor that was used to generate this file.</dd>
908                 </dl>
909
910 <pre>
911 function example_convert($row, $course_id, $table_id_map, $version) {
912     $new_row = array();
913     $new_row[0]  = 0; // auto-increment field
914     $new_row[1]  = $course_id;
915     $new_row[2]  = $row[1]; // the title
916     if (version_compare($version, '1.5.2', '&lt;')) {
917         // this field did not exist prior to 1.5.2
918         $new_row[3] = '';
919     } else {
920         $new_row[3] = $row[2];
921     }
922
923     return $new_row;
924 }
925 </pre>
926 <a name="cron"></a>
927         <h2>Running Cron/Scheduling</h2>
928 <p>The <kbd>module_cron.php</kbd> file can be used to run module related functions at specified intervals. The following example doesn't do much of anything..., but you get the idea.  If cron has been enabled in ATutor, all the module_cron.php files from each module are run at the interval specified when the Cron is setup. See the Cron Setup sections in the Administrator Handbook, and in ATutor in the Administrator's System Preferences.</p>
929
930 <pre>
931 &lt;?php
932 /*******
933  * this function named [module_name]_cron is run by the global cron script at the module's specified
934  * interval.
935  */
936
937 function hello_world_cron() {
938         global $db;
939         debug('yay i am running!');
940 }
941
942 ?>
943 </pre>
944 </body>
945 </html>