2 /************************************************************************/
4 /************************************************************************/
5 /* Copyright (c) 2002-2008 by Greg Gay, Joel Kronenberg & Heidi Hazelton*/
6 /* Adaptive Technology Resource Centre / University of Toronto */
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 /************************************************************************/
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
21 define('AT_MODULE_TYPE_CORE', 1);
22 define('AT_MODULE_TYPE_STANDARD', 2);
23 define('AT_MODULE_TYPE_EXTRA', 4);
25 define('AT_MODULE_DIR_CORE', '_core');
26 define('AT_MODULE_DIR_STANDARD', '_standard');
28 define('AT_MODULE_PATH', realpath(AT_INCLUDE_PATH.'../mods') . DIRECTORY_SEPARATOR);
34 * @author Joel Kronenberg
39 var $_modules = NULL; // array of module refs
41 function ModuleFactory($auto_load = FALSE) {
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);
52 $this->_modules = array();
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;
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) {
75 $all_modules = array();
78 $type = AT_MODULE_TYPE_CORE | AT_MODULE_TYPE_STANDARD | AT_MODULE_TYPE_EXTRA;
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 while($row = mysql_fetch_assoc($result)) {
84 if (!isset($this->_modules[$row['dir_name']])) {
85 $module =& new Module($row);
87 $module =& $this->_modules[$row['dir_name']];
89 $all_modules[$row['dir_name']] =& $module;
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)) {
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;
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;
121 uasort($modules, array($this, 'compare'));
127 function & getModule($module_dir) {
128 if (!isset($this->_modules[$module_dir])) {
130 $sql = "SELECT dir_name, privilege, admin_privilege, status FROM ". TABLE_PREFIX . "modules WHERE dir_name='$module_dir'";
131 $result = mysql_query($sql, $db);
132 if ($row = mysql_fetch_assoc($result)) {
133 $module =& new Module($row);
135 $module =& new Module($module_dir);
137 $this->_modules[$module_dir] =& $module;
139 return $this->_modules[$module_dir];
143 // used for sorting modules
144 function compare($a, $b) {
145 return strnatcasecmp($a->getName(), $b->getName());
153 * @author Joel Kronenberg
160 var $_status; // core|enabled|disabled
161 var $_privilege; // priv bit(s) | 0 (in dec form)
162 var $_admin_privilege; // priv bit(s) | 0 (in dec form)
163 var $_display_defaults; // bit(s)
165 var $_type; // core, standard, extra
166 var $_properties; // array from xml
167 var $_cron_interval; // cron interval
168 var $_cron_last_run; // cron last run date stamp
171 function Module($row) {
172 if (is_array($row)) {
173 $this->_directoryName = $row['dir_name'];
174 $this->_status = $row['status'];
175 $this->_privilege = $row['privilege'];
176 $this->_admin_privilege = $row['admin_privilege'];
177 $this->_display_defaults= isset($row['display_defaults']) ? $row['display_defaults'] : 0;
178 $this->_cron_interval = $row['cron_interval'];
179 $this->_cron_last_run = $row['cron_last_run'];
181 if (strpos($row['dir_name'], AT_MODULE_DIR_CORE) === 0) {
182 $this->_type = AT_MODULE_TYPE_CORE;
183 } else if (strpos($row['dir_name'], AT_MODULE_DIR_STANDARD) === 0) {
184 $this->_type = AT_MODULE_TYPE_STANDARD;
186 $this->_type = AT_MODULE_TYPE_EXTRA;
189 $this->_directoryName = $row;
190 $this->_status = AT_MODULE_STATUS_UNINSTALLED;
191 $this->_privilege = 0;
192 $this->_admin_privilege = 0;
193 $this->_display_defaults= 0;
194 $this->_type = AT_MODULE_TYPE_EXTRA; // standard/core are installed by default
199 function checkStatus($status) { return (bool) ($status & $this->_status); }
200 function isPartiallyUninstalled() { return ($this->_status == AT_MODULE_STATUS_PARTIALLY_UNINSTALLED) ? true : false; }
201 function isUninstalled() { return ($this->_status == AT_MODULE_STATUS_UNINSTALLED) ? true : false; }
202 function isEnabled() { return ($this->_status == AT_MODULE_STATUS_ENABLED) ? true : false; }
203 function isDisabled() { return ($this->_status == AT_MODULE_STATUS_DISABLED) ? true : false; }
204 function isMissing() { return ($this->_status == AT_MODULE_STATUS_MISSING) ? true : false; }
207 function checkType($type) { return (bool) ($type & $this->_type); }
208 function isCore() { return ($this->_type == AT_MODULE_TYPE_CORE) ? true : false; }
209 function isStandard() { return ($this->_type == AT_MODULE_TYPE_STANDARD) ? true : false; }
210 function isExtra() { return ($this->_type == AT_MODULE_TYPE_EXTRA) ? true : false; }
213 function getPrivilege() { return $this->_privilege; }
214 function getAdminPrivilege() { return $this->_admin_privilege; }
217 if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module.php')) {
218 global $_modules, $_pages, $_stacks, $_list; // $_list is for sublinks on "detail view"
220 require(AT_MODULE_PATH . $this->_directoryName.'/module.php');
222 if (isset($this->_pages)) {
223 $_pages = array_merge_recursive((array) $_pages, $this->_pages);
227 if (isset($this->_stacks)) {
229 $_stacks = array_merge((array)$_stacks, $this->_stacks);
232 // sublinks on "detail view"
233 if(isset($this->_list)) {
234 $_list = array_merge((array)$_list, $this->_list);
238 if (isset($_student_tool)) {
239 $this->_student_tool =& $_student_tool;
240 $_modules[] = $this->_student_tool;
244 if (isset($_group_tool)) {
245 $this->_group_tool =& $_group_tool;
251 function _initModuleProperties() {
252 if (!isset($this->_properties)) {
253 require_once(dirname(__FILE__) . '/ModuleParser.class.php');
254 $moduleParser =& new ModuleParser();
255 $moduleParser->parse(@file_get_contents(AT_MODULE_PATH . $this->_directoryName.'/module.xml'));
256 if ($moduleParser->rows[0]) {
257 $this->_properties = $moduleParser->rows[0];
259 $this->_properties = array();
260 $this->setIsMissing(); // the xml file may not be found -> the dir may be missing.
266 * Get the properties of this module as found in the module.xml file
268 * @param array $properties_list list of property names
269 * @return array associative array of property/value pairs
270 * @author Joel Kronenberg
272 function getProperties($properties_list) {
273 $this->_initModuleProperties();
275 if (!$this->_properties) {
278 $properties_list = array_flip($properties_list);
279 foreach ($properties_list as $property => $garbage) {
280 $properties_list[$property] = $this->_properties[$property];
282 return $properties_list;
285 * Get a single property as found in the module.xml file
287 * @param string $property name of the property to return
288 * @return string the value of the property
289 * @author Joel Kronenberg
291 function getProperty($property) {
292 $this->_initModuleProperties();
294 if (!$this->_properties) {
298 return $this->_properties[$property];
301 function getCronInterval() {
302 return $this->_cron_interval;
307 if ($this->isUninstalled()) {
308 $name = $this->getProperty('name');
309 return current($name);
311 return _AT(basename($this->_directoryName));
314 function getDescription($lang = 'en') {
315 $this->_initModuleProperties();
317 if (!$this->_properties) {
321 if (isset($this->_properties['description'][$lang])) {
322 return $this->_properties['description'][$lang];
324 $description = current($this->_properties['description']);
328 function getChildPage($page) {
329 if (!is_array($this->_pages)) {
332 foreach ($this->_pages as $tmp_page => $item) {
333 if (!empty($item['parent']) && $item['parent'] == $page) {
340 * Checks whether or not this module can be backed-up
342 * @return boolean true if this module can be backed-up, false otherwise
343 * @author Joel Kronenberg
345 function isBackupable() {
346 return is_file(AT_MODULE_PATH . $this->_directoryName.'/module_backup.php');
349 function createGroup($group_id) {
350 if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php')) {
351 require_once(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php');
352 $fn_name = basename($this->_directoryName) .'_create_group';
357 function deleteGroup($group_id) {
358 $fn_name = basename($this->_directoryName) .'_delete_group';
360 if (!function_exists($fn_name) && is_file(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php')) {
361 require_once(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php');
363 if (function_exists($fn_name)) {
368 function getGroupTool() {
369 if (!isset($this->_group_tool)) {
373 return $this->_group_tool;
376 function isGroupable() {
377 return is_file(AT_MODULE_PATH . $this->_directoryName.'/module_groups.php');
381 * Backup this module for a given course
383 * @param int $course_id ID of the course to backup
384 * @param object $zipfile a reference to a zipfile object
385 * @author Joel Kronenberg
387 function backup($course_id, &$zipfile) {
390 if (!isset($CSVExport)) {
391 require_once(AT_INCLUDE_PATH . 'classes/CSVExport.class.php');
392 $CSVExport = new CSVExport();
396 if ($this->isBackupable()) {
397 require(AT_MODULE_PATH . $this->_directoryName . '/module_backup.php');
399 foreach ($sql as $file_name => $table_sql) {
400 $content = $CSVExport->export($table_sql, $course_id);
402 $zipfile->add_file($content, $file_name . '.csv', $now);
408 foreach ($dirs as $dir => $path) {
409 $path = str_replace('?', $course_id, $path);
411 $zipfile->add_dir($path , $dir);
418 * Restores this module into the given course
420 * @param int $course_id ID of the course to restore into
421 * @param string $version version number of the ATutor installation used to make this backup
422 * @param string $import_dir the path to the import directory
423 * @author Joel Kronenberg
425 function restore($course_id, $version, $import_dir) {
427 if (!file_exists(AT_MODULE_PATH . $this->_directoryName.'/module_backup.php')) {
431 if (!isset($CSVImport)) {
432 require_once(AT_INCLUDE_PATH . 'classes/CSVImport.class.php');
433 $CSVImport = new CSVImport();
436 require(AT_MODULE_PATH . $this->_directoryName.'/module_backup.php');
439 foreach ($sql as $table_name => $table_sql) {
440 $CSVImport->import($table_name, $import_dir, $course_id, $version);
444 foreach ($dirs as $src => $dest) {
445 $dest = str_replace('?', $course_id, $dest);
446 copys($import_dir.$src, $dest);
452 * Delete this module's course content. If $groups is specified then it will
453 * delete all content for the groups specified.
455 * @param int $course_id ID of the course to delete
456 * @param array $groups Array of groups to delete
457 * @author Joel Kronenberg
459 function delete($course_id, $groups) {
460 if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module_delete.php')) {
461 require(AT_MODULE_PATH . $this->_directoryName.'/module_delete.php');
462 if (function_exists(basename($this->_directoryName).'_delete')) {
463 $fnctn = basename($this->_directoryName).'_delete';
468 foreach ($groups as $group_id) {
469 $this->deleteGroup($group_id);
475 * Enables the installed module
477 * @author Joel Kronenberg
482 $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_ENABLED.' WHERE dir_name="'.$this->_directoryName.'"';
483 $result = mysql_query($sql, $db);
487 * Sets the status to missing if the module dir doesn't exist.
489 * @param boolean $force whether or not to force the module to be missing (used for bundled extra modules upon upgrade)
490 * @author Joel Kronenberg
492 function setIsMissing($force = false) {
494 // if the directory doesn't exist then set the status to MISSING
495 if ($force || !is_dir(AT_MODULE_PATH . $this->_directoryName)) {
496 $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_MISSING.' WHERE dir_name="'.$this->_directoryName.'"';
497 $result = mysql_query($sql, $db);
502 * Disables the installed module
504 * @author Joel Kronenberg
509 // remove any privileges admins, students
510 if ($this->_privilege > 1) {
511 $sql = 'UPDATE '. TABLE_PREFIX . 'course_enrollment SET `privileges`=`privileges`-'.$this->_privilege.' WHERE `privileges` > 1 AND (`privileges` & '.$this->_privilege.')<>0';
512 $result = mysql_query($sql, $db);
515 if ($this->_admin_privilege > 1) {
516 $sql = 'UPDATE '. TABLE_PREFIX . 'admins SET `privileges`=`privileges`-'.$this->_admin_privilege.' WHERE `privileges` > 1 AND (`privileges` & '.$this->_admin_privilege.')<>0';
517 $result = mysql_query($sql, $db);
520 $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_DISABLED.' WHERE dir_name="'.$this->_directoryName.'"';
521 $result = mysql_query($sql, $db);
523 if (function_exists(basename($this->_directoryName).'_disable')) {
524 $fn_name = basename($this->_directoryName).'_disable';
530 * Installs the module
532 * @author Joel Kronenberg
537 // should check if this module is already installed...
539 if (file_exists(AT_MODULE_PATH . $this->_directoryName . '/module_install.php')) {
540 require(AT_MODULE_PATH . $this->_directoryName . '/module_install.php');
543 if (!$msg->containsErrors()) {
546 $sql = "SELECT MAX(`privilege`) AS `privilege`, MAX(admin_privilege) AS admin_privilege FROM ".TABLE_PREFIX."modules";
547 $result = mysql_query($sql, $db);
548 $row = mysql_fetch_assoc($result);
550 if (($_course_privilege === TRUE) || ((string) $_course_privilege == 'new')) {
551 $priv = $row['privilege'] * 2;
552 } else if ($_course_privilege == AT_PRIV_ADMIN) {
553 $priv = AT_PRIV_ADMIN;
558 if (($_admin_privilege === TRUE) || ((string) $_admin_privilege == 'new')) {
559 $admin_priv = $row['admin_privilege'] * 2;
561 $admin_priv = AT_ADMIN_PRIV_ADMIN;
564 if (isset($_cron_interval)) {
565 $_cron_interval = abs($_cron_interval);
570 $sql = 'INSERT INTO '. TABLE_PREFIX . 'modules VALUES ("'.$this->_directoryName.'", '.AT_MODULE_STATUS_DISABLED.', '.$priv.', '.$admin_priv.', '.$_cron_interval.', 0)';
571 mysql_query($sql, $db);
572 if (mysql_affected_rows($db) != 1) {
573 // in case this module has to be re-installed (because it was Missing)
574 $sql = 'UPDATE '. TABLE_PREFIX . 'modules SET status='.AT_MODULE_STATUS_DISABLED.' WHERE dir_name="'.$this->_directoryName.'"';
575 mysql_query($sql, $db);
581 * Uninstalls the module
583 * @author Cindy Qi Li
585 function uninstall($del_data='') {
588 if (file_exists(AT_MODULE_PATH . $this->_directoryName . '/module_uninstall.php') && $del_data == 1)
590 require(AT_MODULE_PATH . $this->_directoryName . '/module_uninstall.php');
593 if (!$msg->containsErrors())
595 require_once(AT_INCLUDE_PATH.'lib/filemanager.inc.php');
597 if (!clr_dir(AT_MODULE_PATH . $this->_directoryName))
598 $msg->addError(array('MODULE_UNINSTALL', '<li>'.AT_MODULE_PATH . $this->_directoryName.' can not be removed. Please manually remove it.</li>'));
601 if (!$msg->containsErrors())
605 $sql = "DELETE FROM ". TABLE_PREFIX . "modules WHERE dir_name = '".$this->_directoryName."'";
606 mysql_query($sql, $db);
609 if ($msg->containsErrors())
613 $sql = "UPDATE ". TABLE_PREFIX . "modules SET status=".AT_MODULE_STATUS_PARTIALLY_UNINSTALLED." WHERE dir_name='".$this->_directoryName."'";
614 mysql_query($sql, $db);
618 function getStudentTools() {
619 if (!isset($this->_student_tool)) {
623 return $this->_student_tool;
628 if ( ($this->_cron_last_run + ($this->_cron_interval * 60)) < time()) {
629 if (is_file(AT_MODULE_PATH . $this->_directoryName.'/module_cron.php')) {
630 require(AT_MODULE_PATH . $this->_directoryName.'/module_cron.php');
631 if (function_exists(basename($this->_directoryName).'_cron')) {
632 $fnctn = basename($this->_directoryName).'_cron';
636 $this->updateCronLastRun();
640 // i'm private! update the last time the cron was run
641 function updateCronLastRun() {
644 $sql = "UPDATE ".TABLE_PREFIX."modules SET cron_last_run=".time()." WHERE dir_name='$this->_directoryName'";
645 mysql_query($sql, $db);