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