4302fe340e461b824bb7b4adf5c6d929f8414dd5
[atutor.git] / docs / mods / _core / modules / classes / Module.class.php
1 <?php
2 /************************************************************************/
3 /* ATutor                                                                                                                               */
4 /************************************************************************/
5 /* Copyright (c) 2002-2010                                              */
6 /* Inclusive Design Institute                                           */
7 /* http://atutor.ca                                                     */
8 /* This program is free software. You can redistribute it and/or        */
9 /* modify it under the terms of the GNU General Public License          */
10 /* as published by the Free Software Foundation.                        */
11 /************************************************************************/
12 // $Id$
13
14 define('AT_MODULE_STATUS_DISABLED',    1);
15 define('AT_MODULE_STATUS_ENABLED',     2);
16 define('AT_MODULE_STATUS_MISSING',     4);
17 define('AT_MODULE_STATUS_UNINSTALLED', 8); // not in the db
18 define('AT_MODULE_STATUS_PARTIALLY_UNINSTALLED', 16); // not in the db
19
20 define('AT_MODULE_TYPE_CORE',     1);
21 define('AT_MODULE_TYPE_STANDARD', 2);
22 define('AT_MODULE_TYPE_EXTRA',    4);
23
24 define('AT_MODULE_DIR_CORE',     '_core');
25 define('AT_MODULE_DIR_STANDARD', '_standard');
26
27 define('AT_MODULE_PATH', realpath(AT_INCLUDE_PATH.'../mods') . DIRECTORY_SEPARATOR);
28
29 /**
30 * ModuleFactory //
31
32 * @access       public
33 * @author       Joel Kronenberg
34 * @package      Module
35 */
36 class ModuleFactory {
37         // private
38         var $_modules = NULL; // array of module refs
39
40         function ModuleFactory($auto_load = FALSE) {
41                 global $db;
42
43                 /* snippit to use when extending Module classes:
44                 $sql    = "SELECT dir_name, privilege, admin_privilege, status FROM ". TABLE_PREFIX . "modules WHERE status=".AT_MODULE_STATUS_ENABLED;
45                 $result = mysql_query($sql, $db);
46                 $row = mysql_fetch_assoc($result);
47                 require(AT_MODULE_PATH . $row['dir_name'].'/module.php');
48                 $module = new PropertiesModule($row);
49                 ***/
50
51                 $this->_modules = array();
52
53                 if ($auto_load == TRUE) {
54                         // initialise enabled modules
55                         $sql    = "SELECT dir_name, privilege, admin_privilege, status, cron_interval, cron_last_run FROM ". TABLE_PREFIX . "modules WHERE status=".AT_MODULE_STATUS_ENABLED;
56                         $result = mysql_query($sql, $db);
57                         while($row = mysql_fetch_assoc($result)) {
58                                 $module = new Module($row);
59                                 $this->_modules[$row['dir_name']] = $module;
60                                 $module->load();
61                         }
62                 }
63         }
64
65         // public
66         // status := enabled | disabled | uninstalled | missing
67         // type  := core | standard | extra
68         // sort  := true | false (by name only)
69         // the results of this method are not cached. call sparingly.
70         function getModules($status, $type = 0, $sort = FALSE) {
71                 global $db;
72
73                 $modules     = array();
74                 $all_modules = array();
75
76                 if ($type == 0) {
77                         $type = AT_MODULE_TYPE_CORE | AT_MODULE_TYPE_STANDARD | AT_MODULE_TYPE_EXTRA;
78                 }
79
80                 $sql    = "SELECT dir_name, privilege, admin_privilege, status, cron_interval, cron_last_run FROM ". TABLE_PREFIX . "modules";
81                 $result = mysql_query($sql, $db);
82                 
83                 while($row = mysql_fetch_assoc($result)) {
84                         if (!isset($this->_modules[$row['dir_name']])) {
85                                 $module = new Module($row);
86                         } else {
87                                 $module = $this->_modules[$row['dir_name']];
88                         }
89                         $all_modules[$row['dir_name']] = $module;
90                 }
91
92                 // small performance addition:
93                 if ($status & AT_MODULE_STATUS_UNINSTALLED) {
94                         $dir = opendir(AT_MODULE_PATH);
95                         while (false !== ($dir_name = readdir($dir))) {
96                                 if (($dir_name == '.') 
97                                         || ($dir_name == '..') 
98                                         || ($dir_name == '.svn') 
99                                         || ($dir_name == AT_MODULE_DIR_CORE) 
100                                         || ($dir_name == AT_MODULE_DIR_STANDARD)) {
101                                         continue;
102                                 }
103
104                                 if (is_dir(AT_MODULE_PATH . $dir_name) && !isset($all_modules[$dir_name])) {
105                                         $module = new Module($dir_name);
106                                         $all_modules[$dir_name] = $module;
107                                 }
108                         }
109                         closedir($dir);
110                 }
111
112                 $keys = array_keys($all_modules);
113                 foreach ($keys as $dir_name) {
114                         $module =$all_modules[$dir_name];
115                         if ($module->checkStatus($status) && $module->checkType($type)) {
116                                 $modules[$dir_name] = $module;
117                         }
118                 }
119
120                 if ($sort) {
121                         uasort($modules, array($this, 'compare'));
122                 }
123                 
124                 return $modules;
125         }
126
127         // public.
128         function & getModule($module_dir) {
129                 if (!isset($this->_modules[$module_dir])) {
130                         global $db;
131                         $sql    = "SELECT dir_name, privilege, admin_privilege, status FROM ". TABLE_PREFIX . "modules WHERE dir_name='$module_dir'";
132                         $result = mysql_query($sql, $db);
133                         if ($row = mysql_fetch_assoc($result)) {
134                                 $module = new Module($row);
135                         } else {
136                                 $module = new Module($module_dir);
137                         }
138                         $this->_modules[$module_dir] =& $module;
139                 }
140                 return $this->_modules[$module_dir];
141         }
142
143         // private
144         // used for sorting modules
145         function compare($a, $b) {
146                 return strnatcasecmp($a->getName(), $b->getName());
147         }
148 }
149
150 /**
151 * Module
152
153 * @access       public
154 * @author       Joel Kronenberg
155 * @package      Module
156 */
157 class Module {
158         // private
159         var $_moduleObj;
160         var $_directoryName;
161         var $_status; // core|enabled|disabled
162         var $_privilege; // priv bit(s) | 0 (in dec form)
163         var $_admin_privilege; // priv bit(s) | 0 (in dec form)
164         var $_display_defaults; // bit(s)
165         var $_pages;
166         var $_type; // core, standard, extra
167         var $_properties; // array from xml
168         var $_cron_interval; // cron interval
169         var $_cron_last_run; // cron last run date stamp
170         var $_content_tools; // content tool icons on "edit content" page
171
172         // constructor
173         function Module($row) {
174                 global $_content_tools;
175                 
176                 if (is_array($row)) {
177                         $this->_directoryName   = $row['dir_name'];
178                         $this->_status          = $row['status'];
179                         $this->_privilege       = $row['privilege'];
180                         $this->_admin_privilege = $row['admin_privilege'];
181                         $this->_display_defaults= isset($row['display_defaults']) ? $row['display_defaults'] : 0;
182                         $this->_cron_interval   = $row['cron_interval'];
183                         $this->_cron_last_run   = $row['cron_last_run'];
184
185                         if (strpos($row['dir_name'], AT_MODULE_DIR_CORE) === 0) {
186                                 $this->_type = AT_MODULE_TYPE_CORE;
187                         } else if (strpos($row['dir_name'], AT_MODULE_DIR_STANDARD) === 0) {
188                                 $this->_type = AT_MODULE_TYPE_STANDARD;
189                         } else {
190                                 $this->_type = AT_MODULE_TYPE_EXTRA;
191                         }
192                 } else {
193                         $this->_directoryName   = $row;
194                         $this->_status          = AT_MODULE_STATUS_UNINSTALLED;
195                         $this->_privilege       = 0;
196                         $this->_admin_privilege = 0;
197                         $this->_display_defaults= 0;
198                         $this->_type            = AT_MODULE_TYPE_EXTRA; // standard/core are installed by default
199                 }
200                 $this->_content_tools   = array();
201         }
202
203         // statuses
204         function checkStatus($status) { return (bool) ($status & $this->_status); }
205         function isPartiallyUninstalled()  { return ($this->_status == AT_MODULE_STATUS_PARTIALLY_UNINSTALLED) ? true : false; }
206         function isUninstalled()  { return ($this->_status == AT_MODULE_STATUS_UNINSTALLED) ? true : false; }
207         function isEnabled()      { return ($this->_status == AT_MODULE_STATUS_ENABLED)     ? true : false; }
208         function isDisabled()     { return ($this->_status == AT_MODULE_STATUS_DISABLED)    ? true : false; }
209         function isMissing()      { return ($this->_status == AT_MODULE_STATUS_MISSING)     ? true : false; }
210
211         // types
212         function checkType($type) { return (bool) ($type & $this->_type); }
213         function isCore()     { return ($this->_type == AT_MODULE_TYPE_CORE)     ? true : false; }
214         function isStandard() { return ($this->_type == AT_MODULE_TYPE_STANDARD) ? true : false; }
215         function isExtra()    { return ($this->_type == AT_MODULE_TYPE_EXTRA)    ? true : false; }
216
217         // privileges
218         function getPrivilege()      { return $this->_privilege;       }
219         function getAdminPrivilege() { return $this->_admin_privilege; }
220
221         function load() {
222                 if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module.php')) {
223                         global $_modules, $_pages, $_stacks, $_list, $_tool, $_content_tools, $_callbacks;  // $_list is for sublinks on "detail view"
224
225                         require(AT_MODULE_PATH . $this->_directoryName.'/module.php');
226
227                         if (isset($this->_pages)) {
228                                 $_pages = array_merge_recursive((array) $_pages, $this->_pages);
229                         }
230
231                         //side menu items
232                         if (isset($this->_stacks)) {
233                                 $count = 0;
234                                 $_stacks = array_merge((array)$_stacks, $this->_stacks);
235                         }
236
237                         // sublinks on "detail view"
238                         if(isset($this->_list)) {
239                                 $_list = array_merge((array)$_list, $this->_list);                      
240                         }
241
242                         if(isset($this->_content_tools)) {
243                                 $_content_tools = array_merge((array)$_content_tools, $this->_content_tools);                   
244                         }
245                         
246                         if(isset($this->_callbacks)) {
247                                 $_callbacks = array_merge((array)$_callbacks, $this->_callbacks);                       
248                         }
249                         
250                         //TODO***********BOLOGNA***********REMOVE ME***********/
251                         //tool manager (content editing)
252                         if(isset($this->_tool)) {
253                                 $_tool = array_merge((array)$_tool, $this->_tool);
254                         }
255
256                         //student tools
257                         if (isset($_student_tool)) {
258                                 $this->_student_tool =& $_student_tool;
259                                 $_modules[] = $this->_student_tool;
260                         }
261
262                         //group tools
263                         if (isset($_group_tool)) {
264                                 $this->_group_tool =& $_group_tool;
265                         }
266                 }                                       
267         }
268
269         // private
270         function _initModuleProperties() {
271                 if (!isset($this->_properties)) {
272                         require_once(dirname(__FILE__) . '/ModuleParser.class.php');
273                         $moduleParser = new ModuleParser();
274                         $moduleParser->parse(@file_get_contents(AT_MODULE_PATH . $this->_directoryName.'/module.xml'));
275                         if ($moduleParser->rows[0]) {
276                                 $this->_properties = $moduleParser->rows[0];
277                         } else {
278                                 $this->_properties = array();
279                                 $this->setIsMissing(); // the xml file may not be found -> the dir may be missing.
280                         }
281                 }
282         }
283
284         /**
285         * Get the properties of this module as found in the module.xml file
286         * @access  public
287         * @param   array $properties_list       list of property names
288         * @return  array associative array of property/value pairs
289         * @author  Joel Kronenberg
290         */
291         function getProperties($properties_list) {
292                 $this->_initModuleProperties();
293
294                 if (!$this->_properties) {
295                         return;
296                 }
297                 $properties_list = array_flip($properties_list);
298                 foreach ($properties_list as $property => $garbage) {
299                         $properties_list[$property] = $this->_properties[$property];
300                 }
301                 return $properties_list;
302         }
303         /**
304         * Get a single property as found in the module.xml file
305         * @access  public
306         * @param   string $property     name of the property to return
307         * @return  string the value of the property 
308         * @author  Joel Kronenberg
309         */
310         function getProperty($property) {
311                 $this->_initModuleProperties();
312
313                 if (!$this->_properties) {
314                         return;
315                 }
316
317                 return $this->_properties[$property];
318         }
319
320         function getCronInterval() {
321                 return $this->_cron_interval;
322
323         }
324
325         function getName() {
326                 if ($this->isUninstalled()) {
327                         $name = $this->getProperty('name');
328                         return current($name);
329                 }
330                 return _AT(basename($this->_directoryName));
331         }
332
333         function getDescription($lang = 'en') {
334                 $this->_initModuleProperties();
335
336                 if (!$this->_properties) {
337                         return;
338                 }
339
340                 if (isset($this->_properties['description'][$lang])) {
341                         return $this->_properties['description'][$lang];
342                 }
343                 $description = current($this->_properties['description']);
344                 return $description;
345         }
346
347         function getChildPage($page) {
348                 if (!is_array($this->_pages)) {
349                         return;
350                 }
351                 foreach ($this->_pages as $tmp_page => $item) {
352                         if (!empty($item['parent']) && $item['parent'] == $page) {
353                                 return $tmp_page;
354                         }
355                 }
356         }
357
358         /**
359         * Checks whether or not this module can be backed-up
360         * @access  public
361         * @return  boolean true if this module can be backed-up, false otherwise
362         * @author  Joel Kronenberg
363         */
364         function isBackupable() {
365                 return is_file(AT_MODULE_PATH . $this->_directoryName.'/module_backup.php');
366         }
367
368         function createGroup($group_id) {
369                 if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php')) {
370                         require_once(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php');
371                         $fn_name = basename($this->_directoryName) .'_create_group';
372                         $fn_name($group_id);
373                 }
374         }
375
376         function deleteGroup($group_id) {
377                 $fn_name = basename($this->_directoryName) .'_delete_group';
378
379                 if (!function_exists($fn_name) && is_file(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php')) {
380                         require_once(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php');
381                 } 
382                 if (function_exists($fn_name)) {
383                         $fn_name($group_id);
384                 }
385         }
386
387         function getGroupTool() {
388                 if (!isset($this->_group_tool)) {
389                         return;
390                 } 
391
392                 return $this->_group_tool;
393         }
394
395         function isGroupable() {
396                 return is_file(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php');
397         }
398
399         /**
400         * Backup this module for a given course
401         * @access  public
402         * @param   int          $course_id      ID of the course to backup
403         * @param   object       $zipfile        a reference to a zipfile object
404         * @author  Joel Kronenberg
405         */
406         function backup($course_id, &$zipfile) {
407                 static $CSVExport;
408
409                 if (!isset($CSVExport)) {
410                         require_once(AT_INCLUDE_PATH . 'classes/CSVExport.class.php');
411                         $CSVExport = new CSVExport();
412                 }
413                 $now = time();
414
415                 if ($this->isBackupable()) {
416                         require(AT_MODULE_PATH . $this->_directoryName . '/module_backup.php');
417                         if (isset($sql)) {
418                                 foreach ($sql as $file_name => $table_sql) {
419                                         $content = $CSVExport->export($table_sql, $course_id);
420                                         if ($content) {
421                                                 $zipfile->add_file($content, $file_name . '.csv', $now);
422                                         }
423                                 }
424                         }
425
426                         if (isset($dirs)) {
427                                 foreach ($dirs as $dir => $path) {
428                                         $path = str_replace('?', $course_id, $path);
429
430                                         $zipfile->add_dir($path , $dir);
431                                 }
432                         }
433                 }
434         }
435
436         /**
437         * Restores this module into the given course
438         * @access  public
439         * @param   int          $course_id      ID of the course to restore into
440         * @param   string       $version        version number of the ATutor installation used to make this backup
441         * @param   string       $import_dir     the path to the import directory
442         * @author  Joel Kronenberg
443         */
444         function restore($course_id, $version, $import_dir) {
445                 static $CSVImport;
446                 if (!file_exists(AT_MODULE_PATH . $this->_directoryName.'/module_backup.php')) {
447                         return;
448                 }
449
450                 if (!isset($CSVImport)) {
451                         require_once(AT_INCLUDE_PATH . 'classes/CSVImport.class.php');
452                         $CSVImport = new CSVImport();
453                 }
454
455                 require(AT_MODULE_PATH . $this->_directoryName.'/module_backup.php');
456
457                 if (isset($sql)) {
458                         foreach ($sql as $table_name => $table_sql) {
459                                 $CSVImport->import($table_name, $import_dir, $course_id, $version);
460                         }
461                 }
462                 if ($this->_directoryName == '_core/content')
463                 {
464                         if (version_compare($version, '1.6.4', '<')) {
465                                 $this->convertContent164($course_id);
466                         }
467                 }
468                 
469                 if (isset($dirs)) {
470                         foreach ($dirs as $src => $dest) {
471                                 $dest = str_replace('?', $course_id, $dest);
472                                 copys($import_dir.$src, $dest);
473                         }
474                 }
475         }
476
477         /**
478         * Delete this module's course content. If $groups is specified then it will
479         * delete all content for the groups specified.
480         * @access  public
481         * @param   int   $course_id     ID of the course to delete
482         * @param   array $groups    Array of groups to delete
483         * @author  Joel Kronenberg
484         */
485         function delete($course_id, $groups) {
486                 if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module_delete.php')) {
487                         require(AT_MODULE_PATH . $this->_directoryName.'/module_delete.php');
488                         if (function_exists(basename($this->_directoryName).'_delete')) {
489                                 $fnctn = basename($this->_directoryName).'_delete';
490                                 $fnctn($course_id);
491                         }
492                 }
493                 if ($groups) {
494                         foreach ($groups as $group_id) {
495                                 $this->deleteGroup($group_id);
496                         }
497                 }
498         }
499
500         /**
501         * Enables the installed module
502         * @access  public
503         * @author  Joel Kronenberg
504         */
505         function enable() {
506                 global $db;
507
508                 $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_ENABLED.' WHERE dir_name="'.$this->_directoryName.'"';
509                 $result = mysql_query($sql, $db);
510         }
511
512         /**
513         * Sets the status to missing if the module dir doesn't exist.
514         * @access  public
515         * @param   boolean $force whether or not to force the module to be missing (used for bundled extra modules upon upgrade)
516         * @author  Joel Kronenberg
517         */
518         function setIsMissing($force = false) {
519                 global $db;
520                 // if the directory doesn't exist then set the status to MISSING
521                 if ($force || !is_dir(AT_MODULE_PATH . $this->_directoryName)) {
522                         $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_MISSING.' WHERE dir_name="'.$this->_directoryName.'"';
523                         $result = mysql_query($sql, $db);
524                 }
525         }
526
527         /**
528         * Disables the installed module
529         * @access  public
530         * @author  Joel Kronenberg
531         */
532         function disable() {
533                 global $db;
534
535                 // remove any privileges admins, students
536                 if ($this->_privilege > 1) {
537                         $sql = 'UPDATE '. TABLE_PREFIX . 'course_enrollment SET `privileges`=`privileges`-'.$this->_privilege.' WHERE `privileges` > 1 AND (`privileges` & '.$this->_privilege.')<>0';
538                         $result = mysql_query($sql, $db);
539                 }
540
541                 if ($this->_admin_privilege > 1) {
542                         $sql = 'UPDATE '. TABLE_PREFIX . 'admins SET `privileges`=`privileges`-'.$this->_admin_privilege.' WHERE `privileges` > 1 AND (`privileges` & '.$this->_admin_privilege.')<>0';
543                         $result = mysql_query($sql, $db);
544                 }
545
546                 $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_DISABLED.' WHERE dir_name="'.$this->_directoryName.'"';
547                 $result = mysql_query($sql, $db);
548
549                 if (function_exists(basename($this->_directoryName).'_disable')) {
550                         $fn_name = basename($this->_directoryName).'_disable';
551                         $fn_name();
552                 }
553         }
554
555         /**
556         * Installs the module
557         * @access  public
558         * @author  Joel Kronenberg
559         */
560         function install() {
561                 global $msg;
562
563                 // should check if this module is already installed...
564
565                 if (file_exists(AT_MODULE_PATH . $this->_directoryName . '/module_install.php')) {
566                         require(AT_MODULE_PATH . $this->_directoryName . '/module_install.php');
567                 }
568
569                 if (!$msg->containsErrors()) {
570                         global $db;
571
572                         $sql = "SELECT MAX(`privilege`) AS `privilege`, MAX(admin_privilege) AS admin_privilege FROM ".TABLE_PREFIX."modules";
573                         $result = mysql_query($sql, $db);
574                         $row = mysql_fetch_assoc($result);
575
576                         if (($_course_privilege === TRUE) || ((string) $_course_privilege == 'new')) {
577                                 $priv = $row['privilege'] * 2;
578                         } else if ($_course_privilege == AT_PRIV_ADMIN) {
579                                 $priv = AT_PRIV_ADMIN;
580                         } else {
581                                 $priv = 0;
582                         }
583
584                         if (($_admin_privilege === TRUE) || ((string) $_admin_privilege == 'new')) {
585                                 $admin_priv = $row['admin_privilege'] * 2;
586                         } else {
587                                 $admin_priv = AT_ADMIN_PRIV_ADMIN;
588                         }
589
590                         if (isset($_cron_interval)) {
591                                 $_cron_interval = abs($_cron_interval);
592                         } else {
593                                 $_cron_interval = 0;
594                         }
595
596                         $sql = 'INSERT INTO '. TABLE_PREFIX . 'modules VALUES ("'.$this->_directoryName.'", '.AT_MODULE_STATUS_DISABLED.', '.$priv.', '.$admin_priv.', '.$_cron_interval.', 0)';
597                         mysql_query($sql, $db);
598                         if (mysql_affected_rows($db) != 1) {
599                                 // in case this module has to be re-installed (because it was Missing)
600                                 $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_DISABLED.' WHERE dir_name="'.$this->_directoryName.'"';
601                                 mysql_query($sql, $db);
602                         }
603                 }
604         }
605
606         /**
607         * Uninstalls the module
608         * @access  public
609         * @author  Cindy Qi Li
610         */
611         function uninstall($del_data='') {
612                 global $msg;
613
614                 if (file_exists(AT_MODULE_PATH . $this->_directoryName . '/module_uninstall.php') && $del_data == 1) 
615                 {
616                         require(AT_MODULE_PATH . $this->_directoryName . '/module_uninstall.php');
617                 }
618
619                 if (!$msg->containsErrors()) 
620                 {
621                         require_once(AT_INCLUDE_PATH.'../mods/_core/file_manager/filemanager.inc.php');
622                                                 
623                         if (!clr_dir(AT_MODULE_PATH . $this->_directoryName))
624                                 $msg->addError(array('MODULE_UNINSTALL', '<li>'.AT_MODULE_PATH . $this->_directoryName.' can not be removed. Please manually remove it.</li>'));
625                 }
626                 
627                 if (!$msg->containsErrors()) 
628                 {
629                         global $db;
630
631                         $sql = "DELETE FROM ". TABLE_PREFIX . "modules WHERE dir_name = '".$this->_directoryName."'";
632                         mysql_query($sql, $db);
633                 }
634
635                 if ($msg->containsErrors()) 
636                 {
637                         global $db;
638
639                         $sql = "UPDATE ". TABLE_PREFIX . "modules SET status=".AT_MODULE_STATUS_PARTIALLY_UNINSTALLED." WHERE dir_name='".$this->_directoryName."'";
640                         mysql_query($sql, $db);
641                 }
642         }
643
644         function getStudentTools() {
645                 if (!isset($this->_student_tool)) {
646                         return FALSE;
647                 } 
648
649                 return $this->_student_tool;
650         }
651
652
653         function runCron() {
654                 if ( ($this->_cron_last_run + ($this->_cron_interval * 60)) < time()) {
655                         if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module_cron.php')) {
656                                 require(AT_MODULE_PATH . $this->_directoryName.'/module_cron.php');
657                                 if (function_exists(basename($this->_directoryName).'_cron')) {
658                                         $fnctn = basename($this->_directoryName).'_cron';
659                                         $fnctn();
660                                 }
661                         }
662                         $this->updateCronLastRun();
663                 }
664         }
665
666         // i'm private! update the last time the cron was run
667         function updateCronLastRun() {
668                 global $db;
669
670                 $sql = "UPDATE ".TABLE_PREFIX."modules SET cron_last_run=".time()." WHERE dir_name='$this->_directoryName'";
671                 mysql_query($sql, $db);
672
673         }
674
675         /**
676          * Get the latest news from the Module. 
677          * @access      public
678          * @author      Harris Wong 
679          * @date        Feb 25, 2010
680          */
681         function getNews(){
682                 global $msg, $enrolled_courses, $db;
683
684                 if (!isset($enrolled_courses)){
685                         $sql = 'SELECT E.approved, E.last_cid, C.* FROM AT_course_enrollment E, AT_courses C WHERE E.member_id='.$_SESSION['member_id'].' AND E.course_id=C.course_id ORDER BY C.title';
686                         $result = mysql_query($sql, $db);
687                         if ($result) {
688                                 while($row = mysql_fetch_assoc($result)){
689                                         $enrolled_courses = $enrolled_courses . $row['course_id'] . ', ';
690                                 }
691                                 $enrolled_courses = substr($enrolled_courses, 0, -2); 
692
693                                 if ($enrolled_courses != ''){
694                                         $enrolled_courses = '(' . $enrolled_courses . ')';
695                                 } 
696                         }
697                 }
698
699                 if (file_exists(AT_MODULE_PATH . $this->_directoryName . '/module_news.php')) {
700                         require(AT_MODULE_PATH . $this->_directoryName . '/module_news.php');
701                         if (function_exists(basename($this->_directoryName).'_news')) {
702                                 $fnctn = basename($this->_directoryName).'_news';
703                                 return $fnctn($course_id);
704                         }
705                 }
706         }
707         
708         /**
709          * Get the output that this module wants to add onto content page. 
710          * @access      public
711          * @author      Cindy Li 
712          * @date        Dec 7, 2010
713          */
714         function getContent($cid){
715                 if (file_exists(AT_MODULE_PATH . $this->_directoryName.'/ModuleCallbacks.class.php') &&
716                     isset($this->_callbacks[basename($this->_directoryName)])) 
717                 {
718                         require(AT_MODULE_PATH . $this->_directoryName.'/ModuleCallbacks.class.php');
719                         if (method_exists($this->_callbacks[basename($this->_directoryName)], "appendContent")) {
720                                 eval('$output = '.$this->_callbacks[basename($this->_directoryName)]."::appendContent($cid);");
721                                 return $output;
722                         }
723                 }
724                 return NULL;
725         }
726         
727         private function convertContent164($course_id) {
728                 global $db;
729                 
730                 /* convert all content nodes to the IMS standard. (adds null nodes for all top pages) */
731                 /* 1. Convert db to a tree */
732                 $sql = 'SELECT * FROM '.TABLE_PREFIX.'content where course_id='.$course_id;
733                 
734                 $result = mysql_query($sql, $db);
735                 $content_array = array(); 
736
737                 while ($row = mysql_fetch_assoc($result)){
738                         $content_array[$row['content_parent_id']][$row['ordering']] = $row['content_id'];
739                 }
740                 $tree = $this->buildTree($content_array[0], $content_array);
741
742                 /* 2. Restructure the tree */
743                 $tree = $this->rebuild($tree);
744
745                 /* 3. Update the Db based on this new tree */
746                 $this->reconstruct($tree, '', 0, TABLE_PREFIX);
747         }
748
749         /** 
750          * Construct a tree based on table entries
751          * @param       array   current node, (current parent)
752          * @param       mixed   a set of parents, where each parents is in the format of [parent]=>children
753          *                                      should remain the same throughout the recursion.
754          * @return      A tree structure representation of the content entries.
755          * @author      Harris Wong
756          */
757         private function buildTree($current, $content_array){
758                 $folder = array();
759                 foreach($current as $order=>$content_id){
760                         //if has children
761                         if (isset($content_array[$content_id])){
762                                 $wrapper[$content_id] = $this->buildTree($content_array[$content_id], $content_array);
763                         }
764         
765                         //no children.
766                         if ($wrapper){
767                                 $folder['order_'.$order] = $wrapper;
768                                 unset($wrapper);
769                         } else {
770                                 $folder['order_'.$order] = $content_id;
771                         }
772                 }       
773                 return $folder;
774         }
775         
776         
777         /**
778          * Transverse the content tree structure, and reconstruct it with the IMS spec.  
779          * This tree has the structure of [order=>array(id)], so the first layer is its order, second is the id
780          * if param merge is true, if node!=null, merge it to top layer, and + offset to all others
781          * @param       mixed   Tree from the buildTree() function, or sub-tree
782          * @param       mixed   the current tree.
783          * @return      A new content tree that meets the IMS specification.
784          * @author      Harris Wong 
785          */
786         private function rebuild($tree, $node=''){
787             $order_offset = 0;
788             $folder = array();
789             if (!is_array($tree)){
790                 return $tree;
791             }
792             if ($node!=''){
793                 $tree['order_0'] = $node;
794                 $order_offset += 1;
795             }
796             //go through the tree
797             foreach($tree as $k=>$v){
798                 if (preg_match('/order\_([\d]+)/', $k, $match)==1){
799                     //if this is the order layer
800                     $folder['order_'.($match[1]+$order_offset)] = $this->rebuild($v);
801                 } else {
802                     //if this is the content layer
803                     if(is_array($v)){
804                         $folder[$k] = $this->rebuild($v, $k);
805                     }
806                 }
807             }
808             return $folder;
809         }
810         
811         /**
812          * Transverse the tree and update/insert entries based on the updated structure.
813          * @param       array   The tree from rebuild(), and the subtree from the recursion.
814          * @param       int             the ordering of this subtree respect to its parent.
815          * @param       int             parent content id
816          * @return      null (nothing to return, it updates the db only)
817          */
818         private function reconstruct($tree, $order, $content_parent_id, $table_prefix){
819                 global $db;
820         
821                 //a content page.
822                 if (!is_array($tree)){
823                         $sql = 'UPDATE '.$table_prefix."content SET ordering=$order, content_parent_id=$content_parent_id WHERE content_id=$tree";
824                         if (!mysql_query($sql, $db)){
825                                 //throw error
826                                 echo mysql_error();
827                         }
828                         return;
829                 }
830                 foreach ($tree as $k=>$v){
831                 if (preg_match('/order\_([\d]+)/', $k, $match)==1){
832                                 //order layer
833                                 $this->reconstruct($v, $match[1], $content_parent_id, $table_prefix);   //inherit the previous layer id
834                         } else {
835                                 //content folder layer
836                                 $sql = 'SELECT * FROM '.$table_prefix."content WHERE content_id=$k";
837                                 $result = mysql_query($sql, $db);
838                                 $old_content_row = mysql_fetch_assoc($result);
839                                 $sql = 'INSERT INTO '.$table_prefix.'content (course_id, content_parent_id, ordering, last_modified, revision, formatting, release_date, keywords, content_path, title, use_customized_head, allow_test_export, content_type) VALUES ('
840                                         .$old_content_row['course_id'] . ', '
841                                         .$content_parent_id . ', '
842                                         .$order . ', '
843                                         .'\''. $old_content_row['last_modified'] . '\', '
844                                         .$old_content_row['revision'] . ', '
845                                         .$old_content_row['formatting'] . ', '
846                                         .'\''. $old_content_row['release_date'] . '\', '
847                                         .'\''. $old_content_row['keywords'] . '\', '
848                                         .'\''. $old_content_row['content_path'] . '\', '
849                                         .'\''. $old_content_row['title'] . '\', '
850                                         .$old_content_row['use_customized_head'] . ', '
851                                         .$old_content_row['allow_test_export'] . ', '
852                                         . '1)';
853                                 
854                                 if (mysql_query($sql, $db)){
855                                         $folder_id = mysql_insert_id();
856                                         $this->reconstruct($v, '', $folder_id, $table_prefix);
857                                 } else {
858                                         //throw error
859                                         echo mysql_error();
860                                 }
861                         }
862                 }
863         }
864 }
865 ?>