Incorrect variable name used for parameter.
[moodle-linuxchix.git] / mod / hotpot / lib.php
blob28dacc265e3e64704c7661c6ded61954d5f03229
1 <?PHP // $Id$
3 /// Library of functions and constants for module hotpot
5 $CFG->hotpotroot = $CFG->dirroot.DIRECTORY_SEPARATOR.'mod'.DIRECTORY_SEPARATOR.'hotpot';
7 define("HOTPOT_JS", "$CFG->wwwroot/mod/hotpot/hotpot-full.js");
9 define("HOTPOT_NO", "0");
10 define("HOTPOT_YES", "1");
12 define("HOTPOT_LOCATION_COURSEFILES", "0");
13 define("HOTPOT_LOCATION_SITEFILES", "1");
15 $HOTPOT_LOCATION = array (
16 HOTPOT_LOCATION_COURSEFILES => get_string("coursefiles"),
17 HOTPOT_LOCATION_SITEFILES => get_string("sitefiles"),
20 define("HOTPOT_OUTPUTFORMAT_BEST", "1");
21 define("HOTPOT_OUTPUTFORMAT_V3", "11");
22 define("HOTPOT_OUTPUTFORMAT_V4", "12");
23 define("HOTPOT_OUTPUTFORMAT_V5", "13");
24 define("HOTPOT_OUTPUTFORMAT_V6", "14");
25 define("HOTPOT_OUTPUTFORMAT_V6_PLUS", "15");
26 define("HOTPOT_OUTPUTFORMAT_FLASH", "20");
27 define("HOTPOT_OUTPUTFORMAT_MOBILE", "30");
29 $HOTPOT_OUTPUTFORMAT = array (
30 HOTPOT_OUTPUTFORMAT_BEST => get_string("outputformat_best", "hotpot"),
31 HOTPOT_OUTPUTFORMAT_V6_PLUS => get_string("outputformat_v6_plus", "hotpot"),
32 HOTPOT_OUTPUTFORMAT_V6 => get_string("outputformat_v6", "hotpot"),
33 // HOTPOT_OUTPUTFORMAT_V5 => get_string("outputformat_v5", "hotpot"),
34 // HOTPOT_OUTPUTFORMAT_V4 => get_string("outputformat_v4", "hotpot"),
35 // HOTPOT_OUTPUTFORMAT_V3 => get_string("outputformat_v3", "hotpot"),
36 // HOTPOT_OUTPUTFORMAT_FLASH => get_string("outputformat_flash", "hotpot"),
37 // HOTPOT_OUTPUTFORMAT_MOBILE => get_string("outputformat_mobile", "hotpot"),
40 $HOTPOT_OUTPUTFORMAT_DIR = array (
41 HOTPOT_OUTPUTFORMAT_V6_PLUS => 'v6',
42 HOTPOT_OUTPUTFORMAT_V6 => 'v6',
43 // HOTPOT_OUTPUTFORMAT_V5 => 'v5',
44 // HOTPOT_OUTPUTFORMAT_V4 => 'v4',
45 // HOTPOT_OUTPUTFORMAT_V3 => 'v3',
46 // HOTPOT_OUTPUTFORMAT_FLASH => 'flash',
47 // HOTPOT_OUTPUTFORMAT_MOBILE => 'mobile',
50 define("HOTPOT_NAVIGATION_BAR", "1");
51 define("HOTPOT_NAVIGATION_FRAME", "2");
52 define("HOTPOT_NAVIGATION_IFRAME", "3");
53 define("HOTPOT_NAVIGATION_BUTTONS", "4");
54 define("HOTPOT_NAVIGATION_GIVEUP", "5");
55 define("HOTPOT_NAVIGATION_NONE", "6");
57 $HOTPOT_NAVIGATION = array (
58 HOTPOT_NAVIGATION_BAR => get_string("navigation_bar", "hotpot"),
59 HOTPOT_NAVIGATION_FRAME => get_string("navigation_frame", "hotpot"),
60 HOTPOT_NAVIGATION_IFRAME => get_string("navigation_iframe", "hotpot"),
61 HOTPOT_NAVIGATION_BUTTONS => get_string("navigation_buttons", "hotpot"),
62 HOTPOT_NAVIGATION_GIVEUP => get_string("navigation_give_up", "hotpot"),
63 HOTPOT_NAVIGATION_NONE => get_string("navigation_none", "hotpot"),
66 define("HOTPOT_JCB", "1");
67 define("HOTPOT_JCLOZE", "2");
68 define("HOTPOT_JCROSS", "3");
69 define("HOTPOT_JMATCH", "4");
70 define("HOTPOT_JMIX", "5");
71 define("HOTPOT_JQUIZ", "6");
72 define("HOTPOT_TEXTOYS_RHUBARB", "7");
73 define("HOTPOT_TEXTOYS_SEQUITUR", "8");
75 define("HOTPOT_JQUIZ_MULTICHOICE", "1");
76 define("HOTPOT_JQUIZ_SHORTANSWER", "2");
77 define("HOTPOT_JQUIZ_HYBRID", "3");
78 define("HOTPOT_JQUIZ_MULTISELECT", "4");
80 define("HOTPOT_GRADEMETHOD_HIGHEST", "1");
81 define("HOTPOT_GRADEMETHOD_AVERAGE", "2");
82 define("HOTPOT_GRADEMETHOD_FIRST", "3");
83 define("HOTPOT_GRADEMETHOD_LAST", "4");
85 $HOTPOT_GRADEMETHOD = array (
86 HOTPOT_GRADEMETHOD_HIGHEST => get_string("gradehighest", "quiz"),
87 HOTPOT_GRADEMETHOD_AVERAGE => get_string("gradeaverage", "quiz"),
88 HOTPOT_GRADEMETHOD_FIRST => get_string("attemptfirst", "quiz"),
89 HOTPOT_GRADEMETHOD_LAST => get_string("attemptlast", "quiz"),
92 function hotpot_add_instance($hp) {
93 /// Given an object containing all the necessary data,
94 /// (defined by the form in mod.html) this function
95 /// will create a new instance and return the id number
96 /// of the new instance.
98 hotpot_set_times($hp);
99 return insert_record("hotpot", $hp);
103 function hotpot_update_instance($hp) {
104 /// Given an object containing all the necessary data,
105 /// (defined by the form in mod.html) this function
106 /// will update an existing instance with new data.
108 hotpot_set_times($hp);
109 $hp->id = $hp->instance;
111 return update_record("hotpot", $hp);
114 function hotpot_set_times(&$hp) {
115 $time = time();
117 $hp->timecreated = $time;
118 $hp->timemodified = $time;
120 $hp->timeopen = make_timestamp(
121 $hp->openyear, $hp->openmonth, $hp->openday,
122 $hp->openhour, $hp->openminute, 0
124 $hp->timeclose = make_timestamp(
125 $hp->closeyear, $hp->closemonth, $hp->closeday,
126 $hp->closehour, $hp->closeminute, 0
130 function hotpot_delete_instance($id) {
131 /// Given an ID of an instance of this module,
132 /// this function will permanently delete the instance
133 /// and any data that depends on it.
135 $result = false;
136 if (delete_records("hotpot", "id", "$id")) {
137 $result = true;
138 if ($attempts = get_records_select("hotpot_attempts", "hotpot='$id'")) {
139 $ids = implode(',', array_keys($attempts));
140 delete_records_select("hotpot_attempts", "id IN ($ids)");
141 delete_records_select("hotpot_questions", "attempt IN ($ids)");
142 delete_records_select("hotpot_responses", "attempt IN ($ids)");
145 return $result;
147 function hotpot_delete_and_notify($table, $select, $strtable) {
148 $count = max(0, count_records_select($table, $select));
149 if ($count) {
150 delete_records_select($table, $select);
151 $count -= max(0, count_records_select($table, $select));
152 if ($count) {
153 notify(get_string("deleted")." $count x $strtable");
158 function hotpot_user_complete($course, $user, $mod, $hp) {
159 /// Print a detailed representation of what a user has done with
160 /// a given particular instance of this module, for user activity reports.
162 $report = hotpot_user_outline($course, $user, $mod, $hp);
163 if (empty($report)) {
164 print get_string("noactivity", "hotpot");
165 } else {
166 $date = userdate($report->time, get_string('strftimerecentfull'));
167 print $report->info.' '.get_string('mostrecently').': '.$date;
169 return true;
172 function hotpot_user_outline($course, $user, $mod, $hp) {
173 /// Return a small object with summary information about what a
174 /// user has done with a given particular instance of this module
175 /// Used for user activity reports.
176 /// $report->time = the time they did it
177 /// $report->info = a short text description
179 $report = NULL;
180 if ($records = get_records_select("hotpot_attempts", "hotpot='$hp->id' AND userid='$user->id'", "timestart ASC", "*")) {
181 $scores = array();
182 foreach ($records as $record){
183 if (empty($report->time)) {
184 $report->time = $record->timestart;
186 $scores[] = hotpot_format_score($record);
188 if (empty($scores)) {
189 $report->time = 0;
190 $report->info = get_string('noactivity', 'hotpot');
191 } else {
192 $report->info = get_string('score', 'quiz').': '.implode(', ', $scores);
195 return $report;
198 function hotpot_format_score($record) {
199 if (isset($record->score)) {
200 $score = $record->score;
201 } else {
202 $str = empty($record->timefinish) ? 'inprogress' : 'abandoned';
203 $score = '<i>'.get_string($str, 'hotpot').'</i>';
205 return $score;
208 function hotpot_print_recent_activity($course, $isteacher, $timestart) {
209 /// Given a course and a time, this module should find recent activity
210 /// that has occurred in hotpot activities and print it out.
211 /// Return true if there was output, or false is there was none.
213 global $CFG;
215 $result = false;
216 if($isteacher){
218 $records = get_records_sql("
219 SELECT
220 h.id AS id,
221 h.name AS name,
222 COUNT(*) AS count_attempts
223 FROM
224 {$CFG->prefix}hotpot AS h,
225 {$CFG->prefix}hotpot_attempts AS a
226 WHERE
227 h.course = $course->id
228 AND h.id = a.hotpot
229 AND a.starttime > $timestart
230 GROUP BY
231 h.id, h.name
232 ");
233 // note that PostGreSQL requires h.name in the GROUP BY clause
235 if($records) {
237 $names = array();
238 foreach ($records as $id => $record){
239 $href = $CFG->wwwroot.'/mod/hotpot/view.php?id='.$id;
240 $name = "&nbsp;<a href=\"$href\">$record->name</a>";
241 if ($record->count_attempts > 1) {
242 $name .= " ($record->count_attempts)";
244 $names[] = $name;
247 print_headline(get_string('modulenameplural', 'hotpot').':');
248 echo '<div class="head"><div class="name">'.implode('<br />', $names).'</div></div>';
250 $result = true;
253 return $result; // True if anything was printed, otherwise false
256 function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") {
257 // Returns all quizzes since a given time.
259 global $CFG;
261 // If $cmid or $userid are specified, then this restricts the results
262 $cm_select = empty($cmid) ? "" : " AND cm.id = '$cmid'";
263 $user_select = empty($userid) ? "" : " AND u.id = '$userid'";
265 $records = get_records_sql("
266 SELECT
267 a.*,
268 h.name, h.course,
269 cm.instance, cm.section,
270 u.firstname, u.lastname, u.picture
271 FROM
272 {$CFG->prefix}hotpot_attempts AS a,
273 {$CFG->prefix}hotpot AS h,
274 {$CFG->prefix}course_modules AS cm,
275 {$CFG->prefix}user AS u
276 WHERE
277 a.timefinish > '$sincetime'
278 AND a.userid = u.id $user_select
279 AND a.hotpot = h.id $cm_select
280 AND cm.instance = h.id
281 AND cm.course = '$courseid'
282 AND h.course = cm.course
283 ORDER BY
284 a.timefinish ASC
287 if (!empty($records)) {
288 foreach ($records as $record) {
289 if (empty($groupid) || ismember($groupid, $record->userid)) {
291 unset($activity);
293 $activity->type = "hotpot";
294 $activity->defaultindex = $index;
295 $activity->instance = $record->hotpot;
297 $activity->name = $record->name;
298 $activity->section = $record->section;
300 $activity->content->attemptid = $record->id;
301 $activity->content->attempt = $record->attempt;
302 $activity->content->score = $record->score;
303 $activity->content->details = $record->details;
304 $activity->content->timestart = $record->timestart;
305 $activity->content->timefinish = $record->timefinish;
307 $activity->user->userid = $record->userid;
308 $activity->user->fullname = fullname($record);
309 $activity->user->picture = $record->picture;
311 $activity->timestamp = $record->timefinish;
313 $activities[] = $activity;
315 $index++;
317 } // end foreach
321 function hotpot_print_recent_mod_activity($activity, $course, $detail=false) {
322 /// Basically, this function prints the results of "hotpot_get_recent_activity"
324 global $CFG, $THEME, $USER;
326 print '<table border="0" cellpadding="3" cellspacing="0">';
328 print '<tr><td bgcolor="'.$THEME->cellcontent2.'" class="forumpostpicture" width="35" valign="top">';
329 print_user_picture($activity->user->userid, $course, $activity->user->picture);
330 print '</td><td width="100%"><font size="2">';
332 if ($detail) {
333 // activity icon
334 $src = "$CFG->modpixpath/$activity->type/icon.gif";
335 print '<img src="'.$src.'" height="16" width="16" alt="'.$activity->type.'" /> ';
337 // link to activity
338 $href = "$CFG->wwwroot/mod/hotpot/view.php?id=$activity->instance";
339 print '<a href="'.$href.'">'.$activity->name.'</a> - ';
341 if (isteacher($course)) {
342 // score (with link to attempt details)
343 $href = "$CFG->wwwroot/mod/hotpot/review.php?hp=$activity->instance&attempt=".$activity->content->attemptid;
344 print '<a href="'.$href.'">('.hotpot_format_score($activity->content).')</a> ';
346 // attempt number
347 print get_string('attempt', 'quiz').' - '.$activity->content->attempt.'<br />';
350 // link to user
351 $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$course";
352 print '<a href="'.$href.'">'.$activity->user->fullname.'</a> ';
354 // time and date
355 print ' - ' . userdate($activity->timestamp);
357 // duration
358 $duration = format_time($activity->content->timestart - $activity->content->timefinish);
359 print " &nbsp; ($duration)";
361 print "</font></td></tr>";
362 print "</table>";
365 function hotpot_cron () {
366 /// Function to be run periodically according to the moodle cron
367 /// This function searches for things that need to be done, such
368 /// as sending out mail, toggling flags etc ...
370 global $CFG;
372 return true;
375 function hotpot_grades($hotpotid) {
376 /// Must return an array of grades for a given instance of this module,
377 /// indexed by user. It also returns a maximum allowed grade.
379 $hotpot = get_record('hotpot', 'id', $hotpotid);
380 $return->grades = hotpot_get_grades($hotpot);
381 $return->maxgrade = $hotpot->grade;
383 return $return;
385 function hotpot_get_grades($hotpot, $user_ids='') {
386 global $CFG;
388 $grades = array();
390 $weighting = $hotpot->grade / 100;
391 $precision = ($hotpot->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0;
393 // set the SQL string to determine the $grade
394 $grade = "";
395 switch ($hotpot->grademethod) {
396 case HOTPOT_GRADEMETHOD_HIGHEST:
397 $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade";
398 break;
399 case HOTPOT_GRADEMETHOD_AVERAGE:
400 // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id)
401 $grade = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade";
402 break;
403 case HOTPOT_GRADEMETHOD_FIRST:
404 if ($CFG->dbtype=='postgres7') {
405 $grade = "MIN(timestart||'_'||(CASE WHEN (score IS NULL) THEN '' ELSE TRIM(ROUND(score * $weighting, $precision)) END)) AS grade";
406 } else {
407 $grade = "MIN(CONCAT(timestart, '_', IF(score IS NULL, '', ROUND(score * $weighting, $precision)))) AS grade";
409 break;
410 case HOTPOT_GRADEMETHOD_LAST:
411 if ($CFG->dbtype=='postgres7') {
412 $grade = "MAX(timestart||'_'||(CASE WHEN (score IS NULL) THEN '' ELSE TRIM(ROUND(score * $weighting, $precision)) END)) AS grade";
413 } else {
414 $grade = "MAX(CONCAT(timestart, '_', IF(score IS NULL, '', ROUND(score * $weighting, $precision)))) AS grade";
416 break;
419 if ($grade) {
420 $userid_condition = empty($user_ids) ? '' : "AND userid IN ($user_ids) ";
422 $grades = get_records_sql_menu("
423 SELECT userid, $grade
424 FROM {$CFG->prefix}hotpot_attempts
425 WHERE timefinish>0 AND hotpot='$hotpot->id' $userid_condition
426 GROUP BY userid
429 if ($hotpot->grademethod==HOTPOT_GRADEMETHOD_FIRST || $hotpot->grademethod==HOTPOT_GRADEMETHOD_LAST) {
430 // remove left hand characters in $grade (up to and including the underscore)
431 foreach ($grades as $userid=>$grade) {
432 $grades[$userid] = substr($grades[$userid], strpos($grades[$userid], '_')+1);
437 return $grades;
439 function hotpot_get_participants($hotpotid) {
440 //Must return an array of user records (all data) who are participants
441 //for a given instance of hotpot. Must include every user involved
442 //in the instance, independient of his role (student, teacher, admin...)
443 //See other modules as example.
444 global $CFG;
446 return get_records_sql("
447 SELECT DISTINCT
449 FROM
450 {$CFG->prefix}user u,
451 {$CFG->prefix}hotpot_attempts a
452 WHERE
453 u.id = a.userid
454 AND a.hotpot = '$hotpotid'
458 function hotpot_scale_used ($hotpotid, $scaleid) {
459 //This function returns if a scale is being used by one hotpot
460 //it it has support for grading and scales. Commented code should be
461 //modified if necessary. See forum, glossary or journal modules
462 //as reference.
464 $report = false;
466 //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid");
468 //if (!empty($rec) && !empty($scaleid)) {
469 // $report = true;
472 return $report;
475 //////////////////////////////////////////////////////////////////////////////////////
476 /// Any other hotpot functions go here. Each of them must have a name that
477 /// starts with hotpot
480 function hotpot_add_attempt($hotpotid, $userid=NULL) {
481 $userid = hotpot_get_userid($userid);
483 hotpot_close_previous_attempts($hotpotid, $userid);
485 $attempt->hotpot = $hotpotid;
486 $attempt->userid = $userid;
487 $attempt->attempt = hotpot_get_next_attempt($hotpotid, $userid);
488 $attempt->timestart = time();
490 return insert_record("hotpot_attempts", $attempt);
492 function hotpot_close_previous_attempts($hotpotid, $userid=NULL, $time=NULL) {
493 /// set previously unfinished attempts of this quiz by this user to "finished"
494 if (empty($time)) {
495 $time = time();
497 $userid = hotpot_get_userid($userid);
498 set_field("hotpot_attempts", "timefinish", $time, "hotpot", $hotpotid, "userid", $userid, "timefinish", 0);
500 function hotpot_get_next_attempt($hotpotid, $userid=NULL) {
501 // get max attempt so far
502 $i = count_records_select(
503 'hotpot_attempts',
504 "hotpot='$hotpotid' AND userid='".hotpot_get_userid($userid)."'",
505 'MAX(attempt)'
507 return empty($i) ? 1 : ($i+1);
509 function hotpot_get_userid($userid=NULL) {
510 global $USER;
511 return isset($userid) ? $userid : $USER->id;
513 function hotpot_get_question_name($question) {
514 $name = '';
515 if (isset($question->text)) {
516 $name =hotpot_strings($question->text);
518 if (empty($name)) {
519 $name = $question->name;
521 return $name;
523 function hotpot_strings($ids) {
525 // array of ids of empty strings
526 static $HOTPOT_EMPTYSTRINGS;
528 if (!isset($HOTPOT_EMPTYSTRINGS)) { // first time only
529 $emptystringids = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0');
530 $HOTPOT_EMPTYSTRINGS = empty($emptystringids) ? array() : array_keys($emptystringids);
533 $strings = array();
534 if (!empty($ids)) {
535 $ids = explode(',', $ids);
536 foreach ($ids as $id) {
537 if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) {
538 $strings[] = hotpot_string($id);
542 return implode(',', $strings);
544 function hotpot_string($id) {
545 return get_field('hotpot_strings', 'string', 'id', $id);
548 //////////////////////////////////////////////////////////////////////////////////////
549 /// the class definitions to handle XML trees
551 // get the standard XML parser supplied with Moodle
552 require_once($CFG->libdir.DIRECTORY_SEPARATOR.'xmlize.php');
554 class hotpot_xml_tree {
555 function hotpot_xml_tree($str, $xml_root='') {
556 if (empty($str)) {
557 $this->xml = array();
558 } else {
559 $str = utf8_encode($str);
560 $this->xml = xmlize($str, 0);
562 $this->xml_root = $xml_root;
564 function xml_value($tags, $more_tags="[0]['#']") {
565 $tags = empty($tags) ? '' : "['".str_replace(",", "'][0]['#']['", $tags)."']";
566 eval('$value = &$this->xml'.$this->xml_root.$tags.$more_tags.';');
567 if (is_string($value)) {
568 $value = utf8_decode($value);
570 // decode angle brackets and replace newlines
571 $value = strtr($value, array('&#x003C;'=>'<', '&#x003E;'=>'>', "\n"=>'<br />'));
573 // encode any orphaned angle brackets back to html entities
574 if (empty($this->tag_pattern)) {
575 $q = "'"; // single quote
576 $qq = '"'; // double quote
577 $this->tag_pattern = '<(([^>'.$q.$qq.']*)|('."{$q}[^$q]*$q".')|('."{$qq}[^$qq]*$qq".'))*>';
579 $value = preg_replace('/<([^>]*'.$this->tag_pattern.')/', '&lt;$1', $value);
580 $value = preg_replace('/('.$this->tag_pattern.'[^<]*)>/', '$1&gt;', $value);
582 return $value;
584 function xml_values($tags) {
585 $i = 0;
586 $values = array();
587 while ($value = $this->xml_value($tags, "[$i]['#']")) {
588 $values[$i++] = $value;
590 return $values;
592 function obj_value(&$obj, $name) {
593 return is_object($obj) ? @$obj->$name : (is_array($obj) ? @$obj[$name] : NULL);
595 function encode_cdata(&$str, $tag) {
597 // conversion tables
598 $HTML_ENTITIES = array(
599 '&apos;' => "'",
600 '&quot;' => '"',
601 '&lt;' => '<',
602 '&gt;' => '>',
603 '&amp;' => '&',
605 $ILLEGAL_STRINGS = array(
606 "\r" => '',
607 "\n" => '&lt;br /&gt;',
608 ']]>' => '&#93;&#93;&#62;',
611 // extract the $tag from the $str(ing), if possible
612 $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is';
613 if (preg_match($pattern, $str, $matches)) {
615 // encode problematic CDATA chars and strings
616 $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS);
618 // if there are any ampersands in "open text"
619 // surround them by CDATA start and end markers
620 // (and convert HTML entities to plain text)
621 $search = '/>([^<]*&[^<]*)</e';
622 $replace = '"><![CDATA[".strtr("$1", $HTML_ENTITIES)."]]><"';
623 $matches[2] = preg_replace($search, $replace, $matches[2]);
625 $str = $matches[1].$matches[2].$matches[3];
630 class hotpot_xml_quiz extends hotpot_xml_tree {
632 // constructor function
633 function hotpot_xml_quiz(&$obj) {
634 // obj can be the $_GET array or a form object/array
636 global $CFG, $HOTPOT_OUTPUTFORMAT, $HOTPOT_OUTPUTFORMAT_DIR;
638 // check xmlize functions are available
639 if (! function_exists("xmlize")) {
640 error('xmlize functions are not available');
643 // extract fields from $obj
644 // course : the course id
645 // reference : the filename within the files folder for this course
646 // navigation : type of navigation required in quiz
647 // forceplugins : force Moodle compatible media players
648 $this->course = $this->obj_value($obj, 'course');
649 $this->reference = $this->obj_value($obj, 'reference');
650 $this->location = $this->obj_value($obj, 'location');
651 $this->navigation = $this->obj_value($obj, 'navigation');
652 $this->forceplugins = $this->obj_value($obj, 'forceplugins');
654 // can't continue if there is no course or reference
655 if (empty($this->course) || empty($this->reference)) {
656 error('Could not create XML tree: missing course or reference');
659 $this->course_homeurl = "$CFG->wwwroot/course/view.php?id=$this->course";
661 // set filedir, filename and filepath
662 switch ($this->location) {
663 case HOTPOT_LOCATION_SITEFILES:
664 $site = get_site();
665 $this->filedir = $site->id;
666 break;
668 case HOTPOT_LOCATION_COURSEFILES:
669 default:
670 $this->filedir = $this->course;
671 break;
673 $this->filename = basename($this->reference);
674 $this->filepath = $CFG->dataroot.DIRECTORY_SEPARATOR.$this->filedir.DIRECTORY_SEPARATOR.$this->reference;
676 // try and open the file
677 if (!file_exists($this->filepath) || !$fp = fopen($this->filepath, 'r')) {
678 error('Could not open the XML source file &quot;'.$this->filename.'&quot;', $this->course_homeurl);
681 // read in the XML source and close the file
682 $this->source = fread($fp, filesize($this->filepath));
683 fclose($fp);
685 // convert relative URLs to absolute URLs
686 $this->hotpot_convert_relative_urls($this->source);
688 // encode "gap fill" text in JCloze exercise
689 $this->encode_cdata($this->source, 'gap-fill');
691 // convert source to xml tree
692 $this->hotpot_xml_tree($this->source);
694 // initialize file type, quiz type and output format
695 $this->html = '';
696 $this->filetype = '';
697 $this->quiztype = '';
698 $this->outputformat = 0;
700 // link <HTML> tag to <html>, if necessary
701 if (isset($this->xml['HTML'])) {
702 $this->xml['html'] = &$this->xml['HTML'];
705 if (isset($this->xml['html'])) {
707 $this->filetype = 'html';
709 // shortcut to source
710 $s = &$this->source;
712 // try to set the quiz type from phrases in the source
714 if (strpos($s, 'QuizForm') && strpos($s, 'CheckForm') && strpos($s, 'CorrectAnswers')) {
715 $this->outputformat = HOTPOT_OUTPUTFORMAT_V3;
716 $this->quiztype = 'jmatch';
718 } else if (strpos($s, 'name="FeedbackFrame"') && strpos($s, 'name="CodeFrame"')) {
719 $this->outputformat = HOTPOT_OUTPUTFORMAT_V3;
720 $this->quiztype = strpos($s, 'QuizForm') ? 'jcb' : strpos($s, 'Cloze') ? 'jcloze' : strpos($s, 'Crossword') ? 'jcross' : strpos($s, 'QForm1') ? 'jquiz' : '';
722 } else if (strpos($s, 'function DynLayer')) {
723 $this->outputformat = HOTPOT_OUTPUTFORMAT_V4;
724 $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' : '';
726 } else if (strpos($s, 'name="TopFrame"') && strpos($s, 'name="BottomFrame"')) {
727 $this->outputformat = HOTPOT_OUTPUTFORMAT_V5;
728 $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' : '';
730 } else if (strpos($s, '<div id="MainDiv" class="StdDiv">')) {
731 $this->outputformat = HOTPOT_OUTPUTFORMAT_V6;
732 $this->quiztype = strpos($s, 'jcb test') ? 'jcb' : strpos($s, '<div id="ClozeDiv">') ? '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' : '';
734 } else if (strpos($s, '<div class="StdDiv">')) { // TexToys
735 $this->outputformat = HOTPOT_OUTPUTFORMAT_V6;
736 $this->quiztype = strpos($s, 'var Words = new Array()') ? 'rhubarb' : strpos($s, 'var Segments = new Array()') ? 'sequitur' : '';
738 } else if (strpos($s, 'D = new Array')) {
739 $this->outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS; // drag and drop (HP5 and HP6)
740 $this->quiztype = (strpos($s, 'F = new Array')) ? 'jmatch' : (strpos($s, 'Drop = new Array')) ? 'jmix' : 0;
743 unset($s);
745 } else {
746 $this->filetype = 'xml';
748 $keys = array_keys($this->xml);
749 foreach ($keys as $key) {
750 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
751 $this->quiztype = strtolower($matches[2]);
752 $this->xml_root = "['$key']['#']";
753 break;
759 // set the real output format from the requested output format
760 $this->real_outputformat = $this->obj_value($obj, 'outputformat');
761 $this->draganddrop = '';
762 if (
763 empty($this->real_outputformat) ||
764 $this->real_outputformat==HOTPOT_OUTPUTFORMAT_BEST ||
765 empty($HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat])
767 // set the best output format for this browser
768 // see http://jp2.php.net/function.get-browser
769 if (function_exists('get_browser') && ini_get('browscap')) {
770 $b = get_browser();
771 // apparently get_browser is a slow
772 // so we should store the results in $this->browser
773 } else {
774 $ua = $_SERVER['HTTP_USER_AGENT'];
775 $b = NULL;
776 // store the results in $this->browser
777 // [parent] => Firefox 0.9
778 // [platform] => WinXP
779 // [browser] => Firefox
780 // [version] => 0.9
781 // [majorver] => 0
782 // [minorver] => 9
784 if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
785 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS;
786 } else {
787 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
791 if ($this->real_outputformat==HOTPOT_OUTPUTFORMAT_V6_PLUS) {
792 if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
793 $this->draganddrop = 'd'; // prefix for templates (can also be "f" ?)
795 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
798 // set template source directory
799 $this->template_dir = $HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat];
800 $this->template_dir_path = $CFG->hotpotroot.DIRECTORY_SEPARATOR.$this->template_dir.DIRECTORY_SEPARATOR.'source';
802 // set the output html
803 $this->html = '';
804 if ($this->filetype=='html') {
805 $this->html = &$this->source;
807 } else {
808 $method = $this->template_dir.'_create_html';
809 if (method_exists($this, $method)) {
810 eval('$this->'.$method.'();');
811 } else {
812 error(
813 $method.'Could not create quiz in &quot;'.$this->template_dir.'&quot; format',
814 $this->course_homeurl
819 } // end constructor function
821 function read_template($filename, $tag='temporary') {
822 // create the file path to the template
823 $filepath = $this->template_dir_path.DIRECTORY_SEPARATOR.$filename;
825 // try and open the template file
826 if (!file_exists($filepath) || !$fp = fopen($filepath, "r")) {
827 error(
828 'Could not open the '.$this->template_dir.' template file &quot;'.$filename.'&quot;',
829 $this->course_homeurl
833 // read in the template and close the file
834 $this->$tag = fread($fp, filesize($filepath));
835 fclose($fp);
837 // expand the blocks and strings in the template
838 $this->expand_blocks($tag);
839 $this->expand_strings($tag);
841 if ($tag=='temporary') {
842 $template = $this->$tag;
843 $this->$tag = '';
844 return $template;
848 function expand_blocks($tag) {
849 // get block $names
850 // [1] the full block name (including optional leading 'str' or 'incl')
851 // [2] leading 'incl' or 'str'
852 // [3] the real block name ([1] without [2])
853 $search = '/\[\/((incl|str)?(\w+))\]/';
854 preg_match_all($search, $this->$tag, $names);
856 $i_max = count($names[0]);
857 for ($i=0; $i<$i_max; $i++) {
859 $method = $this->template_dir.'_expand_'.$names[3][$i];
860 if (method_exists($this, $method)) {
862 eval('$value=$this->'.$method.'();');
864 $search = '/\['.$names[1][$i].'\](.*?)\[\/'.$names[1][$i].'\]/s';
865 preg_match_all($search, $this->$tag, $blocks);
867 $ii_max = count($blocks[0]);
868 for ($ii=0; $ii<$ii_max; $ii++) {
870 $replace = empty($value) ? '' : $blocks[1][$ii];
871 $this->$tag = str_replace($blocks[0][$ii], $replace, $this->$tag);
873 } else {
874 error('Could not expand template block &quot;'.$matches[4][$i].'&quot;', $this->course_homeurl);
875 //print 'Could not expand template block &quot;'.$blockname.'&quot; for '.$tag."<br />\n";
879 function expand_strings($tag, $search='') {
880 if (empty($search)) {
881 // default $search $pattern
882 $search = '/\[(?:bool|int|str)(\\w+)\]/';
884 preg_match_all($search, $this->$tag, $matches);
886 $i_max = count($matches[0]);
887 for ($i=0; $i<$i_max; $i++) {
889 $method = $this->template_dir.'_expand_'.$matches[1][$i];
890 if (method_exists($this, $method)) {
892 eval('$replace=$this->'.$method.'();');
893 $this->$tag = str_replace($matches[0][$i], $replace, $this->$tag);
898 function bool_value($tags, $more_tags="[0]['#']") {
899 $value = $this->xml_value($tags, $more_tags);
900 return empty($value) ? 'false' : 'true';
902 function int_value($tags, $more_tags="[0]['#']") {
903 return intval($this->xml_value($tags, $more_tags));
905 function js_value($tags, $more_tags="[0]['#']", $convert_to_unicode=false) {
906 return $this->js_safe($this->xml_value($tags, $more_tags), $convert_to_unicode);
908 function js_safe($str, $convert_to_unicode=false) {
909 // encode a string for javascript
911 // decode "<" and ">" - not necesary as it was done by xml_value()
912 // $str = strtr($str, array('&#x003C;' => '<', '&#x003E;' => '>'));
914 // escape single quotes and backslashes
915 $str = strtr($str, array("'"=>"\\'", '\\'=>'\\\\'));
917 // convert newlines (win = "\r\n", mac="\r", linix/unix="\n")
918 $nl = '\\n'; // javascript newline
919 $str = strtr($str, array("\r\n"=>$nl, "\r"=>$nl, "\n"=>$nl));
921 // convert (hex and decimal) html entities to unicode, if required
922 if ($convert_to_unicode) {
923 $str = preg_replace('|&#x([0-9A-F]+);|i', '\\u\\1', $str);
924 $str = preg_replace('|&#(\d+);|e', "'\\u'.sprintf('%04X', '\\1')", $str);
927 return $str;
930 // =================================
931 // functions for v6 quizzes
932 // =================================
934 function v6_create_html() {
936 if (isset($_GET['css'])) {
937 $this->css = '';
938 $this->read_template('hp6.cs_', 'css');
940 } else if (isset($_GET['js'])) {
941 $this->js = '';
942 $this->read_template($this->draganddrop.$this->quiztype.'6.js_', 'js');
944 } else {
945 $this->html = '';
946 $this->read_template($this->draganddrop.$this->quiztype.'6.ht_', 'html');
949 // expand special strings, if any
950 $pattern = '';
951 switch ($this->quiztype) {
952 case 'jcloze':
953 $pattern = '/\[(PreloadImageList)\]/';
954 break;
955 case 'jcross':
956 $pattern = '/\[(PreloadImageList|ShowHideClueList)\]/';
957 break;
958 case 'jmatch':
959 $pattern = '/\[(PreloadImageList|QsToShow|FixedArray|DragArray)\]/';
960 break;
961 case 'jmix':
962 $pattern = '/\[(PreloadImageList|SegmentArray|AnswerArray)\]/';
963 break;
964 case 'jquiz':
965 $pattern = '/\[(PreloadImageList|QsToShow)\]/';
966 break;
968 if (!empty($pattern)) {
969 $this->expand_strings('html', $pattern);
973 // captions and messages
975 function v6_expand_AlsoCorrect() {
976 return $this->xml_value("hotpot-config-file,$this->quiztype,also-correct");
978 function v6_expand_CapitalizeFirst() {
979 return $this->xml_value("hotpot-config-file,$this->quiztype,capitalize-first-letter");
981 function v6_expand_CheckCaption() {
982 return $this->xml_value('hotpot-config-file,global,check-caption');
984 function v6_expand_CorrectIndicator() {
985 return $this->xml_value('hotpot-config-file,global,correct-indicator');
987 function v6_expand_Back() {
988 return $this->xml_value('hotpot-config-file,global,include-back');
990 function v6_expand_BackCaption() {
991 return $this->xml_value('hotpot-config-file,global,back-caption');
993 function v6_expand_ClickToAdd() {
994 return $this->xml_value("hotpot-config-file,$this->quiztype,click-to-add");
996 function v6_expand_Contents() {
997 return $this->xml_value('hotpot-config-file,global,include-contents');
999 function v6_expand_ContentsCaption() {
1000 return $this->xml_value('hotpot-config-file,global,contents-caption');
1002 function v6_expand_GuessCorrect() {
1003 return $this->js_value("hotpot-config-file,$this->quiztype,guess-correct");
1005 function v6_expand_GuessIncorrect() {
1006 return $this->js_value("hotpot-config-file,$this->quiztype,guess-incorrect");
1008 function v6_expand_Hint() {
1009 return $this->xml_value("hotpot-config-file,$this->quiztype,include-hint");
1011 function v6_expand_HintCaption() {
1012 return $this->xml_value('hotpot-config-file,global,hint-caption');
1014 function v6_expand_IncorrectIndicator() {
1015 return $this->xml_value('hotpot-config-file,global,incorrect-indicator');
1017 function v6_expand_LastQCaption() {
1018 return $this->xml_value('hotpot-config-file,global,last-q-caption');
1020 function v6_expand_NextCorrect() {
1021 $value = $this->xml_value("hotpot-config-file,$this->quiztype,next-correct-part");
1022 if (empty($value)) { // jquiz
1023 $value = $this->xml_value("hotpot-config-file,$this->quiztype,next-correct-letter");
1025 return $value;
1027 function v6_expand_NextEx() {
1028 return $this->xml_value('hotpot-config-file,global,include-next-ex');
1030 function v6_expand_NextExCaption() {
1031 return $this->xml_value('hotpot-config-file,global,next-ex-caption');
1033 function v6_expand_NextQCaption() {
1034 return $this->xml_value('hotpot-config-file,global,next-q-caption');
1036 function v6_expand_OKCaption() {
1037 return $this->xml_value('hotpot-config-file,global,ok-caption');
1039 function v6_expand_Restart() {
1040 return $this->xml_value("hotpot-config-file,$this->quiztype,include-restart");
1042 function v6_expand_RestartCaption() {
1043 return $this->xml_value('hotpot-config-file,global,restart-caption');
1045 function v6_expand_ShowAllQuestionsCaption() {
1046 return $this->xml_value('hotpot-config-file,global,show-all-questions-caption');
1048 function v6_expand_ShowOneByOneCaption() {
1049 return $this->xml_value('hotpot-config-file,global,show-one-by-one-caption');
1051 function v6_expand_TheseAnswersToo() {
1052 return $this->xml_value("hotpot-config-file,$this->quiztype,also-correct");
1054 function v6_expand_ThisMuch() {
1055 return $this->xml_value("hotpot-config-file,$this->quiztype,this-much-correct");
1057 function v6_expand_Undo() {
1058 return $this->xml_value("hotpot-config-file,$this->quiztype,include-undo");
1060 function v6_expand_UndoCaption() {
1061 return $this->xml_value('hotpot-config-file,global,undo-caption');
1063 function v6_expand_YourScoreIs() {
1064 return $this->xml_value('hotpot-config-file,global,your-score-is');
1067 // reading
1069 function v6_expand_Reading() {
1070 return $this->xml_value('data,reading,include-reading');
1072 function v6_expand_ReadingText() {
1073 $title = $this->v6_expand_ReadingTitle();
1074 $value = $this->xml_value('data,reading,reading-text');
1075 $value = empty($value) ? '' : ('<div class="ReadingText">'.$value.'</div>');
1076 return $title.$value;
1078 function v6_expand_ReadingTitle() {
1079 $value = $this->xml_value('data,reading,reading-title');
1080 return empty($value) ? '' : ('<h3 class="ExerciseSubtitle">'.$value.'</h3>');
1083 // timer
1085 function v6_expand_Timer() {
1086 return $this->xml_value('data,timer,include-timer');
1088 function v6_expand_JSTimer() {
1089 return $this->read_template('hp6timer.js_');
1091 function v6_expand_Seconds() {
1092 return $this->xml_value('data,timer,seconds');
1095 // send results
1097 function v6_expand_SendResults() {
1098 return $this->xml_value("hotpot-config-file,$this->quiztype,send-email");
1100 function v6_expand_JSSendResults() {
1101 return $this->read_template('hp6sendresults.js_');
1103 function v6_expand_FormMailURL() {
1104 return $this->xml_value('hotpot-config-file,global,formmail-url');
1106 function v6_expand_EMail() {
1107 return $this->xml_value('hotpot-config-file,global,email');
1109 function v6_expand_NamePlease() {
1110 return $this->js_value('hotpot-config-file,global,name-please');
1113 // preload images
1115 function v6_expand_PreloadImages() {
1116 $value = $this->v6_expand_PreloadImageList();
1117 return empty($value) ? false : true;
1119 function v6_expand_PreloadImageList() {
1121 // check it has not been set already
1122 if (!isset($this->PreloadImageList)) {
1124 // the list of image urls
1125 $list = array();
1127 // extract <img> tags
1128 $img_tag = htmlspecialchars('|&#x003C;img.*?src="(.*?)".*?&#x003E;|is');
1129 if (preg_match_all($img_tag, $this->source, $matches)) {
1130 $list = $matches[1];
1132 // remove duplicates
1133 $list = array_unique($list);
1136 // convert to comma delimited string
1137 $this->PreloadImageList = empty($list) ? '' : "'".implode(',', $list)."'";
1139 return $this->PreloadImageList;
1142 // html files (all quiz types)
1144 function v6_expand_PlainTitle() {
1145 return $this->xml_value('data,title');
1147 function v6_expand_ExerciseSubtitle() {
1148 return $this->xml_value("hotpot-config-file,$this->quiztype,exercise-subtitle");
1150 function v6_expand_Instructions() {
1151 return $this->xml_value("hotpot-config-file,$this->quiztype,instructions");
1153 function v6_expand_DublinCoreMetadata() {
1154 return ''
1155 . '<link rel="schema.DC" href="'.$this->xml_value('', "['rdf:RDF'][0]['@']['xmlns:dc']").'" />'."\n"
1156 . '<meta name="DC:Creator" content="'.$this->xml_value('rdf:RDF,rdf:Description,dc:creator').'" />'."\n"
1157 . '<meta name="DC:Title" content="'.strip_tags($this->xml_value('rdf:RDF,rdf:Description,dc:title')).'" />'."\n"
1160 function v6_expand_FullVersionInfo() {
1161 global $CFG;
1162 require_once($CFG->hotpotroot.DIRECTORY_SEPARATOR.'version.php'); // set $module
1163 return $this->xml_value('version').'.x (Moodle '.$CFG->release.', hotpot-module '.$this->obj_value($module, 'release').')';
1165 function v6_expand_HeaderCode() {
1166 return $this->xml_value('hotpot-config-file,global,header-code');
1168 function v6_expand_StyleSheet() {
1169 $this->read_template('hp6.cs_', 'css');
1170 return $this->css;
1173 // stylesheet (hp6.cs_)
1175 function v6_expand_PageBGColor() {
1176 return $this->xml_value('hotpot-config-file,global,page-bg-color');
1178 function v6_expand_GraphicURL() {
1179 return $this->xml_value('hotpot-config-file,global,graphic-url');
1181 function v6_expand_ExBGColor() {
1182 return $this->xml_value('hotpot-config-file,global,ex-bg-color');
1185 function v6_expand_FontFace() {
1186 return $this->xml_value('hotpot-config-file,global,font-face');
1188 function v6_expand_FontSize() {
1189 return $this->xml_value('hotpot-config-file,global,font-size');
1191 function v6_expand_TextColor() {
1192 return $this->xml_value('hotpot-config-file,global,text-color');
1194 function v6_expand_TitleColor() {
1195 return $this->xml_value('hotpot-config-file,global,title-color');
1197 function v6_expand_LinkColor() {
1198 return $this->xml_value('hotpot-config-file,global,link-color');
1200 function v6_expand_VLinkColor() {
1201 return $this->xml_value('hotpot-config-file,global,vlink-color');
1204 function v6_expand_NavTextColor() {
1205 return $this->xml_value('hotpot-config-file,global,page-bg-color');
1207 function v6_expand_NavBarColor() {
1208 return $this->xml_value('hotpot-config-file,global,nav-bar-color');
1210 function v6_expand_NavLightColor() {
1211 $color = $this->xml_value('hotpot-config-file,global,nav-bar-color');
1212 return $this->get_halfway_color($color, '#ffffff');
1214 function v6_expand_NavShadeColor() {
1215 $color = $this->xml_value('hotpot-config-file,global,nav-bar-color');
1216 return $this->get_halfway_color($color, '#000000');
1219 function v6_expand_FuncLightColor() { // top-left of buttons
1220 $color = $this->xml_value('hotpot-config-file,global,ex-bg-color');
1221 return $this->get_halfway_color($color, '#ffffff');
1223 function v6_expand_FuncShadeColor() { // bottom right of buttons
1224 $color = $this->xml_value('hotpot-config-file,global,ex-bg-color');
1225 return $this->get_halfway_color($color, '#000000');
1228 function get_halfway_color($x, $y) {
1229 // returns the $color that is half way between $x and $y
1230 $color = $x; // default
1231 $rgb = '/^\#?([0-9a-f])([0-9a-f])([0-9a-f])$/i';
1232 $rrggbb = '/^\#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i';
1233 if ((
1234 preg_match($rgb, $x, $x_matches) ||
1235 preg_match($rrggbb, $x, $x_matches)
1236 ) && (
1237 preg_match($rgb, $y, $y_matches) ||
1238 preg_match($rrggbb, $y, $y_matches)
1239 )) {
1240 $color = '#';
1241 for ($i=1; $i<=3; $i++) {
1242 $x_dec = hexdec($x_matches[$i]);
1243 $y_dec = hexdec($y_matches[$i]);
1244 $color .= sprintf('%02x', min($x_dec, $y_dec) + abs($x_dec-$y_dec)/2);
1247 return $color;
1250 // navigation buttons
1252 function v6_expand_NavButtons() {
1253 $back = $this->v6_expand_Back();
1254 $next_ex = $this->v6_expand_NextEx();
1255 $contents = $this->v6_expand_Contents();
1256 return (empty($back) && empty($next_ex) && empty($contents) ? false : true);
1258 function v6_expand_NavBarJS() {
1259 return $this->v6_expand_NavButtons();
1262 // js files (all quiz types)
1264 function v6_expand_JSBrowserCheck() {
1265 return $this->read_template('hp6browsercheck.js_');
1267 function v6_expand_JSButtons() {
1268 return $this->read_template('hp6buttons.js_');
1270 function v6_expand_JSCard() {
1271 return $this->read_template('hp6card.js_');
1273 function v6_expand_JSCheckShortAnswer() {
1274 return $this->read_template('hp6checkshortanswer.js_');
1276 function v6_expand_JSHotPotNet() {
1277 return $this->read_template('hp6hotpotnet.js_');
1279 function v6_expand_JSShowMessage() {
1280 return $this->read_template('hp6showmessage.js_');
1282 function v6_expand_JSUtilities() {
1283 return $this->read_template('hp6utilities.js_');
1286 // js files
1288 function v6_expand_JSJCloze6() {
1289 return $this->read_template('jcloze6.js_');
1291 function v6_expand_JSJCross6() {
1292 return $this->read_template('jcross6.js_');
1294 function v6_expand_JSJMatch6() {
1295 return $this->read_template('jmatch6.js_');
1297 function v6_expand_JSJMix6() {
1298 return $this->read_template('jmix6.js_');
1300 function v6_expand_JSJQuiz6() {
1301 return $this->read_template('jquiz6.js_');
1304 // drag and drop
1306 function v6_expand_JSDJMatch6() {
1307 return $this->read_template('djmatch6.js_');
1309 function v6_expand_JSDJMix6() {
1310 return $this->read_template('djmix6.js_');
1313 // what are these for?
1315 function v6_expand_JSFJMatch6() {
1316 return $this->read_template('fjmatch6.js_');
1318 function v6_expand_JSFJMix6() {
1319 return $this->read_template('fjmix6.js_');
1322 // jmatch6.js_
1324 function v6_expand_ShuffleQs() {
1325 return $this->bool_value("hotpot-config-file,$this->quiztype,shuffle-questions");
1327 function v6_expand_QsToShow() {
1328 $i = $this->xml_value("hotpot-config-file,$this->quiztype,show-limited-questions");
1329 if ($i) {
1330 $i = $this->xml_value("hotpot-config-file,$this->quiztype,questions-to-show");
1332 if (empty($i)) {
1333 $i = 0;
1334 switch ($this->quiztype) {
1335 case 'jmatch':
1336 $values = $this->xml_values('data,matching-exercise,pair');
1337 $i = count($values);
1338 break;
1339 case 'jquiz':
1340 while ($this->xml_value('data,questions,question-record', "[$i]['#']['question'][0]['#']")) {
1341 $i++;
1343 break;
1344 } // end switch
1346 return $i;
1348 function v6_expand_MatchDivItems() {
1349 $str = '';
1351 $this->get_jmatch_items($l_items=array(), $r_items = array());
1353 $l_keys = $this->shuffle_jmatch_items($l_items);
1354 $r_keys = $this->shuffle_jmatch_items($r_items);
1356 $options = '<option value="x">'.$this->xml_value('data,matching-exercise,default-right-item').'</option>';
1357 foreach ($r_keys as $key) {
1358 $options .= '<option value="'.$key.'">'.$r_items[$key]['text'][0]['#'].'</option>'."\n";
1360 foreach ($l_keys as $key) {
1361 $str .= '<tr><td class="LeftItem">'.$l_items[$key]['text'][0]['#'].'</td>';
1362 $str .= '<td class="RightItem"><select id="s'.$key.'_'.$key.'">'.$options.'</select></td>';
1363 $str .= '<td></td></tr>';
1365 return $str;
1368 // jmix6.js_
1370 function v6_expand_Punctuation() {
1371 $tags = 'data,jumbled-order-exercise';
1372 $chars = array_merge(
1373 $this->jmix_Punctuation("$tags,main-order,segment"),
1374 $this->jmix_Punctuation("$tags,alternate")
1376 $chars = array_unique($chars);
1377 $chars = implode('', $chars);
1378 $chars = $this->js_safe($chars, true);
1379 return $chars;
1381 function jmix_Punctuation($tags) {
1382 $chars = array();
1384 // all punctutation except '&#;' (because they are used in html entities)
1385 $ENTITIES = $this->jmix_encode_punctuation('!"$%'."'".'()*+,-./:<=>?@[\]^_`{|}~');
1386 $pattern = "/&#x([0-9A-F]+);/i";
1387 $i = 0;
1389 // get next segment (or alternate answer)
1390 while ($value = $this->xml_value($tags, "[$i]['#']")) {
1392 // convert low-ascii punctuation to entities
1393 $value = strtr($value, $ENTITIES);
1395 // extract all hex HTML entities
1396 if (preg_match_all($pattern, $value, $matches)) {
1398 // loop through hex entities
1399 $m_max = count($matches[0]);
1400 for ($m=0; $m<$m_max; $m++) {
1402 // convert to hex number
1403 eval('$hex=0x'.$matches[1][$m].';');
1405 // is this a punctuation character?
1406 if (
1407 ($hex>=0x0020 && $hex<=0x00BF) || // ascii punctuation
1408 ($hex>=0x2000 && $hex<=0x206F) || // general punctuation
1409 ($hex>=0x3000 && $hex<=0x303F) || // CJK punctuation
1410 ($hex>=0xFE30 && $hex<=0xFE4F) || // CJK compatability
1411 ($hex>=0xFE50 && $hex<=0xFE6F) || // small form variants
1412 ($hex>=0xFF00 && $hex<=0xFF40) || // halfwidth and fullwidth forms (1)
1413 ($hex>=0xFF5B && $hex<=0xFF65) || // halfwidth and fullwidth forms (2)
1414 ($hex>=0xFFE0 && $hex<=0xFFEE) // halfwidth and fullwidth forms (3)
1416 // add this character
1417 $chars[] = $matches[0][$m];
1421 $i++;
1424 return $chars;
1426 function v6_expand_OpenPunctuation() {
1427 $tags = 'data,jumbled-order-exercise';
1428 $chars = array_merge(
1429 $this->jmix_OpenPunctuation("$tags,main-order,segment"),
1430 $this->jmix_OpenPunctuation("$tags,alternate")
1432 $chars = array_unique($chars);
1433 $chars = implode('', $chars);
1434 $chars = $this->js_safe($chars, true);
1435 return $chars;
1437 function jmix_OpenPunctuation($tags) {
1438 $chars = array();
1440 // unicode punctuation designations (pi="initial quote", ps="open")
1441 // http://www.sql-und-xml.de/unicode-database/pi.html
1442 // http://www.sql-und-xml.de/unicode-database/ps.html
1443 $pi = '0022|0027|00AB|2018|201B|201C|201F|2039';
1444 $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';
1445 $pattern = "/(&#x($pi|$ps);)/i";
1447 $ENTITIES = $this->jmix_encode_punctuation('"'."'".'(<[{');
1449 $i = 0;
1450 while ($value = $this->xml_value($tags, "[$i]['#']")) {
1451 $value = strtr($value, $ENTITIES);
1452 if (preg_match_all($pattern, $value, $matches)) {
1453 $chars = array_merge($chars, $matches[0]);
1455 $i++;
1458 return $chars;
1460 function jmix_encode_punctuation($str) {
1461 $ENTITIES = array();
1462 $i_max = strlen($str);
1463 for ($i=0; $i<$i_max; $i++) {
1464 $ENTITIES[$str{$i}] = '&#x'.sprintf('%04X', ord($str{$i})).';';
1466 return $ENTITIES;
1468 function v6_expand_ExerciseTitle() {
1469 return $this->xml_value('data,title');
1472 // Jmix specials
1474 function v6_expand_SegmentArray() {
1475 $segments = $this->xml_values('data,jumbled-order-exercise,main-order,segment');
1477 $this->seed_random_number_generator();
1478 $keys = array_keys($segments);
1479 shuffle($keys);
1481 $str = '';
1482 for($i=0; $i<count($keys); $i++) {
1483 $str .= "Segments[$i] = new Array();\n";
1484 $str .= "Segments[$i][0] = '".$this->js_safe($segments[$keys[$i]])."';\n";
1485 $str .= "Segments[$i][1] = ".($keys[$i]+1).";\n";
1486 $str .= "Segments[$i][2] = 0;\n";
1488 return $str;
1490 function v6_expand_AnswerArray() {
1492 $segments = $this->xml_values('data,jumbled-order-exercise,main-order,segment');
1493 $alternates = $this->xml_values('data,jumbled-order-exercise,alternate');
1495 $i = 0;
1496 $pattern = '';
1497 $str = 'Answers['.$i++.'] = new Array(';
1498 for($ii=0; $ii<count($segments); $ii++) {
1499 $str .= ($ii==0 ? '' : ',').($ii+1);
1500 $pattern .= (empty($pattern) ? '' : '|').preg_quote($segments[$ii], '/');
1502 $str .= ");\n";
1503 $pattern = '/^('.$pattern.')\\s*/';
1505 foreach ($alternates as $alternate) {
1506 $ii = 0;
1507 $str .= 'Answers['.$i++.'] = new Array(';
1508 while (!empty($alternate) && preg_match($pattern, $alternate, $matches)) {
1509 $iii = array_search($matches[1], $segments);
1510 if (is_int($iii)) {
1511 $str .= ($ii==0 ? '' : ',').($iii+1);
1512 $alternate = substr($alternate, strlen($matches[0]));
1513 $ii++;
1514 } else {
1515 // $matches[1] was not found in $segments!
1516 // something is very wrong, so abort the loop
1517 break;
1520 $str .= ");\n";
1522 return $str;
1525 // ===============================================================
1527 // JMix (jmix6.js_)
1529 function v6_expand_RemainingWords() {
1530 return $this->xml_value("hotpot-config-file,$this->quiztype,remaining-words");
1532 function v6_expand_TimesUp() {
1533 return $this->xml_value('hotpot-config-file,global,times-up');
1536 // nav bar
1538 function v6_expand_NavBar() {
1539 $tag = 'navbar';
1540 $this->read_template('hp6navbar.ht_', $tag);
1541 return $this->$tag;
1543 function v6_expand_TopNavBar() {
1544 return $this->v6_expand_NavBar();
1546 function v6_expand_BottomNavBar() {
1547 return $this->v6_expand_NavBar();
1549 function v6_expand_NextExURL() {
1550 return $this->xml_value("hotpot-config-file,$this->quiztype,next-ex-url");
1553 // hp6navbar.ht_
1555 function v6_expand_NavBarID() {
1556 return ''; // what's this?;
1558 function v6_expand_ContentsURL() {
1559 return $this->xml_value('hotpot-config-file,global,contents-url');
1562 // conditional blocks
1564 function v6_expand_ShowAnswer() {
1565 return $this->xml_value("hotpot-config-file,$this->quiztype,include-show-answer");
1567 function v6_expand_Slide() {
1568 return true; // whats's this (JMatch drag and drop)
1571 // specials (JMatch)
1573 function v6_expand_FixedArray() {
1574 $str = '';
1575 $this->get_jmatch_items($l_items=array(), $r_items = array());
1576 foreach ($l_items as $i=>$item) {
1577 $str .= "F[$i] = new Array();\n";
1578 $str .= "F[$i][0] = '".$this->js_safe($item['text'][0]['#'], true)."';\n";
1579 $str .= "F[$i][1] = ".($i+1).";\n";
1581 return $str;
1583 function v6_expand_DragArray() {
1584 $str = '';
1585 $this->get_jmatch_items($l_items=array(), $r_items = array());
1586 foreach ($r_items as $i=>$item) {
1587 $str .= "D[$i] = new Array();\n";
1588 $str .= "D[$i][0] = '".$this->js_safe($item['text'][0]['#'], true)."';\n";
1589 $str .= "D[$i][1] = ".($i+1).";\n";
1590 $str .= "D[$i][2] = 0;\n";
1592 return $str;
1595 function get_jmatch_items(&$l_items, &$r_items) {
1596 $i = 0;
1597 while(
1598 ($l_item = $this->xml_value('data,matching-exercise,pair',"[$i]['#']['left-item'][0]['#']")) &&
1599 ($r_item = $this->xml_value('data,matching-exercise,pair',"[$i]['#']['right-item'][0]['#']"))
1601 $l_items[] = $l_item;
1602 $r_items[] = $r_item;
1603 $i++;
1606 function shuffle_jmatch_items(&$items) {
1607 // get moveable items
1608 $moveable_keys = array();
1609 for($i=0; $i<count($items); $i++) {
1610 if(empty($items[$i]['fixed'][0]['#'])) {
1611 $moveable_keys[] = $i;
1614 // shuffle moveable items
1615 $this->seed_random_number_generator();
1616 shuffle($moveable_keys);
1618 $keys = array();
1619 for($i=0, $ii=0; $i<count($items); $i++) {
1620 if(empty($items[$i]['fixed'][0]['#'])) {
1621 // moveable items are inserted in a shuffled order
1622 $keys[] = $moveable_keys[$ii++];
1623 } else {
1624 // fixed items stay where they are
1625 $keys[] = $i;
1628 return $keys;
1630 function seed_random_number_generator() {
1631 static $seeded_RNG = FALSE;
1632 if (!$seeded_RNG) {
1633 srand((double) microtime() * 1000000);
1634 $seeded_RNG = TRUE;
1638 // specials (JMix)
1641 // specials (JCloze)
1643 function v6_expand_ItemArray() {
1644 $q = 0;
1645 $str = '';
1646 switch ($this->quiztype) {
1647 case 'jcloze':
1648 $str .= "I = new Array();\n";
1649 $tags = 'data,gap-fill,question-record';
1650 while (($question="[$q]['#']") && $this->xml_value($tags, $question)) {
1651 $a = 0;
1652 $aa = 0;
1653 while (($answer=$question."['answer'][$a]['#']") && $this->xml_value($tags, $answer)) {
1654 $text = $this->js_value($tags, $answer."['text'][0]['#']", true);
1655 if ($text) {
1656 if ($aa==0) { // first time only
1657 $str .= "I[$q] = new Array();\n";
1658 $str .= "I[$q][1] = new Array();\n";
1660 $str .= "I[$q][1][$aa] = new Array();\n";
1661 $str .= "I[$q][1][$aa][0] = '$text';\n";
1662 $aa++;
1664 $a++;
1666 // add clue, if any answers were found
1667 if ($aa) {
1668 $clue = $this->js_value($tags, $question."['clue'][0]['#']", true);
1669 $str .= "I[$q][2]='$clue';\n";
1671 $q++;
1673 break;
1674 case 'jquiz':
1675 $str .= "I=new Array();\n";
1676 $tags = 'data,questions,question-record';
1677 while (($question="[$q]['#']") && $this->xml_value($tags, $question)) {
1679 $question_type = $this->int_value($tags, $question."['question-type'][0]['#']");
1680 $weighting = $this->int_value($tags, $question."['weighting'][0]['#']");
1681 $clue = $this->js_value($tags, $question."['clue'][0]['#']", true);
1683 $answers = $question."['answers'][0]['#']";
1685 $a = 0;
1686 $aa = 0;
1687 while (($answer = $answers."['answer'][$a]['#']") && $this->xml_value($tags, $answer)) {
1688 $text = $this->js_value($tags, $answer."['text'][0]['#']", true);
1689 $feedback = $this->js_value($tags, $answer."['feedback'][0]['#']", true);
1690 $correct = $this->int_value($tags, $answer."['correct'][0]['#']", true);
1691 $percent = $this->int_value($tags, $answer."['percent-correct'][0]['#']", true);
1692 $include = $this->int_value($tags, $answer."['include-in-mc-options'][0]['#']", true);
1693 if ($text) {
1694 if ($aa==0) { // first time only
1695 $str .= "I[$q]=new Array();\n";
1696 $str .= "I[$q][0]=$weighting;\n";
1697 $str .= "I[$q][1]='$clue';\n";
1698 $str .= "I[$q][2]='".($question_type-1)."';\n";
1699 $str .= "I[$q][3]=new Array();\n";
1701 $str .= "I[$q][3][$aa]=new Array('$text','$feedback',$correct,$percent,$include);\n";
1702 $aa++;
1704 $a++;
1706 $q++;
1708 break;
1710 return $str;
1713 function v6_expand_ClozeBody() {
1714 $str = '';
1715 $q = 0;
1716 $tags = 'data,gap-fill';
1717 while ($text = $this->xml_value($tags, "[0]['#'][$q]")) {
1718 $str .= $text;
1719 if (($question="[$q]['#']") && $this->xml_value("$tags,question-record", $question)) {
1720 $str .= '<span class="GapSpan" id="GapSpan'.$q.'"><INPUT type="text" id="Gap'.$q.'" onfocus="TrackFocus('.$q.')" onblur="LeaveGap()" class="GapBox" size="6"></input><button style="line-height: 1.0" class="FuncButton" onfocus="FuncBtnOver(this)" onmouseover="FuncBtnOver(this)" onblur="FuncBtnOut(this)" onmouseout="FuncBtnOut(this)" onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)" onclick="ShowClue('.$q.')">[?]</button></span>';
1722 $q++;
1726 return $str;
1729 // JCloze quiztype
1731 function v6_expand_WordList() {
1732 return $this->xml_value("hotpot-config-file,$this->quiztype,include-word-list");
1734 function v6_expand_Keypad() {
1735 $str = '';
1736 if ($this->bool_value("hotpot-config-file,$this->quiztype,include-keypad")) {
1738 // these characters must always be in the keypad
1739 $chars = array();
1740 $this->add_keypad_chars($chars, $this->xml_value('hotpot-config-file,global,keypad-characters'));
1742 // append other characters used in the answers
1743 $tags = '';
1744 switch ($this->quiztype) {
1745 case 'jcloze':
1746 $tags = 'data,gap-fill,question-record';
1747 break;
1748 case 'jquiz':
1749 $tags = 'data,questions,question-record';
1750 break;
1752 if ($tags) {
1753 $q = 0;
1754 while (($question="[$q]['#']") && $this->xml_value($tags, $question)) {
1756 if ($this->quiztype=='jquiz') {
1757 $answers = $question."['answers'][0]['#']";
1758 } else {
1759 $answers = $question;
1762 $a = 0;
1763 while (($answer=$answers."['answer'][$a]['#']") && $this->xml_value($tags, $answer)) {
1764 $this->add_keypad_chars($chars, $this->xml_value($tags, $answer."['text'][0]['#']"));
1765 $a++;
1767 $q++;
1771 // remove duplicate characters and sort
1772 $chars = array_unique($chars);
1773 usort($chars, "hotpot_sort_keypad_chars");
1775 // create keypad buttons for each character
1776 $str .= '<div class="Keypad">';
1777 foreach ($chars as $char) {
1778 $str .= "<button onclick=\"TypeChars('".$this->js_safe($char, true)."'); return false;\">$char</button>";
1780 $str .= '</div>';
1782 return $str;
1784 function add_keypad_chars(&$chars, $text) {
1785 if (preg_match_all('|&[^;]+;|i', $text, $more_chars)) {
1786 $chars = array_merge($chars, $more_chars[0]);
1789 function v6_expand_Correct() {
1790 return $this->xml_value("hotpot-config-file,$this->quiztype,guesses-correct");
1792 function v6_expand_Incorrect() {
1793 return $this->xml_value("hotpot-config-file,$this->quiztype,guesses-incorrect");
1795 function v6_expand_GiveHint() {
1796 return $this->xml_value("hotpot-config-file,$this->quiztype,next-correct-letter");
1798 function v6_expand_CaseSensitive() {
1799 return $this->xml_value("hotpot-config-file,$this->quiztype,case-sensitive");
1802 // JCross quiztype
1804 function v6_expand_CluesAcrossLabel() {
1805 return $this->xml_value("hotpot-config-file,$this->quiztype,clues-across");
1807 function v6_expand_CluesDownLabel() {
1808 $this->xml_value("hotpot-config-file,$this->quiztype,clues-down");
1809 return '';
1811 function v6_expand_EnterCaption() {
1812 return $this->xml_value("hotpot-config-file,$this->quiztype,enter-caption");
1814 function v6_expand_ShowHideClueList() {
1815 $value = $this->xml_value("hotpot-config-file,$this->quiztype,include-clue-list");
1816 return empty($value) ? ' style="display: none;"' : '';
1819 // JCross specials
1821 function v6_expand_CluesDown() {
1822 return $this->v6_expand_jcross_clues('D');
1824 function v6_expand_CluesAcross() {
1825 return $this->v6_expand_jcross_clues('A');
1827 function v6_expand_jcross_clues($direction) {
1828 // $direction: A(cross) or D(own)
1829 $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0);
1830 $i = 0; // clue index;
1831 $str = '';
1832 for($r=0; $r<=$r_max; $r++) {
1833 for($c=0; $c<=$c_max; $c++) {
1834 $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max);
1835 $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max);
1836 if ($aword || $dword) {
1837 $i++; // increment clue index
1839 // get the definition for this word
1840 $def = '';
1841 $word = ($direction=='A') ? $aword : $dword;
1842 $clues = $this->xml_values('data,crossword,clues,item');
1843 foreach ($clues as $clue) {
1844 if ($clue['word'][0]['#']==$word) {
1845 $def = $clue['def'][0]['#'];
1846 $def = strtr($def, array('&#x003C;'=>'<', '&#x003E;'=>'>', "\n"=>'<br />'));
1847 break;
1851 if (!empty($def)) {
1852 $str .= '<tr><td class="ClueNum">'.$i.'. </td><td id="Clue_'.$direction.'_'.$i.'" class="Clue">'.$def.'</td></tr>';
1857 return $str;
1860 // jcross6.js_
1862 function v6_expand_LetterArray() {
1863 $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0);
1864 $str = '';
1865 for($r=0; $r<=$r_max; $r++) {
1866 $str .= "L[$r] = new Array(";
1867 for($c=0; $c<=$c_max; $c++) {
1868 $str .= ($c>0 ? ',' : '')."'".$this->js_safe($row[$r]['cell'][$c]['#'], true)."'";
1870 $str .= ");\n";
1872 return $str;
1874 function v6_expand_GuessArray() {
1875 $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0);
1876 $str = '';
1877 for($r=0; $r<=$r_max; $r++) {
1878 $str .= "G[$r] = new Array('".str_repeat("','", $c_max)."');\n";
1880 return $str;
1882 function v6_expand_ClueNumArray() {
1883 $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0);
1884 $i = 0; // clue index
1885 $str = '';
1886 for($r=0; $r<=$r_max; $r++) {
1887 $str .= "CL[$r] = new Array(";
1888 for($c=0; $c<=$c_max; $c++) {
1889 if ($c>0) {
1890 $str .= ',';
1892 $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max);
1893 $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max);
1894 if (empty($aword) && empty($dword)) {
1895 $str .= 0;
1896 } else {
1897 $i++; // increment the clue index
1898 $str .= $i;
1901 $str .= ");\n";
1903 return $str;
1905 function v6_expand_GridBody() {
1906 $this->v6_get_jcross_grid($row=NULL, $r_max=0, $c_max=0);
1907 $i = 0; // clue index;
1908 $str = '';
1909 for($r=0; $r<=$r_max; $r++) {
1910 $str .= '<tr id="Row_'.$r.'">';
1911 for($c=0; $c<=$c_max; $c++) {
1912 if (empty($row[$r]['cell'][$c]['#'])) {
1913 $str .= '<td class="BlankCell">&nbsp;</td>';
1914 } else {
1915 $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max);
1916 $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max);
1917 if (empty($aword) && empty($dword)) {
1918 $str .= '<td class="LetterOnlyCell"><span id="L_'.$r.'_'.$c.'">&nbsp;</span></td>';
1919 } else {
1920 $i++; // increment clue index
1921 $str .= '<td class="NumLetterCell"><a href="javascript:void(0);" class="GridNum" onclick="ShowClue('.$i.','.$r.','.$c.')">'.$i.'</a><span class="NumLetterCellText" id="L_'.$r.'_'.$c.'" onclick="ShowClue('.$i.','.$r.','.$c.')">&nbsp;&nbsp;&nbsp;</span></td>';
1925 $str .= '</tr>';
1927 return $str;
1929 function v6_get_jcross_grid(&$row, &$r_max, &$c_max) {
1930 $row = $this->xml_values('data,crossword,grid,row');
1931 $r_max = 0;
1932 $c_max = 0;
1933 if (isset($row) && is_array($row)) {
1934 for($r=0; $r<count($row); $r++) {
1935 if (isset($row[$r]['cell']) && is_array($row[$r]['cell'])) {
1936 for($c=0; $c<count($row[$r]['cell']); $c++) {
1937 if (!empty($row[$r]['cell'][$c]['#'])) {
1938 $r_max = max($r, $r_max);
1939 $c_max = max($c, $c_max);
1941 } // end for $c
1943 } // end for $r
1946 function get_jcross_dword(&$row, $r, $r_max, $c, $c_max) {
1947 $str = '';
1948 if (($r==0 || empty($row[$r-1]['cell'][$c]['#'])) && $r<$r_max && !empty($row[$r+1]['cell'][$c]['#'])) {
1949 $str = $this->get_jcross_word($row, $r, $r_max, $c, $c_max, true);
1951 return $str;
1953 function get_jcross_aword(&$row, $r, $r_max, $c, $c_max) {
1954 $str = '';
1955 if (($c==0 || empty($row[$r]['cell'][$c-1]['#'])) && $c<$c_max && !empty($row[$r]['cell'][$c+1]['#'])) {
1956 $str = $this->get_jcross_word($row, $r, $r_max, $c, $c_max, false);
1958 return $str;
1960 function get_jcross_word(&$row, $r, $r_max, $c, $c_max, $go_down=false) {
1961 $str = '';
1962 while ($r<=$r_max && $c<=$c_max && !empty($row[$r]['cell'][$c]['#'])) {
1963 $str .= $row[$r]['cell'][$c]['#'];
1964 if ($go_down) {
1965 $r++;
1966 } else {
1967 $c++;
1970 return $str;
1973 // specials (JQuiz)
1975 function v6_expand_QuestionOutput() {
1976 $str = '';
1977 $str .= '<ol class="QuizQuestions" id="Questions">'."\n";
1979 $q = 0;
1980 $tags = 'data,questions,question-record';
1981 while (($question="[$q]['#']") && $this->xml_value($tags, $question)) {
1983 // get question
1984 $question_text = $this->xml_value($tags, $question."['question'][0]['#']");
1985 $question_type = $this->xml_value($tags, $question."['question-type'][0]['#']");
1987 // check we have a question
1988 if ($question_text && $question_type) {
1990 $str .= '<li class="QuizQuestion" id="Q_'.$q.'" style="display: none;">';
1991 $str .= '<p class="QuestionText">'.$question_text.'</p>';
1993 if (
1994 $question_type==HOTPOT_JQUIZ_SHORTANSWER ||
1995 $question_type==HOTPOT_JQUIZ_HYBRID
1997 $str .= '<div class="ShortAnswer" id="Q_'.$q.'_SA"><form method="post" action="" onsubmit="return false;"><div>';
1998 $str .= '<INPUT type="text" id="Q_'.$q.'_Guess" onfocus="TrackFocus('."'".'Q_'.$q.'_Guess'."'".')" onblur="LeaveGap()" class="ShortAnswerBox" size="9"></input><br /><br />';
2000 $text = $this->v6_expand_CheckCaption();
2001 $str .= $this->v6_expand_jquiz_button($text, "CheckShortAnswer($q)");
2003 if ($this->v6_expand_hint()) {
2004 $text = $this->v6_expand_HintCaption();
2005 $str .= $this->v6_expand_jquiz_button($text, "ShowHint($q)");
2008 if ($this->v6_expand_ShowAnswer()) {
2009 $text = $this->v6_expand_ShowAnswerCaption();
2010 $str .= $this->v6_expand_jquiz_button($text, "ShowAnswers($q)");
2013 $str .= '</div></form></div>';
2016 if (
2017 $question_type==HOTPOT_JQUIZ_MULTICHOICE ||
2018 $question_type==HOTPOT_JQUIZ_HYBRID ||
2019 $question_type==HOTPOT_JQUIZ_MULTISELECT
2022 switch ($question_type) {
2023 case HOTPOT_JQUIZ_MULTICHOICE:
2024 $str .= '<ol class="MCAnswers">'."\n";
2025 break;
2026 case HOTPOT_JQUIZ_HYBRID:
2027 $str .= '<ol class="MCAnswers" id="Q_'.$q.'_Hybrid_MC" style="display: none;">'."\n";
2028 break;
2029 case HOTPOT_JQUIZ_MULTISELECT:
2030 $str .= '<ol class="MSelAnswers">'."\n";
2031 break;
2034 $a = 0;
2035 $aa = 0;
2036 $answers = $question."['answers'][0]['#']";
2037 while (($answer = $answers."['answer'][$a]['#']") && $this->xml_value($tags, $answer)) {
2038 $text = $this->xml_value($tags, $answer."['text'][0]['#']");
2039 if ($text) {
2040 switch ($question_type) {
2041 case HOTPOT_JQUIZ_MULTICHOICE:
2042 case HOTPOT_JQUIZ_HYBRID:
2043 $include = $this->int_value($tags, $answer."['include-in-mc-options'][0]['#']");
2044 if ($include) {
2045 $str .= '<li id="Q_'.$q.'_'.$aa.'"><button class="FuncButton" onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)" id="Q_'.$q.'_'.$aa.'_Btn" onclick="CheckMCAnswer('.$q.','.$aa.',this)">&nbsp;&nbsp;?&nbsp;&nbsp;</button>&nbsp;&nbsp;'.$text.'</li>'."\n";
2047 break;
2048 case HOTPOT_JQUIZ_MULTISELECT:
2049 $str .= '<li id="Q_'.$q.'_'.$aa.'"><form method="post" action="" onsubmit="return false;"><div><INPUT type="checkbox" id="Q_'.$q.'_'.$aa.'_Chk" class="MSelCheckbox" />'.$text.'</div></form></li>'."\n";
2050 break;
2052 $aa++;
2054 $a++;
2057 $str .= '</ol>';
2059 if ($question_type==HOTPOT_JQUIZ_MULTISELECT) {
2060 $text = $this->v6_expand_CheckCaption();
2061 $str .= $this->v6_expand_jquiz_button($text, "CheckMultiSelAnswer($q)");
2065 $str .= "</li>\n";
2067 $q++;
2069 } // end while $question
2071 $str .= "</ol>\n";
2072 return $str;
2075 function v6_expand_jquiz_button($text, $onclick) {
2076 return '<button class="FuncButton" onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)" onclick="'.$onclick.'">'.$text.'</button>';
2079 // jquiz.js_
2081 function v6_expand_MultiChoice() {
2082 return $this->v6_jquiz_question_type(HOTPOT_JQUIZ_MULTICHOICE);
2084 function v6_expand_ShortAnswer() {
2085 return $this->v6_jquiz_question_type(HOTPOT_JQUIZ_SHORTANSWER);
2087 function v6_expand_MultiSelect() {
2088 return $this->v6_jquiz_question_type(HOTPOT_JQUIZ_MULTISELECT);
2090 function v6_jquiz_question_type($type) {
2091 // does this quiz have any questions of the given $type?
2092 $flag = false;
2094 $q = 0;
2095 $tags = 'data,questions,question-record';
2096 while (($question = "[$q]['#']") && $this->xml_value($tags, $question)) {
2097 $question_type = $this->xml_value($tags, $question."['question-type'][0]['#']");
2098 if ($question_type==$type || ($question_type==HOTPOT_JQUIZ_HYBRID && ($type==HOTPOT_JQUIZ_MULTICHOICE || $type==HOTPOT_JQUIZ_SHORTANSWER))) {
2099 $flag = true;
2100 break;
2102 $q++;
2104 return $flag;
2106 function v6_expand_CorrectFirstTime() {
2107 return $this->js_value('hotpot-config-file,global,correct-first-time');
2109 function v6_expand_ContinuousScoring() {
2110 return $this->bool_value("hotpot-config-file,$this->quiztype,continuous-scoring");
2112 function v6_expand_ShowCorrectFirstTime() {
2113 return $this->bool_value("hotpot-config-file,$this->quiztype,show-correct-first-time");
2115 function v6_expand_ShuffleAs() {
2116 return $this->bool_value("hotpot-config-file,$this->quiztype,shuffle-answers");
2119 function v6_expand_DefaultRight() {
2120 return $this->v6_expand_GuessCorrect();
2122 function v6_expand_DefaultWrong() {
2123 return $this->v6_expand_GuessIncorrect();
2125 function v6_expand_ShowAllQuestionsCaptionJS() {
2126 return $this->v6_expand_ShowAllQuestionsCaption();
2128 function v6_expand_ShowOneByOneCaptionJS() {
2129 return $this->v6_expand_ShowOneByOneCaption();
2132 // hp6checkshortanswers.js_ (JQuiz)
2134 function v6_expand_CorrectList() {
2135 return $this->xml_value("hotpot-config-file,$this->quiztype,correct-answers");
2137 function v6_expand_HybridTries() {
2138 return $this->xml_value("hotpot-config-file,$this->quiztype,short-answer-tries-on-hybrid-q");
2140 function v6_expand_PleaseEnter() {
2141 return $this->xml_value("hotpot-config-file,$this->quiztype,enter-a-guess");
2143 function v6_expand_PartlyIncorrect() {
2144 return $this->xml_value("hotpot-config-file,$this->quiztype,partly-incorrect");
2146 function v6_expand_ShowAnswerCaption() {
2147 return $this->xml_value("hotpot-config-file,$this->quiztype,show-answer-caption");
2149 function v6_expand_ShowAlsoCorrect() {
2150 return $this->bool_value('hotpot-config-file,global,show-also-correct');
2153 // insert forms and messages
2155 function insert_script($src='') {
2156 if (empty($src)) {
2157 $src = HOTPOT_JS;
2159 $url = '<SCRIPT src="'.$src.'" type="text/javascript" language="javascript"></SCRIPT>'."\n";
2160 $this->html = preg_replace('|</head>|', $url.'</head>', $this->html, 1);
2162 function insert_submission_form($attemptid) {
2163 $form_name = 'store';
2164 $form_fields = '';
2165 $form_fields .= '<INPUT type="hidden" name="attemptid" value="'.$attemptid.'">'."\n";
2166 $form_fields .= '<INPUT type="hidden" name="starttime" value="">'."\n";
2167 $form_fields .= '<INPUT type="hidden" name="endtime" value="">'."\n";
2168 $form_fields .= '<INPUT type="hidden" name="mark" value="">'."\n";
2169 $form_fields .= '<INPUT type="hidden" name="detail" value="">'."\n";
2170 $this->insert_form(
2171 '<!-- BeginSubmissionForm -->', '<!-- EndSubmissionForm -->', $form_name, $form_fields
2174 function insert_giveup_form($attemptid) {
2175 $form_name = 'giveup';
2176 $form_fields = '';
2177 $form_fields .= '<INPUT type="hidden" name="attemptid" value="'.$attemptid.'">'."\n";
2178 $form_fields .= '<INPUT type="submit" value="'.get_string('giveup', 'hotpot').'" class="FuncButton" onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">';
2179 $this->insert_form(
2180 '<!-- BeginTopNavButtons -->', '<!-- EndTopNavButtons -->', $form_name, $form_fields, true
2183 function remove_nav_buttons() {
2184 $this->insert_form(
2185 '<!-- BeginTopNavButtons -->', '<!-- EndTopNavButtons -->'
2187 $this->insert_form(
2188 '<!-- BeginBottomNavButtons -->', '<!-- EndBottomNavButtons -->'
2191 function insert_form($startblock, $endblock, $form_name=NULL, $form_fields='', $center=false) {
2192 global $CFG;
2193 $form = '';
2194 if (isset($form_name)) {
2195 $form .= '<FORM action="'.$CFG->wwwroot.'/mod/hotpot/attempt.php" method="POST" name="'.$form_name.'" target="'.$CFG->framename.'">'."\n";
2196 $form .= $form_fields;
2197 $form .= '</FORM>'."\n";
2198 if ($center) {
2199 $form = '<DIV style="margin-left: auto; margin-right: auto; text-align: center">'."\n".$form.'</DIV>'."\n";
2202 if (
2203 is_int($start = strpos($this->html, $startblock)) &&
2204 is_int($end = strpos($this->html, $endblock, $start))
2206 $this->html = substr($this->html, 0, $start).$startblock.$form.substr($this->html, $end);
2209 function insert_message($start_str, $message, $color="red", $align="center") {
2210 $message = '<p align="'.$align.'" style="color:'.$color.';text-align:'.$align.';"><b>'.$message."</b></p>\n";
2211 if (is_int($start = strpos($this->html, $start_str))) {
2212 $start += strlen($start_str);
2213 $this->html = substr($this->html, 0, $start).$message.substr($this->html, $start);
2217 function adjust_media_urls() {
2219 if ($this->forceplugins) {
2221 // make sure the Moodle media plugin is available
2222 global $CFG;
2223 include_once "$CFG->dirroot/filter/mediaplugin/filter.php";
2225 // exclude swf files from the filter
2226 $CFG->filter_mediaplugin_ignore_swf = true;
2228 $s = '\s+'; // at least one space
2229 $n = '[^>]*'; // any character inside a tag
2230 $q = '["'."']?"; // single, double, or no quote
2231 $Q = '[^"'."' >]*"; // any charater inside a quoted string
2233 // patterns to media files types and paths
2234 $filetype = "avi|mpeg|mpg|mp3|mov|wmv";
2235 $filepath = "$Q\.($filetype)";
2237 // pattern to match <param> tags which contain the file path
2238 // wmp : url
2239 // quicktime : src
2240 // realplayer : src
2241 // flash : movie (doesn't need replacing)
2242 $url_param = "/<param$s{$n}name=$q(src|url)$q$s{$n}value=$q($filepath)$q$n>/is";
2244 // pattern to match <a> tags which link to multimedia files (not swf)
2245 $link = "/<a$s{$n}href=$q($filepath)$q$n>(.*?)<\/a>/is";
2247 // extract <object> tags
2248 preg_match_all("|<object$n>(.*?)</object>|is", $this->html, $objects);
2250 $i_max = count($objects[0]);
2251 for ($i=0; $i<$i_max; $i++) {
2253 $url = '';
2254 if (preg_match($url_param, $objects[1][$i], $matches)) {
2255 $url = $matches[2];
2256 } else if (preg_match($link, $objects[1][$i], $matches)) {
2257 $url = $matches[1];
2260 if ($url) {
2261 $txt = trim(strip_tags($objects[1][$i]));
2263 // if url is in the query string, remove the leading characters
2264 $url = preg_replace('/^[^?]*\?([^=]+=[^&]*&)*[^=]+=([^&]*)$/', '$2', $url, 1);
2266 $new_object = mediaplugin_filter($this->filedir, '<a href="'.$url.'">'.$txt.'</a>');
2267 $new_object = preg_replace("|(<a$n>.*<\/a>)(.*<object$n>.*<embed$n>.*)(</embed>.*</object>.*)$|is", '$2$1$3', $new_object);
2269 $this->html = str_replace($objects[0][$i], $new_object, $this->html);
2277 function hotpot_convert_relative_urls(&$str) {
2278 $tagopen = '(?:(<)|(&lt;)|(&amp;#x003C;))'; // left angle bracket
2279 $tagclose = '(?(2)>|(?(3)&gt;|(?(4)&amp;#x003E;)))'; // right angle bracket (to match left angle bracket)
2281 $space = '\s+'; // at least one space
2282 $anychar = '(?:.*?)'; // any character
2284 $quoteopen = '("|&quot;|&amp;quot;)'; // open quote
2285 $quoteclose = '\\5'; // close quote (to match open quote)
2287 $url = '\S+?\.\S+?';
2288 $replace = "hotpot_convert_relative_url('".$this->get_baseurl()."', '".$this->reference."', '\\1', '\\6', '\\7')";
2290 $tags = array('a'=>'href','img'=>'src','param'=>'value');
2291 foreach ($tags as $tag=>$attribute) {
2293 $search = "%($tagopen$tag$space$anychar$attribute=$quoteopen)($url)($quoteclose$anychar$tagclose)%ise";
2294 $str = preg_replace($search, $replace, $str);
2297 function get_baseurl() {
2298 // set the url base (first time only)
2299 if (!isset($this->baseurl)) {
2300 global $CFG;
2301 if ($CFG->slasharguments) {
2302 $this->baseurl = "$CFG->wwwroot/file.php/$this->filedir/";
2303 } else {
2304 $this->baseurl = "$CFG->wwwroot/file.php?file=/$this->filedir/";
2307 return $this->baseurl;
2309 } // end class
2311 function hotpot_convert_relative_url($baseurl, $reference, $opentag, $url, $closetag) {
2313 // try and parse the $url into $matches
2314 // [1] path
2315 // [2] query string, if any
2316 // [3] anchor fragment, if any
2317 if (preg_match('|^([^?]*)((?:\\?[^#]*)?)((?:#.*)?)$|', $url, $matches)) {
2318 $url = $matches[1];
2319 $query = $matches[2];
2320 $fragment = $matches[3];
2321 } else {
2322 $query = '';
2323 $fragment = '';
2326 $url = hotpot_convert_url($baseurl, $reference, $url);
2328 // try and parse the query string arguments into $matches
2329 // [1] names
2330 // [2] values
2331 if ($query && preg_match_all('|([^=]+)=([^&]*)|', substr($query, 1), $matches)) {
2333 $query = array();
2335 // the values of the following arguments are considered to be URLs
2336 $url_names = array('src');
2338 // loop through the arguments in the query string
2339 $i_max = count($matches[0]);
2340 for ($i=0; $i<$i_max; $i++) {
2342 $name = $matches[1][$i];
2343 $value = $matches[2][$i];
2345 // convert $value if it is a URL
2346 if (in_array(strtolower($name), $url_names)) {
2347 $value = hotpot_convert_url($baseurl, $reference, $value);
2350 $query[] = "$name=$value";
2353 $query = '?'.implode('&', $query);
2356 // remove the slashes that were added by the "e" modifier of preg_replace
2357 $url = stripslashes("$opentag$url$query$fragment$closetag");
2359 return $url;
2362 function hotpot_convert_url($baseurl, $reference, $url) {
2363 // exclude absolute urls
2364 if (!preg_match('%^(http://|/)%i', $url)) {
2366 // get the subdirectory, $dir, of the quiz $reference
2367 $dir = dirname($reference);
2369 // allow for leading "./" and "../"
2370 while (preg_match('|^(\.{1,2})/(.*)$|', $url, $matches)) {
2371 if ($matches[1]=='..') {
2372 $dir = dirname($dir);
2374 $url = $matches[2];
2377 // add subdirectory, $dir, to $baseurl, if necessary
2378 if ($dir && $dir!='.') {
2379 $baseurl .= "$dir/";
2382 // prefix $url with $baseurl
2383 $url = "$baseurl$url";
2385 return $url;
2388 function hotpot_print_show_links($course, $location, $reference, $actions='', $spacer=' &nbsp; ', $new_window=false) {
2389 global $CFG;
2390 if (is_string($actions)) {
2391 if (empty($actions)) {
2392 $actions = 'showxmlsource,showxmltree,showhtmlsource';
2394 $actions = explode(',', $actions);
2396 $strenterafilename = get_string('enterafilename', 'hotpot');
2397 $html = <<<END_OF_SCRIPT
2398 <SCRIPT>
2399 <!--
2400 function setLink(lnk) {
2401 var form = document.forms['form'];
2402 return setLinkAttribute(lnk, 'reference', form) && setLinkAttribute(lnk, 'location', form);
2404 function setLinkAttribute(lnk, name, form) {
2405 // set link attribute value using
2406 // f(orm) name and e(lement) name
2408 var r = true; // result
2410 var obj = (form) ? form.elements[name] : null;
2411 if (obj) {
2412 r = false;
2413 var v = getObjValue(obj);
2414 if (v=='') {
2415 alert('$strenterafilename');
2416 } else {
2417 var s = lnk.href;
2418 var i = s.indexOf('?');
2419 if (i>=0) {
2420 i = s.indexOf(name+'=', i+1);
2421 if (i>=0) {
2422 i += name.length+1;
2423 var ii = s.indexOf('&', i);
2424 if (ii<0) {
2425 ii = s.length;
2427 lnk.href = s.substring(0, i) + v + s.substring(ii);
2428 r = true;
2433 return r;
2435 function getObjValue(obj) {
2436 var v = ''; // the value
2437 var t = (obj && obj.type) ? obj.type : "";
2438 if (t=="text" || t=="textarea" || t=="hidden") {
2439 v = obj.value;
2440 } else if (t=="select-one" || t=="select-multiple") {
2441 var l = obj.options.length;
2442 for (var i=0; i<l; i++) {
2443 if (obj.options[i].selected) {
2444 v += (v=="" ? "" : ",") + obj.options[i].value;
2448 return v;
2450 function getDir(s) {
2451 if (s.charAt(0)!='/') {
2452 s = '/' + s;
2454 var i = s.lastIndexOf('/');
2455 return s.substring(0, i);
2457 //-->
2458 </SCRIPT>
2459 END_OF_SCRIPT;
2460 foreach ($actions as $action) {
2461 $html .= $spacer
2462 . '<a href="'
2463 . $CFG->wwwroot.'/mod/hotpot/show.php'
2464 . '?course='.$course.'&location='.$location.'&reference='.urlencode($reference).'&action='.$action
2465 . '"'
2466 . ' onclick="return setLink(this);"'
2467 . ($new_window ? ' target="_blank"' : '')
2468 . '>'.get_string($action, 'hotpot').'</a>'
2471 print '<span class="helplink">'.$html.'</span>';
2473 function hotpot_sort_keypad_chars($a, $b) {
2474 $a = hotpot_keypad_sort_value($a);
2475 $b = hotpot_keypad_sort_value($b);
2476 return ($a<$b) ? -1 : ($a==$b ? 0 : 1);
2478 function hotpot_keypad_sort_value($char) {
2480 // hexadecimal
2481 if (preg_match('|&#x([0-9A-F]+);|ie', $char, $matches)) {
2482 $ord = hexdec($matches[1]);
2484 // decimal
2485 } else if (preg_match('|&#(\d+);|i', $char, $matches)) {
2486 $ord = intval($matches[1]);
2488 // other html entity
2489 } else if (preg_match('|&[^;]+;|', $char, $matches)) {
2490 $char = html_entity_decode($matches[0]);
2491 $ord = empty($char) ? 0 : ord($char);
2493 // not an html entity
2494 } else {
2495 $char = trim($char);
2496 $ord = empty($char) ? 0 : ord($char);
2499 // lowercase letters (plain or accented)
2500 if (($ord>=97 && $ord<=122) || ($ord>=224 && $ord<=255)) {
2501 $sort_value = ($ord-31).'.'.sprintf('%04d', $ord);
2503 // all other characters
2504 } else {
2505 $sort_value = $ord;
2508 return $sort_value;
2512 // ===================================================
2513 // function for adding attempt questions and responses
2514 // ===================================================
2516 function hotpot_add_attempt_details(&$attempt) {
2518 // if you have trouble with recursively encoded ampersands, you can correct them with this:
2519 //$attempt->details = preg_replace('|&(amp;)+#|i', '&#', $attempt->details);
2521 // encode the ampersands, so that HTML entities are preserved in the XML parser
2522 $details = str_replace('&', '&amp;', $attempt->details);
2524 // parse the attempt details as xml
2525 $details = new hotpot_xml_tree($details, "['hpjsresult']['#']");
2527 $num = -1;
2528 $q_num = -1;
2529 $question = NULL;
2530 $reponse = NULL;
2532 $i = 0;
2533 $tags = 'fields,field';
2535 while (($field="[$i]['#']") && $details->xml_value($tags, $field)) {
2537 $name = $details->xml_value($tags, $field."['fieldname'][0]['#']");
2538 $data = $details->xml_value($tags, $field."['fielddata'][0]['#']");
2540 // parse the field name into $matches
2541 // [1] quiz type
2542 // [2] attempt detail name
2543 if (preg_match('|^(\w+?)_(\w+)$|', $name, $matches)) {
2545 $quiztype = strtolower($matches[1]);
2546 $name = strtolower($matches[2]);
2548 // parse the attempt detail $name into $matches
2549 // [1] question number
2550 // [2] question detail name
2551 if (preg_match('|^q(\d+)_(\w+)$|', $name, $matches)) {
2553 // question number and detail name
2554 $num = $matches[1];
2555 $name = strtolower($matches[2]);
2556 $data = addslashes($data);
2558 // is this a new question (or the first one)?
2559 if ($q_num != $num) {
2561 // add previous question and response, if any
2562 hotpot_add_response($attempt, $question, $response);
2564 // initialize question object
2565 $question = NULL;
2566 $question->name = '';
2567 $question->text = '';
2568 $question->hotpot = $attempt->hotpot;
2570 // initialize response object
2571 $response = NULL;
2572 $response->attempt = $attempt->id;
2574 // update question number
2575 $q_num = $num;
2578 // adjust field name and value, and set question type
2579 // (may not be necessary one day)
2580 hotpot_adjust_response_field($quiztype, $question, $num, $name, $data);
2582 // add $data to the question/response details
2583 switch ($name) {
2584 case 'name':
2585 case 'type':
2586 $question->$name = $data;
2587 break;
2588 case 'text':
2589 $question->$name = hotpot_string_id($data);
2590 break;
2592 case 'correct':
2593 case 'ignored':
2594 case 'wrong':
2595 $response->$name = hotpot_string_ids($data);
2596 break;
2598 case 'score':
2599 case 'weighting':
2600 case 'hints':
2601 case 'clues':
2602 case 'checks':
2603 $response->$name = intval($data);
2604 break;
2607 } else { // attempt details
2609 // adjust field name and value
2610 hotpot_adjust_response_field($quiztype, $question, $num='', $name, $data);
2612 // add $data to the attempt details
2613 if ($name=='penalties') {
2614 $attempt->$name = intval($data);
2619 $i++;
2620 } // end while
2622 // add the final question and response, if any
2623 hotpot_add_response($attempt, $question, $response);
2627 function hotpot_add_response(&$attempt, &$question, &$response) {
2629 global $db, $next_url;
2631 if (isset($question) && isset($question->name)) {
2632 if (!$question->id = get_field('hotpot_questions', 'id', 'name', $question->name, 'hotpot', $attempt->hotpot)) {
2633 if(!$question->id = insert_record('hotpot_questions', $question)) {
2634 error("Could not add question record for attempt '$attempt->id': ".$db->ErrorMsg(), $next_url);
2637 if (isset($response)) {
2638 $response->question = $question->id;
2639 if(!$response->id = insert_record('hotpot_responses', $response)) {
2640 error("Could not add response record for attempt '$attempt->id': ".$db->ErrorMsg(), $next_url);
2645 function hotpot_adjust_response_field($quiztype, &$question, &$num, &$name, &$data) {
2646 switch ($quiztype) {
2647 case 'jcb':
2648 $question->type = HOTPOT_JCB;
2649 break;
2650 case 'jcloze':
2651 $question->type = HOTPOT_JCLOZE;
2652 $question->name = $num;
2653 switch ($name) {
2654 case 'penalties':
2655 $name = 'checks';
2656 if (is_numeric($data)) {
2657 $data++;
2659 break;
2660 case 'clue_shown':
2661 $name = 'clues';
2662 $data = ($data=='HOTPOT_YES' ? 1 : 0);
2663 break;
2664 case 'clue_text':
2665 $name = 'text';
2666 break;
2668 break;
2669 case 'jcross':
2670 $question->type = HOTPOT_JCROSS;
2671 switch ($name) {
2672 case 'across':
2673 case 'down':
2674 $question->name = $num.'_'.$name;
2675 $name = 'correct';
2676 break;
2677 case 'across_clue':
2678 case 'down_clue':
2679 $name = 'text';
2680 break;
2682 break;
2683 case 'jmatch':
2684 $question->type = HOTPOT_JMATCH;
2685 switch ($name) {
2686 case 'attempts':
2687 $name = 'penalties';
2688 if (is_numeric($data) && $data>0) {
2689 $data--;
2691 break;
2692 case 'lhs':
2693 $name = 'name';
2694 break;
2695 case 'rhs':
2696 $name = 'correct';
2697 break;
2699 break;
2700 case 'jmix':
2701 $question->type = HOTPOT_JMIX;
2702 $question->name = $num;
2703 switch ($name) {
2704 case 'wrongguesses':
2705 $name = 'checks';
2706 if (is_numeric($data)) {
2707 $data++;
2709 break;
2710 case 'right':
2711 $name = 'correct';
2712 break;
2714 break;
2715 case 'jquiz':
2716 switch ($name) {
2717 case 'type':
2718 $data = HOTPOT_JQUIZ;
2719 switch ($data) {
2720 case 'multiple-choice':
2721 $data .= '.'.HOTPOT_JQUIZ_MULTICHOICE;
2722 break;
2723 case 'short-answer':
2724 $data .= '.'.HOTPOT_JQUIZ_SHORTANSWER;
2725 break;
2726 case 'hybrid':
2727 $data .= '.'.HOTPOT_JQUIZ_HYBRID;
2728 break;
2729 case 'multi-select':
2730 $data .= '.'.HOTPOT_JQUIZ_MULTISELECT;
2731 case 'n/a':
2732 default:
2733 // do nothing more
2734 break;
2736 break;
2737 case 'question':
2738 $name = 'name';
2739 break;
2741 break;
2743 case 'rhubarb':
2744 $question->type = HOTPOT_TEXTOYS_RHUBARB;
2745 if (empty($question->name)) {
2746 $question->name = $num;
2748 break;
2750 case 'sequitur':
2751 $question->type = HOTPOT_TEXTOYS_SEQUITUR;
2752 break;
2755 function hotpot_string_ids($field_value) {
2756 $ids = array();
2757 $strings = explode(',', $field_value);
2758 foreach($strings as $str) {
2759 $ids[] = hotpot_string_id($str);
2761 return implode(',', $ids);
2763 function hotpot_string_id($str) {
2764 $id = '';
2765 if ($str) {
2767 // get the id from the table if it is already there
2768 if (!$id = get_field('hotpot_strings', 'id', 'string', $str)) {
2770 // create a string "object"
2771 $string = NULL;
2772 $string->string = $str;
2774 // try and add the new string to the table
2775 if (!$id = insert_record('hotpot_strings', $string)) {
2776 global $db;
2777 error("Could not add string record for '".htmlspecialchars($str)."': ".$db->ErrorMsg());
2781 return $id;
2784 if (!function_exists('html_entity_decode')) {
2785 // add this function for php version<4.3
2786 function html_entity_decode($str) {
2787 $t = get_html_translation_table(HTML_ENTITIES);
2788 $t = array_flip($t);
2789 return strtr($str, $t);
2794 if (!function_exists('required_param')) {
2795 // add this function for Moodle version<1.4.2
2796 function required_param($varname, $options="") {
2797 if (isset($_POST[$varname])) {
2798 $param = $_POST[$varname];
2799 } else if (isset($_GET[$varname])) {
2800 $param = $_GET[$varname];
2801 } else { // missing
2802 error('A required parameter ('.$varname.') was missing');
2804 return $param;
2807 if (!function_exists('optional_param')) {
2808 // add this function for Moodle version<1.4.2
2809 function optional_param($varname, $default=NULL, $options="") {
2810 if (isset($_POST[$varname])) {
2811 $param = $_POST[$varname];
2812 } else if (isset($_GET[$varname])) {
2813 $param = $_GET[$varname];
2814 } else {
2815 $param = $default;
2817 return $param;