Automatic installer.php lang files by installer_builder (20070726)
[moodle-linuxchix.git] / mod / hotpot / lib.php
blobb1191e8d90236d26abe67fc717bae465b26db4a6
1 <?PHP // $Id$
3 //////////////////////////////////
4 /// CONFIGURATION settings
6 if (!isset($CFG->hotpot_showtimes)) {
7 set_config("hotpot_showtimes", 0);
9 if (!isset($CFG->hotpot_excelencodings)) {
10 set_config("hotpot_excelencodings", "");
13 //////////////////////////////////
14 /// CONSTANTS and GLOBAL VARIABLES
16 $CFG->hotpotroot = "$CFG->dirroot/mod/hotpot";
17 $CFG->hotpottemplate = "$CFG->hotpotroot/template";
18 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
19 $CFG->hotpotismobile = preg_match('/Alcatel|ATTWS|DoCoMo|Doris|Hutc3G|J-PHONE|Java|KDDI|KGT|LGE|MOT|Nokia|portalmmm|ReqwirelessWeb|SAGEM|SHARP|SIE-|SonyEricsson|Teleport|UP\.Browser|UPG1|Wapagsim/', $_SERVER['HTTP_USER_AGENT']);
20 } else {
21 $CFG->hotpotismobile = false;
24 define("HOTPOT_JS", "$CFG->wwwroot/mod/hotpot/hotpot-full.js");
26 define("HOTPOT_NO", "0");
27 define("HOTPOT_YES", "1");
29 define ("HOTPOT_TEXTSOURCE_QUIZ", "0");
30 define ("HOTPOT_TEXTSOURCE_FILENAME", "1");
31 define ("HOTPOT_TEXTSOURCE_FILEPATH", "2");
32 define ("HOTPOT_TEXTSOURCE_SPECIFIC", "3");
34 define("HOTPOT_LOCATION_COURSEFILES", "0");
35 define("HOTPOT_LOCATION_SITEFILES", "1");
37 $HOTPOT_LOCATION = array (
38 HOTPOT_LOCATION_COURSEFILES => get_string("coursefiles"),
39 HOTPOT_LOCATION_SITEFILES => get_string("sitefiles"),
42 define("HOTPOT_OUTPUTFORMAT_BEST", "1");
43 define("HOTPOT_OUTPUTFORMAT_V3", "10");
44 define("HOTPOT_OUTPUTFORMAT_V4", "11");
45 define("HOTPOT_OUTPUTFORMAT_V5", "12");
46 define("HOTPOT_OUTPUTFORMAT_V5_PLUS", "13");
47 define("HOTPOT_OUTPUTFORMAT_V6", "14");
48 define("HOTPOT_OUTPUTFORMAT_V6_PLUS", "15");
49 define("HOTPOT_OUTPUTFORMAT_FLASH", "20");
50 define("HOTPOT_OUTPUTFORMAT_MOBILE", "30");
52 $HOTPOT_OUTPUTFORMAT = array (
53 HOTPOT_OUTPUTFORMAT_BEST => get_string("outputformat_best", "hotpot"),
54 HOTPOT_OUTPUTFORMAT_V6_PLUS => get_string("outputformat_v6_plus", "hotpot"),
55 HOTPOT_OUTPUTFORMAT_V6 => get_string("outputformat_v6", "hotpot"),
56 HOTPOT_OUTPUTFORMAT_V5_PLUS => get_string("outputformat_v5_plus", "hotpot"),
57 HOTPOT_OUTPUTFORMAT_V5 => get_string("outputformat_v5", "hotpot"),
58 HOTPOT_OUTPUTFORMAT_V4 => get_string("outputformat_v4", "hotpot"),
59 HOTPOT_OUTPUTFORMAT_V3 => get_string("outputformat_v3", "hotpot"),
60 HOTPOT_OUTPUTFORMAT_FLASH => get_string("outputformat_flash", "hotpot"),
61 HOTPOT_OUTPUTFORMAT_MOBILE => get_string("outputformat_mobile", "hotpot"),
63 $HOTPOT_OUTPUTFORMAT_DIR = array (
64 HOTPOT_OUTPUTFORMAT_V6_PLUS => 'v6',
65 HOTPOT_OUTPUTFORMAT_V6 => 'v6',
66 HOTPOT_OUTPUTFORMAT_V5_PLUS => 'v5',
67 HOTPOT_OUTPUTFORMAT_V5 => 'v5',
68 HOTPOT_OUTPUTFORMAT_V4 => 'v4',
69 HOTPOT_OUTPUTFORMAT_V3 => 'v3',
70 HOTPOT_OUTPUTFORMAT_FLASH => 'flash',
71 HOTPOT_OUTPUTFORMAT_MOBILE => 'mobile',
73 foreach ($HOTPOT_OUTPUTFORMAT_DIR as $format=>$dir) {
74 if (is_file("$CFG->hotpottemplate/$dir.php") && is_dir("$CFG->hotpottemplate/$dir")) {
75 // do nothing ($format is available)
76 } else {
77 // $format is not available, so remove it
78 unset($HOTPOT_OUTPUTFORMAT[$format]);
79 unset($HOTPOT_OUTPUTFORMAT_DIR[$format]);
82 define("HOTPOT_NAVIGATION_BAR", "1");
83 define("HOTPOT_NAVIGATION_FRAME", "2");
84 define("HOTPOT_NAVIGATION_IFRAME", "3");
85 define("HOTPOT_NAVIGATION_BUTTONS", "4");
86 define("HOTPOT_NAVIGATION_GIVEUP", "5");
87 define("HOTPOT_NAVIGATION_NONE", "6");
89 $HOTPOT_NAVIGATION = array (
90 HOTPOT_NAVIGATION_BAR => get_string("navigation_bar", "hotpot"),
91 HOTPOT_NAVIGATION_FRAME => get_string("navigation_frame", "hotpot"),
92 HOTPOT_NAVIGATION_IFRAME => get_string("navigation_iframe", "hotpot"),
93 HOTPOT_NAVIGATION_BUTTONS => get_string("navigation_buttons", "hotpot"),
94 HOTPOT_NAVIGATION_GIVEUP => get_string("navigation_give_up", "hotpot"),
95 HOTPOT_NAVIGATION_NONE => get_string("navigation_none", "hotpot"),
98 define("HOTPOT_JCB", "1");
99 define("HOTPOT_JCLOZE", "2");
100 define("HOTPOT_JCROSS", "3");
101 define("HOTPOT_JMATCH", "4");
102 define("HOTPOT_JMIX", "5");
103 define("HOTPOT_JQUIZ", "6");
104 define("HOTPOT_TEXTOYS_RHUBARB", "7");
105 define("HOTPOT_TEXTOYS_SEQUITUR", "8");
107 $HOTPOT_QUIZTYPE = array(
108 HOTPOT_JCB => 'JCB',
109 HOTPOT_JCLOZE => 'JCloze',
110 HOTPOT_JCROSS => 'JCross',
111 HOTPOT_JMATCH => 'JMatch',
112 HOTPOT_JMIX => 'JMix',
113 HOTPOT_JQUIZ => 'JQuiz',
114 HOTPOT_TEXTOYS_RHUBARB => 'Rhubarb',
115 HOTPOT_TEXTOYS_SEQUITUR => 'Sequitur'
118 define("HOTPOT_JQUIZ_MULTICHOICE", "1");
119 define("HOTPOT_JQUIZ_SHORTANSWER", "2");
120 define("HOTPOT_JQUIZ_HYBRID", "3");
121 define("HOTPOT_JQUIZ_MULTISELECT", "4");
123 define("HOTPOT_GRADEMETHOD_HIGHEST", "1");
124 define("HOTPOT_GRADEMETHOD_AVERAGE", "2");
125 define("HOTPOT_GRADEMETHOD_FIRST", "3");
126 define("HOTPOT_GRADEMETHOD_LAST", "4");
128 $HOTPOT_GRADEMETHOD = array (
129 HOTPOT_GRADEMETHOD_HIGHEST => get_string("gradehighest", "quiz"),
130 HOTPOT_GRADEMETHOD_AVERAGE => get_string("gradeaverage", "quiz"),
131 HOTPOT_GRADEMETHOD_FIRST => get_string("attemptfirst", "quiz"),
132 HOTPOT_GRADEMETHOD_LAST => get_string("attemptlast", "quiz"),
135 define("HOTPOT_STATUS_INPROGRESS", "1");
136 define("HOTPOT_STATUS_TIMEDOUT", "2");
137 define("HOTPOT_STATUS_ABANDONED", "3");
138 define("HOTPOT_STATUS_COMPLETED", "4");
140 $HOTPOT_STATUS = array (
141 HOTPOT_STATUS_INPROGRESS => get_string("inprogress", "hotpot"),
142 HOTPOT_STATUS_TIMEDOUT => get_string("timedout", "hotpot"),
143 HOTPOT_STATUS_ABANDONED => get_string("abandoned", "hotpot"),
144 HOTPOT_STATUS_COMPLETED => get_string("completed", "hotpot"),
147 define("HOTPOT_FEEDBACK_NONE", "0");
148 define("HOTPOT_FEEDBACK_WEBPAGE", "1");
149 define("HOTPOT_FEEDBACK_FORMMAIL", "2");
150 define("HOTPOT_FEEDBACK_MOODLEFORUM", "3");
151 define("HOTPOT_FEEDBACK_MOODLEMESSAGING", "4");
153 $HOTPOT_FEEDBACK = array (
154 HOTPOT_FEEDBACK_NONE => get_string("feedbacknone", "hotpot"),
155 HOTPOT_FEEDBACK_WEBPAGE => get_string("feedbackwebpage", "hotpot"),
156 HOTPOT_FEEDBACK_FORMMAIL => get_string("feedbackformmail", "hotpot"),
157 HOTPOT_FEEDBACK_MOODLEFORUM => get_string("feedbackmoodleforum", "hotpot"),
158 HOTPOT_FEEDBACK_MOODLEMESSAGING => get_string("feedbackmoodlemessaging", "hotpot"),
160 if (empty($CFG->messaging)) { // Moodle 1.4 (and less)
161 unset($HOTPOT_FEEDBACK[HOTPOT_FEEDBACK_MOODLEMESSAGING]);
164 define("HOTPOT_DISPLAYNEXT_QUIZ", "0");
165 define("HOTPOT_DISPLAYNEXT_COURSE", "1");
166 define("HOTPOT_DISPLAYNEXT_INDEX", "2");
169 * If start and end date for the quiz are more than this many seconds apart
170 * they will be represented by two separate events in the calendar
172 define("HOTPOT_MAX_EVENT_LENGTH", "432000"); // 5 days maximum
174 //////////////////////////////////
175 /// CORE FUNCTIONS
178 // possible return values:
179 // false:
180 // display moderr.html (if exists) OR "Could not update" and return to couse view
181 // string:
182 // display as error message and return to course view
183 // true (or non-zero number):
184 // continue to $hotpot->redirect (if set) OR hotpot/view.php (to displsay quiz)
186 // $hotpot is an object containing the values of the form in mod.html
187 // i.e. all the fields in the 'hotpot' table, plus the following:
188 // $hotpot->course : an id in the 'course' table
189 // $hotpot->coursemodule : an id in the 'course_modules' table
190 // $hotpot->section : an id in the 'course_sections' table
191 // $hotpot->module : an id in the 'modules' table
192 // $hotpot->modulename : always 'hotpot'
193 // $hotpot->instance : an id in the 'hotpot' table
194 // $hotpot->mode : 'add' or 'update'
195 // $hotpot->sesskey : unique string required for Moodle's session management
197 function hotpot_add_instance(&$hotpot) {
198 if (hotpot_set_form_values($hotpot)) {
199 if ($result = insert_record('hotpot', $hotpot)) {
200 $hotpot->id = $result;
201 hotpot_update_events($hotpot);
203 } else {
204 $result= false;
206 return $result;
209 function hotpot_update_instance(&$hotpot) {
210 if (hotpot_set_form_values($hotpot)) {
211 $hotpot->id = $hotpot->instance;
212 if ($result = update_record('hotpot', $hotpot)) {
213 hotpot_update_events($hotpot);
215 } else {
216 $result= false;
218 return $result;
220 function hotpot_update_events($hotpot) {
222 // remove any previous calendar events for this hotpot
223 delete_records('event', 'modulename', 'hotpot', 'instance', $hotpot->id);
225 $event = new stdClass();
226 $event->description = addslashes($hotpot->summary);
227 $event->courseid = $hotpot->course;
228 $event->groupid = 0;
229 $event->userid = 0;
230 $event->modulename = 'hotpot';
231 $event->instance = $hotpot->id;
232 $event->timestart = $hotpot->timeopen;
233 if ($cm = get_coursemodule_from_id('hotpot', $hotpot->id)) {
234 $event->visible = hotpot_is_visible($cm);
235 } else {
236 $event->visible = 1;
239 if ($hotpot->timeclose && $hotpot->timeopen) {
240 // we have both a start and an end date
241 $event->eventtype = 'open';
242 $event->timeduration = ($hotpot->timeclose - $hotpot->timeopen);
244 if ($event->timeduration > HOTPOT_MAX_EVENT_LENGTH) { /// Long durations create two events
246 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotopens', 'hotpot').')';
247 $event->timeduration = 0;
248 add_event($event);
250 $event->timestart = $hotpot->timeclose;
251 $event->eventtype = 'close';
252 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotcloses', 'hotpot').')';
253 unset($event->id);
254 add_event($event);
255 } else { // single event with duration
256 $event->name = $hotpot->name;
257 add_event($event);
259 } elseif ($hotpot->timeopen) { // only an open date
260 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotopens', 'hotpot').')';
261 $event->eventtype = 'open';
262 $event->timeduration = 0;
263 add_event($event);
264 } elseif ($hotpot->timeclose) { // only a closing date
265 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotcloses', 'hotpot').')';
266 $event->timestart = $hotpot->timeclose;
267 $event->eventtype = 'close';
268 $event->timeduration = 0;
269 add_event($event);
273 function hotpot_set_form_values(&$hotpot) {
274 $ok = true;
275 $hotpot->errors = array(); // these will be reported by moderr.html
277 if (empty($hotpot->reference)) {
278 $ok = false;
279 $hotpot->errors['reference']= get_string('error_nofilename', 'hotpot');
282 if ($hotpot->studentfeedbackurl=='http://') {
283 $hotpot->studentfeedbackurl = '';
286 if (empty($hotpot->studentfeedbackurl)) {
287 switch ($hotpot->studentfeedback) {
288 case HOTPOT_FEEDBACK_WEBPAGE:
289 $ok = false;
290 $hotpot->errors['studentfeedbackurl']= get_string('error_nofeedbackurlwebpage', 'hotpot');
291 break;
292 case HOTPOT_FEEDBACK_FORMMAIL:
293 $ok = false;
294 $hotpot->errors['studentfeedbackurl']= get_string('error_nofeedbackurlformmail', 'hotpot');
295 break;
299 $time = time();
300 $hotpot->timecreated = $time;
301 $hotpot->timemodified = $time;
303 if (empty($hotpot->enabletimeopen)) {
304 $hotpot->timeopen = 0;
305 } else {
306 $hotpot->timeopen = make_timestamp(
307 $hotpot->openyear, $hotpot->openmonth, $hotpot->openday,
308 $hotpot->openhour, $hotpot->openminute, 0
312 if (empty($hotpot->enabletimeclose)) {
313 $hotpot->timeclose = 0;
314 } else {
315 $hotpot->timeclose = make_timestamp(
316 $hotpot->closeyear, $hotpot->closemonth, $hotpot->closeday,
317 $hotpot->closehour, $hotpot->closeminute, 0
321 if ($hotpot->quizchain==HOTPOT_YES) {
322 switch ($hotpot->mode) {
323 case 'add':
324 $ok = hotpot_add_chain($hotpot);
325 break;
326 case 'update':
327 $ok = hotpot_update_chain($hotpot);
328 break;
330 } else { // $hotpot->quizchain==HOTPOT_NO
331 hotpot_set_name_summary_reference($hotpot);
334 switch ($hotpot->displaynext) {
335 // N.B. redirection only works for Moodle 1.5+
336 case HOTPOT_DISPLAYNEXT_COURSE:
337 $hotpot->redirect = true;
338 $hotpot->redirecturl = "view.php?id=$hotpot->course";
339 break;
340 case HOTPOT_DISPLAYNEXT_INDEX:
341 $hotpot->redirect = true;
342 $hotpot->redirecturl = "../mod/hotpot/index.php?id=$hotpot->course";
343 break;
344 default:
345 // use Moodle default action (i.e. go on to display the hotpot quiz)
348 // if ($ok && $hotpot->setdefaults) {
349 if ($ok) {
350 set_user_preference('hotpot_timeopen', $hotpot->timeopen);
351 set_user_preference('hotpot_timeclose', $hotpot->timeclose);
352 set_user_preference('hotpot_navigation', $hotpot->navigation);
353 set_user_preference('hotpot_outputformat', $hotpot->outputformat);
354 set_user_preference('hotpot_studentfeedback', $hotpot->studentfeedback);
355 set_user_preference('hotpot_studentfeedbackurl', $hotpot->studentfeedbackurl);
356 set_user_preference('hotpot_forceplugins', $hotpot->forceplugins);
357 set_user_preference('hotpot_shownextquiz', $hotpot->shownextquiz);
358 set_user_preference('hotpot_review', $hotpot->review);
359 set_user_preference('hotpot_grade', $hotpot->grade);
360 set_user_preference('hotpot_grademethod', $hotpot->grademethod);
361 set_user_preference('hotpot_attempts', $hotpot->attempts);
362 set_user_preference('hotpot_subnet', $hotpot->subnet);
363 set_user_preference('hotpot_displaynext', $hotpot->displaynext);
364 if ($hotpot->mode=='add') {
365 set_user_preference('hotpot_quizchain', $hotpot->quizchain);
366 set_user_preference('hotpot_namesource', $hotpot->namesource);
367 set_user_preference('hotpot_summarysource', $hotpot->summarysource);
370 return $ok;
372 function hotpot_get_chain(&$cm) {
373 // get details of course_modules in this section
374 $course_module_ids = get_field('course_sections', 'sequence', 'id', $cm->section);
375 if (empty($course_module_ids)) {
376 $hotpot_modules = array();
377 } else {
378 $hotpot_modules = get_records_select('course_modules', "id IN ($course_module_ids) AND module=$cm->module");
379 if (empty($hotpot_modules)) {
380 $hotpot_modules = array();
384 // get ids of hotpot modules in this section
385 $ids = array();
386 foreach ($hotpot_modules as $hotpot_module) {
387 $ids[] = $hotpot_module->instance;
390 // get details of hotpots in this section
391 if (empty($ids)) {
392 $hotpots = array();
393 } else {
394 $hotpots = get_records_list('hotpot', 'id', implode(',', $ids));
397 $found = false;
398 $chain = array();
400 // loop through course_modules in this section
401 $ids = explode(',', $course_module_ids);
402 foreach ($ids as $id) {
404 // check this course_module is a hotpot activity
405 if (isset($hotpot_modules[$id])) {
407 // store details of this course module and hotpot activity
408 $hotpot_id = $hotpot_modules[$id]->instance;
409 $chain[$id] = &$hotpot_modules[$id];
410 $chain[$id]->hotpot = &$hotpots[$hotpot_id];
412 // set $found, if this is the course module we're looking for
413 if (isset($cm->coursemodule)) {
414 if ($id==$cm->coursemodule) {
415 $found = true;
417 } else {
418 if ($id==$cm->id) {
419 $found = true;
423 // is this the end of a chain
424 if (empty($hotpots[$hotpot_id]->shownextquiz)) {
425 if ($found) {
426 break; // out of loop
427 } else {
428 // restart chain (target cm has not been found yet)
429 $chain = array();
433 } // end foreach $ids
435 return $found ? $chain : false;
437 function hotpot_is_visible(&$cm) {
438 if (!isset($cm->sectionvisible)) {
439 if ($section = get_record('course_sections', 'id', $cm->section)) {
440 $cm->sectionvisible = $section->visible;
441 } else {
442 error('Course module record contains invalid section');
446 if (empty($cm->sectionvisible)) {
447 $visible = HOTPOT_NO;
448 } else {
449 $visible = HOTPOT_YES;
450 if (empty($cm->visible)) {
451 if ($chain = hotpot_get_chain($cm)) {
452 $startofchain = array_shift($chain);
453 $visible = $startofchain->visible;
457 return $visible;
459 function hotpot_add_chain(&$hotpot) {
460 /// add a chain of hotpot actiivities
462 global $CFG, $course;
464 $ok = true;
465 $hotpot->names = array();
466 $hotpot->summaries = array();
467 $hotpot->references = array();
469 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
471 if (isset($xml_quiz->error)) {
472 $hotpot->errors['reference'] = $xml_quiz->error;
473 $ok = false;
475 } else if (is_dir($xml_quiz->filepath)) {
477 // get list of hotpot files in this folder
478 if ($dh = @opendir($xml_quiz->filepath)) {
479 while ($file = @readdir($dh)) {
480 if (preg_match('/\.(jbc|jcl|jcw|jmt|jmx|jqz|htm|html)$/', $file)) {
481 $hotpot->references[] = "$xml_quiz->reference/$file";
484 closedir($dh);
486 // get titles
487 foreach ($hotpot->references as $i=>$reference) {
488 $filepath = $xml_quiz->fileroot.'/'.$reference;
489 hotpot_get_titles_and_next_ex($hotpot, $filepath);
490 $hotpot->names[$i] = $hotpot->exercisetitle;
491 $hotpot->summaries[$i] = $hotpot->exercisesubtitle;
494 } else {
495 $ok = false;
496 $hotpot->errors['reference'] = get_string('error_couldnotopenfolder', 'hotpot', $hotpot->reference);
499 } else if (is_file($xml_quiz->filepath)) {
501 $filerootlength = strlen($xml_quiz->fileroot) + 1;
503 while ($xml_quiz->filepath) {
504 hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath, true);
505 $hotpot->names[] = $hotpot->exercisetitle;
506 $hotpot->summaries[] = $hotpot->exercisesubtitle;
507 $hotpot->references[] = substr($xml_quiz->filepath, $filerootlength);
509 if ($hotpot->nextexercise) {
510 $filepath = $xml_quiz->fileroot.'/'.$xml_quiz->filesubdir.$hotpot->nextexercise;
512 // check file is not already in chain
513 $reference = substr($filepath, $filerootlength);
514 if (in_array($reference, $hotpot->references)) {
515 $filepath = '';
517 } else {
518 $filepath = '';
520 if ($filepath && file_exists($filepath) && is_file($filepath) && is_readable($filepath)) {
521 $xml_quiz->filepath = $filepath;
522 } else {
523 $xml_quiz->filepath = false; // finish while loop
525 } // end while
527 } else {
528 $ok = false;
529 $hotpot->errors['reference'] = get_string('error_notfileorfolder', 'hotpot', $hotpot->reference);
532 if (empty($hotpot->references) && empty($hotpot->errors['reference'])) {
533 $ok = false;
534 $hotpot->errors['reference'] = get_string('error_noquizzesfound', 'hotpot', $hotpot->reference);
537 if ($ok) {
538 $hotpot->visible = HOTPOT_YES;
540 if (trim($hotpot->name)=='') {
541 $hotpot->name = get_string("modulename", $hotpot->modulename);
543 $hotpot->specificname = $hotpot->name;
544 $hotpot->specificsummary = $hotpot->summary;
546 // add all except last activity in chain
548 $i_max = count($hotpot->references)-1;
549 for ($i=0; $i<$i_max; $i++) {
551 hotpot_set_name_summary_reference($hotpot, $i);
552 $hotpot->reference = addslashes($hotpot->reference);
554 if (!$hotpot->instance = insert_record("hotpot", $hotpot)) {
555 error("Could not add a new instance of $hotpot->modulename", "view.php?id=$hotpot->course");
558 // store (hotpot table) id of start of chain
559 if ($i==0) {
560 $hotpot->startofchain = $hotpot->instance;
563 if (isset($course->groupmode)) {
564 $hotpot->groupmode = $course->groupmode;
567 if (! $hotpot->coursemodule = add_course_module($hotpot)) {
568 error("Could not add a new course module");
570 if (! $sectionid = add_mod_to_section($hotpot) ) {
571 error("Could not add the new course module to that section");
574 if (! set_field("course_modules", "section", $sectionid, "id", $hotpot->coursemodule)) {
575 error("Could not update the course module with the correct section");
578 add_to_log($hotpot->course, "course", "add mod",
579 "../mod/$hotpot->modulename/view.php?id=$hotpot->coursemodule",
580 "$hotpot->modulename $hotpot->instance"
582 add_to_log($hotpot->course, $hotpot->modulename, "add",
583 "view.php?id=$hotpot->coursemodule",
584 "$hotpot->instance", $hotpot->coursemodule
587 // hide tail of chain
588 if ($hotpot->shownextquiz==HOTPOT_YES) {
589 $hotpot->visible = HOTPOT_NO;
591 } // end for ($hotpot->references)
593 // settings for final activity in chain
594 hotpot_set_name_summary_reference($hotpot, $i);
595 $hotpot->reference = addslashes($hotpot->references[$i]);
596 $hotpot->shownextquiz = HOTPOT_NO;
598 if (isset($hotpot->startofchain)) {
599 // redirection only works for Moodle 1.5+
600 $hotpot->redirect = true;
601 $hotpot->redirecturl = "$CFG->wwwroot/mod/hotpot/view.php?hp=$hotpot->startofchain";
603 } // end if $ok
605 return $ok;
607 function hotpot_set_name_summary_reference(&$hotpot, $chain_index=NULL) {
609 $xml_quiz = NULL;
611 $textfields = array('name', 'summary');
612 foreach ($textfields as $textfield) {
614 $textsource = $textfield.'source';
616 // are we adding a chain?
617 if (isset($chain_index)) {
619 switch ($hotpot->$textsource) {
620 case HOTPOT_TEXTSOURCE_QUIZ:
621 if ($textfield=='name') {
622 $hotpot->exercisetitle = $hotpot->names[$chain_index];
623 } else if ($textfield=='summary') {
624 $hotpot->exercisesubtitle = $hotpot->summaries[$chain_index];
626 break;
627 case HOTPOT_TEXTSOURCE_SPECIFIC:
628 $specifictext = 'specific'.$textfield;
629 if (empty($hotpot->$specifictext) && trim($hotpot->$specifictext)=='') {
630 $hotpot->$textfield = '';
631 } else {
632 $hotpot->$textfield = $hotpot->$specifictext.' ('.($chain_index+1).')';
634 break;
636 $hotpot->reference = $hotpot->references[$chain_index];
639 if ($hotpot->$textsource==HOTPOT_TEXTSOURCE_QUIZ) {
640 if (empty($xml_quiz) && !isset($chain_index)) {
641 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
642 hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath);
644 if ($textfield=='name') {
645 $hotpot->$textfield = addslashes($hotpot->exercisetitle);
646 } else if ($textfield=='summary') {
647 $hotpot->$textfield = addslashes($hotpot->exercisesubtitle);
650 switch ($hotpot->$textsource) {
651 case HOTPOT_TEXTSOURCE_FILENAME:
652 $hotpot->$textfield = basename($hotpot->reference);
653 break;
654 case HOTPOT_TEXTSOURCE_FILEPATH:
655 $hotpot->$textfield = '';
656 // continue to next lines
657 default:
658 if (empty($hotpot->$textfield)) {
659 $hotpot->$textfield = str_replace('/', ' ', $hotpot->reference);
661 } // end switch
662 } // end foreach
664 function hotpot_get_titles_and_next_ex(&$hotpot, $filepath, $get_next=false) {
666 $hotpot->exercisetitle = '';
667 $hotpot->exercisesubtitle = '';
668 $hotpot->nextexercise = '';
670 // read the quiz file source
671 if ($source = file_get_contents($filepath)) {
673 $next = '';
674 $title = '';
675 $subtitle = '';
677 if (preg_match('|\.html?$|', $filepath)) {
678 // html file
679 if (preg_match('|<h2[^>]*class="ExerciseTitle"[^>]*>(.*?)</h2>|is', $source, $matches)) {
680 $title = trim(strip_tags($matches[1]));
682 if (empty($title)) {
683 if (preg_match('|<title[^>]*>(.*?)</title>|is', $source, $matches)) {
684 $title = trim(strip_tags($matches[1]));
687 if (preg_match('|<h3[^>]*class="ExerciseSubtitle"[^>]*>(.*?)</h3>|is', $source, $matches)) {
688 $subtitle = trim(strip_tags($matches[1]));
690 if ($get_next) {
691 if (preg_match('|<div[^>]*class="NavButtonBar"[^>]*>(.*?)</div>|is', $source, $matches)) {
692 $navbuttonbar = $matches[1];
693 if (preg_match_all('|<button[^>]*class="NavButton"[^>]*onclick="'."location='([^']*)'".'[^"]*"[^>]*>|is', $navbuttonbar, $matches)) {
694 $lastbutton = count($matches[0])-1;
695 $next = $matches[1][$lastbutton];
700 } else {
701 // xml file (...maybe)
702 $xml_tree = new hotpot_xml_tree($source);
703 $xml_tree->filetype = '';
705 $keys = array_keys($xml_tree->xml);
706 foreach ($keys as $key) {
707 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
708 $xml_tree->filetype = 'xml';
709 $xml_tree->xml_root = "['$key']['#']";
710 $xml_tree->quiztype = strtolower($matches[2]);
711 break;
714 if ($xml_tree->filetype=='xml') {
716 $title = strip_tags($xml_tree->xml_value('data,title'));
717 $subtitle = $xml_tree->xml_value('hotpot-config-file,'.$xml_tree->quiztype.',exercise-subtitle');
719 if ($get_next) {
720 $include = $xml_tree->xml_value('hotpot-config-file,global,include-next-ex');
721 if (!empty($include)) {
722 $next = $xml_tree->xml_value("hotpot-config-file,$xml_tree->quiztype,next-ex-url");
723 if (is_array($next)) {
724 $next = $next[0]; // in case "next-ex-url" was repeated in the xml file
731 $hotpot->nextexercise = $next;
732 $hotpot->exercisetitle = (empty($title) || is_array($title)) ? basename($filepath) : $title;
733 $hotpot->exercisesubtitle = (empty($subtitle) || is_array($subtitle)) ? $hotpot->exercisetitle : $subtitle;
736 function hotpot_get_all_instances_in_course($modulename, $course) {
737 /// called from index.php
739 global $CFG;
740 $instances = array();
742 if (isset($CFG->release) && substr($CFG->release, 0, 3)>=1.2) {
743 $groupmode = 'cm.groupmode,';
744 } else {
745 $groupmode = '';
748 $query = "
749 SELECT
750 cm.id AS coursemodule,
751 cm.course AS course,
752 cm.module AS module,
753 cm.instance AS instance,
754 -- cm.section AS section,
755 cm.visible AS visible,
756 $groupmode
757 -- cs.section AS sectionnumber,
758 cs.section AS section,
759 cs.sequence AS sequence,
760 cs.visible AS sectionvisible,
761 thismodule.*
762 FROM
763 {$CFG->prefix}course_modules cm,
764 {$CFG->prefix}course_sections cs,
765 {$CFG->prefix}modules m,
766 {$CFG->prefix}$modulename thismodule
767 WHERE
768 m.name = '$modulename' AND
769 m.id = cm.module AND
770 cm.course = '$course->id' AND
771 cm.section = cs.id AND
772 cm.instance = thismodule.id
774 if ($rawmods = get_records_sql($query)) {
776 // cache $isteacher setting
778 $isteacher = has_capability('mod/hotpot:viewreport', get_context_instance(CONTEXT_MODULE, $course->id));
780 $explodesection = array();
781 $order = array();
783 foreach ($rawmods as $rawmod) {
785 if (empty($explodesection[$rawmod->section])) {
786 $explodesection[$rawmod->section] = true;
788 $coursemodules = explode(',', $rawmod->sequence);
789 foreach ($coursemodules as $i=>$coursemodule) {
790 $order[$coursemodule] = sprintf('%d.%04d', $rawmod->section, $i);
794 if ($isteacher) {
795 $visible = true;
796 } else if ($modulename=='hotpot') {
797 $visible = hotpot_is_visible($rawmod);
798 } else {
799 $visible = $rawmod->visible;
802 if ($visible) {
803 $instances[$order[$rawmod->coursemodule]] = $rawmod;
806 } // end foreach $modinfo
808 ksort($instances);
809 $instances = array_values($instances);
812 return $instances;
815 function hotpot_update_chain(&$hotpot) {
816 /// update a chain of hotpot actiivities
818 $ok = true;
819 if ($hotpot_modules = hotpot_get_chain($hotpot)) {
821 // skip updating of these fields
822 $skipfields = array('id', 'course', 'name', 'reference', 'summary', 'shownextquiz');
823 $fields = array();
825 foreach ($hotpot_modules as $hotpot_module) {
827 if ($hotpot->instance==$hotpot_module->id) {
828 // don't need to update this hotpot
830 } else {
831 // shortcut to hotpot record
832 $thishotpot = &$hotpot_module->hotpot;
834 // get a list of fields to update (first time only)
835 if (empty($fields)) {
836 $fields = array_keys(get_object_vars($thishotpot));
839 // assume update is NOT required
840 $require_update = false;
842 // update field values (except $skipfields)
843 foreach($fields as $field) {
844 if (in_array($field, $skipfields) || $thishotpot->$field==$hotpot->$field) {
845 // update not required for this field
846 } else {
847 $require_update = true;
848 $thishotpot->$field = $hotpot->$field;
852 // update $thishotpot, if required
853 if ($require_update && !update_record("hotpot", $thishotpot)) {
854 error("Could not update the $hotpot->modulename", "view.php?id=$hotpot->course");
857 } // end foreach $ids
859 return $ok;
861 function hotpot_delete_instance($id) {
862 /// Given an ID of an instance of this module,
863 /// this function will permanently delete the instance
864 /// and any data that depends on it.
866 $result = false;
867 if (delete_records("hotpot", "id", "$id")) {
868 $result = true;
869 delete_records("hotpot_questions", "hotpot", "$id");
870 if ($attempts = get_records_select("hotpot_attempts", "hotpot='$id'")) {
871 $ids = implode(',', array_keys($attempts));
872 delete_records_select("hotpot_attempts", "id IN ($ids)");
873 delete_records_select("hotpot_details", "attempt IN ($ids)");
874 delete_records_select("hotpot_responses", "attempt IN ($ids)");
876 // remove calendar events for this hotpot
877 delete_records('event', 'modulename', 'hotpot', 'instance', $id);
879 return $result;
881 function hotpot_delete_and_notify($table, $select, $strtable) {
882 $count = max(0, count_records_select($table, $select));
883 if ($count) {
884 delete_records_select($table, $select);
885 $count -= max(0, count_records_select($table, $select));
886 if ($count) {
887 notify(get_string('deleted')." $count x $strtable");
892 function hotpot_user_complete($course, $user, $mod, $hotpot) {
893 /// Print a detailed representation of what a user has done with
894 /// a given particular instance of this module, for user activity reports.
896 $report = hotpot_user_outline($course, $user, $mod, $hotpot);
897 if (empty($report)) {
898 print get_string("noactivity", "hotpot");
899 } else {
900 $date = userdate($report->time, get_string('strftimerecentfull'));
901 print $report->info.' '.get_string('mostrecently').': '.$date;
903 return true;
906 function hotpot_user_outline($course, $user, $mod, $hotpot) {
907 /// Return a small object with summary information about what a
908 /// user has done with a given particular instance of this module
909 /// Used for user activity reports.
910 /// $report->time = the time they did it
911 /// $report->info = a short text description
913 $report = NULL;
914 if ($records = get_records_select("hotpot_attempts", "hotpot='$hotpot->id' AND userid='$user->id'", "timestart ASC", "*")) {
915 $report = new stdClass();
916 $scores = array();
917 foreach ($records as $record){
918 if (empty($report->time)) {
919 $report->time = $record->timestart;
921 $scores[] = hotpot_format_score($record);
923 if (empty($scores)) {
924 $report->time = 0;
925 $report->info = get_string('noactivity', 'hotpot');
926 } else {
927 $report->info = get_string('score', 'quiz').': '.implode(', ', $scores);
930 return $report;
933 function hotpot_format_score($record, $undefined='&nbsp;') {
934 if (isset($record->score)) {
935 $score = $record->score;
936 } else {
937 $score = $undefined;
939 return $score;
942 function hotpot_format_status($record, $undefined='&nbsp;') {
943 global $HOTPOT_STATUS;
945 if (isset($record->status) || isset($HOTPOT_STATUS[$record->status])) {
946 $status = $HOTPOT_STATUS[$record->status];
947 } else {
948 $status = $undefined;
950 return $status;
953 function hotpot_print_recent_activity($course, $isteacher, $timestart) {
954 /// Given a course and a time, this module should find recent activity
955 /// that has occurred in hotpot activities and print it out.
956 /// Return true if there was output, or false is there was none.
958 global $CFG;
959 $result = false;
961 $records = get_records_sql("
962 SELECT
963 h.id AS id,
964 h.name AS name,
965 COUNT(*) AS count_attempts
966 FROM
967 {$CFG->prefix}hotpot h,
968 {$CFG->prefix}hotpot_attempts a
969 WHERE
970 h.course = $course->id
971 AND h.id = a.hotpot
972 AND a.id = a.clickreportid
973 AND a.starttime > $timestart
974 GROUP BY
975 h.id, h.name
977 // note that PostGreSQL requires h.name in the GROUP BY clause
979 if($records) {
980 $names = array();
981 foreach ($records as $id => $record){
982 if ($cm = get_coursemodule_from_instance('hotpot', $record->id, $course->id)) {
983 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
985 if (has_capability('mod/hotpot:viewreport', $context)) {
986 $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$id";
987 $name = '&nbsp;<a href="'.$href.'">'.$record->name.'</a>';
988 if ($record->count_attempts > 1) {
989 $name .= " ($record->count_attempts)";
991 $names[] = $name;
995 if (count($names) > 0) {
996 print_headline(get_string('modulenameplural', 'hotpot').':');
998 if ($CFG->version >= 2005050500) { // Moodle 1.5+
999 echo '<div class="head"><div class="name">'.implode('<br />', $names).'</div></div>';
1000 } else { // Moodle 1.4.x (or less)
1001 echo '<font size="1">'.implode('<br />', $names).'</font>';
1003 $result = true;
1006 return $result; // True if anything was printed, otherwise false
1009 function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") {
1010 // Returns all quizzes since a given time.
1012 global $CFG;
1014 // If $cmid or $userid are specified, then this restricts the results
1015 $cm_select = empty($cmid) ? "" : " AND cm.id = '$cmid'";
1016 $user_select = empty($userid) ? "" : " AND u.id = '$userid'";
1018 $records = get_records_sql("
1019 SELECT
1020 a.*,
1021 h.name, h.course,
1022 cm.instance, cm.section,
1023 u.firstname, u.lastname, u.picture
1024 FROM
1025 {$CFG->prefix}hotpot_attempts a,
1026 {$CFG->prefix}hotpot h,
1027 {$CFG->prefix}course_modules cm,
1028 {$CFG->prefix}user u
1029 WHERE
1030 a.timefinish > '$sincetime'
1031 AND a.id = a.clickreportid
1032 AND a.userid = u.id $user_select
1033 AND a.hotpot = h.id $cm_select
1034 AND cm.instance = h.id
1035 AND cm.course = '$courseid'
1036 AND h.course = cm.course
1037 ORDER BY
1038 a.timefinish ASC
1041 if (!empty($records)) {
1042 foreach ($records as $record) {
1043 if (empty($groupid) || ismember($groupid, $record->userid)) {
1045 unset($activity);
1047 $activity->type = "hotpot";
1048 $activity->defaultindex = $index;
1049 $activity->instance = $record->hotpot;
1051 $activity->name = $record->name;
1052 $activity->section = $record->section;
1054 $activity->content->attemptid = $record->id;
1055 $activity->content->attempt = $record->attempt;
1056 $activity->content->score = $record->score;
1057 $activity->content->timestart = $record->timestart;
1058 $activity->content->timefinish = $record->timefinish;
1060 $activity->user->userid = $record->userid;
1061 $activity->user->fullname = fullname($record);
1062 $activity->user->picture = $record->picture;
1064 $activity->timestamp = $record->timefinish;
1066 $activities[] = $activity;
1068 $index++;
1070 } // end foreach
1074 function hotpot_print_recent_mod_activity($activity, $course, $detail=false) {
1075 /// Basically, this function prints the results of "hotpot_get_recent_activity"
1077 global $CFG, $THEME, $USER;
1079 print '<table border="0" cellpadding="3" cellspacing="0">';
1081 print '<tr><td bgcolor="'.$THEME->cellcontent2.'" class="forumpostpicture" width="35" valign="top">';
1082 print_user_picture($activity->user->userid, $course, $activity->user->picture);
1083 print '</td><td width="100%"><font size="2">';
1085 if ($detail) {
1086 // activity icon
1087 $src = "$CFG->modpixpath/$activity->type/icon.gif";
1088 print '<img src="'.$src.'" class="icon" alt="'.$activity->type.'" /> ';
1090 // link to activity
1091 $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$activity->instance";
1092 print '<a href="'.$href.'">'.$activity->name.'</a> - ';
1094 if (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE, $course))) {
1095 // score (with link to attempt details)
1096 $href = "$CFG->wwwroot/mod/hotpot/review.php?hp=$activity->instance&attempt=".$activity->content->attemptid;
1097 print '<a href="'.$href.'">('.hotpot_format_score($activity->content).')</a> ';
1099 // attempt number
1100 print get_string('attempt', 'quiz').' - '.$activity->content->attempt.'<br />';
1103 // link to user
1104 $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$course";
1105 print '<a href="'.$href.'">'.$activity->user->fullname.'</a> ';
1107 // time and date
1108 print ' - ' . userdate($activity->timestamp);
1110 // duration
1111 $duration = format_time($activity->content->timestart - $activity->content->timefinish);
1112 print " &nbsp; ($duration)";
1114 print "</font></td></tr>";
1115 print "</table>";
1118 function hotpot_cron () {
1119 /// Function to be run periodically according to the moodle cron
1120 /// This function searches for things that need to be done, such
1121 /// as sending out mail, toggling flags etc ...
1123 global $CFG;
1125 return true;
1128 function hotpot_grades($hotpotid) {
1129 /// Must return an array of grades for a given instance of this module,
1130 /// indexed by user. It also returns a maximum allowed grade.
1132 $hotpot = get_record('hotpot', 'id', $hotpotid);
1133 $return->grades = hotpot_get_grades($hotpot);
1134 $return->maxgrade = $hotpot->grade;
1136 return $return;
1138 function hotpot_get_grades($hotpot, $user_ids='') {
1139 global $CFG;
1141 $grades = array();
1143 $weighting = $hotpot->grade / 100;
1144 $precision = hotpot_get_precision($hotpot);
1146 // set the SQL string to determine the $grade
1147 $grade = "";
1148 switch ($hotpot->grademethod) {
1149 case HOTPOT_GRADEMETHOD_HIGHEST:
1150 $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade";
1151 break;
1152 case HOTPOT_GRADEMETHOD_AVERAGE:
1153 // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id)
1154 $grade = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade";
1155 break;
1156 case HOTPOT_GRADEMETHOD_FIRST:
1157 $grade = "ROUND(score * $weighting, $precision)";
1158 $grade = sql_concat('timestart', "'_'", $grade);
1159 $grade = "MIN($grade) AS grade";
1160 break;
1161 case HOTPOT_GRADEMETHOD_LAST:
1162 $grade = "ROUND(score * $weighting, $precision)";
1163 $grade = sql_concat('timestart', "'_'", $grade);
1164 $grade = "MAX($grade) AS grade";
1165 break;
1168 if ($grade) {
1169 $userid_condition = empty($user_ids) ? '' : "AND userid IN ($user_ids) ";
1170 $grades = get_records_sql_menu("
1171 SELECT userid, $grade
1172 FROM {$CFG->prefix}hotpot_attempts
1173 WHERE timefinish>0 AND hotpot='$hotpot->id' $userid_condition
1174 GROUP BY userid
1176 if ($grades) {
1177 if ($hotpot->grademethod==HOTPOT_GRADEMETHOD_FIRST || $hotpot->grademethod==HOTPOT_GRADEMETHOD_LAST) {
1178 // remove left hand characters in $grade (up to and including the underscore)
1179 foreach ($grades as $userid=>$grade) {
1180 $grades[$userid] = substr($grades[$userid], strpos($grades[$userid], '_')+1);
1186 return $grades;
1188 function hotpot_get_precision(&$hotpot) {
1189 return ($hotpot->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0;
1192 function hotpot_get_participants($hotpotid) {
1193 //Must return an array of user ids who are participants
1194 //for a given instance of hotpot. Must include every user involved
1195 //in the instance, independient of his role (student, teacher, admin...)
1196 //See other modules as example.
1197 global $CFG;
1199 return get_records_sql("
1200 SELECT DISTINCT
1201 u.id, u.id
1202 FROM
1203 {$CFG->prefix}user u,
1204 {$CFG->prefix}hotpot_attempts a
1205 WHERE
1206 u.id = a.userid
1207 AND a.hotpot = '$hotpotid'
1211 function hotpot_scale_used ($hotpotid, $scaleid) {
1212 //This function returns if a scale is being used by one hotpot
1213 //it it has support for grading and scales. Commented code should be
1214 //modified if necessary. See forum, glossary or journal modules
1215 //as reference.
1217 $report = false;
1219 //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid");
1221 //if (!empty($rec) && !empty($scaleid)) {
1222 // $report = true;
1225 return $report;
1228 //////////////////////////////////////////////////////////
1229 /// Any other hotpot functions go here.
1230 /// Each of them must have a name that starts with hotpot
1233 function hotpot_add_attempt($hotpotid) {
1234 global $db, $CFG, $USER;
1236 // get start time of this attempt
1237 $time = time();
1239 // set all previous "in progress" attempts at this quiz to "abandoned"
1240 if ($attempts = get_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id' AND status='".HOTPOT_STATUS_INPROGRESS."'")) {
1241 foreach ($attempts as $attempt) {
1242 if ($attempt->timefinish==0) {
1243 $attempt->timefinish = $time;
1245 if ($attempt->clickreportid==0) {
1246 $attempt->clickreportid = $attempt->id;
1248 $attempt->status = HOTPOT_STATUS_ABANDONED;
1249 update_record('hotpot_attempts', $attempt);
1253 // create and add new attempt record
1254 $attempt = new stdClass();
1255 $attempt->hotpot = $hotpotid;
1256 $attempt->userid = $USER->id;
1257 $attempt->attempt = hotpot_get_next_attempt($hotpotid);
1258 $attempt->timestart = $time;
1260 return insert_record("hotpot_attempts", $attempt);
1262 function hotpot_get_next_attempt($hotpotid) {
1263 global $USER;
1265 // get max attempt so far
1266 $i = count_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id'", 'MAX(attempt)');
1268 return empty($i) ? 1 : ($i+1);
1270 function hotpot_get_question_name($question) {
1271 $name = '';
1272 if (isset($question->text)) {
1273 $name = hotpot_strings($question->text);
1275 if (empty($name)) {
1276 $name = $question->name;
1278 return $name;
1280 function hotpot_strings($ids) {
1282 // array of ids of empty strings
1283 static $HOTPOT_EMPTYSTRINGS;
1285 if (!isset($HOTPOT_EMPTYSTRINGS)) { // first time only
1286 // get ids of empty strings
1287 $emptystrings = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0');
1288 $HOTPOT_EMPTYSTRINGS = empty($emptystrings) ? array() : array_keys($emptystrings);
1291 $strings = array();
1292 if (!empty($ids)) {
1293 $ids = explode(',', $ids);
1294 foreach ($ids as $id) {
1295 if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) {
1296 $strings[] = hotpot_string($id);
1300 return implode(',', $strings);
1302 function hotpot_string($id) {
1303 return get_field('hotpot_strings', 'string', 'id', $id);
1306 //////////////////////////////////////////////////////////////////////////////////////
1307 /// the class definitions to handle XML trees
1309 // get the standard XML parser supplied with Moodle
1310 require_once("$CFG->libdir/xmlize.php");
1312 // get the default class for hotpot quiz templates
1313 require_once("$CFG->hotpottemplate/default.php");
1315 class hotpot_xml_tree {
1316 function hotpot_xml_tree($str, $xml_root='') {
1317 if (empty($str)) {
1318 $this->xml = array();
1319 } else {
1320 if (empty($CFG->unicodedb)) {
1321 $str = utf8_encode($str);
1323 $this->xml = xmlize($str, 0);
1325 $this->xml_root = $xml_root;
1327 function xml_value($tags, $more_tags="[0]['#']") {
1329 $tags = empty($tags) ? '' : "['".str_replace(",", "'][0]['#']['", $tags)."']";
1330 eval('$value = &$this->xml'.$this->xml_root.$tags.$more_tags.';');
1332 if (is_string($value)) {
1333 if (empty($CFG->unicodedb)) {
1334 $value = utf8_decode($value);
1337 // decode angle brackets
1338 $value = strtr($value, array('&#x003C;'=>'<', '&#x003E;'=>'>', '&#x0026;'=>'&'));
1340 // remove white space between <table>, <ul|OL|DL> and <OBJECT|EMBED> parts
1341 // (so it doesn't get converted to <br />)
1342 $htmltags = '('
1343 . 'TABLE|/?CAPTION|/?COL|/?COLGROUP|/?TBODY|/?TFOOT|/?THEAD|/?TD|/?TH|/?TR'
1344 . '|OL|UL|/?LI'
1345 . '|DL|/?DT|/?DD'
1346 . '|EMBED|OBJECT|APPLET|/?PARAM'
1347 //. '|SELECT|/?OPTION'
1348 //. '|FIELDSET|/?LEGEND'
1349 //. '|FRAMESET|/?FRAME'
1350 . ')'
1353 $space = '(\s|(<br[^>]*>))+';
1354 $search = '#(<'.$htmltags.'[^>]*'.'>)'.$space.'(?='.'<)#is';
1355 $value = preg_replace($search, '\\1', $value);
1357 // replace remaining newlines with <br />
1358 $value = str_replace("\n", '<br />', $value);
1360 // encode unicode characters as HTML entities
1361 // (in particular, accented charaters that have not been encoded by HP)
1363 // unicode characters can be detected by checking the hex value of a character
1364 // 00 - 7F : ascii char (roman alphabet + punctuation)
1365 // 80 - BF : byte 2, 3 or 4 of a unicode char
1366 // C0 - DF : 1st byte of 2-byte char
1367 // E0 - EF : 1st byte of 3-byte char
1368 // F0 - FF : 1st byte of 4-byte char
1369 // if the string doesn't match the above, it might be
1370 // 80 - FF : single-byte, non-ascii char
1371 $search = '#('.'[\xc0-\xdf][\x80-\xbf]'.'|'.'[\xe0-\xef][\x80-\xbf]{2}'.'|'.'[\xf0-\xff][\x80-\xbf]{3}'.'|'.'[\x80-\xff]'.')#se';
1372 $value = preg_replace($search, "hotpot_utf8_to_html_entity('\\1')", $value);
1374 return $value;
1376 function xml_values($tags) {
1377 $i = 0;
1378 $values = array();
1379 while ($value = $this->xml_value($tags, "[$i]['#']")) {
1380 $values[$i++] = $value;
1382 return $values;
1384 function obj_value(&$obj, $name) {
1385 return is_object($obj) ? @$obj->$name : (is_array($obj) ? @$obj[$name] : NULL);
1387 function encode_cdata(&$str, $tag) {
1389 // conversion tables
1390 static $HTML_ENTITIES = array(
1391 '&apos;' => "'",
1392 '&quot;' => '"',
1393 '&lt;' => '<',
1394 '&gt;' => '>',
1395 '&amp;' => '&',
1397 static $ILLEGAL_STRINGS = array(
1398 "\r\n" => '&lt;br /&gt;',
1399 "\r" => '&lt;br /&gt;',
1400 "\n" => '&lt;br /&gt;',
1401 '[' => '&#91;',
1402 ']' => '&#93;'
1405 // extract the $tag from the $str(ing), if possible
1406 $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is';
1407 if (preg_match($pattern, $str, $matches)) {
1409 // encode problematic CDATA chars and strings
1410 $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS);
1412 // if there are any ampersands in "open text"
1413 // surround them by CDATA start and end markers
1414 // (and convert HTML entities to plain text)
1415 $search = '/>([^<]*&[^<]*)</e';
1416 $replace = '"><![CDATA[".strtr("$1", $HTML_ENTITIES)."]]><"';
1417 $matches[2] = preg_replace($search, $replace, $matches[2]);
1419 $str = $matches[1].$matches[2].$matches[3];
1424 class hotpot_xml_quiz extends hotpot_xml_tree {
1426 // constructor function
1427 function hotpot_xml_quiz(&$obj, $read_file=true, $parse_xml=true, $convert_urls=true, $report_errors=true, $create_html=true) {
1428 // obj can be the $_GET array or a form object/array
1430 global $CFG, $HOTPOT_OUTPUTFORMAT, $HOTPOT_OUTPUTFORMAT_DIR;
1432 // check xmlize functions are available
1433 if (! function_exists("xmlize")) {
1434 error('xmlize functions are not available');
1437 $this->read_file = $read_file;
1438 $this->parse_xml = $parse_xml;
1439 $this->convert_urls = $convert_urls;
1440 $this->report_errors = $report_errors;
1441 $this->create_html = $create_html;
1443 // extract fields from $obj
1444 // course : the course id
1445 // reference : the filename within the files folder
1446 // location : "site" files folder or "course" files folder
1447 // navigation : type of navigation required in quiz
1448 // forceplugins : force Moodle compatible media players
1449 $this->course = $this->obj_value($obj, 'course');
1450 $this->reference = $this->obj_value($obj, 'reference');
1451 $this->location = $this->obj_value($obj, 'location');
1452 $this->navigation = $this->obj_value($obj, 'navigation');
1453 $this->forceplugins = $this->obj_value($obj, 'forceplugins');
1455 // can't continue if there is no course or reference
1456 if (empty($this->course) || empty($this->reference)) {
1457 $this->error = get_string('error_nocourseorfilename', 'hotpot');
1458 if ($this->report_errors) {
1459 error($this->error);
1461 return;
1464 $this->course_homeurl = "$CFG->wwwroot/course/view.php?id=$this->course";
1466 // set filedir, filename and filepath
1467 switch ($this->location) {
1468 case HOTPOT_LOCATION_SITEFILES:
1469 $site = get_site();
1470 $this->filedir = $site->id;
1471 break;
1473 case HOTPOT_LOCATION_COURSEFILES:
1474 default:
1475 $this->filedir = $this->course;
1476 break;
1478 $this->filesubdir = dirname($this->reference);
1479 if ($this->filesubdir=='.') {
1480 $this->filesubdir = '';
1482 if ($this->filesubdir) {
1483 $this->filesubdir .= '/';
1485 $this->filename = basename($this->reference);
1486 $this->fileroot = "$CFG->dataroot/$this->filedir";
1487 $this->filepath = "$this->fileroot/$this->reference";
1489 // read the file, if required
1490 if ($this->read_file) {
1492 if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
1493 $this->error = get_string('error_couldnotopensourcefile', 'hotpot', $this->filepath);
1494 if ($this->report_errors) {
1495 error($this->error, $this->course_homeurl);
1497 return;
1500 // read in the XML source
1501 $this->source = file_get_contents($this->filepath);
1503 // convert relative URLs to absolute URLs
1504 if ($this->convert_urls) {
1505 $this->hotpot_convert_relative_urls($this->source);
1508 $this->html = '';
1509 $this->quiztype = '';
1510 $this->outputformat = 0;
1512 // is this an html file?
1513 if (preg_match('|\.html?$|', $this->filename)) {
1515 $this->filetype = 'html';
1516 $this->html = &$this->source;
1518 // relative URLs in stylesheets
1519 $search = '|'.'(<style[^>]*>)'.'(.*?)'.'(</style>)'.'|ise';
1520 $replace = "stripslashes('\\1').hotpot_convert_stylesheets_urls('".$this->get_baseurl()."','".$this->reference."','\\2'.'\\3')";
1521 $this->source = preg_replace($search, $replace, $this->source);
1523 // relative URLs in "PreloadImages(...);"
1524 $search = '|'.'(?<='.'PreloadImages'.'\('.')'."([^)]+?)".'(?='.'\);'.')'.'|se';
1525 $replace = "hotpot_convert_preloadimages_urls('".$this->get_baseurl()."','".$this->reference."','\\1')";
1526 $this->source = preg_replace($search, $replace, $this->source);
1528 // relative URLs in <button class="NavButton" ... onclick="location='...'">
1529 $search = '|'.'(?<='.'onclick="'."location='".')'."([^']*)".'(?='."'; return false;".'")'.'|ise';
1530 $replace = "hotpot_convert_navbutton_url('".$this->get_baseurl()."','".$this->reference."','\\1','".$this->course."')";
1531 $this->source = preg_replace($search, $replace, $this->source);
1533 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1534 $search = '|'.'(?<='.'onclick="'."window.open\\('".')'."([^']*)".'(?='."'\\);return false;".'")'.'|ise';
1535 $replace = "hotpot_convert_url('".$this->get_baseurl()."','".$this->reference."','\\1')";
1536 $this->source = preg_replace($search, $replace, $this->source);
1538 } else {
1540 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1541 $search = '|'.'(?<='.'onclick=&quot;'."window.open\\(&apos;".')'."(.*?)".'(?='."&apos;\\);return false;".'&quot;)'.'|ise';
1542 $replace = "hotpot_convert_url('".$this->get_baseurl()."','".$this->reference."','\\1')";
1543 $this->source = preg_replace($search, $replace, $this->source);
1545 if ($this->parse_xml) {
1547 $this->filetype = 'xml';
1549 // encode "gap fill" text in JCloze exercise
1550 $this->encode_cdata($this->source, 'gap-fill');
1552 // convert source to xml tree
1553 $this->hotpot_xml_tree($this->source);
1555 $keys = array_keys($this->xml);
1556 foreach ($keys as $key) {
1557 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
1558 $this->quiztype = strtolower($matches[2]);
1559 $this->xml_root = "['$key']['#']";
1560 break;
1565 if ($this->create_html) {
1567 // set the real output format from the requested output format
1568 $this->real_outputformat = $this->obj_value($obj, 'outputformat');
1569 $this->draganddrop = '';
1570 if (
1571 empty($this->real_outputformat) ||
1572 $this->real_outputformat==HOTPOT_OUTPUTFORMAT_BEST ||
1573 empty($HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat])
1575 if ($CFG->hotpotismobile && isset($HOTPOT_OUTPUTFORMAT_DIR[HOTPOT_OUTPUTFORMAT_MOBILE])) {
1576 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_MOBILE;
1577 } else { // PC
1578 if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
1579 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS;
1580 } else {
1581 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
1586 if ($this->real_outputformat==HOTPOT_OUTPUTFORMAT_V6_PLUS) {
1587 if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
1588 $this->draganddrop = 'd'; // prefix for templates (can also be "f" ?)
1590 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
1593 // set path(s) to template
1594 $this->template_dir = $HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat];
1595 $this->template_dirpath = $CFG->hotpottemplate.'/'.$this->template_dir;
1596 $this->template_filepath = $CFG->hotpottemplate.'/'.$this->template_dir.'.php';
1598 // check template class exists
1599 if (!file_exists($this->template_filepath) || !is_readable($this->template_filepath)) {
1600 $this->error = get_string('error_couldnotopentemplate', 'hotpot', $this->template_dir);
1601 if ($this->report_errors) {
1602 error($this->error, $this->course_homeurl);
1604 return;
1607 // get default and output-specfic template classes
1608 include($this->template_filepath);
1610 // create html (using the template for the specified output format)
1611 $this->template = new hotpot_xml_quiz_template($this);
1612 $this->html = &$this->template->html;
1614 } // end $this->create_html
1615 } // end if html/xml file
1616 } // end if $this->read_file
1617 } // end constructor function
1619 function hotpot_convert_relative_urls(&$str) {
1620 $tagopen = '(?:(<)|(&lt;)|(&amp;#x003C;))'; // left angle bracket
1621 $tagclose = '(?(2)>|(?(3)&gt;|(?(4)&amp;#x003E;)))'; // right angle bracket (to match left angle bracket)
1623 $space = '\s+'; // at least one space
1624 $anychar = '(?:[^>]*?)'; // any character
1626 $quoteopen = '("|&quot;|&amp;quot;)'; // open quote
1627 $quoteclose = '\\5'; // close quote (to match open quote)
1629 $replace = "hotpot_convert_relative_url('".$this->get_baseurl()."', '".$this->reference."', '\\1', '\\6', '\\7')";
1631 $tags = array('script'=>'src', 'link'=>'href', 'a'=>'href','img'=>'src','param'=>'value', 'object'=>'data', 'embed'=>'src');
1632 foreach ($tags as $tag=>$attribute) {
1633 if ($tag=='param') {
1634 $url = '\S+?\.\S+?'; // must include a filename and have no spaces
1635 } else {
1636 $url = '.*?';
1638 $search = "%($tagopen$tag$space$anychar$attribute=$quoteopen)($url)($quoteclose$anychar$tagclose)%ise";
1639 $str = preg_replace($search, $replace, $str);
1643 function get_baseurl() {
1644 // set the url base (first time only)
1645 if (!isset($this->baseurl)) {
1646 global $CFG;
1647 if ($CFG->slasharguments) {
1648 $this->baseurl = "$CFG->wwwroot/file.php/$this->filedir/";
1649 } else {
1650 $this->baseurl = "$CFG->wwwroot/file.php?file=/$this->filedir/";
1653 return $this->baseurl;
1657 // insert forms and messages
1659 function remove_nav_buttons() {
1660 $search = '#<!-- Begin(Top|Bottom)NavButtons -->(.*?)<!-- End(Top|Bottom)NavButtons -->#s';
1661 $this->html = preg_replace($search, '', $this->html);
1663 function insert_script($src=HOTPOT_JS) {
1664 $script = '<script src="'.$src.'" type="text/javascript"></script>'."\n";
1665 $this->html = preg_replace('|</head>|i', $script.'</head>', $this->html, 1);
1667 function insert_submission_form($attemptid, $startblock, $endblock, $keep_contents=false) {
1668 $form_name = 'store';
1669 $form_fields = ''
1670 . '<input type="hidden" name="attemptid" value="'.$attemptid.'" />'
1671 . '<input type="hidden" name="starttime" value="" />'
1672 . '<input type="hidden" name="endtime" value="" />'
1673 . '<input type="hidden" name="mark" value="" />'
1674 . '<input type="hidden" name="detail" value="" />'
1675 . '<input type="hidden" name="status" value="" />'
1677 $this->insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents);
1679 function insert_giveup_form($attemptid, $startblock, $endblock, $keep_contents=false) {
1680 $form_name = ''; // no <form> tag will be generated
1681 $form_fields = ''
1682 . '<button onclick="Finish('.HOTPOT_STATUS_ABANDONED.')" class="FuncButton" '
1683 . 'onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" '
1684 . 'onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" '
1685 . 'onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">'
1686 . get_string('giveup', 'hotpot').'</button>'
1688 $this->insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, true);
1690 function insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, $center=false) {
1691 global $CFG;
1692 $search = '#('.preg_quote($startblock).')(.*?)('.preg_quote($endblock).')#s';
1693 $replace = $form_fields;
1694 if ($keep_contents) {
1695 $replace .= '\\2';
1697 if ($form_name) {
1698 $replace = '<form action="'.$CFG->wwwroot.'/mod/hotpot/attempt.php" method="post" name="'.$form_name.'"'.$CFG->frametarget.'>'.$replace.'</form>';
1700 if ($center) {
1701 $replace = '<div style="margin-left:auto; margin-right:auto; text-align: center;">'.$replace.'</div>';
1703 $replace = '\\1'.$replace.'\\3';
1704 $this->html = preg_replace($search, $replace, $this->html, 1);
1706 function insert_message($start_str, $message, $color='red', $align='center') {
1707 $message = '<p align="'.$align.'" style="text-align:'.$align.'"><b><font color="'.$color.'">'.$message."</font></b></p>\n";
1708 $this->html = preg_replace('|'.preg_quote($start_str).'|', $start_str.$message, $this->html, 1);
1711 function adjust_media_urls() {
1713 if ($this->forceplugins) {
1715 // make sure the Moodle media plugin is available
1716 global $CFG;
1717 include_once "$CFG->dirroot/filter/mediaplugin/filter.php";
1719 // exclude swf files from the filter
1720 //$CFG->filter_mediaplugin_ignore_swf = true;
1722 $space = '\s(?:.+\s)?';
1723 $quote = '["'."']?"; // single, double, or no quote
1725 // patterns to media files types and paths
1726 $filetype = "avi|mpeg|mpg|mp3|mov|wmv";
1727 $filepath = ".*?\.($filetype)";
1729 $tagopen = '(?:(<)|(\\\\u003C))'; // left angle-bracket (uses two parenthese)
1730 $tagclose = '(?(1)>|(?(2)\\\\u003E))'; // right angle-bracket (to match the left one)
1731 $tagreopen = '(?(1)<|(?(2)\\\\u003C))'; // another left angle-bracket (to match the first one)
1733 // pattern to match <PARAM> tags which contain the file path
1734 // wmp : url
1735 // quicktime : src
1736 // realplayer : src
1737 // flash : movie (doesn't need replacing)
1738 $param_url = "/{$tagopen}param{$space}name=$quote(?:movie|src|url)$quote{$space}value=$quote($filepath)$quote.*?$tagclose/is";
1740 // pattern to match <a> tags which link to multimedia files
1741 $link_url = "/{$tagopen}a{$space}href=$quote($filepath)$quote.*?$tagclose.*?$tagreopen\/A$tagclose/is";
1743 // extract <object> tags
1744 preg_match_all("/{$tagopen}object\s.*?{$tagclose}(.*?){$tagreopen}\/object{$tagclose}/is", $this->html, $objects);
1746 $i_max = count($objects[0]);
1747 for ($i=0; $i<$i_max; $i++) {
1749 // extract URL from <PARAM> or <A>
1750 $url = '';
1751 if (preg_match($param_url, $objects[3][$i], $matches) || preg_match($link_url, $objects[3][$i], $matches)) {
1752 $url = $matches[3];
1755 if ($url) {
1756 // strip inner tags (e.g. <embed>)
1757 $txt = preg_replace("/$tagopen.*?$tagclose/", '', $objects[3][$i]);
1759 // if url is in the query string, remove the leading characters
1760 $url = preg_replace('/^[^?]*\?([^=]+=[^&]*&)*[^=]+=([^&]*)$/', '$2', $url, 1);
1761 $link = '<a href="'.$url.'">'.$txt.'</a>';
1763 $new_object = mediaplugin_filter($this->filedir, $link);
1764 $new_object = str_replace($link, '', $new_object);
1765 $new_object = str_replace('&amp;', '&', $new_object);
1767 $this->html = str_replace($objects[0][$i], $new_object, $this->html);
1773 } // end class
1775 function hotpot_convert_stylesheets_urls($baseurl, $reference, $css, $stripslashes=true) {
1776 if ($stripslashes) {
1777 $css = stripslashes($css);
1779 $search = '|'.'(?<='.'url'.'\('.')'."(.+?)".'(?='.'\)'.')'.'|ise';
1780 $replace = "hotpot_convert_url('".$baseurl."','".$reference."','\\1')";
1781 return preg_replace($search, $replace, $css);
1783 function hotpot_convert_preloadimages_urls($baseurl, $reference, $urls, $stripslashes=true) {
1784 if ($stripslashes) {
1785 $urls = stripslashes($urls);
1787 $search = '|(?<=["'."'])([^,'".'"]*?)(?=["'."'])|ise";
1788 $replace = "hotpot_convert_url('".$baseurl."','".$reference."','\\1')";
1789 return preg_replace($search, $replace, $urls);
1791 function hotpot_convert_navbutton_url($baseurl, $reference, $url, $course, $stripslashes=true) {
1792 global $CFG;
1794 if ($stripslashes) {
1795 $url = stripslashes($url);
1797 $url = hotpot_convert_url($baseurl, $reference, $url, false);
1799 // is this a $url for another hotpot in this course ?
1800 if (preg_match("|^$baseurl(.*)$|", $url, $matches)) {
1801 if ($records = get_records_select('hotpot', "course='$course' AND reference='".$matches[1]."'")) {
1802 $ids = array_keys($records);
1803 $url = "$CFG->wwwroot/mod/hotpot/view.php?hp=".$ids[0];
1807 return $url;
1810 function hotpot_convert_relative_url($baseurl, $reference, $opentag, $url, $closetag, $stripslashes=true) {
1811 if ($stripslashes) {
1812 $opentag = stripslashes($opentag);
1813 $url = stripslashes($url);
1814 $closetag = stripslashes($closetag);
1817 // catch <PARAM name="FlashVars" value="TheSound=soundfile.mp3">
1818 // ampersands can appear as "&", "&amp;" or "&amp;#x0026;amp;"
1819 if (preg_match('|^'.'\w+=[^&]+'.'('.'&((amp;#x0026;)?amp;)?'.'\w+=[^&]+)*'.'$|', $url)) {
1820 $query = $url;
1821 $url = '';
1822 $fragment = '';
1824 // parse the $url into $matches
1825 // [1] path
1826 // [2] query string, if any
1827 // [3] anchor fragment, if any
1828 } else if (preg_match('|^'.'([^?]*)'.'((?:\\?[^#]*)?)'.'((?:#.*)?)'.'$|', $url, $matches)) {
1829 $url = $matches[1];
1830 $query = $matches[2];
1831 $fragment = $matches[3];
1833 // these appears to be no query or fragment in this url
1834 } else {
1835 $query = '';
1836 $fragment = '';
1839 if ($url) {
1840 $url = hotpot_convert_url($baseurl, $reference, $url, false);
1843 if ($query) {
1844 $search = '#'.'(file|src|thesound)='."([^&]+)".'#ise';
1845 $replace = "'\\1='.hotpot_convert_url('".$baseurl."','".$reference."','\\2')";
1846 $query = preg_replace($search, $replace, $query);
1849 $url = $opentag.$url.$query.$fragment.$closetag;
1851 return $url;
1854 function hotpot_convert_url($baseurl, $reference, $url, $stripslashes=true) {
1855 // maintain a cache of converted urls
1856 static $HOTPOT_RELATIVE_URLS = array();
1858 if ($stripslashes) {
1859 $url = stripslashes($url);
1862 // is this an absolute url? (or javascript pseudo url)
1863 if (preg_match('%^(http://|/|javascript:)%i', $url)) {
1864 // do nothing
1866 // has this relative url already been converted?
1867 } else if (isset($HOTPOT_RELATIVE_URLS[$url])) {
1868 $url = $HOTPOT_RELATIVE_URLS[$url];
1870 } else {
1871 $relativeurl = $url;
1873 // get the subdirectory, $dir, of the quiz $reference
1874 $dir = dirname($reference);
1876 // allow for leading "./" and "../"
1877 while (preg_match('|^(\.{1,2})/(.*)$|', $url, $matches)) {
1878 if ($matches[1]=='..') {
1879 $dir = dirname($dir);
1881 $url = $matches[2];
1884 // add subdirectory, $dir, to $baseurl, if necessary
1885 if ($dir && $dir<>'.') {
1886 $baseurl .= "$dir/";
1889 // prefix $url with $baseurl
1890 $url = "$baseurl$url";
1892 // add url to cache
1893 $HOTPOT_RELATIVE_URLS[$relativeurl] = $url;
1895 return $url;
1898 // ===================================================
1899 // function for adding attempt questions and responses
1900 // ===================================================
1902 function hotpot_add_attempt_details(&$attempt) {
1904 // encode ampersands so that HTML entities are preserved in the XML parser
1905 // N.B. ampersands inside <![CDATA[ ]]> blocks do NOT need to be encoded
1907 $old = &$attempt->details; // shortcut to "old" details
1908 $new = '';
1909 $str_start = 0;
1910 while (($cdata_start = strpos($old, '<![CDATA[', $str_start)) && ($cdata_end = strpos($old, ']]>', $cdata_start))) {
1911 $cdata_end += 3;
1912 $new .= str_replace('&', '&amp;', substr($old, $str_start, $cdata_start-$str_start)).substr($old, $cdata_start, $cdata_end-$cdata_start);
1913 $str_start = $cdata_end;
1915 $new .= str_replace('&', '&amp;', substr($old, $str_start));
1916 unset($old);
1918 // parse the attempt details as xml
1919 $details = new hotpot_xml_tree($new, "['hpjsresult']['#']");
1921 $num = -1;
1922 $q_num = -1;
1923 $question = NULL;
1924 $reponse = NULL;
1926 $i = 0;
1927 $tags = 'fields,field';
1929 while (($field="[$i]['#']") && $details->xml_value($tags, $field)) {
1931 $name = $details->xml_value($tags, $field."['fieldname'][0]['#']");
1932 $data = $details->xml_value($tags, $field."['fielddata'][0]['#']");
1934 // parse the field name into $matches
1935 // [1] quiz type
1936 // [2] attempt detail name
1937 if (preg_match('/^(\w+?)_(\w+)$/', $name, $matches)) {
1938 $quiztype = strtolower($matches[1]);
1939 $name = strtolower($matches[2]);
1941 // parse the attempt detail $name into $matches
1942 // [1] question number
1943 // [2] question detail name
1944 if (preg_match('/^q(\d+)_(\w+)$/', $name, $matches)) {
1945 $num = $matches[1];
1946 $name = strtolower($matches[2]);
1947 $data = addslashes($data);
1949 // adjust JCross question numbers
1950 if (preg_match('/^(across|down)(.*)$/', $name, $matches)) {
1951 $num .= '_'.$matches[1]; // e.g. 01_across, 02_down
1952 $name = $matches[2];
1953 if (substr($name, 0, 1)=='_') {
1954 $name = substr($name, 1); // remove leading '_'
1958 // is this a new question (or the first one)?
1959 if ($q_num<>$num) {
1961 // add previous question and response, if any
1962 hotpot_add_response($attempt, $question, $response);
1964 // initialize question object
1965 $question = NULL;
1966 $question->name = '';
1967 $question->text = '';
1968 $question->hotpot = $attempt->hotpot;
1970 // initialize response object
1971 $response = NULL;
1972 $response->attempt = $attempt->id;
1974 // update question number
1975 $q_num = $num;
1978 // adjust field name and value, and set question type
1979 // (may not be necessary one day)
1980 hotpot_adjust_response_field($quiztype, $question, $num, $name, $data);
1982 // add $data to the question/response details
1983 switch ($name) {
1984 case 'name':
1985 case 'type':
1986 $question->$name = $data;
1987 break;
1988 case 'text':
1989 $question->$name = hotpot_string_id($data);
1990 break;
1992 case 'correct':
1993 case 'ignored':
1994 case 'wrong':
1995 $response->$name = hotpot_string_ids($data);
1996 break;
1998 case 'score':
1999 case 'weighting':
2000 case 'hints':
2001 case 'clues':
2002 case 'checks':
2003 $response->$name = intval($data);
2004 break;
2007 } else { // attempt details
2009 // adjust field name and value
2010 hotpot_adjust_response_field($quiztype, $question, $num='', $name, $data);
2012 // add $data to the attempt details
2013 if ($name=='penalties') {
2014 $attempt->$name = intval($data);
2019 $i++;
2020 } // end while
2022 // add the final question and response, if any
2023 hotpot_add_response($attempt, $question, $response);
2025 function hotpot_add_response(&$attempt, &$question, &$response) {
2026 global $db, $next_url;
2028 $loopcount = 1;
2030 $looping = isset($question) && isset($question->name) && isset($response);
2031 while ($looping) {
2033 if ($loopcount==1) {
2034 $questionname = $question->name;
2037 $question->md5key = md5($question->name);
2038 if (!$question->id = get_field('hotpot_questions', 'id', 'hotpot', $attempt->hotpot, 'md5key', $question->md5key, 'name', $question->name)) {
2039 // add question record
2040 if (!$question->id = insert_record('hotpot_questions', $question)) {
2041 error("Could not add question record (attempt_id=$attempt->id): ".$db->ErrorMsg(), $next_url);
2045 if (record_exists('hotpot_responses', 'attempt', $attempt->id, 'question', $question->id)) {
2046 // there is already a response to this question for this attempt
2047 // probably because this quiz has two questions with the same text
2048 // e.g. Which one of these answers is correct?
2050 // To workaround this, we create new question names
2051 // e.g. Which one of these answers is correct? (2)
2052 // until we get a question name for which there is no response yet on this attempt
2054 $loopcount++;
2055 $question->name = "$questionname ($loopcount)";
2057 // This method fails to correctly identify questions in
2058 // quizzes which allow questions to be shuffled or omitted.
2059 // As yet, there is no workaround for such cases.
2061 } else {
2062 $response->question = $question->id;
2064 // add response record
2065 if(!$response->id = insert_record('hotpot_responses', $response)) {
2066 error("Could not add response record (attempt_id=$attempt->id, question_id=$question->id): ".$db->ErrorMsg(), $next_url);
2069 // we can stop looping now
2070 $looping = false;
2072 } // end while
2074 function hotpot_adjust_response_field($quiztype, &$question, &$num, &$name, &$data) {
2075 switch ($quiztype) {
2076 case 'jbc':
2077 $question->type = HOTPOT_JCB;
2078 switch ($name) {
2079 case 'right':
2080 $name = 'correct';
2081 break;
2083 break;
2084 case 'jcloze':
2085 $question->type = HOTPOT_JCLOZE;
2086 if (is_numeric($num)) {
2087 $question->name = $num;
2089 switch ($name) {
2090 case 'penalties':
2091 if (is_numeric($num)) {
2092 $name = 'checks';
2093 if (is_numeric($data)) {
2094 $data++;
2097 break;
2098 case 'clue_shown':
2099 $name = 'clues';
2100 $data = ($data=='YES' ? 1 : 0);
2101 break;
2102 case 'clue_text':
2103 $name = 'text';
2104 break;
2106 break;
2107 case 'jcross':
2108 $question->type = HOTPOT_JCROSS;
2109 $question->name = $num;
2110 switch ($name) {
2111 case '': // HotPot v2.0.x
2112 $name = 'correct';
2113 break;
2114 case 'clue':
2115 $name = 'text';
2116 break;
2118 break;
2119 case 'jmatch':
2120 $question->type = HOTPOT_JMATCH;
2121 switch ($name) {
2122 case 'attempts':
2123 $name = 'penalties';
2124 if (is_numeric($data) && $data>0) {
2125 $data--;
2127 break;
2128 case 'lhs':
2129 $name = 'name';
2130 break;
2131 case 'rhs':
2132 $name = 'correct';
2133 break;
2135 break;
2136 case 'jmix':
2137 $question->type = HOTPOT_JMIX;
2138 $question->name = $num;
2139 switch ($name) {
2140 // keep these in for "restore" of courses
2141 // which were backed up with HotPot v2.0.x
2142 case 'wrongguesses':
2143 $name = 'checks';
2144 if (is_numeric($data)) {
2145 $data++;
2147 break;
2148 case 'right':
2149 $name = 'correct';
2150 break;
2152 break;
2153 break;
2154 case 'jquiz':
2155 switch ($name) {
2156 case 'type':
2157 $data = HOTPOT_JQUIZ;
2158 switch ($data) {
2159 case 'multiple-choice':
2160 $data .= '.'.HOTPOT_JQUIZ_MULTICHOICE;
2161 break;
2162 case 'short-answer':
2163 $data .= '.'.HOTPOT_JQUIZ_SHORTANSWER;
2164 break;
2165 case 'hybrid':
2166 $data .= '.'.HOTPOT_JQUIZ_HYBRID;
2167 break;
2168 case 'multi-select':
2169 $data .= '.'.HOTPOT_JQUIZ_MULTISELECT;
2170 case 'n/a':
2171 default:
2172 // do nothing more
2173 break;
2175 break;
2176 case 'question':
2177 $name = 'name';
2178 break;
2180 break;
2182 case 'rhubarb':
2183 $question->type = HOTPOT_TEXTOYS_RHUBARB;
2184 if (empty($question->name)) {
2185 $question->name = $num;
2187 break;
2189 case 'sequitur':
2190 $question->type = HOTPOT_TEXTOYS_SEQUITUR;
2191 break;
2194 function hotpot_string_ids($field_value) {
2195 $ids = array();
2196 $strings = explode(',', $field_value);
2197 foreach($strings as $str) {
2198 if ($id = hotpot_string_id($str)) {
2199 $ids[] = $id;
2202 return implode(',', $ids);
2204 function hotpot_string_id($str) {
2205 $id = '';
2206 if (isset($str) && $str<>'') {
2208 // get the id from the table if it is already there
2209 $md5key = md5($str);
2210 if (!$id = get_field('hotpot_strings', 'id', 'md5key', $md5key, 'string', $str)) {
2212 // create a string record
2213 $record = new stdClass();
2214 $record->string = $str;
2215 $record->md5key = $md5key;
2217 // try and add the new string record
2218 if (!$id = insert_record('hotpot_strings', $record)) {
2219 global $db;
2220 error("Could not add string record for '".htmlspecialchars($str)."': ".$db->ErrorMsg());
2224 return $id;
2227 function hotpot_get_view_actions() {
2228 return array('view','view all','report');
2231 function hotpot_get_post_actions() {
2232 return array('attempt','review','submit');
2235 if (!function_exists('file_get_contents')) {
2236 // add this function for php version<4.3
2237 function file_get_contents($filepath) {
2238 $contents = file($filepath);
2239 if (is_array($contents)) {
2240 $contents = implode('', $contents);
2242 return $contents;
2245 if (!function_exists('html_entity_decode')) {
2246 // add this function for php version<4.3
2247 function html_entity_decode($str) {
2248 $t = get_html_translation_table(HTML_ENTITIES);
2249 $t = array_flip($t);
2250 return strtr($str, $t);
2255 // required for Moodle 1.x
2256 if (!isset($CFG->pixpath)) {
2257 $CFG->pixpath = "$CFG->wwwroot/pix";
2260 if (!function_exists('fullname')) {
2261 // add this function for Moodle 1.x
2262 function fullname($user) {
2263 return "$user->firstname $user->lastname";
2266 if (!function_exists('get_user_preferences')) {
2267 // add this function for Moodle 1.x
2268 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
2269 return $default;
2272 if (!function_exists('set_user_preference')) {
2273 // add this function for Moodle 1.x
2274 function set_user_preference($name, $value, $otheruser=NULL) {
2275 return false;
2278 if (!function_exists('get_coursemodule_from_id')) {
2279 // add this function for Moodle < 1.5.4
2280 function get_coursemodule_from_id($modulename, $cmid, $courseid=0) {
2281 global $CFG;
2282 return get_record_sql("
2283 SELECT
2284 cm.*, m.name, md.name as modname
2285 FROM
2286 {$CFG->prefix}course_modules cm,
2287 {$CFG->prefix}modules md,
2288 {$CFG->prefix}$modulename m
2289 WHERE
2290 ".($courseid ? "cm.course = '$courseid' AND " : '')."
2291 cm.id = '$cmid' AND
2292 cm.instance = m.id AND
2293 md.name = '$modulename' AND
2294 md.id = cm.module
2298 if (!function_exists('get_coursemodule_from_instance')) {
2299 // add this function for Moodle < 1.5.4
2300 function get_coursemodule_from_instance($modulename, $instance, $courseid=0) {
2301 global $CFG;
2302 return get_record_sql("
2303 SELECT
2304 cm.*, m.name, md.name as modname
2305 FROM
2306 {$CFG->prefix}course_modules cm,
2307 {$CFG->prefix}modules md,
2308 {$CFG->prefix}$modulename m
2309 WHERE
2310 ".($courseid ? "cm.course = '$courseid' AND" : '')."
2311 cm.instance = m.id AND
2312 md.name = '$modulename' AND
2313 md.id = cm.module AND
2314 m.id = '$instance'
2318 function hotpot_utf8_to_html_entity($char) {
2319 // http://www.zend.com/codex.php?id=835&single=1
2321 // array used to figure what number to decrement from character order value
2322 // according to number of characters used to map unicode to ascii by utf-8
2323 static $HOTPOT_UTF8_DECREMENT = array(
2324 1=>0, 2=>192, 3=>224, 4=>240
2327 // the number of bits to shift each character by
2328 static $HOTPOT_UTF8_SHIFT = array(
2329 1=>array(0=>0),
2330 2=>array(0=>6, 1=>0),
2331 3=>array(0=>12, 1=>6, 2=>0),
2332 4=>array(0=>18, 1=>12, 2=>6, 3=>0)
2335 $dec = 0;
2336 $len = strlen($char);
2337 for ($pos=0; $pos<$len; $pos++) {
2338 $ord = ord ($char{$pos});
2339 $ord -= ($pos ? 128 : $HOTPOT_UTF8_DECREMENT[$len]);
2340 $dec += ($ord << $HOTPOT_UTF8_SHIFT[$len][$pos]);
2342 return '&#x'.sprintf('%04X', $dec).';';
2345 function hotpot_print_show_links($course, $location, $reference, $actions='', $spacer=' &nbsp; ', $new_window=false) {
2346 global $CFG;
2347 if (is_string($actions)) {
2348 if (empty($actions)) {
2349 $actions = 'showxmlsource,showxmltree,showhtmlsource';
2351 $actions = explode(',', $actions);
2353 $strenterafilename = get_string('enterafilename', 'hotpot');
2354 $html = <<<END_OF_SCRIPT
2355 <script type="text/javascript">
2356 //<![CDATA[
2357 function setLink(lnk) {
2358 var form = document.forms['form'];
2359 return setLinkAttribute(lnk, 'reference', form) && setLinkAttribute(lnk, 'location', form);
2361 function setLinkAttribute(lnk, name, form) {
2362 // set link attribute value using
2363 // f(orm) name and e(lement) name
2365 var r = true; // result
2367 var obj = (form) ? form.elements[name] : null;
2368 if (obj) {
2369 r = false;
2370 var v = getObjValue(obj);
2371 if (v=='') {
2372 alert('$strenterafilename');
2373 } else {
2374 var s = lnk.href;
2375 var i = s.indexOf('?');
2376 if (i>=0) {
2377 i = s.indexOf(name+'=', i+1);
2378 if (i>=0) {
2379 i += name.length+1;
2380 var ii = s.indexOf('&', i);
2381 if (ii<0) {
2382 ii = s.length;
2384 lnk.href = s.substring(0, i) + v + s.substring(ii);
2385 r = true;
2390 return r;
2392 function getObjValue(obj) {
2393 var v = ''; // the value
2394 var t = (obj && obj.type) ? obj.type : "";
2395 if (t=="text" || t=="textarea" || t=="hidden") {
2396 v = obj.value;
2397 } else if (t=="select-one" || t=="select-multiple") {
2398 var l = obj.options.length;
2399 for (var i=0; i<l; i++) {
2400 if (obj.options[i].selected) {
2401 v += (v=="" ? "" : ",") + obj.options[i].value;
2405 return v;
2407 function getDir(s) {
2408 if (s.charAt(0)!='/') {
2409 s = '/' + s;
2411 var i = s.lastIndexOf('/');
2412 return s.substring(0, i);
2414 //]]>
2415 </script>
2416 END_OF_SCRIPT;
2418 foreach ($actions as $action) {
2419 $html .= $spacer
2420 . '<a href="'
2421 . $CFG->wwwroot.'/mod/hotpot/show.php'
2422 . '?course='.$course.'&location='.$location.'&reference='.urlencode($reference).'&action='.$action
2423 . '"'
2424 . ' onclick="return setLink(this);"'
2425 . ($new_window ? ' target="_blank"' : '')
2426 . '>'.get_string($action, 'hotpot').'</a>'
2429 print '<span class="helplink">'.$html.'</span>';