diff --git a/mod/hotpot/README.TXT b/mod/hotpot/README.TXT index 411570779df..baf1654d342 100644 --- a/mod/hotpot/README.TXT +++ b/mod/hotpot/README.TXT @@ -1,4 +1,4 @@ -This is v2.1.11 of the HotPot module +This is v2.1.12 of the HotPot module This module allows teachers to administer Hot Potatoes and TexToys quizzes via Moodle. It has been tested on: - Hot Potatoes 6 diff --git a/mod/hotpot/lib.php b/mod/hotpot/lib.php index 9df8ec77673..ee321a818a5 100644 --- a/mod/hotpot/lib.php +++ b/mod/hotpot/lib.php @@ -1,2362 +1,2362 @@ -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); - set_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
'; + + 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