hotpotroot = $CFG->dirroot.DIRECTORY_SEPARATOR.'mod'.DIRECTORY_SEPARATOR.'hotpot'; define("HOTPOT_JS", "$CFG->wwwroot/mod/hotpot/hotpot-full.js"); define("HOTPOT_NO", "0"); define("HOTPOT_YES", "1"); 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", "11"); define("HOTPOT_OUTPUTFORMAT_V4", "12"); define("HOTPOT_OUTPUTFORMAT_V5", "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 => 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"), ); 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"); 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"), ); function hotpot_add_instance($hp) { /// Given an object containing all the necessary data, /// (defined by the form in mod.html) this function /// will create a new instance and return the id number /// of the new instance. hotpot_set_times($hp); return insert_record("hotpot", $hp); } function hotpot_update_instance($hp) { /// Given an object containing all the necessary data, /// (defined by the form in mod.html) this function /// will update an existing instance with new data. hotpot_set_times($hp); $hp->id = $hp->instance; return update_record("hotpot", $hp); } function hotpot_set_times(&$hp) { $time = time(); $hp->timecreated = $time; $hp->timemodified = $time; $hp->timeopen = make_timestamp( $hp->openyear, $hp->openmonth, $hp->openday, $hp->openhour, $hp->openminute, 0 ); $hp->timeclose = make_timestamp( $hp->closeyear, $hp->closemonth, $hp->closeday, $hp->closehour, $hp->closeminute, 0 ); } 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; 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_questions", "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) { if (isset($record->score)) { $score = $record->score; } else { $str = empty($record->timefinish) ? 'inprogress' : 'abandoned'; $score = ''.get_string($str, 'hotpot').''; } return $score; } 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.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?id=$id"; $name = " $record->name"; if ($record->count_attempts > 1) { $name >= " ($record->count_attempts)"; } $names[] = $name; } print '
Hot Potatoes Activity:
'.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.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)) { $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->details = $record->details; $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?id=$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->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0; // 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 ($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_participants($hotpotid) { //Must return an array of user records (all data) 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.* 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, $userid=NULL) { $userid = hotpot_get_userid($userid); hotpot_close_previous_attempts($hotpotid, $userid); $attempt->hotpot = $hotpotid; $attempt->userid = $userid; $attempt->attempt = hotpot_get_next_attempt($hotpotid, $userid); $attempt->timestart = time(); return insert_record("hotpot_attempts", $attempt); } function hotpot_close_previous_attempts($hotpotid, $userid=NULL, $time=NULL) { /// set previously unfinished attempts of this quiz by this user to "finished" if (empty($time)) { $time = time(); } $userid = hotpot_get_userid($userid); set_field("hotpot_attempts", "timefinish", $time, "hotpot", $hotpotid, "userid", $userid, "timefinish", 0); } function hotpot_get_next_attempt($hotpotid, $userid=NULL) { // get max attempt so far $i = count_records_select( 'hotpot_attempts', "hotpot='$hotpotid' AND userid='".hotpot_get_userid($userid)."'", 'MAX(attempt)' ); return empty($i) ? 1 : ($i+1); } function hotpot_get_userid($userid=NULL) { global $USER; return isset($userid) ? $userid : $USER->id; } 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 $emptystringids = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0'); $HOTPOT_EMPTYSTRINGS = empty($emptystringids) ? array() : array_keys($emptystringids); } $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.DIRECTORY_SEPARATOR.'xmlize.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 and replace newlines $value = strtr($value, array('<'=>'<', '>'=>'>', "\n"=>'
')); // 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 $HTML_ENTITIES = array( ''' => "'", '"' => '"', '<' => '<', '>' => '>', '&' => '&', ); $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 = '/>([^<]*&[^<]*)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)) { error('Could not create XML tree: missing course or reference'); } $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->filename = basename($this->reference); $this->filepath = $CFG->dataroot.DIRECTORY_SEPARATOR.$this->filedir.DIRECTORY_SEPARATOR.$this->reference; // try and open the file if (!file_exists($this->filepath) || !$fp = fopen($this->filepath, 'r')) { error('Could not open the XML source file "'.$this->filename.'"', $this->course_homeurl); } // read in the XML source and close the file $this->source = fread($fp, filesize($this->filepath)); fclose($fp); // convert relative URLs to absolute URLs $this->hotpot_convert_relative_urls($this->source); // encode "gap fill" text in JCloze exercise $this->encode_cdata($this->source, 'gap-fill'); // convert source to xml tree $this->hotpot_xml_tree($this->source); // initialize file type, quiz type and output format $this->html = ''; $this->filetype = ''; $this->quiztype = ''; $this->outputformat = 0; // link tag to , if necessary if (isset($this->xml['HTML'])) { $this->xml['html'] = &$this->xml['HTML']; } if (isset($this->xml['html'])) { $this->filetype = 'html'; // shortcut to source $s = &$this->source; // try to set the quiz type from phrases in the source if (strpos($s, 'QuizForm') && strpos($s, 'CheckForm') && strpos($s, 'CorrectAnswers')) { $this->outputformat = HOTPOT_OUTPUTFORMAT_V3; $this->quiztype = 'jmatch'; } else if (strpos($s, 'name="FeedbackFrame"') && strpos($s, 'name="CodeFrame"')) { $this->outputformat = HOTPOT_OUTPUTFORMAT_V3; $this->quiztype = strpos($s, 'QuizForm') ? 'jcb' : strpos($s, 'Cloze') ? 'jcloze' : strpos($s, 'Crossword') ? 'jcross' : strpos($s, 'QForm1') ? 'jquiz' : ''; } else if (strpos($s, 'function DynLayer')) { $this->outputformat = HOTPOT_OUTPUTFORMAT_V4; $this->quiztype = (strpos($s, 'QForm') && strpos($s, 'QForm.FB[QNum]')) ? 'jcb' : strpos($s, 'Cloze') ? 'jcloze' : strpos($s, 'Crossword') ? 'jcross' : strpos($s, 'ExCheck') ? 'jmatch' : (strpos($s, 'QForm') && strpos($s, 'QForm.Answer')) ? 'jquiz' : ''; } else if (strpos($s, 'name="TopFrame"') && strpos($s, 'name="BottomFrame"')) { $this->outputformat = HOTPOT_OUTPUTFORMAT_V5; $this->quiztype = (strpos($s, 'QForm') && strpos($s, 'FB_[QNum]_[ANum]')) ? 'jcb' : strpos($s, 'form name="Cloze"') ? 'jcloze' : strpos($s, 'AnswerForm') ? 'jcross' : (strpos($s, 'QForm') && strpos($s, 'sel[INum]')) ? 'jmatch' : strpos($s, 'ButtonForm') ? 'jmix' : (strpos($s, 'QForm[QNum]') && strpos($s, 'Buttons[QNum]')) ? 'jquiz' : ''; } else if (strpos($s, '
')) { $this->outputformat = HOTPOT_OUTPUTFORMAT_V6; $this->quiztype = strpos($s, 'jcb test') ? 'jcb' : strpos($s, '
') ? 'jcloze' : (strpos($s, 'GridDiv') || strpos($s, 'Clues')) ? 'jcross' : strpos($s, 'MatchDiv') ? 'jmatch' : strpos($s, 'SegmentDiv') ? 'jmix' : ((strpos($s, 'QForm') && strpos($s, 'QForm.Guess')) || strpos($s, 'Questions')) ? 'jquiz' : ''; } else if (strpos($s, '
')) { // TexToys $this->outputformat = HOTPOT_OUTPUTFORMAT_V6; $this->quiztype = strpos($s, 'var Words = new Array()') ? 'rhubarb' : strpos($s, 'var Segments = new Array()') ? 'sequitur' : ''; } else if (strpos($s, 'D = new Array')) { $this->outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS; // drag and drop (HP5 and HP6) $this->quiztype = (strpos($s, 'F = new Array')) ? 'jmatch' : (strpos($s, 'Drop = new Array')) ? 'jmix' : 0; } unset($s); } else { $this->filetype = 'xml'; $keys = array_keys($this->xml); foreach ($keys as $key) { if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) { $this->quiztype = strtolower($matches[2]); $this->xml_root = "['$key']['#']"; break; } } } // set the real output format from the requested output format $this->real_outputformat = $this->obj_value($obj, 'outputformat'); $this->draganddrop = ''; if ( empty($this->real_outputformat) || $this->real_outputformat==HOTPOT_OUTPUTFORMAT_BEST || empty($HOTPOT_OUTPUTFORMAT[$this->real_outputformat]) ) { // set the best output format for this browser // see http://jp2.php.net/function.get-browser if (function_exists('get_browser') && ini_get('browscap')) { $b = get_browser(); // apparently get_browser is a slow // so we should store the results in $this->browser } else { $ua = $_SERVER['HTTP_USER_AGENT']; $b = NULL; // store the results in $this->browser // [parent] => Firefox 0.9 // [platform] => WinXP // [browser] => Firefox // [version] => 0.9 // [majorver] => 0 // [minorver] => 9 } if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') { $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS; } else { $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6; } } if ($this->real_outputformat==HOTPOT_OUTPUTFORMAT_V6_PLUS) { if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') { $this->draganddrop = 'd'; // prefix for templates (can also be "f" ?) } $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6; } // set source directory $this->real_outputformat_txt = $HOTPOT_OUTPUTFORMAT[$this->real_outputformat]; $this->source_dir = $CFG->hotpotroot.DIRECTORY_SEPARATOR.$this->real_outputformat_txt; // set the output html $this->html = ''; if ($this->filetype=='html') { $this->html = &$this->source; } else { $method = $this->real_outputformat_txt.'_create_html'; if (method_exists($this, $method)) { eval('$this->'.$method.'();'); } else { error( $method.'Could not create quiz in "'.$this->real_outputformat_txt.'" format', $this->course_homeurl ); } } } // end constructor function function read_template($filename, $tag='temporary') { // create the file path $filepath = $this->source_dir.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.$filename; // try and open the template file if (!file_exists($filepath) || !$fp = fopen($filepath, "r")) { error( 'Could not open the '.$this->real_outputformat_txt.' template file "'.$filename.'"', $this->course_homeurl ); } // read in the template and close the file $this->$tag = fread($fp, filesize($filepath)); fclose($fp); // expand the blocks and strings in the template $this->expand_blocks($tag); $this->expand_strings($tag); if ($tag=='temporary') { $template = $this->$tag; $this->$tag = ''; return $template; } } function expand_blocks($tag) { // get block $names // [1] the full block name (including optional leading 'str' or 'incl') // [2] leading 'incl' or 'str' // [3] the real block name ([1] without [2]) $search = '/\[\/((incl|str)?(\w+))\]/'; preg_match_all($search, $this->$tag, $names); $i_max = count($names[0]); for ($i=0; $i<$i_max; $i++) { $method = $this->real_outputformat_txt.'_expand_'.$names[3][$i]; if (method_exists($this, $method)) { eval('$value=$this->'.$method.'();'); $search = '/\['.$names[1][$i].'\](.*?)\[\/'.$names[1][$i].'\]/s'; preg_match_all($search, $this->$tag, $blocks); $ii_max = count($blocks[0]); for ($ii=0; $ii<$ii_max; $ii++) { $replace = empty($value) ? '' : $blocks[1][$ii]; $this->$tag = str_replace($blocks[0][$ii], $replace, $this->$tag); } } else { error('Could not expand template block "'.$matches[4][$i].'"', $this->course_homeurl); //print 'Could not expand template block "'.$blockname.'" for '.$tag."
\n"; } } } function expand_strings($tag, $search='') { if (empty($search)) { // default $search $pattern $search = '/\[(?:bool|int|str)(\\w+)\]/'; } preg_match_all($search, $this->$tag, $matches); $i_max = count($matches[0]); for ($i=0; $i<$i_max; $i++) { $method = $this->real_outputformat_txt.'_expand_'.$matches[1][$i]; if (method_exists($this, $method)) { eval('$replace=$this->'.$method.'();'); $this->$tag = str_replace($matches[0][$i], $replace, $this->$tag); } } } function bool_value($tags, $more_tags="[0]['#']") { $value = $this->xml_value($tags, $more_tags); return empty($value) ? 'false' : 'true'; } function int_value($tags, $more_tags="[0]['#']") { return intval($this->xml_value($tags, $more_tags)); } function js_value($tags, $more_tags="[0]['#']", $convert_to_unicode=false) { return $this->js_safe($this->xml_value($tags, $more_tags), $convert_to_unicode); } function js_safe($str, $convert_to_unicode=false) { // encode a string for javascript // decode "<" and ">" - not necesary as it was done by xml_value() // $str = strtr($str, array('<' => '<', '>' => '>')); // escape single quotes and backslashes $str = strtr($str, array("'"=>"\\'", '\\'=>'\\\\')); // convert newlines (win = "\r\n", mac="\r", linix/unix="\n") $nl = '\\n'; // javascript newline $str = strtr($str, array("\r\n"=>$nl, "\r"=>$nl, "\n"=>$nl)); // convert (hex and decimal) html entities to unicode, if required if ($convert_to_unicode) { $str = preg_replace('|&#x([0-9A-F]+);|i', '\\u\\1', $str); $str = preg_replace('|&#(\d+);|e', "'\\u'.sprintf('%04X', '\\1')", $str); } return $str; } // ================================= // functions for v6 quizzes // ================================= function v6_create_html() { if (isset($_GET['css'])) { $this->css = ''; $this->read_template('hp6.cs_', 'css'); } else if (isset($_GET['js'])) { $this->js = ''; $this->read_template($this->draganddrop.$this->quiztype.'6.js_', 'js'); } else { $this->html = ''; $this->read_template($this->draganddrop.$this->quiztype.'6.ht_', 'html'); } // expand special strings, if any $pattern = ''; switch ($this->quiztype) { case 'jcloze': $pattern = '/\[(PreloadImageList)\]/'; break; case 'jcross': $pattern = '/\[(PreloadImageList|ShowHideClueList)\]/'; break; case 'jmatch': $pattern = '/\[(PreloadImageList|QsToShow|FixedArray|DragArray)\]/'; break; case 'jmix': $pattern = '/\[(PreloadImageList|SegmentArray|AnswerArray)\]/'; break; case 'jquiz': $pattern = '/\[(PreloadImageList|QsToShow)\]/'; break; } if (!empty($pattern)) { $this->expand_strings('html', $pattern); } } // captions and messages function v6_expand_AlsoCorrect() { return $this->xml_value("hotpot-config-file,$this->quiztype,also-correct"); } function v6_expand_CapitalizeFirst() { return $this->xml_value("hotpot-config-file,$this->quiztype,capitalize-first-letter"); } function v6_expand_CheckCaption() { return $this->xml_value('hotpot-config-file,global,check-caption'); } function v6_expand_CorrectIndicator() { return $this->xml_value('hotpot-config-file,global,correct-indicator'); } function v6_expand_Back() { return $this->xml_value('hotpot-config-file,global,include-back'); } function v6_expand_BackCaption() { return $this->xml_value('hotpot-config-file,global,back-caption'); } function v6_expand_ClickToAdd() { return $this->xml_value("hotpot-config-file,$this->quiztype,click-to-add"); } function v6_expand_Contents() { return $this->xml_value('hotpot-config-file,global,include-contents'); } function v6_expand_ContentsCaption() { return $this->xml_value('hotpot-config-file,global,contents-caption'); } function v6_expand_GuessCorrect() { return $this->js_value("hotpot-config-file,$this->quiztype,guess-correct"); } function v6_expand_GuessIncorrect() { return $this->js_value("hotpot-config-file,$this->quiztype,guess-incorrect"); } function v6_expand_Hint() { return $this->xml_value("hotpot-config-file,$this->quiztype,include-hint"); } function v6_expand_HintCaption() { return $this->xml_value('hotpot-config-file,global,hint-caption'); } function v6_expand_IncorrectIndicator() { return $this->xml_value('hotpot-config-file,global,incorrect-indicator'); } function v6_expand_LastQCaption() { return $this->xml_value('hotpot-config-file,global,last-q-caption'); } function v6_expand_NextCorrect() { $value = $this->xml_value("hotpot-config-file,$this->quiztype,next-correct-part"); if (empty($value)) { // jquiz $value = $this->xml_value("hotpot-config-file,$this->quiztype,next-correct-letter"); } return $value; } function v6_expand_NextEx() { return $this->xml_value('hotpot-config-file,global,include-next-ex'); } function v6_expand_NextExCaption() { return $this->xml_value('hotpot-config-file,global,next-ex-caption'); } function v6_expand_NextQCaption() { return $this->xml_value('hotpot-config-file,global,next-q-caption'); } function v6_expand_OKCaption() { return $this->xml_value('hotpot-config-file,global,ok-caption'); } function v6_expand_Restart() { return $this->xml_value("hotpot-config-file,$this->quiztype,include-restart"); } function v6_expand_RestartCaption() { return $this->xml_value('hotpot-config-file,global,restart-caption'); } function v6_expand_ShowAllQuestionsCaption() { return $this->xml_value('hotpot-config-file,global,show-all-questions-caption'); } function v6_expand_ShowOneByOneCaption() { return $this->xml_value('hotpot-config-file,global,show-one-by-one-caption'); } function v6_expand_TheseAnswersToo() { return $this->xml_value("hotpot-config-file,$this->quiztype,also-correct"); } function v6_expand_ThisMuch() { return $this->xml_value("hotpot-config-file,$this->quiztype,this-much-correct"); } function v6_expand_Undo() { return $this->xml_value("hotpot-config-file,$this->quiztype,include-undo"); } function v6_expand_UndoCaption() { return $this->xml_value('hotpot-config-file,global,undo-caption'); } function v6_expand_YourScoreIs() { return $this->xml_value('hotpot-config-file,global,your-score-is'); } // reading function v6_expand_Reading() { return $this->xml_value('data,reading,include-reading'); } function v6_expand_ReadingText() { $title = $this->v6_expand_ReadingTitle(); $value = $this->xml_value('data,reading,reading-text'); $value = empty($value) ? '' : ('
'.$value.'
'); return $title.$value; } function v6_expand_ReadingTitle() { $value = $this->xml_value('data,reading,reading-title'); return empty($value) ? '' : ('

