hotpot_showtimes)) { set_config("hotpot_showtimes", 0); } if (!isset($CFG->hotpot_excelencodings)) { set_config("hotpot_excelencodings", ""); } ////////////////////////////////// /// CONSTANTS and GLOBAL VARIABLES $CFG->hotpotroot = "$CFG->dirroot/mod/hotpot"; $CFG->hotpottemplate = "$CFG->hotpotroot/template"; $CFG->hotpotismobile = preg_match('/Alcatel|ATTWS|DoCoMo|Doris|Hutc3G|J-PHONE|Java|KDDI|KGT|LGE|MOT|Nokia|portalmmm|ReqwirelessWeb|SAGEM|SHARP|SIE-|SonyEricsson|Teleport|UP\.Browser|UPG1|Wapagsim/', $_SERVER['HTTP_USER_AGENT']); define("HOTPOT_JS", "$CFG->wwwroot/mod/hotpot/hotpot-full.js"); define("HOTPOT_NO", "0"); define("HOTPOT_YES", "1"); define ("HOTPOT_TEXTSOURCE_QUIZ", "0"); define ("HOTPOT_TEXTSOURCE_FILENAME", "1"); define ("HOTPOT_TEXTSOURCE_FILEPATH", "2"); define ("HOTPOT_TEXTSOURCE_SPECIFIC", "3"); define("HOTPOT_LOCATION_COURSEFILES", "0"); define("HOTPOT_LOCATION_SITEFILES", "1"); $HOTPOT_LOCATION = array ( HOTPOT_LOCATION_COURSEFILES => get_string("coursefiles"), HOTPOT_LOCATION_SITEFILES => get_string("sitefiles"), ); define("HOTPOT_OUTPUTFORMAT_BEST", "1"); define("HOTPOT_OUTPUTFORMAT_V3", "10"); define("HOTPOT_OUTPUTFORMAT_V4", "11"); define("HOTPOT_OUTPUTFORMAT_V5", "12"); define("HOTPOT_OUTPUTFORMAT_V5_PLUS", "13"); define("HOTPOT_OUTPUTFORMAT_V6", "14"); define("HOTPOT_OUTPUTFORMAT_V6_PLUS", "15"); define("HOTPOT_OUTPUTFORMAT_FLASH", "20"); define("HOTPOT_OUTPUTFORMAT_MOBILE", "30"); $HOTPOT_OUTPUTFORMAT = array ( HOTPOT_OUTPUTFORMAT_BEST => get_string("outputformat_best", "hotpot"), HOTPOT_OUTPUTFORMAT_V6_PLUS => get_string("outputformat_v6_plus", "hotpot"), HOTPOT_OUTPUTFORMAT_V6 => get_string("outputformat_v6", "hotpot"), HOTPOT_OUTPUTFORMAT_V5_PLUS => get_string("outputformat_v5_plus", "hotpot"), HOTPOT_OUTPUTFORMAT_V5 => get_string("outputformat_v5", "hotpot"), HOTPOT_OUTPUTFORMAT_V4 => get_string("outputformat_v4", "hotpot"), HOTPOT_OUTPUTFORMAT_V3 => get_string("outputformat_v3", "hotpot"), HOTPOT_OUTPUTFORMAT_FLASH => get_string("outputformat_flash", "hotpot"), HOTPOT_OUTPUTFORMAT_MOBILE => get_string("outputformat_mobile", "hotpot"), ); $HOTPOT_OUTPUTFORMAT_DIR = array ( HOTPOT_OUTPUTFORMAT_V6_PLUS => 'v6', HOTPOT_OUTPUTFORMAT_V6 => 'v6', HOTPOT_OUTPUTFORMAT_V5_PLUS => 'v5', HOTPOT_OUTPUTFORMAT_V5 => 'v5', HOTPOT_OUTPUTFORMAT_V4 => 'v4', HOTPOT_OUTPUTFORMAT_V3 => 'v3', HOTPOT_OUTPUTFORMAT_FLASH => 'flash', HOTPOT_OUTPUTFORMAT_MOBILE => 'mobile', ); foreach ($HOTPOT_OUTPUTFORMAT_DIR as $format=>$dir) { if (is_file("$CFG->hotpottemplate/$dir.php") && is_dir("$CFG->hotpottemplate/$dir")) { // do nothing ($format is available) } else { // $format is not available, so remove it unset($HOTPOT_OUTPUTFORMAT[$format]); unset($HOTPOT_OUTPUTFORMAT_DIR[$format]); } } define("HOTPOT_NAVIGATION_BAR", "1"); define("HOTPOT_NAVIGATION_FRAME", "2"); define("HOTPOT_NAVIGATION_IFRAME", "3"); define("HOTPOT_NAVIGATION_BUTTONS", "4"); define("HOTPOT_NAVIGATION_GIVEUP", "5"); define("HOTPOT_NAVIGATION_NONE", "6"); $HOTPOT_NAVIGATION = array ( HOTPOT_NAVIGATION_BAR => get_string("navigation_bar", "hotpot"), HOTPOT_NAVIGATION_FRAME => get_string("navigation_frame", "hotpot"), HOTPOT_NAVIGATION_IFRAME => get_string("navigation_iframe", "hotpot"), HOTPOT_NAVIGATION_BUTTONS => get_string("navigation_buttons", "hotpot"), HOTPOT_NAVIGATION_GIVEUP => get_string("navigation_give_up", "hotpot"), HOTPOT_NAVIGATION_NONE => get_string("navigation_none", "hotpot"), ); define("HOTPOT_JCB", "1"); define("HOTPOT_JCLOZE", "2"); define("HOTPOT_JCROSS", "3"); define("HOTPOT_JMATCH", "4"); define("HOTPOT_JMIX", "5"); define("HOTPOT_JQUIZ", "6"); define("HOTPOT_TEXTOYS_RHUBARB", "7"); define("HOTPOT_TEXTOYS_SEQUITUR", "8"); $HOTPOT_QUIZTYPE = array( HOTPOT_JCB => 'JCB', HOTPOT_JCLOZE => 'JCloze', HOTPOT_JCROSS => 'JCross', HOTPOT_JMATCH => 'JMatch', HOTPOT_JMIX => 'JMix', HOTPOT_JQUIZ => 'JQuiz', HOTPOT_TEXTOYS_RHUBARB => 'Rhubarb', HOTPOT_TEXTOYS_SEQUITUR => 'Sequitur' ); define("HOTPOT_JQUIZ_MULTICHOICE", "1"); define("HOTPOT_JQUIZ_SHORTANSWER", "2"); define("HOTPOT_JQUIZ_HYBRID", "3"); define("HOTPOT_JQUIZ_MULTISELECT", "4"); define("HOTPOT_GRADEMETHOD_HIGHEST", "1"); define("HOTPOT_GRADEMETHOD_AVERAGE", "2"); define("HOTPOT_GRADEMETHOD_FIRST", "3"); define("HOTPOT_GRADEMETHOD_LAST", "4"); $HOTPOT_GRADEMETHOD = array ( HOTPOT_GRADEMETHOD_HIGHEST => get_string("gradehighest", "quiz"), HOTPOT_GRADEMETHOD_AVERAGE => get_string("gradeaverage", "quiz"), HOTPOT_GRADEMETHOD_FIRST => get_string("attemptfirst", "quiz"), HOTPOT_GRADEMETHOD_LAST => get_string("attemptlast", "quiz"), ); define("HOTPOT_STATUS_INPROGRESS", "1"); define("HOTPOT_STATUS_TIMEDOUT", "2"); define("HOTPOT_STATUS_ABANDONED", "3"); define("HOTPOT_STATUS_COMPLETED", "4"); $HOTPOT_STATUS = array ( HOTPOT_STATUS_INPROGRESS => get_string("inprogress", "hotpot"), HOTPOT_STATUS_TIMEDOUT => get_string("timedout", "hotpot"), HOTPOT_STATUS_ABANDONED => get_string("abandoned", "hotpot"), HOTPOT_STATUS_COMPLETED => get_string("completed", "hotpot"), ); define("HOTPOT_FEEDBACK_NONE", "0"); define("HOTPOT_FEEDBACK_WEBPAGE", "1"); define("HOTPOT_FEEDBACK_FORMMAIL", "2"); define("HOTPOT_FEEDBACK_MOODLEFORUM", "3"); define("HOTPOT_FEEDBACK_MOODLEMESSAGING", "4"); $HOTPOT_FEEDBACK = array ( HOTPOT_FEEDBACK_NONE => get_string("feedbacknone", "hotpot"), HOTPOT_FEEDBACK_WEBPAGE => get_string("feedbackwebpage", "hotpot"), HOTPOT_FEEDBACK_FORMMAIL => get_string("feedbackformmail", "hotpot"), HOTPOT_FEEDBACK_MOODLEFORUM => get_string("feedbackmoodleforum", "hotpot"), HOTPOT_FEEDBACK_MOODLEMESSAGING => get_string("feedbackmoodlemessaging", "hotpot"), ); if (empty($CFG->messaging)) { // Moodle 1.4 (and less) unset($HOTPOT_FEEDBACK[HOTPOT_FEEDBACK_MOODLEMESSAGING]); } define("HOTPOT_DISPLAYNEXT_QUIZ", "0"); define("HOTPOT_DISPLAYNEXT_COURSE", "1"); define("HOTPOT_DISPLAYNEXT_INDEX", "2"); ////////////////////////////////// /// CORE FUNCTIONS // possible return values: // false: // display moderr.html (if exists) OR "Could not update" and return to couse view // string: // display as error message and return to course view // true (or non-zero number): // continue to $hp->redirect (if set) OR hotpot/view.php (to displsay quiz) // $hp is an object containing the values of the form in mod.html // i.e. all the fields in the 'hotpot' table, plus the following: // $hp->course : an id in the 'course' table // $hp->coursemodule : an id in the 'course_modules' table // $hp->section : an id in the 'course_sections' table // $hp->module : an id in the 'modules' table // $hp->modulename : always 'hotpot' // $hp->instance : an id in the 'hotpot' table // $hp->mode : 'add' or 'update' // $hp->sesskey : unique string required for Moodle's session management function hotpot_add_instance(&$hp) { if (hotpot_set_form_values($hp)) { $result = insert_record("hotpot", $hp); } else { $result= false; } return $result; } function hotpot_update_instance(&$hp) { if (hotpot_set_form_values($hp)) { $hp->id = $hp->instance; $result = update_record("hotpot", $hp); } else { $result= false; } return $result; } function hotpot_set_form_values(&$hp) { $ok = true; $hp->errors = array(); // these will be reported by moderr.html if (empty($hp->reference)) { $ok = false; $hp->errors['reference']= get_string('error_nofilename', 'hotpot'); } if ($hp->studentfeedbackurl=='http://') { $hp->studentfeedbackurl = ''; } if (empty($hp->studentfeedbackurl)) { switch ($hp->studentfeedback) { case HOTPOT_FEEDBACK_WEBPAGE: $ok = false; $hp->errors['studentfeedbackurl']= get_string('error_nofeedbackurlwebpage', 'hotpot'); break; case HOTPOT_FEEDBACK_FORMMAIL: $ok = false; $hp->errors['studentfeedbackurl']= get_string('error_nofeedbackurlformmail', 'hotpot'); break; } } $time = time(); $hp->timecreated = $time; $hp->timemodified = $time; if (empty($hp->enabletimeopen)) { $hp->timeopen = 0; } else { $hp->timeopen = make_timestamp( $hp->openyear, $hp->openmonth, $hp->openday, $hp->openhour, $hp->openminute, 0 ); } if (empty($hp->enabletimeclose)) { $hp->timeclose = 0; } else { $hp->timeclose = make_timestamp( $hp->closeyear, $hp->closemonth, $hp->closeday, $hp->closehour, $hp->closeminute, 0 ); } if ($hp->quizchain==HOTPOT_YES) { switch ($hp->mode) { case 'add': $ok = hotpot_add_chain($hp); break; case 'update': $ok = hotpot_update_chain($hp); break; } } else { // $hp->quizchain==HOTPOT_NO hotpot_set_name_summary_reference($hp); } switch ($hp->displaynext) { // N.B. redirection only works for Moodle 1.5+ case HOTPOT_DISPLAYNEXT_COURSE: $hp->redirect = true; $hp->redirecturl = "view.php?id=$hp->course"; break; case HOTPOT_DISPLAYNEXT_INDEX: $hp->redirect = true; $hp->redirecturl = "../mod/hotpot/index.php?id=$hp->course"; break; default: // use Moodle default action (i.e. go on to display the hotpot quiz) } // if ($ok && $hp->setdefaults) { if ($ok) { set_user_preference('hotpot_timeopen', $hp->timeopen); set_user_preference('hotpot_timeclose', $hp->timeclose); set_user_preference('hotpot_navigation', $hp->navigation); set_user_preference('hotpot_outputformat', $hp->outputformat); set_user_preference('hotpot_studentfeedback', $hp->studentfeedback); set_user_preference('hotpot_studentfeedbackurl', $hp->studentfeedbackurl); set_user_preference('hotpot_forceplugins', $hp->forceplugins); set_user_preference('hotpot_shownextquiz', $hp->shownextquiz); set_user_preference('hotpot_review', $hp->review); set_user_preference('hotpot_grade', $hp->grade); set_user_preference('hotpot_grademethod', $hp->grademethod); set_user_preference('hotpot_attempts', $hp->attempts); get_user_preference('hotpot_subnet', $hp->subnet); set_user_preference('hotpot_displaynext', $hp->displaynext); if ($hp->mode=='add') { set_user_preference('hotpot_quizchain', $hp->quizchain); set_user_preference('hotpot_namesource', $hp->namesource); set_user_preference('hotpot_summarysource', $hp->summarysource); } } return $ok; } function hotpot_get_chain(&$cm) { // get details of course_modules in this section $course_module_ids = get_field('course_sections', 'sequence', 'id', $cm->section); if (empty($course_module_ids)) { $hotpot_modules = array(); } else { $hotpot_modules = get_records_select('course_modules', "id IN ($course_module_ids) AND module=$cm->module"); if (empty($hotpot_modules)) { $hotpot_modules = array(); } } // get ids of hotpot modules in this section $ids = array(); foreach ($hotpot_modules as $hotpot_module) { $ids[] = $hotpot_module->instance; } // get details of hotpots in this section if (empty($ids)) { $hotpots = array(); } else { $hotpots = get_records_list('hotpot', 'id', implode(',', $ids)); } $found = false; $chain = array(); // loop through course_modules in this section $ids = explode(',', $course_module_ids); foreach ($ids as $id) { // check this course_module is a hotpot activity if (isset($hotpot_modules[$id])) { // store details of this course module and hotpot activity $hotpot_id = $hotpot_modules[$id]->instance; $chain[$id] = &$hotpot_modules[$id]; $chain[$id]->hotpot = &$hotpots[$hotpot_id]; // set $found, if this is the course module we're looking for if (isset($cm->coursemodule)) { if ($id==$cm->coursemodule) { $found = true; } } else { if ($id==$cm->id) { $found = true; } } // is this the end of a chain if (empty($hotpots[$hotpot_id]->shownextquiz)) { if ($found) { break; // out of loop } else { // restart chain (target cm has not been found yet) $chain = array(); } } } } // end foreach $ids return $found ? $chain : false; } function hotpot_is_visible(&$cm) { if (!isset($cm->sectionvisible)) { if ($section = get_record('course_sections', 'id', $cm->section)) { $cm->sectionvisible = $section->visible; } else { error('Course module record contains invalid section'); } } if (empty($cm->sectionvisible)) { $visible = HOTPOT_NO; } else { $visible = HOTPOT_YES; if (empty($cm->visible)) { if ($chain = hotpot_get_chain($cm)) { $startofchain = array_shift($chain); $visible = $startofchain->visible; } } } return $visible; } function hotpot_add_chain(&$hp) { /// add a chain of hotpot actiivities global $CFG, $course; $ok = true; $hp->names = array(); $hp->summaries = array(); $hp->references = array(); $xml_quiz = new hotpot_xml_quiz($hp, false, false, false, false, false); if (isset($xml_quiz->error)) { $hp->errors['reference'] = $xml_quiz->error; $ok = false; } else if (is_dir($xml_quiz->filepath)) { // get list of hotpot files in this folder if ($dh = @opendir($xml_quiz->filepath)) { while ($file = @readdir($dh)) { if (preg_match('/\.(jbc|jcl|jcw|jmt|jmx|jqz|htm|html)$/', $file)) { $hp->references[] = "$xml_quiz->reference/$file"; } } closedir($dh); // get titles foreach ($hp->references as $i=>$reference) { $filepath = $xml_quiz->fileroot.'/'.$reference; hotpot_get_titles_and_next_ex($hp, $filepath); $hp->names[$i] = $hp->exercisetitle; $hp->summaries[$i] = $hp->exercisesubtitle; } } else { $ok = false; $hp->errors['reference'] = get_string('error_couldnotopenfolder', 'hotpot', $hp->reference); } } else if (is_file($xml_quiz->filepath)) { $filerootlength = strlen($xml_quiz->fileroot) + 1; while ($xml_quiz->filepath) { hotpot_get_titles_and_next_ex($hp, $xml_quiz->filepath, true); $hp->names[] = $hp->exercisetitle; $hp->summaries[] = $hp->exercisesubtitle; $hp->references[] = substr($xml_quiz->filepath, $filerootlength); if ($hp->nextexercise) { $filepath = $xml_quiz->fileroot.'/'.$xml_quiz->filesubdir.$hp->nextexercise; // check file is not already in chain $reference = substr($filepath, $filerootlength); if (in_array($reference, $hp->references)) { $filepath = ''; } } else { $filepath = ''; } if ($filepath && file_exists($filepath) && is_file($filepath) && is_readable($filepath)) { $xml_quiz->filepath = $filepath; } else { $xml_quiz->filepath = false; // finish while loop } } // end while } else { $ok = false; $hp->errors['reference'] = get_string('error_notfileorfolder', 'hotpot', $hp->reference); } if (empty($hp->references) && empty($hp->errors['reference'])) { $ok = false; $hp->errors['reference'] = get_string('error_noquizzesfound', 'hotpot', $hp->reference); } if ($ok) { $hp->visible = HOTPOT_YES; if (trim($hp->name)=='') { $hp->name = get_string("modulename", $hp->modulename); } $hp->specificname = $hp->name; $hp->specificsummary = $hp->summary; // add all except last activity in chain $i_max = count($hp->references)-1; for ($i=0; $i<$i_max; $i++) { hotpot_set_name_summary_reference($hp, $i); $hp->reference = addslashes($hp->reference); if (!$hp->instance = insert_record("hotpot", $hp)) { error("Could not add a new instance of $hp->modulename", "view.php?id=$hp->course"); } // store (hotpot table) id of start of chain if ($i==0) { $hp->startofchain = $hp->instance; } if (isset($course->groupmode)) { $hp->groupmode = $course->groupmode; } if (! $hp->coursemodule = add_course_module($hp)) { error("Could not add a new course module"); } if (! $sectionid = add_mod_to_section($hp) ) { error("Could not add the new course module to that section"); } if (! set_field("course_modules", "section", $sectionid, "id", $hp->coursemodule)) { error("Could not update the course module with the correct section"); } add_to_log($hp->course, "course", "add mod", "../mod/$hp->modulename/view.php?id=$hp->coursemodule", "$hp->modulename $hp->instance" ); add_to_log($hp->course, $hp->modulename, "add", "view.php?id=$hp->coursemodule", "$hp->instance", $hp->coursemodule ); // hide tail of chain if ($hp->shownextquiz==HOTPOT_YES) { $hp->visible = HOTPOT_NO; } } // end for ($hp->references) // settings for final activity in chain hotpot_set_name_summary_reference($hp, $i); $hp->reference = addslashes($hp->references[$i]); $hp->shownextquiz = HOTPOT_NO; if (isset($hp->startofchain)) { // redirection only works for Moodle 1.5+ $hp->redirect = true; $hp->redirecturl = "$CFG->wwwroot/mod/hotpot/view.php?hp=$hp->startofchain"; } } // end if $ok return $ok; } function hotpot_set_name_summary_reference(&$hp, $chain_index=NULL) { $xml_quiz = NULL; $textfields = array('name', 'summary'); foreach ($textfields as $textfield) { $textsource = $textfield.'source'; // are we adding a chain? if (isset($chain_index)) { switch ($hp->$textsource) { case HOTPOT_TEXTSOURCE_QUIZ: if ($textfield=='name') { $hp->exercisetitle = $hp->names[$chain_index]; } else if ($textfield=='summary') { $hp->exercisesubtitle = $hp->summaries[$chain_index]; } break; case HOTPOT_TEXTSOURCE_SPECIFIC: $specifictext = 'specific'.$textfield; if (empty($hp->$specifictext) && trim($hp->$specifictext)=='') { $hp->$textfield = ''; } else { $hp->$textfield = $hp->$specifictext.' ('.($chain_index+1).')'; } break; } $hp->reference = $hp->references[$chain_index]; } if ($hp->$textsource==HOTPOT_TEXTSOURCE_QUIZ) { if (empty($xml_quiz) && !isset($chain_index)) { $xml_quiz = new hotpot_xml_quiz($hp, false, false, false, false, false); hotpot_get_titles_and_next_ex($hp, $xml_quiz->filepath); } if ($textfield=='name') { $hp->$textfield = addslashes($hp->exercisetitle); } else if ($textfield=='summary') { $hp->$textfield = addslashes($hp->exercisesubtitle); } } switch ($hp->$textsource) { case HOTPOT_TEXTSOURCE_FILENAME: $hp->$textfield = basename($hp->reference); break; case HOTPOT_TEXTSOURCE_FILEPATH: $hp->$textfield = ''; // continue to next lines default: if (empty($hp->$textfield)) { $hp->$textfield = str_replace('/', ' ', $hp->reference); } } // end switch } // end foreach } function hotpot_get_titles_and_next_ex(&$hp, $filepath, $get_next=false) { $hp->exercisetitle = ''; $hp->exercisesubtitle = ''; $hp->nextexercise = ''; // read the quiz file source if ($source = file_get_contents($filepath)) { $next = ''; $title = ''; $subtitle = ''; if (preg_match('|\.html?$|', $filepath)) { // html file if (preg_match('|]*class="ExerciseTitle"[^>]*>(.*?)|is', $source, $matches)) { $title = trim(strip_tags($matches[1])); } if (empty($title)) { if (preg_match('|]*>(.*?)|is', $source, $matches)) { $title = trim(strip_tags($matches[1])); } } if (preg_match('|]*class="ExerciseSubtitle"[^>]*>(.*?)|is', $source, $matches)) { $subtitle = trim(strip_tags($matches[1])); } if ($get_next) { if (preg_match('|]*class="NavButtonBar"[^>]*>(.*?)|is', $source, $matches)) { $navbuttonbar = $matches[1]; if (preg_match_all('|]*class="NavButton"[^>]*onclick="'."location='([^']*)'".'[^"]*"[^>]*>|is', $navbuttonbar, $matches)) { $lastbutton = count($matches[0])-1; $next = $matches[1][$lastbutton]; } } } } else { // xml file (...maybe) $xml_tree = new hotpot_xml_tree($source); $xml_tree->filetype = ''; $keys = array_keys($xml_tree->xml); foreach ($keys as $key) { if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) { $xml_tree->filetype = 'xml'; $xml_tree->xml_root = "['$key']['#']"; $xml_tree->quiztype = strtolower($matches[2]); break; } } if ($xml_tree->filetype=='xml') { $title = strip_tags($xml_tree->xml_value('data,title')); $subtitle = $xml_tree->xml_value('hotpot-config-file,'.$xml_tree->quiztype.',exercise-subtitle'); if ($get_next) { $include = $xml_tree->xml_value('hotpot-config-file,global,include-next-ex'); if (!empty($include)) { $next = $xml_tree->xml_value("hotpot-config-file,$xml_tree->quiztype,next-ex-url"); if (is_array($next)) { $next = $next[0]; // in case "next-ex-url" was repeated in the xml file } } } } } $hp->nextexercise = $next; $hp->exercisetitle = (empty($title) || is_array($title)) ? basename($filepath) : $title; $hp->exercisesubtitle = (empty($subtitle) || is_array($subtitle)) ? $hp->exercisetitle : $subtitle; } } function hotpot_get_all_instances_in_course($modulename, $course) { /// called from index.php global $CFG; $instances = array(); if (isset($CFG->release) && substr($CFG->release, 0, 3)>=1.2) { $groupmode = 'cm.groupmode,'; } else { $groupmode = ''; } $query = " SELECT cm.id AS coursemodule, cm.course AS course, cm.module AS module, cm.instance AS instance, -- cm.section AS section, cm.visible AS visible, $groupmode -- cs.section AS sectionnumber, cs.section AS section, cs.sequence AS sequence, cs.visible AS sectionvisible, thismodule.* FROM {$CFG->prefix}course_modules AS cm, {$CFG->prefix}course_sections AS cs, {$CFG->prefix}modules AS m, {$CFG->prefix}$modulename AS thismodule WHERE m.name = '$modulename' AND m.id = cm.module AND cm.course = '$course->id' AND cm.section = cs.id AND cm.instance = thismodule.id "; if ($rawmods = get_records_sql($query)) { // cache $isteacher setting $isteacher = isteacher($course->id); $explodesection = array(); $order = array(); foreach ($rawmods as $rawmod) { if (empty($explodesection[$rawmod->section])) { $explodesection[$rawmod->section] = true; $coursemodules = explode(',', $rawmod->sequence); foreach ($coursemodules as $i=>$coursemodule) { $order[$coursemodule] = sprintf('%d.%04d', $rawmod->section, $i); } } if ($isteacher) { $visible = true; } else if ($modulename=='hotpot') { $visible = hotpot_is_visible($rawmod); } else { $visible = $rawmod->visible; } if ($visible) { $instances[$order[$rawmod->coursemodule]] = $rawmod; } } // end foreach $modinfo ksort($instances); $instances = array_values($instances); } return $instances; } function hotpot_update_chain(&$hp) { /// update a chain of hotpot actiivities $ok = true; if ($hotpot_modules = hotpot_get_chain($hp)) { // skip updating of these fields $skipfields = array('id', 'course', 'name', 'reference', 'summary', 'shownextquiz'); $fields = array(); foreach ($hotpot_modules as $hotpot_module) { if ($hp->instance==$hotpot_module->id) { // don't need to update this hotpot } else { // shortcut to hotpot record $hotpot = &$hotpot_module->hotpot; // get a list of fields to update (first time only) if (empty($fields)) { $fields = array_keys(get_object_vars($hotpot)); } // assume update is NOT required $require_update = false; // update field values (except $skipfields) foreach($fields as $field) { if (in_array($field, $skipfields) || $hotpot->$field==$hp->$field) { // update not required for this field } else { $require_update = true; $hotpot->$field = $hp->$field; } } // update this $hotpot, if required if ($require_update && !update_record("hotpot", $hotpot)) { error("Could not update the $hp->modulename", "view.php?id=$hp->course"); } } } // end foreach $ids } return $ok; } function hotpot_delete_instance($id) { /// Given an ID of an instance of this module, /// this function will permanently delete the instance /// and any data that depends on it. $result = false; if (delete_records("hotpot", "id", "$id")) { $result = true; delete_records("hotpot_questions", "hotpot", "$id"); if ($attempts = get_records_select("hotpot_attempts", "hotpot='$id'")) { $ids = implode(',', array_keys($attempts)); delete_records_select("hotpot_attempts", "id IN ($ids)"); delete_records_select("hotpot_details", "attempt IN ($ids)"); delete_records_select("hotpot_responses", "attempt IN ($ids)"); } } return $result; } function hotpot_delete_and_notify($table, $select, $strtable) { $count = max(0, count_records_select($table, $select)); if ($count) { delete_records_select($table, $select); $count -= max(0, count_records_select($table, $select)); if ($count) { notify(get_string('deleted')." $count x $strtable"); } } } function hotpot_user_complete($course, $user, $mod, $hp) { /// Print a detailed representation of what a user has done with /// a given particular instance of this module, for user activity reports. $report = hotpot_user_outline($course, $user, $mod, $hp); if (empty($report)) { print get_string("noactivity", "hotpot"); } else { $date = userdate($report->time, get_string('strftimerecentfull')); print $report->info.' '.get_string('mostrecently').': '.$date; } return true; } function hotpot_user_outline($course, $user, $mod, $hp) { /// Return a small object with summary information about what a /// user has done with a given particular instance of this module /// Used for user activity reports. /// $report->time = the time they did it /// $report->info = a short text description $report = NULL; if ($records = get_records_select("hotpot_attempts", "hotpot='$hp->id' AND userid='$user->id'", "timestart ASC", "*")) { $scores = array(); foreach ($records as $record){ if (empty($report->time)) { $report->time = $record->timestart; } $scores[] = hotpot_format_score($record); } if (empty($scores)) { $report->time = 0; $report->info = get_string('noactivity', 'hotpot'); } else { $report->info = get_string('score', 'quiz').': '.implode(', ', $scores); } } return $report; } function hotpot_format_score($record, $undefined=' ') { if (isset($record->score)) { $score = $record->score; } else { $score = $undefined; } return $score; } function hotpot_format_status($record, $undefined=' ') { global $HOTPOT_STATUS; if (isset($record->status) || isset($HOTPOT_STATUS[$record->status])) { $status = $HOTPOT_STATUS[$record->status]; } else { $status = $undefined; } return $status; } function hotpot_print_recent_activity($course, $isteacher, $timestart) { /// Given a course and a time, this module should find recent activity /// that has occurred in hotpot activities and print it out. /// Return true if there was output, or false is there was none. global $CFG; $result = false; if($isteacher){ $records = get_records_sql(" SELECT h.id AS id, h.name AS name, COUNT(*) AS count_attempts FROM {$CFG->prefix}hotpot AS h, {$CFG->prefix}hotpot_attempts AS a WHERE h.course = $course->id AND h.id = a.hotpot AND a.id = a.clickreportid AND a.starttime > $timestart GROUP BY h.id, h.name "); // note that PostGreSQL requires h.name in the GROUP BY clause if($records) { $names = array(); foreach ($records as $id => $record){ $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$id"; $name = ' '.$record->name.''; if ($record->count_attempts > 1) { $name .= " ($record->count_attempts)"; } $names[] = $name; } print_headline(get_string('modulenameplural', 'hotpot').':'); if ($CFG->version >= 2005050500) { // Moodle 1.5+ echo '
'.implode('
', $names).'
'; } else { // Moodle 1.4.x (or less) echo ''.implode('
', $names).'
'; } $result = true; } } return $result; // True if anything was printed, otherwise false } function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") { // Returns all quizzes since a given time. global $CFG; // If $cmid or $userid are specified, then this restricts the results $cm_select = empty($cmid) ? "" : " AND cm.id = '$cmid'"; $user_select = empty($userid) ? "" : " AND u.id = '$userid'"; $records = get_records_sql(" SELECT a.*, h.name, h.course, cm.instance, cm.section, u.firstname, u.lastname, u.picture FROM {$CFG->prefix}hotpot_attempts AS a, {$CFG->prefix}hotpot AS h, {$CFG->prefix}course_modules AS cm, {$CFG->prefix}user AS u WHERE a.timefinish > '$sincetime' AND a.id = a.clickreportid AND a.userid = u.id $user_select AND a.hotpot = h.id $cm_select AND cm.instance = h.id AND cm.course = '$courseid' AND h.course = cm.course ORDER BY a.timefinish ASC "); if (!empty($records)) { foreach ($records as $record) { if (empty($groupid) || ismember($groupid, $record->userid)) { unset($activity); $activity->type = "hotpot"; $activity->defaultindex = $index; $activity->instance = $record->hotpot; $activity->name = $record->name; $activity->section = $record->section; $activity->content->attemptid = $record->id; $activity->content->attempt = $record->attempt; $activity->content->score = $record->score; $activity->content->timestart = $record->timestart; $activity->content->timefinish = $record->timefinish; $activity->user->userid = $record->userid; $activity->user->fullname = fullname($record); $activity->user->picture = $record->picture; $activity->timestamp = $record->timefinish; $activities[] = $activity; $index++; } } // end foreach } } function hotpot_print_recent_mod_activity($activity, $course, $detail=false) { /// Basically, this function prints the results of "hotpot_get_recent_activity" global $CFG, $THEME, $USER; print ''; print '"; print "
'; print_user_picture($activity->user->userid, $course, $activity->user->picture); print ''; if ($detail) { // activity icon $src = "$CFG->modpixpath/$activity->type/icon.gif"; print ''.$activity->type.' '; // link to activity $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$activity->instance"; print ''.$activity->name.' - '; } if (isteacher($course)) { // score (with link to attempt details) $href = "$CFG->wwwroot/mod/hotpot/review.php?hp=$activity->instance&attempt=".$activity->content->attemptid; print '('.hotpot_format_score($activity->content).') '; // attempt number print get_string('attempt', 'quiz').' - '.$activity->content->attempt.'
'; } // link to user $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$course"; print ''.$activity->user->fullname.' '; // time and date print ' - ' . userdate($activity->timestamp); // duration $duration = format_time($activity->content->timestart - $activity->content->timefinish); print "   ($duration)"; print "
"; } function hotpot_cron () { /// Function to be run periodically according to the moodle cron /// This function searches for things that need to be done, such /// as sending out mail, toggling flags etc ... global $CFG; return true; } function hotpot_grades($hotpotid) { /// Must return an array of grades for a given instance of this module, /// indexed by user. It also returns a maximum allowed grade. $hotpot = get_record('hotpot', 'id', $hotpotid); $return->grades = hotpot_get_grades($hotpot); $return->maxgrade = $hotpot->grade; return $return; } function hotpot_get_grades($hotpot, $user_ids='') { global $CFG; $grades = array(); $weighting = $hotpot->grade / 100; $precision = hotpot_get_precision($hotpot); // set the SQL string to determine the $grade $grade = ""; switch ($hotpot->grademethod) { case HOTPOT_GRADEMETHOD_HIGHEST: $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade"; break; case HOTPOT_GRADEMETHOD_AVERAGE: // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id) $grade = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade"; break; case HOTPOT_GRADEMETHOD_FIRST: if ($CFG->dbtype=='postgres7') { $grade = "MIN(timestart||'_'||(CASE WHEN (score IS NULL) THEN '' ELSE TRIM(ROUND(score * $weighting, $precision)) END)) AS grade"; } else { $grade = "MIN(CONCAT(timestart, '_', IF(score IS NULL, '', ROUND(score * $weighting, $precision)))) AS grade"; } break; case HOTPOT_GRADEMETHOD_LAST: if ($CFG->dbtype=='postgres7') { $grade = "MAX(timestart||'_'||(CASE WHEN (score IS NULL) THEN '' ELSE TRIM(ROUND(score * $weighting, $precision)) END)) AS grade"; } else { $grade = "MAX(CONCAT(timestart, '_', IF(score IS NULL, '', ROUND(score * $weighting, $precision)))) AS grade"; } break; } if ($grade) { $userid_condition = empty($user_ids) ? '' : "AND userid IN ($user_ids) "; $grades = get_records_sql_menu(" SELECT userid, $grade FROM {$CFG->prefix}hotpot_attempts WHERE timefinish>0 AND hotpot='$hotpot->id' $userid_condition GROUP BY userid "); if ($grades) { if ($hotpot->grademethod==HOTPOT_GRADEMETHOD_FIRST || $hotpot->grademethod==HOTPOT_GRADEMETHOD_LAST) { // remove left hand characters in $grade (up to and including the underscore) foreach ($grades as $userid=>$grade) { $grades[$userid] = substr($grades[$userid], strpos($grades[$userid], '_')+1); } } } } return $grades; } function hotpot_get_precision(&$hotpot) { return ($hotpot->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0; } function hotpot_get_participants($hotpotid) { //Must return an array of user ids who are participants //for a given instance of hotpot. Must include every user involved //in the instance, independient of his role (student, teacher, admin...) //See other modules as example. global $CFG; return get_records_sql(" SELECT DISTINCT u.id, u.id FROM {$CFG->prefix}user u, {$CFG->prefix}hotpot_attempts a WHERE u.id = a.userid AND a.hotpot = '$hotpotid' "); } function hotpot_scale_used ($hotpotid, $scaleid) { //This function returns if a scale is being used by one hotpot //it it has support for grading and scales. Commented code should be //modified if necessary. See forum, glossary or journal modules //as reference. $report = false; //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid"); // //if (!empty($rec) && !empty($scaleid)) { // $report = true; //} return $report; } ////////////////////////////////////////////////////////// /// Any other hotpot functions go here. /// Each of them must have a name that starts with hotpot function hotpot_add_attempt($hotpotid) { global $db, $CFG, $USER; $time = time(); switch (strtolower($CFG->dbtype)) { case 'mysql': $timefinish = "IF(a.timefinish IS NULL, '$time', a.timefinish)"; $clickreportid = "IF(a.clickreportid IS NULL, a.id, a.clickreportid)"; break; case 'postgres7': $timefinish = "WHEN(a.timefinish IS NULL) THEN '$time' ELSE a.timefinish"; $clickreportid = "WHEN(a.clickreportid IS NULL) THEN a.id ELSE a.clickreportid"; break; } // set all previous "in progress" attempts at this quiz to "abandoned" $db->Execute(" UPDATE {$CFG->prefix}hotpot_attempts as a SET a.timefinish = $timefinish, a.status = '".HOTPOT_STATUS_ABANDONED."', a.clickreportid = $clickreportid WHERE a.hotpot='$hotpotid' AND a.userid='$USER->id' AND a.status='".HOTPOT_STATUS_INPROGRESS."' "); // create and add new attempt record $attempt->hotpot = $hotpotid; $attempt->userid = $USER->id; $attempt->attempt = hotpot_get_next_attempt($hotpotid); $attempt->timestart = time(); return insert_record("hotpot_attempts", $attempt); } function hotpot_get_next_attempt($hotpotid) { global $USER; // get max attempt so far $i = count_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id'", 'MAX(attempt)'); return empty($i) ? 1 : ($i+1); } function hotpot_get_question_name($question) { $name = ''; if (isset($question->text)) { $name = hotpot_strings($question->text); } if (empty($name)) { $name = $question->name; } return $name; } function hotpot_strings($ids) { // array of ids of empty strings static $HOTPOT_EMPTYSTRINGS; if (!isset($HOTPOT_EMPTYSTRINGS)) { // first time only // get ids of empty strings $emptystrings = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0'); $HOTPOT_EMPTYSTRINGS = empty($emptystrings) ? array() : array_keys($emptystrings); } $strings = array(); if (!empty($ids)) { $ids = explode(',', $ids); foreach ($ids as $id) { if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) { $strings[] = hotpot_string($id); } } } return implode(',', $strings); } function hotpot_string($id) { return get_field('hotpot_strings', 'string', 'id', $id); } ////////////////////////////////////////////////////////////////////////////////////// /// the class definitions to handle XML trees // get the standard XML parser supplied with Moodle require_once("$CFG->libdir/xmlize.php"); // get the default class for hotpot quiz templates require_once("$CFG->hotpottemplate/default.php"); class hotpot_xml_tree { function hotpot_xml_tree($str, $xml_root='') { if (empty($str)) { $this->xml = array(); } else { $str = utf8_encode($str); $this->xml = xmlize($str, 0); } $this->xml_root = $xml_root; } function xml_value($tags, $more_tags="[0]['#']") { $tags = empty($tags) ? '' : "['".str_replace(",", "'][0]['#']['", $tags)."']"; eval('$value = &$this->xml'.$this->xml_root.$tags.$more_tags.';'); if (is_string($value)) { $value = utf8_decode($value); // decode angle brackets $value = strtr($value, array('<'=>'<', '>'=>'>')); // remove white space between , and parts // (so it doesn't get converted to
) $htmltags = '(' . 'TABLE|/?CAPTION|/?COL|/?COLGROUP|/?TBODY|/?TFOOT|/?THEAD|/?TD|/?TH|/?TR' . '|OL|UL|/?LI' . '|DL|/?DT|/?DD' . '|EMBED|OBJECT|APPLET|/?PARAM' //. '|SELECT|/?OPTION' //. '|FIELDSET|/?LEGEND' //. '|FRAMESET|/?FRAME' . ')' ; $search = '#(<'.$htmltags.'[^>]*'.'>)\s+'.'(?='.'<'.')#is'; $value = preg_replace($search, '\\1', $value); // replace remaining newlines with
$value = str_replace("\n", '
', $value); // encode unicode characters as HTML entities // (in particular, accented charaters that have not been encoded by HP) // unicode characetsr can be detected by checking the hex value of a character // 00 - 7F : ascii char (roman alphabet + punctuation) // 80 - BF : byte 2, 3 or 4 of a unicode char // C0 - DF : 1st byte of 2-byte char // E0 - EF : 1st byte of 3-byte char // F0 - FF : 1st byte of 4-byte char // if the string doesn't match the above, it might be // 80 - FF : single-byte, non-ascii char $search = '#('.'[\xc0-\xdf][\x80-\xbf]'.'|'.'[\xe0-\xef][\x80-\xbf]{2}'.'|'.'[\xf0-\xff][\x80-\xbf]{3}'.'|'.'[\x80-\xff]'.')#se'; $value = preg_replace($search, "hotpot_utf8_to_html_entity('\\1')", $value); // NOTICE // ====== // the following lines have been removed because // the final "preg_replace" takes several SECONDS to run // encode any orphaned angle brackets back to html entities //if (empty($this->tag_pattern)) { // $q = "'"; // single quote // $qq = '"'; // double quote // $this->tag_pattern = '<(([^>'.$q.$qq.']*)|('."{$q}[^$q]*$q".')|('."{$qq}[^$qq]*$qq".'))*>'; //} //$value = preg_replace('/<([^>]*'.$this->tag_pattern.')/', '<$1', $value); //$value = preg_replace('/('.$this->tag_pattern.'[^<]*)>/', '$1>', $value); } return $value; } function xml_values($tags) { $i = 0; $values = array(); while ($value = $this->xml_value($tags, "[$i]['#']")) { $values[$i++] = $value; } return $values; } function obj_value(&$obj, $name) { return is_object($obj) ? @$obj->$name : (is_array($obj) ? @$obj[$name] : NULL); } function encode_cdata(&$str, $tag) { // conversion tables static $HTML_ENTITIES = array( ''' => "'", '"' => '"', '<' => '<', '>' => '>', '&' => '&', ); static $ILLEGAL_STRINGS = array( "\r" => '', "\n" => '<br />', ']]>' => ']]>', ); // extract the $tag from the $str(ing), if possible $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is'; if (preg_match($pattern, $str, $matches)) { // encode problematic CDATA chars and strings $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS); // if there are any ampersands in "open text" // surround them by CDATA start and end markers // (and convert HTML entities to plain text) $search = '/>([^<]*&[^<]*)read_file = $read_file; $this->parse_xml = $parse_xml; $this->convert_urls = $convert_urls; $this->report_errors = $report_errors; $this->create_html = $create_html; // extract fields from $obj // course : the course id // reference : the filename within the files folder // location : "site" files folder or "course" files folder // navigation : type of navigation required in quiz // forceplugins : force Moodle compatible media players $this->course = $this->obj_value($obj, 'course'); $this->reference = $this->obj_value($obj, 'reference'); $this->location = $this->obj_value($obj, 'location'); $this->navigation = $this->obj_value($obj, 'navigation'); $this->forceplugins = $this->obj_value($obj, 'forceplugins'); // can't continue if there is no course or reference if (empty($this->course) || empty($this->reference)) { $this->error = get_string('error_nocourseorfilename', 'hotpot'); if ($this->report_errors) { error($this->error); } return; } $this->course_homeurl = "$CFG->wwwroot/course/view.php?id=$this->course"; // set filedir, filename and filepath switch ($this->location) { case HOTPOT_LOCATION_SITEFILES: $site = get_site(); $this->filedir = $site->id; break; case HOTPOT_LOCATION_COURSEFILES: default: $this->filedir = $this->course; break; } $this->filesubdir = dirname($this->reference); if ($this->filesubdir=='.') { $this->filesubdir = ''; } if ($this->filesubdir) { $this->filesubdir .= '/'; } $this->filename = basename($this->reference); $this->fileroot = "$CFG->dataroot/$this->filedir"; $this->filepath = "$this->fileroot/$this->reference"; // read the file, if required if ($this->read_file) { if (!file_exists($this->filepath) || !is_readable($this->filepath)) { $this->error = get_string('error_couldnotopensourcefile', 'hotpot', $this->filepath); if ($this->report_errors) { error($this->error, $this->course_homeurl); } return; } // read in the XML source $this->source = file_get_contents($this->filepath); // convert relative URLs to absolute URLs if ($this->convert_urls) { $this->hotpot_convert_relative_urls($this->source); } $this->html = ''; $this->quiztype = ''; $this->outputformat = 0; // is this an html file? if (preg_match('|\.html?$|', $this->filename)) { $this->filetype = 'html'; $this->html = &$this->source; // relative URLs in stylesheets $search = '|'.'(]*>)'.'(.*?)'.'()'.'|ise'; $replace = "stripslashes('\\1').hotpot_convert_stylesheets_urls('".$this->get_baseurl()."','".$this->reference."','\\2'.'\\3')"; $this->source = preg_replace($search, $replace, $this->source); // relative URLs in "PreloadImages(...);" $search = '|'.'(?<='.'PreloadImages'.'\('.')'."([^)]+?)".'(?='.'\);'.')'.'|se'; $replace = "hotpot_convert_preloadimages_urls('".$this->get_baseurl()."','".$this->reference."','\\1')"; $this->source = preg_replace($search, $replace, $this->source); // relative URLs in