'.$value.'

'); } // timer function v6_expand_Timer() { return $this->xml_value('data,timer,include-timer'); } function v6_expand_JSTimer() { return $this->read_template('hp6timer.js_'); } function v6_expand_Seconds() { return $this->xml_value('data,timer,seconds'); } // send results function v6_expand_SendResults() { return $this->xml_value("hotpot-config-file,$this->quiztype,send-email"); } function v6_expand_JSSendResults() { return $this->read_template('hp6sendresults.js_'); } function v6_expand_FormMailURL() { return $this->xml_value('hotpot-config-file,global,formmail-url'); } function v6_expand_EMail() { return $this->xml_value('hotpot-config-file,global,email'); } function v6_expand_NamePlease() { return $this->js_value('hotpot-config-file,global,name-please'); } // preload images function v6_expand_PreloadImages() { $value = $this->v6_expand_PreloadImageList(); return empty($value) ? false : true; } function v6_expand_PreloadImageList() { // check it has not been set already if (!isset($this->PreloadImageList)) { // the list of image urls $list = array(); // extract tags $img_tag = htmlspecialchars('|<img.*?src="(.*?)".*?>|is'); if (preg_match_all($img_tag, $this->source, $matches)) { $list = $matches[1]; // remove duplicates $list = array_unique($list); } // convert to comma delimited string $this->PreloadImageList = empty($list) ? '' : "'".implode(',', $list)."'"; } return $this->PreloadImageList; } // html files (all quiz types) function v6_expand_PlainTitle() { return $this->xml_value('data,title'); } function v6_expand_ExerciseSubtitle() { return $this->xml_value("hotpot-config-file,$this->quiztype,exercise-subtitle"); } function v6_expand_Instructions() { return $this->xml_value("hotpot-config-file,$this->quiztype,instructions"); } function v6_expand_DublinCoreMetadata() { return '' . ''."\n" . ''."\n" . ''."\n" ; } function v6_expand_FullVersionInfo() { global $CFG; require_once($CFG->hotpotroot.DIRECTORY_SEPARATOR.'version.php'); // set $module return $this->xml_value('version').'.x (Moodle '.$CFG->release.', hotpot-module '.$this->obj_value($module, 'release').')'; } function v6_expand_HeaderCode() { return $this->xml_value('hotpot-config-file,global,header-code'); } function v6_expand_StyleSheet() { $this->read_template('hp6.cs_', 'css'); return $this->css; } // stylesheet (hp6.cs_) function v6_expand_PageBGColor() { return $this->xml_value('hotpot-config-file,global,page-bg-color'); } function v6_expand_GraphicURL() { return $this->xml_value('hotpot-config-file,global,graphic-url'); } function v6_expand_ExBGColor() { return $this->xml_value('hotpot-config-file,global,ex-bg-color'); } function v6_expand_FontFace() { return $this->xml_value('hotpot-config-file,global,font-face'); } function v6_expand_FontSize() { return $this->xml_value('hotpot-config-file,global,font-size'); } function v6_expand_TextColor() { return $this->xml_value('hotpot-config-file,global,text-color'); } function v6_expand_TitleColor() { return $this->xml_value('hotpot-config-file,global,title-color'); } function v6_expand_LinkColor() { return $this->xml_value('hotpot-config-file,global,link-color'); } function v6_expand_VLinkColor() { return $this->xml_value('hotpot-config-file,global,vlink-color'); } function v6_expand_NavTextColor() { return $this->xml_value('hotpot-config-file,global,page-bg-color'); } function v6_expand_NavBarColor() { return $this->xml_value('hotpot-config-file,global,nav-bar-color'); } function v6_expand_NavLightColor() { $color = $this->xml_value('hotpot-config-file,global,nav-bar-color'); return $this->get_halfway_color($color, '#ffffff'); } function v6_expand_NavShadeColor() { $color = $this->xml_value('hotpot-config-file,global,nav-bar-color'); return $this->get_halfway_color($color, '#000000'); } function v6_expand_FuncLightColor() { // top-left of buttons $color = $this->xml_value('hotpot-config-file,global,ex-bg-color'); return $this->get_halfway_color($color, '#ffffff'); } function v6_expand_FuncShadeColor() { // bottom right of buttons $color = $this->xml_value('hotpot-config-file,global,ex-bg-color'); return $this->get_halfway_color($color, '#000000'); } function get_halfway_color($x, $y) { // returns the $color that is half way between $x and $y $color = $x; // default $rgb = '/^\#?([0-9a-f])([0-9a-f])([0-9a-f])$/i'; $rrggbb = '/^\#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i'; if (( preg_match($rgb, $x, $x_matches) || preg_match($rrggbb, $x, $x_matches) ) && ( preg_match($rgb, $y, $y_matches) || preg_match($rrggbb, $y, $y_matches) )) { $color = '#'; for ($i=1; $i<=3; $i++) { $x_dec = hexdec($x_matches[$i]); $y_dec = hexdec($y_matches[$i]); $color .= sprintf('%02x', min($x_dec, $y_dec) + abs($x_dec-$y_dec)/2); } } return $color; } // navigation buttons function v6_expand_NavButtons() { $back = $this->v6_expand_Back(); $next_ex = $this->v6_expand_NextEx(); $contents = $this->v6_expand_Contents(); return (empty($back) && empty($next_ex) && empty($contents) ? false : true); } function v6_expand_NavBarJS() { return $this->v6_expand_NavButtons(); } // js files (all quiz types) function v6_expand_JSBrowserCheck() { return $this->read_template('hp6browsercheck.js_'); } function v6_expand_JSButtons() { return $this->read_template('hp6buttons.js_'); } function v6_expand_JSCard() { return $this->read_template('hp6card.js_'); } function v6_expand_JSCheckShortAnswer() { return $this->read_template('hp6checkshortanswer.js_'); } function v6_expand_JSHotPotNet() { return $this->read_template('hp6hotpotnet.js_'); } function v6_expand_JSShowMessage() { return $this->read_template('hp6showmessage.js_'); } function v6_expand_JSUtilities() { return $this->read_template('hp6utilities.js_'); } // js files function v6_expand_JSJCloze6() { return $this->read_template('jcloze6.js_'); } function v6_expand_JSJCross6() { return $this->read_template('jcross6.js_'); } function v6_expand_JSJMatch6() { return $this->read_template('jmatch6.js_'); } function v6_expand_JSJMix6() { return $this->read_template('jmix6.js_'); } function v6_expand_JSJQuiz6() { return $this->read_template('jquiz6.js_'); } // drag and drop function v6_expand_JSDJMatch6() { return $this->read_template('djmatch6.js_'); } function v6_expand_JSDJMix6() { return $this->read_template('djmix6.js_'); } // what are these for? function v6_expand_JSFJMatch6() { return $this->read_template('fjmatch6.js_'); } function v6_expand_JSFJMix6() { return $this->read_template('fjmix6.js_'); } // jmatch6.js_ function v6_expand_ShuffleQs() { return $this->bool_value("hotpot-config-file,$this->quiztype,shuffle-questions"); } function v6_expand_QsToShow() { $i = $this->xml_value("hotpot-config-file,$this->quiztype,show-limited-questions"); if ($i) { $i = $this->xml_value("hotpot-config-file,$this->quiztype,questions-to-show"); } if (empty($i)) { $i = 0; switch ($this->quiztype) { case 'jmatch': $values = $this->xml_values('data,matching-exercise,pair'); $i = count($values); break; case 'jquiz': while ($this->xml_value('data,questions,question-record', "[$i]['#']['question'][0]['#']")) { $i++; } break; } // end switch } return $i; } function v6_expand_MatchDivItems() { $str = ''; $this->get_jmatch_items($l_items=array(), $r_items = array()); $l_keys = $this->shuffle_jmatch_items($l_items); $r_keys = $this->shuffle_jmatch_items($r_items); $options = ''; foreach ($r_keys as $key) { $options .= ''."\n"; } foreach ($l_keys as $key) { $str .= ''.$l_items[$key]['text'][0]['#'].''; $str .= ''; $str .= ''; } return $str; } // jmix6.js_ function v6_expand_Punctuation() { $tags = 'data,jumbled-order-exercise'; $chars = array_merge( $this->jmix_Punctuation("$tags,main-order,segment"), $this->jmix_Punctuation("$tags,alternate") ); $chars = array_unique($chars); $chars = implode('', $chars); $chars = $this->js_safe($chars, true); return $chars; } function jmix_Punctuation($tags) { $chars = array(); // all punctutation except '&#;' (because they are used in html entities) $ENTITIES = $this->jmix_encode_punctuation('!"$%'."'".'()*+,-./:<=>?@[\]^_`{|}~'); $pattern = "/&#x([0-9A-F]+);/i"; $i = 0; // get next segment (or alternate answer) while ($value = $this->xml_value($tags, "[$i]['#']")) { // convert low-ascii punctuation to entities $value = strtr($value, $ENTITIES); // extract all hex HTML entities if (preg_match_all($pattern, $value, $matches)) { // loop through hex entities $m_max = count($matches[0]); for ($m=0; $m<$m_max; $m++) { // convert to hex number eval('$hex=0x'.$matches[1][$m].';'); // is this a punctuation character? if ( ($hex>=0x0020 && $hex<=0x00BF) || // ascii punctuation ($hex>=0x2000 && $hex<=0x206F) || // general punctuation ($hex>=0x3000 && $hex<=0x303F) || // CJK punctuation ($hex>=0xFE30 && $hex<=0xFE4F) || // CJK compatability ($hex>=0xFE50 && $hex<=0xFE6F) || // small form variants ($hex>=0xFF00 && $hex<=0xFF40) || // halfwidth and fullwidth forms (1) ($hex>=0xFF5B && $hex<=0xFF65) || // halfwidth and fullwidth forms (2) ($hex>=0xFFE0 && $hex<=0xFFEE) // halfwidth and fullwidth forms (3) ) { // add this character $chars[] = $matches[0][$m]; } } } $i++; } return $chars; } function v6_expand_OpenPunctuation() { $tags = 'data,jumbled-order-exercise'; $chars = array_merge( $this->jmix_OpenPunctuation("$tags,main-order,segment"), $this->jmix_OpenPunctuation("$tags,alternate") ); $chars = array_unique($chars); $chars = implode('', $chars); $chars = $this->js_safe($chars, true); return $chars; } function jmix_OpenPunctuation($tags) { $chars = array(); // unicode punctuation designations (pi="initial quote", ps="open") // http://www.sql-und-xml.de/unicode-database/pi.html // http://www.sql-und-xml.de/unicode-database/ps.html $pi = '0022|0027|00AB|2018|201B|201C|201F|2039'; $ps = '0028|005B|007B|0F3A|0F3C|169B|201A|201E|2045|207D|208D|2329|23B4|2768|276A|276C|276E|2770|2772|2774|27E6|27E8|27EA|2983|2985|2987|2989|298B|298D|298F|2991|2993|2995|2997|29D8|29DA|29FC|3008|300A|300C|300E|3010|3014|3016|3018|301A|301D|FD3E|FE35|FE37|FE39|FE3B|FE3D|FE3F|FE41|FE43|FE47|FE59|FE5B|FE5D|FF08|FF3B|FF5B|FF5F|FF62'; $pattern = "/(&#x($pi|$ps);)/i"; $ENTITIES = $this->jmix_encode_punctuation('"'."'".'(<[{'); $i = 0; while ($value = $this->xml_value($tags, "[$i]['#']")) { $value = strtr($value, $ENTITIES); if (preg_match_all($pattern, $value, $matches)) { $chars = array_merge($chars, $matches[0]); } $i++; } return $chars; } function jmix_encode_punctuation($str) { $ENTITIES = array(); $i_max = strlen($str); for ($i=0; $i<$i_max; $i++) { $ENTITIES[$str{$i}] = '&#x'.sprintf('%04X', ord($str{$i})).';'; } return $ENTITIES; } function v6_expand_ExerciseTitle() { return $this->xml_value('data,title'); } // Jmix specials function v6_expand_SegmentArray() { $segments = $this->xml_values('data,jumbled-order-exercise,main-order,segment'); $this->seed_random_number_generator(); $keys = array_keys($segments); shuffle($keys); $str = ''; for($i=0; $ijs_safe($segments[$keys[$i]])."';\n"; $str .= "Segments[$i][1] = ".($keys[$i]+1).";\n"; $str .= "Segments[$i][2] = 0;\n"; } return $str; } function v6_expand_AnswerArray() { $segments = $this->xml_values('data,jumbled-order-exercise,main-order,segment'); $alternates = $this->xml_values('data,jumbled-order-exercise,alternate'); $i = 0; $pattern = ''; $str = 'Answers['.$i++.'] = new Array('; for($ii=0; $iixml_value("hotpot-config-file,$this->quiztype,remaining-words"); } function v6_expand_TimesUp() { return $this->xml_value('hotpot-config-file,global,times-up'); } // nav bar function v6_expand_NavBar() { $tag = 'navbar'; $this->read_template('hp6navbar.ht_', $tag); return $this->$tag; } function v6_expand_TopNavBar() { return $this->v6_expand_NavBar(); } function v6_expand_BottomNavBar() { return $this->v6_expand_NavBar(); } function v6_expand_NextExURL() { return $this->xml_value("hotpot-config-file,$this->quiztype,next-ex-url"); } // hp6navbar.ht_ function v6_expand_NavBarID() { return ''; // what's this?; } function v6_expand_ContentsURL() { return $this->xml_value('hotpot-config-file,global,contents-url'); } // conditional blocks function v6_expand_ShowAnswer() { return $this->xml_value("hotpot-config-file,$this->quiztype,include-show-answer"); } function v6_expand_Slide() { return true; // whats's this (JMatch drag and drop) } // specials (JMatch) function v6_expand_FixedArray() { $str = ''; $this->get_jmatch_items($l_items=array(), $r_items = array()); foreach ($l_items as $i=>$item) { $str .= "F[$i] = new Array();\n"; $str .= "F[$i][0] = '".$this->js_safe($item['text'][0]['#'], true)."';\n"; $str .= "F[$i][1] = ".($i+1).";\n"; } return $str; } function v6_expand_DragArray() { $str = ''; $this->get_jmatch_items($l_items=array(), $r_items = array()); foreach ($r_items as $i=>$item) { $str .= "D[$i] = new Array();\n"; $str .= "D[$i][0] = '".$this->js_safe($item['text'][0]['#'], true)."';\n"; $str .= "D[$i][1] = ".($i+1).";\n"; $str .= "D[$i][2] = 0;\n"; } return $str; } function get_jmatch_items(&$l_items, &$r_items) { $i = 0; while( ($l_item = $this->xml_value('data,matching-exercise,pair',"[$i]['#']['left-item'][0]['#']")) && ($r_item = $this->xml_value('data,matching-exercise,pair',"[$i]['#']['right-item'][0]['#']")) ) { $l_items[] = $l_item; $r_items[] = $r_item; $i++; } } function shuffle_jmatch_items(&$items) { // get moveable items $moveable_keys = array(); for($i=0; $iseed_random_number_generator(); shuffle($moveable_keys); $keys = array(); for($i=0, $ii=0; $iquiztype) { case 'jcloze': $str .= "I = new Array();\n"; $tags = 'data,gap-fill,question-record'; while (($question="[$q]['#']") && $this->xml_value($tags, $question)) { $a = 0; $aa = 0; while (($answer=$question."['answer'][$a]['#']") && $this->xml_value($tags, $answer)) { $text = $this->js_value($tags, $answer."['text'][0]['#']", true); if ($text) { if ($aa==0) { // first time only $str .= "I[$q] = new Array();\n"; $str .= "I[$q][1] = new Array();\n"; } $str .= "I[$q][1][$aa] = new Array();\n"; $str .= "I[$q][1][$aa][0] = '$text';\n"; $aa++; } $a++; } // add clue, if any answers were found if ($aa) { $clue = $this->js_value($tags, $question."['clue'][0]['#']", true); $str .= "I[$q][2]='$clue';\n"; } $q++; } break; case 'jquiz': $str .= "I=new Array();\n"; $tags = 'data,questions,question-record'; while (($question="[$q]['#']") && $this->xml_value($tags, $question)) { $question_type = $this->int_value($tags, $question."['question-type'][0]['#']"); $weighting = $this->int_value($tags, $question."['weighting'][0]['#']"); $clue = $this->js_value($tags, $question."['clue'][0]['#']", true); $answers = $question."['answers'][0]['#']"; $a = 0; $aa = 0; while (($answer = $answers."['answer'][$a]['#']") && $this->xml_value($tags, $answer)) { $text = $this->js_value($tags, $answer."['text'][0]['#']", true); $feedback = $this->js_value($tags, $answer."['feedback'][0]['#']", true); $correct = $this->int_value($tags, $answer."['correct'][0]['#']", true); $percent = $this->int_value($tags, $answer."['percent-correct'][0]['#']", true); $include = $this->int_value($tags, $answer."['include-in-mc-options'][0]['#']", true); if ($text) { if ($aa==0) { // first time only $str .= "I[$q]=new Array();\n"; $str .= "I[$q][0]=$weighting;\n"; $str .= "I[$q][1]='$clue';\n"; $str .= "I[$q][2]='".($question_type-1)."';\n"; $str .= "I[$q][3]=new Array();\n"; } $str .= "I[$q][3][$aa]=new Array('$text','$feedback',$correct,$percent,$include);\n"; $aa++; } $a++; } $q++; } break; } return $str; } function v6_expand_ClozeBody() { $str = ''; $q = 0; $tags = 'data,gap-fill'; while ($text = $this->xml_value($tags, "[0]['#'][$q]")) { $str .= $text; if (($question="[$q]['#']") && $this->xml_value("$tags,question-record", $question)) { $str .= ''; } $q++; } return $str; } // JCloze quiztype function v6_expand_WordList() { return $this->xml_value("hotpot-config-file,$this->quiztype,include-word-list"); } function v6_expand_Keypad() { $str = ''; if ($this->bool_value("hotpot-config-file,$this->quiztype,include-keypad")) { // these characters must always be in the keypad $chars = array(); $this->add_keypad_chars($chars, $this->xml_value('hotpot-config-file,global,keypad-characters')); // append other characters used in the answers $tags = ''; switch ($this->quiztype) { case 'jcloze': $tags = 'data,gap-fill,question-record'; break; case 'jquiz': $tags = 'data,questions,question-record'; break; } if ($tags) { $q = 0; while (($question="[$q]['#']") && $this->xml_value($tags, $question)) { if ($this->quiztype=='jquiz') { $answers = $question."['answers'][0]['#']"; } else { $answers = $question; } $a = 0; while (($answer=$answers."['answer'][$a]['#']") && $this->xml_value($tags, $answer)) { $this->add_keypad_chars($chars, $this->xml_value($tags, $answer."['text'][0]['#']")); $a++; } $q++; } } // remove duplicate characters and sort $chars = array_unique($chars); usort($chars, "hotpot_sort_keypad_chars"); // create keypad buttons for each character $str .= '
'; foreach ($chars as $char) { $str .= ""; } $str .= '
'; } return $str; } function add_keypad_chars(&$chars, $text) { if (preg_match_all('|&[^;]+;|i', $text, $more_chars)) { $chars = array_merge($chars, $more_chars[0]); } } function v6_expand_Correct() { return $this->xml_value("hotpot-config-file,$this->quiztype,guesses-correct"); } function v6_expand_Incorrect() { return $this->xml_value("hotpot-config-file,$this->quiztype,guesses-incorrect"); } function v6_expand_GiveHint() { return $this->xml_value("hotpot-config-file,$this->quiztype,next-correct-letter"); } function v6_expand_CaseSensitive() { return $this->xml_value("hotpot-config-file,$this->quiztype,case-sensitive"); } // JCross quiztype function v6_expand_CluesAcrossLabel() { return $this->xml_value("hotpot-config-file,$this->quiztype,clues-across"); } function v6_expand_CluesDownLabel() { $this->xml_value("hotpot-config-file,$this->quiztype,clues-down"); return ''; } function v6_expand_EnterCaption() { return $this->xml_value("hotpot-config-file,$this->quiztype,enter-caption"); } function v6_expand_ShowHideClueList() { $value = $this->xml_value("hotpot-config-file,$this->quiztype,include-clue-list"); return empty($value) ? ' style="display: none;"' : ''; } // JCross specials function v6_expand_CluesDown() { return $this->v6_expand_jcross_clues('D'); } function v6_expand_CluesAcross() { return $this->v6_expand_jcross_clues('A'); } function v6_expand_jcross_clues($direction) { // $direction: A(cross) or D(own) $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0); $i = 0; // clue index; $str = ''; for($r=0; $r<=$r_max; $r++) { for($c=0; $c<=$c_max; $c++) { $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max); $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max); if ($aword || $dword) { $i++; // increment clue index // get the definition for this word $def = ''; $word = ($direction=='A') ? $aword : $dword; $clues = $this->xml_values('data,crossword,clues,item'); foreach ($clues as $clue) { if ($clue['word'][0]['#']==$word) { $def = $clue['def'][0]['#']; $def = strtr($def, array('<'=>'<', '>'=>'>', "\n"=>'
')); break; } } if (!empty($def)) { $str .= ''.$i.'. '.$def.''; } } } } return $str; } // jcross6.js_ function v6_expand_LetterArray() { $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0); $str = ''; for($r=0; $r<=$r_max; $r++) { $str .= "L[$r] = new Array("; for($c=0; $c<=$c_max; $c++) { $str .= ($c>0 ? ',' : '')."'".$this->js_safe($row[$r]['cell'][$c]['#'], true)."'"; } $str .= ");\n"; } return $str; } function v6_expand_GuessArray() { $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0); $str = ''; for($r=0; $r<=$r_max; $r++) { $str .= "G[$r] = new Array('".str_repeat("','", $c_max)."');\n"; } return $str; } function v6_expand_ClueNumArray() { $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0); $i = 0; // clue index $str = ''; for($r=0; $r<=$r_max; $r++) { $str .= "CL[$r] = new Array("; for($c=0; $c<=$c_max; $c++) { if ($c>0) { $str .= ','; } $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max); $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max); if (empty($aword) && empty($dword)) { $str .= 0; } else { $i++; // increment the clue index $str .= $i; } } $str .= ");\n"; } return $str; } function v6_expand_GridBody() { $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0); $i = 0; // clue index; $str = ''; for($r=0; $r<=$r_max; $r++) { $str .= ''; for($c=0; $c<=$c_max; $c++) { if (empty($row[$r]['cell'][$c]['#'])) { $str .= ' '; } else { $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max); $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max); if (empty($aword) && empty($dword)) { $str .= ' '; } else { $i++; // increment clue index $str .= ''.$i.'   '; } } } $str .= ''; } return $str; } function v6_get_jcross_grid(&$row, &$r_max, &$c_max) { $row = $this->xml_values('data,crossword,grid,row'); $r_max = 0; $c_max = 0; if (isset($row) && is_array($row)) { for($r=0; $rget_jcross_word($row, $r, $r_max, $c, $c_max, true); } return $str; } function get_jcross_aword(&$row, $r, $r_max, $c, $c_max) { $str = ''; if (($c==0 || empty($row[$r]['cell'][$c-1]['#'])) && $c<$c_max && !empty($row[$r]['cell'][$c+1]['#'])) { $str = $this->get_jcross_word($row, $r, $r_max, $c, $c_max, false); } return $str; } function get_jcross_word(&$row, $r, $r_max, $c, $c_max, $go_down=false) { $str = ''; while ($r<=$r_max && $c<=$c_max && !empty($row[$r]['cell'][$c]['#'])) { $str .= $row[$r]['cell'][$c]['#']; if ($go_down) { $r++; } else { $c++; } } return $str; } // specials (JQuiz) function v6_expand_QuestionOutput() { $str = ''; $str .= '
    '."\n"; $q = 0; $tags = 'data,questions,question-record'; while (($question="[$q]['#']") && $this->xml_value($tags, $question)) { // get question $question_text = $this->xml_value($tags, $question."['question'][0]['#']"); $question_type = $this->xml_value($tags, $question."['question-type'][0]['#']"); // check we have a question if ($question_text && $question_type) { $str .= '