MDL-16106 report - fix old double-column trick causing problems in backup logs. Thank...
[moodle.git] / mod / hotpot / lib.php
blob36d902703335d5edd5fd030b3241f6c22beb2835
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);
202 hotpot_grade_item_update(stripslashes_recursive($hotpot));
204 } else {
205 $result= false;
207 return $result;
210 function hotpot_update_instance(&$hotpot) {
211 if (hotpot_set_form_values($hotpot)) {
212 $hotpot->id = $hotpot->instance;
213 if ($result = update_record('hotpot', $hotpot)) {
214 hotpot_update_events($hotpot);
215 //hotpot_grade_item_update(stripslashes_recursive($hotpot));
216 hotpot_update_grades(stripslashes_recursive($hotpot));
218 } else {
219 $result= false;
221 return $result;
223 function hotpot_update_events($hotpot) {
225 // remove any previous calendar events for this hotpot
226 delete_records('event', 'modulename', 'hotpot', 'instance', $hotpot->id);
228 $event = new stdClass();
229 $event->description = addslashes($hotpot->summary);
230 $event->courseid = $hotpot->course;
231 $event->groupid = 0;
232 $event->userid = 0;
233 $event->modulename = 'hotpot';
234 $event->instance = $hotpot->id;
235 $event->timestart = $hotpot->timeopen;
236 if ($cm = get_coursemodule_from_id('hotpot', $hotpot->id)) {
237 $event->visible = hotpot_is_visible($cm);
238 } else {
239 $event->visible = 1;
242 if ($hotpot->timeclose && $hotpot->timeopen) {
243 // we have both a start and an end date
244 $event->eventtype = 'open';
245 $event->timeduration = ($hotpot->timeclose - $hotpot->timeopen);
247 if ($event->timeduration > HOTPOT_MAX_EVENT_LENGTH) { /// Long durations create two events
249 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotopens', 'hotpot').')';
250 $event->timeduration = 0;
251 add_event($event);
253 $event->timestart = $hotpot->timeclose;
254 $event->eventtype = 'close';
255 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotcloses', 'hotpot').')';
256 unset($event->id);
257 add_event($event);
258 } else { // single event with duration
259 $event->name = $hotpot->name;
260 add_event($event);
262 } elseif ($hotpot->timeopen) { // only an open date
263 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotopens', 'hotpot').')';
264 $event->eventtype = 'open';
265 $event->timeduration = 0;
266 add_event($event);
267 } elseif ($hotpot->timeclose) { // only a closing date
268 $event->name = addslashes($hotpot->name).' ('.get_string('hotpotcloses', 'hotpot').')';
269 $event->timestart = $hotpot->timeclose;
270 $event->eventtype = 'close';
271 $event->timeduration = 0;
272 add_event($event);
276 function hotpot_set_form_values(&$hotpot) {
277 $ok = true;
278 $hotpot->errors = array(); // these will be reported by moderr.html
280 if (empty($hotpot->reference)) {
281 $ok = false;
282 $hotpot->errors['reference']= get_string('error_nofilename', 'hotpot');
285 if (empty($hotpot->studentfeedbackurl) || $hotpot->studentfeedbackurl=='http://') {
286 $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->mode)) {
304 // moodle 1.9 (from mod_form.lib)
305 if ($hotpot->add) {
306 $hotpot->mode = 'add';
307 } else if ($hotpot->update) {
308 $hotpot->mode = 'update';
309 } else {
310 $hotpot->mode = '';
313 if ($hotpot->quizchain==HOTPOT_YES) {
314 switch ($hotpot->mode) {
315 case 'add':
316 $ok = hotpot_add_chain($hotpot);
317 break;
318 case 'update':
319 $ok = hotpot_update_chain($hotpot);
320 break;
322 } else { // $hotpot->quizchain==HOTPOT_NO
323 hotpot_set_name_summary_reference($hotpot);
326 if (isset($hotpot->displaynext)) {
327 switch ($hotpot->displaynext) {
328 // N.B. redirection only works for Moodle 1.5+
329 case HOTPOT_DISPLAYNEXT_COURSE:
330 $hotpot->redirect = true;
331 $hotpot->redirecturl = "view.php?id=$hotpot->course";
332 break;
333 case HOTPOT_DISPLAYNEXT_INDEX:
334 $hotpot->redirect = true;
335 $hotpot->redirecturl = "../mod/hotpot/index.php?id=$hotpot->course";
336 break;
337 default:
338 // use Moodle default action (i.e. go on to display the hotpot quiz)
340 } else {
341 $hotpot->displaynext = HOTPOT_DISPLAYNEXT_QUIZ;
344 // if ($ok && $hotpot->setdefaults) {
345 if ($ok) {
346 set_user_preference('hotpot_timeopen', $hotpot->timeopen);
347 set_user_preference('hotpot_timeclose', $hotpot->timeclose);
348 set_user_preference('hotpot_navigation', $hotpot->navigation);
349 set_user_preference('hotpot_outputformat', $hotpot->outputformat);
350 set_user_preference('hotpot_studentfeedback', $hotpot->studentfeedback);
351 set_user_preference('hotpot_studentfeedbackurl', $hotpot->studentfeedbackurl);
352 set_user_preference('hotpot_forceplugins', $hotpot->forceplugins);
353 set_user_preference('hotpot_shownextquiz', $hotpot->shownextquiz);
354 set_user_preference('hotpot_review', $hotpot->review);
355 set_user_preference('hotpot_grade', $hotpot->grade);
356 set_user_preference('hotpot_grademethod', $hotpot->grademethod);
357 set_user_preference('hotpot_attempts', $hotpot->attempts);
358 set_user_preference('hotpot_subnet', $hotpot->subnet);
359 set_user_preference('hotpot_displaynext', $hotpot->displaynext);
360 if ($hotpot->mode=='add') {
361 set_user_preference('hotpot_quizchain', $hotpot->quizchain);
362 set_user_preference('hotpot_namesource', $hotpot->namesource);
363 set_user_preference('hotpot_summarysource', $hotpot->summarysource);
366 return $ok;
368 function hotpot_get_chain(&$cm) {
369 // get details of course_modules in this section
370 $course_module_ids = get_field('course_sections', 'sequence', 'id', $cm->section);
371 if (empty($course_module_ids)) {
372 $hotpot_modules = array();
373 } else {
374 $hotpot_modules = get_records_select('course_modules', "id IN ($course_module_ids) AND module=$cm->module");
375 if (empty($hotpot_modules)) {
376 $hotpot_modules = array();
380 // get ids of hotpot modules in this section
381 $ids = array();
382 foreach ($hotpot_modules as $hotpot_module) {
383 $ids[] = $hotpot_module->instance;
386 // get details of hotpots in this section
387 if (empty($ids)) {
388 $hotpots = array();
389 } else {
390 $hotpots = get_records_list('hotpot', 'id', implode(',', $ids));
393 $found = false;
394 $chain = array();
396 // loop through course_modules in this section
397 $ids = explode(',', $course_module_ids);
398 foreach ($ids as $id) {
400 // check this course_module is a hotpot activity
401 if (isset($hotpot_modules[$id])) {
403 // store details of this course module and hotpot activity
404 $hotpot_id = $hotpot_modules[$id]->instance;
405 $chain[$id] = &$hotpot_modules[$id];
406 $chain[$id]->hotpot = &$hotpots[$hotpot_id];
408 // set $found, if this is the course module we're looking for
409 if (isset($cm->coursemodule)) {
410 if ($id==$cm->coursemodule) {
411 $found = true;
413 } else {
414 if ($id==$cm->id) {
415 $found = true;
419 // is this the end of a chain
420 if (empty($hotpots[$hotpot_id]->shownextquiz)) {
421 if ($found) {
422 break; // out of loop
423 } else {
424 // restart chain (target cm has not been found yet)
425 $chain = array();
429 } // end foreach $ids
431 return $found ? $chain : false;
433 function hotpot_is_visible(&$cm) {
434 global $CFG, $COURSE;
436 // check grouping
437 $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
438 if (empty($CFG->enablegroupings) || empty($cm->groupmembersonly) || has_capability('moodle/site:accessallgroups', $modulecontext)) {
439 // groupings not applicable
440 } else if (!isguestuser() && groups_has_membership($cm)) {
441 // user is in one of the groups in the allowed grouping
442 } else {
443 // user is not in the required grouping and does not have sufficiently privileges to view this hotpot activity
444 return false;
447 // check if user can view hidden activities
448 if (isset($COURSE->context)) {
449 $coursecontext = &$COURSE->context;
450 } else {
451 $coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course);
453 if (has_capability('moodle/course:viewhiddenactivities', $coursecontext)) {
454 return true; // user can view hidden activities
457 if (!isset($cm->sectionvisible)) {
458 if (! $section = get_record('course_sections', 'id', $cm->section)) {
459 error('Course module record contains invalid section');
461 $cm->sectionvisible = $section->visible;
463 if (empty($cm->sectionvisible)) {
464 $visible = HOTPOT_NO;
465 } else {
466 $visible = HOTPOT_YES;
467 if (empty($cm->visible)) {
468 if ($chain = hotpot_get_chain($cm)) {
469 $startofchain = array_shift($chain);
470 $visible = $startofchain->visible;
474 return $visible;
476 function hotpot_add_chain(&$hotpot) {
477 /// add a chain of hotpot actiivities
479 global $CFG, $course;
481 $ok = true;
482 $hotpot->names = array();
483 $hotpot->summaries = array();
484 $hotpot->references = array();
486 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
488 if (isset($xml_quiz->error)) {
489 $hotpot->errors['reference'] = $xml_quiz->error;
490 $ok = false;
492 } else if (is_dir($xml_quiz->filepath)) {
494 // get list of hotpot files in this folder
495 if ($dh = @opendir($xml_quiz->filepath)) {
496 while (false !== ($file = @readdir($dh))) {
497 if (preg_match('/\.(jbc|jcl|jcw|jmt|jmx|jqz|htm|html)$/', $file)) {
498 $hotpot->references[] = "$xml_quiz->reference/$file";
501 closedir($dh);
503 // get titles
504 foreach ($hotpot->references as $i=>$reference) {
505 $filepath = $xml_quiz->fileroot.'/'.$reference;
506 hotpot_get_titles_and_next_ex($hotpot, $filepath);
507 $hotpot->names[$i] = $hotpot->exercisetitle;
508 $hotpot->summaries[$i] = $hotpot->exercisesubtitle;
511 } else {
512 $ok = false;
513 $hotpot->errors['reference'] = get_string('error_couldnotopenfolder', 'hotpot', $hotpot->reference);
516 } else if (is_file($xml_quiz->filepath)) {
518 $filerootlength = strlen($xml_quiz->fileroot) + 1;
520 while ($xml_quiz->filepath) {
521 hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath, true);
522 $hotpot->names[] = $hotpot->exercisetitle;
523 $hotpot->summaries[] = $hotpot->exercisesubtitle;
524 $hotpot->references[] = substr($xml_quiz->filepath, $filerootlength);
526 if ($hotpot->nextexercise) {
527 $filepath = $xml_quiz->fileroot.'/'.$xml_quiz->filesubdir.$hotpot->nextexercise;
529 // check file is not already in chain
530 $reference = substr($filepath, $filerootlength);
531 if (in_array($reference, $hotpot->references)) {
532 $filepath = '';
534 } else {
535 $filepath = '';
537 if ($filepath && file_exists($filepath) && is_file($filepath) && is_readable($filepath)) {
538 $xml_quiz->filepath = $filepath;
539 } else {
540 $xml_quiz->filepath = false; // finish while loop
542 } // end while
544 } else {
545 $ok = false;
546 $hotpot->errors['reference'] = get_string('error_notfileorfolder', 'hotpot', $hotpot->reference);
549 if (empty($hotpot->references) && empty($hotpot->errors['reference'])) {
550 $ok = false;
551 $hotpot->errors['reference'] = get_string('error_noquizzesfound', 'hotpot', $hotpot->reference);
554 if ($ok) {
555 $hotpot->visible = HOTPOT_YES;
557 if (trim($hotpot->name)=='') {
558 $hotpot->name = get_string("modulename", $hotpot->modulename);
560 $hotpot->specificname = $hotpot->name;
561 $hotpot->specificsummary = $hotpot->summary;
563 // add all except last activity in chain
565 $i_max = count($hotpot->references)-1;
566 for ($i=0; $i<$i_max; $i++) {
568 hotpot_set_name_summary_reference($hotpot, $i);
569 $hotpot->reference = addslashes($hotpot->reference);
571 if (!$hotpot->instance = insert_record("hotpot", $hotpot)) {
572 error("Could not add a new instance of $hotpot->modulename", "view.php?id=$hotpot->course");
575 // store (hotpot table) id of start of chain
576 if ($i==0) {
577 $hotpot->startofchain = $hotpot->instance;
580 if (isset($course->groupmode)) {
581 $hotpot->groupmode = $course->groupmode;
584 if (! $hotpot->coursemodule = add_course_module($hotpot)) {
585 error("Could not add a new course module");
587 if (! $sectionid = add_mod_to_section($hotpot) ) {
588 error("Could not add the new course module to that section");
591 if (! set_field("course_modules", "section", $sectionid, "id", $hotpot->coursemodule)) {
592 error("Could not update the course module with the correct section");
595 add_to_log($hotpot->course, "course", "add mod",
596 "../mod/$hotpot->modulename/view.php?id=$hotpot->coursemodule",
597 "$hotpot->modulename $hotpot->instance"
599 add_to_log($hotpot->course, $hotpot->modulename, "add",
600 "view.php?id=$hotpot->coursemodule",
601 "$hotpot->instance", $hotpot->coursemodule
604 // hide tail of chain
605 if ($hotpot->shownextquiz==HOTPOT_YES) {
606 $hotpot->visible = HOTPOT_NO;
608 } // end for ($hotpot->references)
610 // settings for final activity in chain
611 hotpot_set_name_summary_reference($hotpot, $i);
612 $hotpot->reference = addslashes($hotpot->references[$i]);
613 $hotpot->shownextquiz = HOTPOT_NO;
615 if (isset($hotpot->startofchain)) {
616 // redirection only works for Moodle 1.5+
617 $hotpot->redirect = true;
618 $hotpot->redirecturl = "$CFG->wwwroot/mod/hotpot/view.php?hp=$hotpot->startofchain";
620 } // end if $ok
622 return $ok;
624 function hotpot_set_name_summary_reference(&$hotpot, $chain_index=NULL) {
626 $xml_quiz = NULL;
628 $textfields = array('name', 'summary');
629 foreach ($textfields as $textfield) {
631 $textsource = $textfield.'source';
633 // are we adding a chain?
634 if (isset($chain_index)) {
636 switch ($hotpot->$textsource) {
637 case HOTPOT_TEXTSOURCE_QUIZ:
638 if ($textfield=='name') {
639 $hotpot->exercisetitle = $hotpot->names[$chain_index];
640 } else if ($textfield=='summary') {
641 $hotpot->exercisesubtitle = $hotpot->summaries[$chain_index];
643 break;
644 case HOTPOT_TEXTSOURCE_SPECIFIC:
645 $specifictext = 'specific'.$textfield;
646 if (empty($hotpot->$specifictext) && trim($hotpot->$specifictext)=='') {
647 $hotpot->$textfield = '';
648 } else {
649 $hotpot->$textfield = $hotpot->$specifictext.' ('.($chain_index+1).')';
651 break;
653 $hotpot->reference = $hotpot->references[$chain_index];
656 if ($hotpot->$textsource==HOTPOT_TEXTSOURCE_QUIZ) {
657 if (empty($xml_quiz) && !isset($chain_index)) {
658 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
659 hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath);
661 if ($textfield=='name') {
662 $hotpot->$textfield = addslashes($hotpot->exercisetitle);
663 } else if ($textfield=='summary') {
664 $hotpot->$textfield = addslashes($hotpot->exercisesubtitle);
667 switch ($hotpot->$textsource) {
668 case HOTPOT_TEXTSOURCE_FILENAME:
669 $hotpot->$textfield = basename($hotpot->reference);
670 break;
671 case HOTPOT_TEXTSOURCE_FILEPATH:
672 $hotpot->$textfield = '';
673 // continue to next lines
674 default:
675 if (empty($hotpot->$textfield)) {
676 $hotpot->$textfield = str_replace('/', ' ', $hotpot->reference);
678 } // end switch
679 } // end foreach
681 function hotpot_get_titles_and_next_ex(&$hotpot, $filepath, $get_next=false) {
683 $hotpot->exercisetitle = '';
684 $hotpot->exercisesubtitle = '';
685 $hotpot->nextexercise = '';
687 // read the quiz file source
688 if ($source = file_get_contents($filepath)) {
690 $next = '';
691 $title = '';
692 $subtitle = '';
694 if (preg_match('|\.html?$|', $filepath)) {
695 // html file
696 if (preg_match('|<h2[^>]*class="ExerciseTitle"[^>]*>(.*?)</h2>|is', $source, $matches)) {
697 $title = trim(strip_tags($matches[1]));
699 if (empty($title)) {
700 if (preg_match('|<title[^>]*>(.*?)</title>|is', $source, $matches)) {
701 $title = trim(strip_tags($matches[1]));
704 if (preg_match('|<h3[^>]*class="ExerciseSubtitle"[^>]*>(.*?)</h3>|is', $source, $matches)) {
705 $subtitle = trim(strip_tags($matches[1]));
707 if ($get_next) {
708 if (preg_match('|<div[^>]*class="NavButtonBar"[^>]*>(.*?)</div>|is', $source, $matches)) {
709 $navbuttonbar = $matches[1];
710 if (preg_match_all('|<button[^>]*onclick="'."location='([^']*)'".'[^"]*"[^>]*>|is', $navbuttonbar, $matches)) {
711 $lastbutton = count($matches[0])-1;
712 $next = $matches[1][$lastbutton];
717 } else {
718 // xml file (...maybe)
719 $xml_tree = new hotpot_xml_tree($source);
720 $xml_tree->filetype = '';
722 $keys = array_keys($xml_tree->xml);
723 foreach ($keys as $key) {
724 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
725 $xml_tree->filetype = 'xml';
726 $xml_tree->xml_root = "['$key']['#']";
727 $xml_tree->quiztype = strtolower($matches[2]);
728 break;
731 if ($xml_tree->filetype=='xml') {
733 $title = strip_tags($xml_tree->xml_value('data,title'));
734 $subtitle = $xml_tree->xml_value('hotpot-config-file,'.$xml_tree->quiztype.',exercise-subtitle');
736 if ($get_next) {
737 $include = $xml_tree->xml_value('hotpot-config-file,global,include-next-ex');
738 if (!empty($include)) {
739 $next = $xml_tree->xml_value("hotpot-config-file,$xml_tree->quiztype,next-ex-url");
740 if (is_array($next)) {
741 $next = $next[0]; // in case "next-ex-url" was repeated in the xml file
748 $hotpot->nextexercise = $next;
749 $hotpot->exercisetitle = (empty($title) || is_array($title)) ? basename($filepath) : $title;
750 $hotpot->exercisesubtitle = (empty($subtitle) || is_array($subtitle)) ? $hotpot->exercisetitle : $subtitle;
753 function hotpot_get_all_instances_in_course($modulename, $course) {
754 /// called from index.php
756 global $CFG;
757 $instances = array();
759 if (isset($CFG->release) && substr($CFG->release, 0, 3)>=1.2) {
760 $groupmode = 'cm.groupmode,';
761 } else {
762 $groupmode = '';
765 $query = "
766 SELECT
767 cm.id AS coursemodule,
768 cm.course AS course,
769 cm.module AS module,
770 cm.instance AS instance,
771 -- cm.section AS section,
772 cm.visible AS visible,
773 $groupmode
774 -- cs.section AS sectionnumber,
775 cs.section AS section,
776 cs.sequence AS sequence,
777 cs.visible AS sectionvisible,
778 thismodule.*
779 FROM
780 {$CFG->prefix}course_modules cm,
781 {$CFG->prefix}course_sections cs,
782 {$CFG->prefix}modules m,
783 {$CFG->prefix}$modulename thismodule
784 WHERE
785 m.name = '$modulename' AND
786 m.id = cm.module AND
787 cm.course = '$course->id' AND
788 cm.section = cs.id AND
789 cm.instance = thismodule.id
791 if ($rawmods = get_records_sql($query)) {
793 // cache $isteacher setting
795 $isteacher = has_capability('mod/hotpot:viewreport', get_context_instance(CONTEXT_COURSE, $course->id));
797 $explodesection = array();
798 $order = array();
800 foreach ($rawmods as $rawmod) {
802 if (empty($explodesection[$rawmod->section])) {
803 $explodesection[$rawmod->section] = true;
805 $coursemodules = explode(',', $rawmod->sequence);
806 foreach ($coursemodules as $i=>$coursemodule) {
807 $order[$coursemodule] = sprintf('%d.%04d', $rawmod->section, $i);
811 if ($isteacher) {
812 $visible = true;
813 } else if ($modulename=='hotpot') {
814 $visible = hotpot_is_visible($rawmod);
815 } else {
816 $visible = $rawmod->visible;
819 if ($visible) {
820 $instances[$order[$rawmod->coursemodule]] = $rawmod;
823 } // end foreach $modinfo
825 ksort($instances);
826 $instances = array_values($instances);
829 return $instances;
832 function hotpot_update_chain(&$hotpot) {
833 /// update a chain of hotpot actiivities
835 $ok = true;
836 if ($hotpot_modules = hotpot_get_chain($hotpot)) {
838 // skip updating of these fields
839 $skipfields = array('id', 'course', 'name', 'reference', 'summary', 'shownextquiz');
840 $fields = array();
842 foreach ($hotpot_modules as $hotpot_module) {
844 if ($hotpot->instance==$hotpot_module->id) {
845 // don't need to update this hotpot
847 } else {
848 // shortcut to hotpot record
849 $thishotpot = &$hotpot_module->hotpot;
851 // get a list of fields to update (first time only)
852 if (empty($fields)) {
853 $fields = array_keys(get_object_vars($thishotpot));
856 // assume update is NOT required
857 $require_update = false;
859 // update field values (except $skipfields)
860 foreach($fields as $field) {
861 if (in_array($field, $skipfields) || $thishotpot->$field==$hotpot->$field) {
862 // update not required for this field
863 } else {
864 $require_update = true;
865 $thishotpot->$field = $hotpot->$field;
869 // update $thishotpot, if required
870 if ($require_update && !update_record("hotpot", $thishotpot)) {
871 error("Could not update the $hotpot->modulename", "view.php?id=$hotpot->course");
874 } // end foreach $ids
876 return $ok;
878 function hotpot_delete_instance($id) {
879 /// Given an ID of an instance of this module,
880 /// this function will permanently delete the instance
881 /// and any data that depends on it.
883 if (! $hotpot = get_record("hotpot", "id", $id)) {
884 return false;
887 if (! delete_records("hotpot", "id", "$id")) {
888 return false;
891 delete_records("hotpot_questions", "hotpot", "$id");
892 if ($attempts = get_records_select("hotpot_attempts", "hotpot='$id'")) {
893 $ids = implode(',', array_keys($attempts));
894 delete_records_select("hotpot_attempts", "id IN ($ids)");
895 delete_records_select("hotpot_details", "attempt IN ($ids)");
896 delete_records_select("hotpot_responses", "attempt IN ($ids)");
899 // remove calendar events for this hotpot
900 delete_records('event', 'modulename', 'hotpot', 'instance', $id);
902 // remove grade item for this hotpot
903 hotpot_grade_item_delete($hotpot);
905 return true;
907 function hotpot_delete_and_notify($table, $select, $strtable) {
908 $count = max(0, count_records_select($table, $select));
909 if ($count) {
910 delete_records_select($table, $select);
911 $count -= max(0, count_records_select($table, $select));
912 if ($count) {
913 notify(get_string('deleted')." $count x $strtable");
918 function hotpot_user_complete($course, $user, $mod, $hotpot) {
919 /// Print a detailed representation of what a user has done with
920 /// a given particular instance of this module, for user activity reports.
922 $report = hotpot_user_outline($course, $user, $mod, $hotpot);
923 if (empty($report)) {
924 print get_string("noactivity", "hotpot");
925 } else {
926 $date = userdate($report->time, get_string('strftimerecentfull'));
927 print $report->info.' '.get_string('mostrecently').': '.$date;
929 return true;
932 function hotpot_user_outline($course, $user, $mod, $hotpot) {
933 /// Return a small object with summary information about what a
934 /// user has done with a given particular instance of this module
935 /// Used for user activity reports.
936 /// $report->time = the time they did it
937 /// $report->info = a short text description
939 $report = NULL;
940 if ($records = get_records_select("hotpot_attempts", "hotpot='$hotpot->id' AND userid='$user->id'", "timestart ASC", "*")) {
941 $report = new stdClass();
942 $scores = array();
943 foreach ($records as $record){
944 if (empty($report->time)) {
945 $report->time = $record->timestart;
947 $scores[] = hotpot_format_score($record);
949 if (empty($scores)) {
950 $report->time = 0;
951 $report->info = get_string('noactivity', 'hotpot');
952 } else {
953 $report->info = get_string('score', 'quiz').': '.implode(', ', $scores);
956 return $report;
959 function hotpot_format_score($record, $undefined='&nbsp;') {
960 if (isset($record->score)) {
961 $score = $record->score;
962 } else {
963 $score = $undefined;
965 return $score;
968 function hotpot_format_status($record, $undefined='&nbsp;') {
969 global $HOTPOT_STATUS;
971 if (isset($record->status) || isset($HOTPOT_STATUS[$record->status])) {
972 $status = $HOTPOT_STATUS[$record->status];
973 } else {
974 $status = $undefined;
976 return $status;
979 function hotpot_print_recent_activity($course, $isteacher, $timestart) {
980 /// Given a course and a time, this module should find recent activity
981 /// that has occurred in hotpot activities and print it out.
982 /// Return true if there was output, or false is there was none.
984 global $CFG;
985 $result = false;
987 $records = get_records_sql("
988 SELECT
989 h.id AS id,
990 h.name AS name,
991 COUNT(*) AS count_attempts
992 FROM
993 {$CFG->prefix}hotpot h,
994 {$CFG->prefix}hotpot_attempts a
995 WHERE
996 h.course = $course->id
997 AND h.id = a.hotpot
998 AND a.id = a.clickreportid
999 AND a.starttime > $timestart
1000 GROUP BY
1001 h.id, h.name
1003 // note that PostGreSQL requires h.name in the GROUP BY clause
1005 if($records) {
1006 $names = array();
1007 foreach ($records as $id => $record){
1008 if ($cm = get_coursemodule_from_instance('hotpot', $record->id, $course->id)) {
1009 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1011 if (has_capability('mod/hotpot:viewreport', $context)) {
1012 $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$id";
1013 $name = '&nbsp;<a href="'.$href.'">'.$record->name.'</a>';
1014 if ($record->count_attempts > 1) {
1015 $name .= " ($record->count_attempts)";
1017 $names[] = $name;
1021 if (count($names) > 0) {
1022 print_headline(get_string('modulenameplural', 'hotpot').':');
1024 if ($CFG->version >= 2005050500) { // Moodle 1.5+
1025 echo '<div class="head"><div class="name">'.implode('<br />', $names).'</div></div>';
1026 } else { // Moodle 1.4.x (or less)
1027 echo '<font size="1">'.implode('<br />', $names).'</font>';
1029 $result = true;
1032 return $result; // True if anything was printed, otherwise false
1035 function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") {
1036 // Returns all quizzes since a given time.
1038 global $CFG;
1040 // If $cmid or $userid are specified, then this restricts the results
1041 $cm_select = empty($cmid) ? "" : " AND cm.id = '$cmid'";
1042 $user_select = empty($userid) ? "" : " AND u.id = '$userid'";
1044 $records = get_records_sql("
1045 SELECT
1046 a.*,
1047 h.name, h.course,
1048 cm.instance, cm.section,
1049 u.firstname, u.lastname, u.picture
1050 FROM
1051 {$CFG->prefix}hotpot_attempts a,
1052 {$CFG->prefix}hotpot h,
1053 {$CFG->prefix}course_modules cm,
1054 {$CFG->prefix}user u
1055 WHERE
1056 a.timefinish > '$sincetime'
1057 AND a.id = a.clickreportid
1058 AND a.userid = u.id $user_select
1059 AND a.hotpot = h.id $cm_select
1060 AND cm.instance = h.id
1061 AND cm.course = '$courseid'
1062 AND h.course = cm.course
1063 ORDER BY
1064 a.timefinish ASC
1067 if (!empty($records)) {
1068 foreach ($records as $record) {
1069 if (empty($groupid) || groups_is_member($groupid, $record->userid)) {
1071 unset($activity);
1073 $activity->type = "hotpot";
1074 $activity->defaultindex = $index;
1075 $activity->instance = $record->hotpot;
1077 $activity->name = $record->name;
1078 $activity->section = $record->section;
1080 $activity->content->attemptid = $record->id;
1081 $activity->content->attempt = $record->attempt;
1082 $activity->content->score = $record->score;
1083 $activity->content->timestart = $record->timestart;
1084 $activity->content->timefinish = $record->timefinish;
1086 $activity->user->userid = $record->userid;
1087 $activity->user->fullname = fullname($record);
1088 $activity->user->picture = $record->picture;
1090 $activity->timestamp = $record->timefinish;
1092 $activities[] = $activity;
1094 $index++;
1096 } // end foreach
1100 function hotpot_print_recent_mod_activity($activity, $course, $detail=false) {
1101 /// Basically, this function prints the results of "hotpot_get_recent_activity"
1103 global $CFG, $THEME, $USER;
1105 if (isset($THEME->cellcontent2)) {
1106 $bgcolor = ' bgcolor="'.$THEME->cellcontent2.'"';
1107 } else {
1108 $bgcolor = '';
1111 print '<table border="0" cellpadding="3" cellspacing="0">';
1113 print '<tr><td'.$bgcolor.' class="forumpostpicture" width="35" valign="top">';
1114 print_user_picture($activity->user->userid, $course, $activity->user->picture);
1115 print '</td><td width="100%"><font size="2">';
1117 if ($detail) {
1118 // activity icon
1119 $src = "$CFG->modpixpath/$activity->type/icon.gif";
1120 print '<img src="'.$src.'" class="icon" alt="'.$activity->type.'" /> ';
1122 // link to activity
1123 $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$activity->instance";
1124 print '<a href="'.$href.'">'.$activity->name.'</a> - ';
1126 if (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE, $course))) {
1127 // score (with link to attempt details)
1128 $href = "$CFG->wwwroot/mod/hotpot/review.php?hp=$activity->instance&attempt=".$activity->content->attemptid;
1129 print '<a href="'.$href.'">('.hotpot_format_score($activity->content).')</a> ';
1131 // attempt number
1132 print get_string('attempt', 'quiz').' - '.$activity->content->attempt.'<br />';
1135 // link to user
1136 $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$course";
1137 print '<a href="'.$href.'">'.$activity->user->fullname.'</a> ';
1139 // time and date
1140 print ' - ' . userdate($activity->timestamp);
1142 // duration
1143 $duration = format_time($activity->content->timestart - $activity->content->timefinish);
1144 print " &nbsp; ($duration)";
1146 print "</font></td></tr>";
1147 print "</table>";
1150 function hotpot_cron () {
1151 /// Function to be run periodically according to the moodle cron
1152 /// This function searches for things that need to be done, such
1153 /// as sending out mail, toggling flags etc ...
1155 global $CFG;
1157 return true;
1160 function hotpot_grades($hotpotid) {
1161 /// Must return an array of grades for a given instance of this module,
1162 /// indexed by user. It also returns a maximum allowed grade.
1164 $hotpot = get_record('hotpot', 'id', $hotpotid);
1165 $return->grades = hotpot_get_grades($hotpot);
1166 $return->maxgrade = $hotpot->grade;
1168 return $return;
1170 function hotpot_get_grades($hotpot, $user_ids='') {
1171 global $CFG;
1173 $grades = array();
1175 $weighting = $hotpot->grade / 100;
1176 $precision = hotpot_get_precision($hotpot);
1178 // set the SQL string to determine the $grade
1179 $grade = "";
1180 switch ($hotpot->grademethod) {
1181 case HOTPOT_GRADEMETHOD_HIGHEST:
1182 $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade";
1183 break;
1184 case HOTPOT_GRADEMETHOD_AVERAGE:
1185 // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id)
1186 $grade = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade";
1187 break;
1188 case HOTPOT_GRADEMETHOD_FIRST:
1189 $grade = "ROUND(score * $weighting, $precision)";
1190 $grade = sql_concat('timestart', "'_'", $grade);
1191 $grade = "MIN($grade) AS grade";
1192 break;
1193 case HOTPOT_GRADEMETHOD_LAST:
1194 $grade = "ROUND(score * $weighting, $precision)";
1195 $grade = sql_concat('timestart', "'_'", $grade);
1196 $grade = "MAX($grade) AS grade";
1197 break;
1200 if ($grade) {
1201 $userid_condition = empty($user_ids) ? '' : "AND userid IN ($user_ids) ";
1202 $grades = get_records_sql_menu("
1203 SELECT userid, $grade
1204 FROM {$CFG->prefix}hotpot_attempts
1205 WHERE timefinish>0 AND hotpot='$hotpot->id' $userid_condition
1206 GROUP BY userid
1208 if ($grades) {
1209 if ($hotpot->grademethod==HOTPOT_GRADEMETHOD_FIRST || $hotpot->grademethod==HOTPOT_GRADEMETHOD_LAST) {
1210 // remove left hand characters in $grade (up to and including the underscore)
1211 foreach ($grades as $userid=>$grade) {
1212 $grades[$userid] = substr($grades[$userid], strpos($grades[$userid], '_')+1);
1218 return $grades;
1220 function hotpot_get_precision(&$hotpot) {
1221 return ($hotpot->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0;
1225 * Return grade for given user or all users.
1227 * @param object $hotpot
1228 * @param int $userid optional user id, 0 means all users
1229 * @return array array of grades, false if none
1231 function hotpot_get_user_grades($hotpot, $userid=0) {
1232 $grades = array();
1233 if ($hotpotgrades = hotpot_get_grades($hotpot, $userid)) {
1234 foreach ($hotpotgrades as $hotpotuserid => $hotpotgrade) {
1235 $grades[$hotpotuserid] = new stdClass();
1236 $grades[$hotpotuserid]->id = $hotpotuserid;
1237 $grades[$hotpotuserid]->userid = $hotpotuserid;
1238 $grades[$hotpotuserid]->rawgrade = $hotpotgrade;
1241 if (count($grades)) {
1242 return $grades;
1243 } else {
1244 return false;
1249 * Update grades in central gradebook
1250 * this function is called from db/upgrade.php
1251 * it is initially called with no arguments, which forces it to get a list of all hotpots
1252 * it then iterates through the hotpots, calling itself to create a grade record for each hotpot
1254 * @param object $hotpot null means all hotpots
1255 * @param int $userid specific user only, 0 means all users
1257 function hotpot_update_grades($hotpot=null, $userid=0, $nullifnone=true) {
1258 global $CFG;
1259 if (! function_exists('grade_update')) {
1260 require_once($CFG->libdir.'/gradelib.php');
1262 if (is_null($hotpot)) {
1263 // update (=create) grades for all hotpots
1264 $sql = "
1265 SELECT h.*, cm.idnumber as cmidnumber
1266 FROM {$CFG->prefix}hotpot h, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
1267 WHERE m.name='hotpot' AND m.id=cm.module AND cm.instance=h.id"
1269 if ($rs = get_recordset_sql($sql)) {
1270 while ($hotpot = rs_fetch_next_record($rs)) {
1271 hotpot_update_grades($hotpot, 0, false);
1273 rs_close($rs);
1275 } else {
1276 // update (=create) grade for a single hotpot
1277 if ($grades = hotpot_get_user_grades($hotpot, $userid)) {
1278 hotpot_grade_item_update($hotpot, $grades);
1280 } else if ($userid && $nullifnone) {
1281 // no grades for this user, but we must force the creation of a "null" grade record
1282 $grade = new object();
1283 $grade->userid = $userid;
1284 $grade->rawgrade = null;
1285 hotpot_grade_item_update($hotpot, $grade);
1287 } else {
1288 // no grades and no userid
1289 hotpot_grade_item_update($hotpot);
1295 * Update/create grade item for given hotpot
1297 * @param object $hotpot object with extra cmidnumber
1298 * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
1299 * @return object grade_item
1301 function hotpot_grade_item_update($hotpot, $grades=null) {
1302 global $CFG;
1303 if (! function_exists('grade_update')) {
1304 require_once($CFG->libdir.'/gradelib.php');
1306 $params = array('itemname' => $hotpot->name);
1307 if (array_key_exists('cmidnumber', $hotpot)) {
1308 //cmidnumber may not be always present
1309 $params['idnumber'] = $hotpot->cmidnumber;
1311 if ($hotpot->grade > 0) {
1312 $params['gradetype'] = GRADE_TYPE_VALUE;
1313 $params['grademax'] = $hotpot->grade;
1314 $params['grademin'] = 0;
1316 } else {
1317 $params['gradetype'] = GRADE_TYPE_NONE;
1318 // Note: when adding a new activity, a gradeitem will *not*
1319 // be created in the grade book if gradetype==GRADE_TYPE_NONE
1320 // A gradeitem will be created later if gradetype changes to GRADE_TYPE_VALUE
1321 // However, the gradeitem will *not* be deleted if the activity's
1322 // gradetype changes back from GRADE_TYPE_VALUE to GRADE_TYPE_NONE
1323 // Therefore, we give the user the ability to force the removal of empty gradeitems
1324 if (! empty($hotpot->removegradeitem)) {
1325 $params['deleted'] = true;
1328 return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, $grades, $params);
1332 * Delete grade item for given hotpot
1334 * @param object $hotpot object
1335 * @return object grade_item
1337 function hotpot_grade_item_delete($hotpot) {
1338 global $CFG;
1339 if (! function_exists('grade_update')) {
1340 require_once($CFG->libdir.'/gradelib.php');
1342 return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, null, array('deleted'=>1));
1345 function hotpot_get_participants($hotpotid) {
1346 //Must return an array of user ids who are participants
1347 //for a given instance of hotpot. Must include every user involved
1348 //in the instance, independient of his role (student, teacher, admin...)
1349 //See other modules as example.
1350 global $CFG;
1352 return get_records_sql("
1353 SELECT DISTINCT
1354 u.id, u.id
1355 FROM
1356 {$CFG->prefix}user u,
1357 {$CFG->prefix}hotpot_attempts a
1358 WHERE
1359 u.id = a.userid
1360 AND a.hotpot = '$hotpotid'
1364 function hotpot_scale_used ($hotpotid, $scaleid) {
1365 //This function returns if a scale is being used by one hotpot
1366 //it it has support for grading and scales. Commented code should be
1367 //modified if necessary. See forum, glossary or journal modules
1368 //as reference.
1370 $report = false;
1372 //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid");
1374 //if (!empty($rec) && !empty($scaleid)) {
1375 // $report = true;
1378 return $report;
1382 * Checks if scale is being used by any instance of hotpot
1384 * This is used to find out if scale used anywhere
1385 * @param $scaleid int
1386 * @return boolean True if the scale is used by any hotpot
1388 function hotpot_scale_used_anywhere($scaleid) {
1389 return false;
1392 //////////////////////////////////////////////////////////
1393 /// Any other hotpot functions go here.
1394 /// Each of them must have a name that starts with hotpot
1397 function hotpot_add_attempt($hotpotid) {
1398 global $db, $CFG, $USER;
1400 // get start time of this attempt
1401 $time = time();
1403 // set all previous "in progress" attempts at this quiz to "abandoned"
1404 if ($attempts = get_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id' AND status='".HOTPOT_STATUS_INPROGRESS."'")) {
1405 foreach ($attempts as $attempt) {
1406 if ($attempt->timefinish==0) {
1407 $attempt->timefinish = $time;
1409 if ($attempt->clickreportid==0) {
1410 $attempt->clickreportid = $attempt->id;
1412 $attempt->status = HOTPOT_STATUS_ABANDONED;
1413 update_record('hotpot_attempts', $attempt);
1417 // create and add new attempt record
1418 $attempt = new stdClass();
1419 $attempt->hotpot = $hotpotid;
1420 $attempt->userid = $USER->id;
1421 $attempt->attempt = hotpot_get_next_attempt($hotpotid);
1422 $attempt->timestart = $time;
1424 return insert_record("hotpot_attempts", $attempt);
1426 function hotpot_get_next_attempt($hotpotid) {
1427 global $USER;
1429 // get max attempt so far
1430 $i = count_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id'", 'MAX(attempt)');
1432 return empty($i) ? 1 : ($i+1);
1434 function hotpot_get_question_name($question) {
1435 $name = '';
1436 if (isset($question->text)) {
1437 $name = hotpot_strings($question->text);
1439 if (empty($name)) {
1440 $name = $question->name;
1442 return $name;
1444 function hotpot_strings($ids) {
1446 // array of ids of empty strings
1447 static $HOTPOT_EMPTYSTRINGS;
1449 if (!isset($HOTPOT_EMPTYSTRINGS)) { // first time only
1450 // get ids of empty strings
1451 $emptystrings = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0');
1452 $HOTPOT_EMPTYSTRINGS = empty($emptystrings) ? array() : array_keys($emptystrings);
1455 $strings = array();
1456 if (!empty($ids)) {
1457 $ids = explode(',', $ids);
1458 foreach ($ids as $id) {
1459 if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) {
1460 $strings[] = hotpot_string($id);
1464 return implode(',', $strings);
1466 function hotpot_string($id) {
1467 return get_field('hotpot_strings', 'string', 'id', $id);
1470 //////////////////////////////////////////////////////////////////////////////////////
1471 /// the class definitions to handle XML trees
1473 // get the standard XML parser supplied with Moodle
1474 require_once("$CFG->libdir/xmlize.php");
1476 // get the default class for hotpot quiz templates
1477 require_once("$CFG->hotpottemplate/default.php");
1479 class hotpot_xml_tree {
1480 function hotpot_xml_tree($str, $xml_root='') {
1481 if (empty($str)) {
1482 $this->xml = array();
1483 } else {
1484 if (empty($CFG->unicodedb)) {
1485 $str = utf8_encode($str);
1487 $this->xml = xmlize($str, 0);
1489 $this->xml_root = $xml_root;
1491 function xml_value($tags, $more_tags="[0]['#']") {
1493 $tags = empty($tags) ? '' : "['".str_replace(",", "'][0]['#']['", $tags)."']";
1494 eval('$value = &$this->xml'.$this->xml_root.$tags.$more_tags.';');
1496 if (is_string($value)) {
1497 if (empty($CFG->unicodedb)) {
1498 $value = utf8_decode($value);
1501 // decode angle brackets
1502 $value = strtr($value, array('&#x003C;'=>'<', '&#x003E;'=>'>', '&#x0026;'=>'&'));
1504 // remove white space between <table>, <ul|OL|DL> and <OBJECT|EMBED> parts
1505 // (so it doesn't get converted to <br />)
1506 $htmltags = '('
1507 . 'TABLE|\\/?CAPTION|\\/?COL|\\/?COLGROUP|\\/?TBODY|\\/?TFOOT|\\/?THEAD|\\/?TD|\\/?TH|\\/?TR'
1508 . '|OL|UL|\\/?LI'
1509 . '|DL|\\/?DT|\\/?DD'
1510 . '|EMBED|OBJECT|APPLET|\\/?PARAM'
1511 //. '|SELECT|\\/?OPTION'
1512 //. '|FIELDSET|\\/?LEGEND'
1513 //. '|FRAMESET|\\/?FRAME'
1514 . ')'
1517 $space = '(?:\s|(?:<br[^>]*>))+';
1518 $search = '/(<'.$htmltags.'[^>]*'.'>)'.$space.'(?='.'<)/is';
1519 $value = preg_replace($search, '\\1', $value);
1521 // replace remaining newlines with <br />
1522 $value = str_replace("\n", '<br />', $value);
1524 // encode unicode characters as HTML entities
1525 // (in particular, accented charaters that have not been encoded by HP)
1527 // unicode characters can be detected by checking the hex value of a character
1528 // 00 - 7F : ascii char (roman alphabet + punctuation)
1529 // 80 - BF : byte 2, 3 or 4 of a unicode char
1530 // C0 - DF : 1st byte of 2-byte char
1531 // E0 - EF : 1st byte of 3-byte char
1532 // F0 - FF : 1st byte of 4-byte char
1533 // if the string doesn't match the above, it might be
1534 // 80 - FF : single-byte, non-ascii char
1535 $search = '/'.'[\xc0-\xdf][\x80-\xbf]'.'|'.'[\xe0-\xef][\x80-\xbf]{2}'.'|'.'[\xf0-\xff][\x80-\xbf]{3}'.'|'.'[\x80-\xff]'.'/';
1536 $value = preg_replace_callback($search, array(&$this, 'xml_value_callback'), $value);
1538 return $value;
1540 function xml_value_callback(&$matches) {
1541 return hotpot_utf8_to_html_entity($matches[0]);
1543 function xml_values($tags) {
1544 $i = 0;
1545 $values = array();
1546 while ($value = $this->xml_value($tags, "[$i]['#']")) {
1547 $values[$i++] = $value;
1549 return $values;
1551 function obj_value(&$obj, $name) {
1552 return is_object($obj) ? @$obj->$name : (is_array($obj) ? @$obj[$name] : NULL);
1554 function encode_cdata(&$str, $tag) {
1556 static $ILLEGAL_STRINGS = array(
1557 "\r\n" => '&lt;br /&gt;',
1558 "\r" => '&lt;br /&gt;',
1559 "\n" => '&lt;br /&gt;',
1560 '[' => '&#91;',
1561 ']' => '&#93;'
1564 // extract the $tag from the $str(ing), if possible
1565 $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is';
1566 if (preg_match($pattern, $str, $matches)) {
1568 // encode problematic CDATA chars and strings
1569 $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS);
1571 // if there are any ampersands in "open text"
1572 // surround them by CDATA start and end markers
1573 // (and convert HTML entities to plain text)
1574 $search = '/(?<=>)'.'[^<]*&[^<]*'.'(?=<)/';
1575 $matches[2] = preg_replace_callback($search, array(&$this, 'encode_cdata_callback'), $matches[2]);
1577 $str = $matches[1].$matches[2].$matches[3];
1580 function encode_cdata_callback(&$matches) {
1581 static $HTML_ENTITIES = array(
1582 '&apos;' => "'",
1583 '&quot;' => '"',
1584 '&lt;' => '<',
1585 '&gt;' => '>',
1586 '&amp;' => '&',
1588 return '<![CDATA['.strtr($matches[0], $HTML_ENTITIES).']]>';
1592 class hotpot_xml_quiz extends hotpot_xml_tree {
1594 // constructor function
1595 function hotpot_xml_quiz(&$obj, $read_file=true, $parse_xml=true, $convert_urls=true, $report_errors=true, $create_html=true) {
1596 // obj can be the $_GET array or a form object/array
1598 global $CFG, $HOTPOT_OUTPUTFORMAT, $HOTPOT_OUTPUTFORMAT_DIR;
1600 // check xmlize functions are available
1601 if (! function_exists("xmlize")) {
1602 error('xmlize functions are not available');
1605 $this->read_file = $read_file;
1606 $this->parse_xml = $parse_xml;
1607 $this->convert_urls = $convert_urls;
1608 $this->report_errors = $report_errors;
1609 $this->create_html = $create_html;
1611 // extract fields from $obj
1612 // course : the course id
1613 // reference : the filename within the files folder
1614 // location : "site" files folder or "course" files folder
1615 // navigation : type of navigation required in quiz
1616 // forceplugins : force Moodle compatible media players
1617 $this->course = $this->obj_value($obj, 'course');
1618 $this->reference = $this->obj_value($obj, 'reference');
1619 $this->location = $this->obj_value($obj, 'location');
1620 $this->navigation = $this->obj_value($obj, 'navigation');
1621 $this->forceplugins = $this->obj_value($obj, 'forceplugins');
1623 // can't continue if there is no course or reference
1624 if (empty($this->course) || empty($this->reference)) {
1625 $this->error = get_string('error_nocourseorfilename', 'hotpot');
1626 if ($this->report_errors) {
1627 error($this->error);
1629 return;
1632 $this->course_homeurl = "$CFG->wwwroot/course/view.php?id=$this->course";
1634 // set filedir, filename and filepath
1635 switch ($this->location) {
1636 case HOTPOT_LOCATION_SITEFILES:
1637 $site = get_site();
1638 $this->filedir = $site->id;
1639 break;
1641 case HOTPOT_LOCATION_COURSEFILES:
1642 default:
1643 $this->filedir = $this->course;
1644 break;
1646 $this->filesubdir = dirname($this->reference);
1647 if ($this->filesubdir=='.') {
1648 $this->filesubdir = '';
1650 if ($this->filesubdir) {
1651 $this->filesubdir .= '/';
1653 $this->filename = basename($this->reference);
1654 $this->fileroot = "$CFG->dataroot/$this->filedir";
1655 $this->filepath = "$this->fileroot/$this->reference";
1657 // read the file, if required
1658 if ($this->read_file) {
1660 if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
1661 $this->error = get_string('error_couldnotopensourcefile', 'hotpot', $this->filepath);
1662 if ($this->report_errors) {
1663 error($this->error, $this->course_homeurl);
1665 return;
1668 // read in the XML source
1669 $this->source = file_get_contents($this->filepath);
1671 // convert relative URLs to absolute URLs
1672 if ($this->convert_urls) {
1673 $this->hotpot_convert_relative_urls($this->source);
1676 $this->html = '';
1677 $this->quiztype = '';
1678 $this->outputformat = 0;
1680 // is this an html file?
1681 if (preg_match('|\.html?$|', $this->filename)) {
1683 $this->filetype = 'html';
1684 $this->html = &$this->source;
1686 // relative URLs in stylesheets
1687 $search = '/'.'(<style[^>]*>)'.'(.*?)'.'(<\/style>)'.'/is';
1688 $this->source = preg_replace_callback($search, array(&$this, 'callback_stylesheets_urls'), $this->source);
1690 // relative URLs in "PreloadImages(...);"
1691 $search = '/'.'(?<='.'PreloadImages'.'\('.')'."([^)]+?)".'(?='.'\);'.')'.'/is';
1692 $this->source = preg_replace_callback($search, array(&$this, 'callback_preloadimages_urls'), $this->source);
1694 // relative URLs in <button class="NavButton" ... onclick="location='...'">
1695 $search = '/'.'(?<='.'onclick="'."location='".')'."([^']*)".'(?='."'; return false;".'")'.'/is';
1696 $this->source = preg_replace_callback($search, array(&$this, 'callback_navbutton_url'), $this->source);
1698 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1699 $search = '/'.'(?<='.'onclick="'."window.open\\('".')'."([^']*)".'(?='."'\\);return false;".'")'.'/is';
1700 $this->source = preg_replace_callback($search, array(&$this, 'callback_url'), $this->source);
1702 } else {
1704 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1705 $search = '/'.'(?<='.'onclick=&quot;'."window.open\\(&apos;".')'."(.*?)".'(?='."&apos;\\);return false;".'&quot;)'.'/is';
1706 $this->source = preg_replace_callback($search, array(&$this, 'callback_url'), $this->source);
1708 if ($this->parse_xml) {
1710 $this->filetype = 'xml';
1712 // encode "gap fill" text in JCloze exercise
1713 $this->encode_cdata($this->source, 'gap-fill');
1715 // convert source to xml tree
1716 $this->hotpot_xml_tree($this->source);
1718 $keys = array_keys($this->xml);
1719 foreach ($keys as $key) {
1720 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
1721 $this->quiztype = strtolower($matches[2]);
1722 $this->xml_root = "['$key']['#']";
1723 break;
1728 if ($this->create_html) {
1730 // set the real output format from the requested output format
1731 $this->real_outputformat = $this->obj_value($obj, 'outputformat');
1732 $this->draganddrop = '';
1733 if (
1734 empty($this->real_outputformat) ||
1735 $this->real_outputformat==HOTPOT_OUTPUTFORMAT_BEST ||
1736 empty($HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat])
1738 if ($CFG->hotpotismobile && isset($HOTPOT_OUTPUTFORMAT_DIR[HOTPOT_OUTPUTFORMAT_MOBILE])) {
1739 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_MOBILE;
1740 } else { // PC
1741 if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
1742 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS;
1743 } else {
1744 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
1749 if ($this->real_outputformat==HOTPOT_OUTPUTFORMAT_V6_PLUS) {
1750 if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
1751 $this->draganddrop = 'd'; // prefix for templates (can also be "f" ?)
1753 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
1756 // set path(s) to template
1757 $this->template_dir = $HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat];
1758 $this->template_dirpath = $CFG->hotpottemplate.'/'.$this->template_dir;
1759 $this->template_filepath = $CFG->hotpottemplate.'/'.$this->template_dir.'.php';
1761 // check template class exists
1762 if (!file_exists($this->template_filepath) || !is_readable($this->template_filepath)) {
1763 $this->error = get_string('error_couldnotopentemplate', 'hotpot', $this->template_dir);
1764 if ($this->report_errors) {
1765 error($this->error, $this->course_homeurl);
1767 return;
1770 // get default and output-specfic template classes
1771 include($this->template_filepath);
1773 // create html (using the template for the specified output format)
1774 $this->template = new hotpot_xml_quiz_template($this);
1775 $this->html = &$this->template->html;
1777 } // end $this->create_html
1778 } // end if html/xml file
1779 } // end if $this->read_file
1780 } // end constructor function
1782 function callback_stylesheets_urls(&$matches) {
1783 return $matches[1].hotpot_convert_stylesheets_urls($this->get_baseurl(), $this->reference , $matches[2], false).$matches[3];
1785 function callback_preloadimages_urls(&$matches) {
1786 return hotpot_convert_preloadimages_urls($this->get_baseurl(), $this->reference, $matches[1], false);
1788 function callback_navbutton_url(&$matches) {
1789 return hotpot_convert_navbutton_url($this->get_baseurl(), $this->reference, $matches[1], $this->course, false);
1791 function callback_url(&$matches) {
1792 return hotpot_convert_url($this->get_baseurl(), $this->reference, $matches[1], false);
1794 function callback_relative_url(&$matches) {
1795 return hotpot_convert_relative_url($this->get_baseurl(), $this->reference, $matches[1], $matches[6], $matches[7], false);
1798 function hotpot_convert_relative_urls(&$str) {
1799 $tagopen = '(?:(<)|(&lt;)|(&amp;#x003C;))'; // left angle bracket
1800 $tagclose = '(?(2)>|(?(3)&gt;|(?(4)&amp;#x003E;)))'; // right angle bracket (to match left angle bracket)
1802 $space = '\s+'; // at least one space
1803 $anychar = '(?:[^>]*?)'; // any character
1805 $quoteopen = '("|&quot;|&amp;quot;)'; // open quote
1806 $quoteclose = '\\5'; // close quote (to match open quote)
1808 $tags = array('script'=>'src', 'link'=>'href', 'a'=>'href','img'=>'src','param'=>'value', 'object'=>'data', 'embed'=>'src');
1809 foreach ($tags as $tag=>$attribute) {
1810 if ($tag=='param') {
1811 $url = '\S+?\.\S+?'; // must include a filename and have no spaces
1812 } else {
1813 $url = '.*?';
1815 $search = "/($tagopen$tag$space$anychar$attribute=$quoteopen)($url)($quoteclose$anychar$tagclose)/is";
1816 $str = preg_replace_callback($search, array(&$this, 'callback_relative_url'), $str);
1820 function get_baseurl() {
1821 // set the url base (first time only)
1822 if (!isset($this->baseurl)) {
1823 global $CFG;
1824 require_once($CFG->libdir.'/filelib.php');
1825 $this->baseurl = get_file_url($this->filedir).'/';
1827 return $this->baseurl;
1831 // insert forms and messages
1833 function remove_nav_buttons() {
1834 $search = '/<!-- Begin(Top|Bottom)NavButtons -->(.*?)<!-- End(Top|Bottom)NavButtons -->/s';
1835 $this->html = preg_replace($search, '', $this->html);
1837 function insert_script($src=HOTPOT_JS) {
1838 $script = '<script src="'.$src.'" type="text/javascript"></script>'."\n";
1839 $this->html = preg_replace('|</head>|i', $script.'</head>', $this->html, 1);
1841 function insert_submission_form($attemptid, $startblock, $endblock, $keep_contents=false, $targetframe='') {
1842 $form_id = 'store';
1843 $form_fields = ''
1844 . '<fieldset style="display:none">'
1845 . '<input type="hidden" name="attemptid" value="'.$attemptid.'" />'
1846 . '<input type="hidden" name="starttime" value="" />'
1847 . '<input type="hidden" name="endtime" value="" />'
1848 . '<input type="hidden" name="mark" value="" />'
1849 . '<input type="hidden" name="detail" value="" />'
1850 . '<input type="hidden" name="status" value="" />'
1851 . '</fieldset>'
1853 $this->insert_form($startblock, $endblock, $form_id, $form_fields, $keep_contents, false, $targetframe);
1855 function insert_giveup_form($attemptid, $startblock, $endblock, $keep_contents=false) {
1856 $form_id = ''; // no <form> tag will be generated
1857 $form_fields = ''
1858 . '<button onclick="Finish('.HOTPOT_STATUS_ABANDONED.')" class="FuncButton" '
1859 . 'onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" '
1860 . 'onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" '
1861 . 'onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">'
1862 . get_string('giveup', 'hotpot').'</button>'
1864 $this->insert_form($startblock, $endblock, $form_id, $form_fields, $keep_contents, true);
1866 function insert_form($startblock, $endblock, $form_id, $form_fields, $keep_contents, $center=false, $targetframe='') {
1867 global $CFG;
1868 $search = '/('.preg_quote($startblock, '/').')(.*?)('.preg_quote($endblock, '/').')/s';
1869 $replace = $form_fields;
1870 if ($keep_contents) {
1871 $replace .= '\\2';
1873 if ($targetframe) {
1874 $frametarget = ' onsubmit="'."this.target='$targetframe';".'"';
1875 } else if (! empty($CFG->framename)) {
1876 $frametarget = ' onsubmit="'."this.target='$CFG->framename';".'"';
1877 } else if (! empty($CFG->frametarget)) {
1878 $frametarget = $CFG->frametarget;
1879 } else {
1880 $frametarget = '';
1882 if ($form_id) {
1883 $replace = '<form action="'.$CFG->wwwroot.'/mod/hotpot/attempt.php" method="post" id="'.$form_id.'"'.$frametarget.'>'.$replace.'</form>';
1885 if ($center) {
1886 $replace = '<div style="margin-left:auto; margin-right:auto; text-align: center;">'.$replace.'</div>';
1888 $replace = '\\1'.$replace.'\\3';
1889 $this->html = preg_replace($search, $replace, $this->html, 1);
1891 function insert_message($start_str, $message, $color='red', $align='center') {
1892 $message = '<p align="'.$align.'" style="text-align:'.$align.'"><b><font color="'.$color.'">'.$message."</font></b></p>\n";
1893 $this->html = preg_replace('|'.preg_quote($start_str).'|', $start_str.$message, $this->html, 1);
1896 function adjust_media_urls() {
1898 if ($this->forceplugins) {
1900 // make sure the Moodle media plugin is available
1901 global $CFG;
1902 //include_once "$CFG->dirroot/filter/mediaplugin/filter.php";
1903 include_once "$CFG->dirroot/mod/hotpot/mediaplayers/moodle/filter.php";
1905 $space = '\s(?:.+\s)?';
1906 $quote = '["'."']?"; // single, double, or no quote
1908 // patterns to media files types and paths
1909 $filetypes = "avi|mpeg|mpg|mp3|mov|wmv|flv";
1910 if ($CFG->filter_mediaplugin_enable_swf) {
1911 $filetypes .= '|swf';
1913 $filepath = '[^"'."']*".'\\.(?:'.$filetypes.')[^"'."']*";
1915 $tagopen = '(?:(<)|(\\\\u003C))'; // left angle-bracket (uses two parenthese)
1916 $tagchars = '(?(1)[^>]|(?(2).(?!\\\\u003E)))*?'; // string of chars inside the tag
1917 $tagclose = '(?(1)>|(?(2)\\\\u003E))'; // right angle-bracket (to match the left one)
1918 $tagreopen = '(?(1)<|(?(2)\\\\u003C))'; // another left angle-bracket (to match the first one)
1920 // pattern to match <param> tags which contain the file path
1921 $param_names = 'movie|src|url|flashvars';
1922 // wmp : url
1923 // quicktime : src
1924 // realplayer : src
1925 // flash : movie, flashvars
1926 $param_url = '/'.$tagopen.'param'.'\s'.$tagchars.'name="(?:'.$param_names.')"'.$tagchars.'value="('.$filepath.')"'.$tagchars.$tagclose.'/is';
1928 // pattern to match <a> tags which link to multimedia files
1929 $link_url = '/'.$tagopen.'a'.'\s'.$tagchars.'href="('.$filepath.')"'.$tagchars.$tagclose.'.*?'.$tagreopen.'\/a'.$tagclose.'/is';
1931 // extract <object> tags
1932 $object_tag = '/'.$tagopen.'object'.'\s'.$tagchars.$tagclose.'(.*?)'.'(?:'.$tagreopen.'\/object'.$tagclose.')+/is';
1933 preg_match_all($object_tag, $this->html, $objects);
1935 $i_max = count($objects[0]);
1936 for ($i=0; $i<$i_max; $i++) {
1938 // extract URL from <param> or <a>
1939 $url = '';
1940 if (preg_match($param_url, $objects[3][$i], $matches) || preg_match($link_url, $objects[3][$i], $matches)) {
1941 $url = $matches[3];
1943 if ($url) {
1944 // strip inner tags (e.g. <embed>)
1945 $txt = preg_replace("/$tagopen.*?$tagclose/", '', $objects[3][$i]);
1947 // if url is in the query string, remove the leading characters
1948 $url = preg_replace('/^([^=]+=[^&]*&)*[^=]+=(http:[^&]*)$/', '$2', $url, 1);
1949 $link = '<a href="'.$url.'">'.$txt.'</a>';
1951 $new_object = hotpot_mediaplayer_moodle($this, $link);
1952 $new_object = str_replace($link, '', $new_object);
1953 $new_object = str_replace('&amp;', '&', $new_object);
1955 $this->html = str_replace($objects[0][$i], $new_object, $this->html);
1961 } // end class
1963 function hotpot_stripslashes($str) {
1964 // strip slashes from double quotes, single quotes and back slashes
1965 // the slashes were added by preg_replace() when using the "e" modifier
1966 static $escapedchars = array('\\\\', '\\"', "\\'");
1967 static $unescapedchars = array('\\', '"', "'");
1968 return str_replace($escapedchars, $unescapedchars, $str);
1970 function hotpot_convert_stylesheets_urls($baseurl, $reference, $css, $stripslashes=true) {
1971 if ($stripslashes) {
1972 $css = hotpot_stripslashes($css);
1974 $search = '/(?<=url\()'.'(?:.+?)'.'(?=\))/is';
1975 if (preg_match_all($search, $css, $matches, PREG_OFFSET_CAPTURE)) {
1976 $i_max = count($matches[0]) - 1;
1977 for ($i=$i_max; $i>=0; $i--) {
1978 $match = $matches[0][$i][0];
1979 $start = $matches[0][$i][1];
1980 $replace = hotpot_convert_url($baseurl, $reference, $match, false);
1981 $css = substr_replace($css, $replace, $start, strlen($match));
1984 return $css;
1986 function hotpot_convert_preloadimages_urls($baseurl, $reference, $urls, $stripslashes=true) {
1987 if ($stripslashes) {
1988 $urls = hotpot_stripslashes($urls);
1990 $search = '|(?<=["'."'])(?:[^,'".'"]*?)(?=["'."'])|is";
1991 if (preg_match_all($search, $urls, $matches, PREG_OFFSET_CAPTURE)) {
1992 $i_max = count($matches[0]) - 1;
1993 for ($i=$i_max; $i>=0; $i--) {
1994 $match = $matches[0][$i][0];
1995 $start = $matches[0][$i][1];
1996 $replace = hotpot_convert_url($baseurl, $reference, $match, false);
1997 $urls = substr_replace($urls, $replace, $start, strlen($match));
2000 return $urls;
2002 function hotpot_convert_navbutton_url($baseurl, $reference, $url, $course, $stripslashes=true) {
2003 global $CFG;
2005 if ($stripslashes) {
2006 $url = hotpot_stripslashes($url);
2008 $url = hotpot_convert_url($baseurl, $reference, $url, false);
2010 // is this a $url for another hotpot in this course ?
2011 if (preg_match("/^".preg_quote($baseurl, '/')."(.*)$/", $url, $matches)) {
2012 if ($records = get_records_select('hotpot', "course='$course' AND reference='".$matches[1]."'")) {
2013 $ids = array_keys($records);
2014 $url = "$CFG->wwwroot/mod/hotpot/view.php?hp=".$ids[0];
2018 return $url;
2021 function hotpot_convert_relative_url($baseurl, $reference, $opentag, $url, $closetag, $stripslashes=true) {
2022 if ($stripslashes) {
2023 $opentag = hotpot_stripslashes($opentag);
2024 $url = hotpot_stripslashes($url);
2025 $closetag = hotpot_stripslashes($closetag);
2028 // catch <PARAM name="FlashVars" value="TheSound=soundfile.mp3">
2029 // ampersands can appear as "&", "&amp;" or "&amp;#x0026;amp;"
2030 if (preg_match('/^'.'\w+=[^&]+'.'('.'&((amp;#x0026;)?amp;)?'.'\w+=[^&]+)*'.'$/', $url)) {
2031 $query = $url;
2032 $url = '';
2033 $fragment = '';
2035 // parse the $url into $matches
2036 // [1] path
2037 // [2] query string, if any
2038 // [3] anchor fragment, if any
2039 } else if (preg_match('/^'.'([^?]*)'.'((?:\\?[^#]*)?)'.'((?:#.*)?)'.'$/', $url, $matches)) {
2040 $url = $matches[1];
2041 $query = $matches[2];
2042 $fragment = $matches[3];
2044 // these appears to be no query or fragment in this url
2045 } else {
2046 $query = '';
2047 $fragment = '';
2050 if ($url) {
2051 $url = hotpot_convert_url($baseurl, $reference, $url, false);
2054 if ($query) {
2055 $search = '/'.'(file|src|thesound|mp3)='."([^&]+)".'/is';
2056 if (preg_match_all($search, $query, $matches, PREG_OFFSET_CAPTURE)) {
2057 $i_max = count($matches[0]) - 1;
2058 for ($i=$i_max; $i>=0; $i--) {
2059 $match = $matches[2][$i][0];
2060 $start = $matches[2][$i][1];
2061 $replace = hotpot_convert_url($baseurl, $reference, $match, false);
2062 $query = substr_replace($query, $replace, $start, strlen($match));
2067 $url = $opentag.$url.$query.$fragment.$closetag;
2069 return $url;
2072 function hotpot_convert_url($baseurl, $reference, $url, $stripslashes=true) {
2073 // maintain a cache of converted urls
2074 static $HOTPOT_RELATIVE_URLS = array();
2076 if ($stripslashes) {
2077 $url = hotpot_stripslashes($url);
2080 // is this an absolute url? (or javascript pseudo url)
2081 if (preg_match('%^(http://|https://|/|javascript:)%i', $url)) {
2082 // do nothing
2084 // has this relative url already been converted?
2085 } else if (isset($HOTPOT_RELATIVE_URLS[$url])) {
2086 $url = $HOTPOT_RELATIVE_URLS[$url];
2088 } else {
2089 $relativeurl = $url;
2091 // get the subdirectory, $dir, of the quiz $reference
2092 $dir = dirname($reference);
2094 // allow for leading "./" and "../"
2095 while (preg_match('|^(\.{1,2})/(.*)$|', $url, $matches)) {
2096 if ($matches[1]=='..') {
2097 $dir = dirname($dir);
2099 $url = $matches[2];
2102 // add subdirectory, $dir, to $baseurl, if necessary
2103 if ($dir && $dir<>'.') {
2104 $baseurl .= "$dir/";
2107 // prefix $url with $baseurl
2108 $url = "$baseurl$url";
2110 // add url to cache
2111 $HOTPOT_RELATIVE_URLS[$relativeurl] = $url;
2113 return $url;
2116 // ===================================================
2117 // function for adding attempt questions and responses
2118 // ===================================================
2120 function hotpot_add_attempt_details(&$attempt) {
2122 // encode ampersands so that HTML entities are preserved in the XML parser
2123 // N.B. ampersands inside <![CDATA[ ]]> blocks do NOT need to be encoded
2125 $old = &$attempt->details; // shortcut to "old" details
2126 $new = '';
2127 $str_start = 0;
2128 while (($cdata_start = strpos($old, '<![CDATA[', $str_start)) && ($cdata_end = strpos($old, ']]>', $cdata_start))) {
2129 $cdata_end += 3;
2130 $new .= str_replace('&', '&amp;', substr($old, $str_start, $cdata_start-$str_start)).substr($old, $cdata_start, $cdata_end-$cdata_start);
2131 $str_start = $cdata_end;
2133 $new .= str_replace('&', '&amp;', substr($old, $str_start));
2134 unset($old);
2136 // parse the attempt details as xml
2137 $details = new hotpot_xml_tree($new, "['hpjsresult']['#']");
2139 $num = -1;
2140 $q_num = -1;
2141 $question = NULL;
2142 $reponse = NULL;
2144 $i = 0;
2145 $tags = 'fields,field';
2147 while (($field="[$i]['#']") && $details->xml_value($tags, $field)) {
2149 $name = $details->xml_value($tags, $field."['fieldname'][0]['#']");
2150 $data = $details->xml_value($tags, $field."['fielddata'][0]['#']");
2152 // parse the field name into $matches
2153 // [1] quiz type
2154 // [2] attempt detail name
2155 if (preg_match('/^(\w+?)_(\w+)$/', $name, $matches)) {
2156 $quiztype = strtolower($matches[1]);
2157 $name = strtolower($matches[2]);
2159 // parse the attempt detail $name into $matches
2160 // [1] question number
2161 // [2] question detail name
2162 if (preg_match('/^q(\d+)_(\w+)$/', $name, $matches)) {
2163 $num = $matches[1];
2164 $name = strtolower($matches[2]);
2165 $data = addslashes($data);
2167 // adjust JCross question numbers
2168 if (preg_match('/^(across|down)(.*)$/', $name, $matches)) {
2169 $num .= '_'.$matches[1]; // e.g. 01_across, 02_down
2170 $name = $matches[2];
2171 if (substr($name, 0, 1)=='_') {
2172 $name = substr($name, 1); // remove leading '_'
2176 // is this a new question (or the first one)?
2177 if ($q_num<>$num) {
2179 // add previous question and response, if any
2180 hotpot_add_response($attempt, $question, $response);
2182 // initialize question object
2183 $question = NULL;
2184 $question->name = '';
2185 $question->text = '';
2186 $question->hotpot = $attempt->hotpot;
2188 // initialize response object
2189 $response = NULL;
2190 $response->attempt = $attempt->id;
2192 // update question number
2193 $q_num = $num;
2196 // adjust field name and value, and set question type
2197 // (may not be necessary one day)
2198 hotpot_adjust_response_field($quiztype, $question, $num, $name, $data);
2200 // add $data to the question/response details
2201 switch ($name) {
2202 case 'name':
2203 case 'type':
2204 $question->$name = $data;
2205 break;
2206 case 'text':
2207 $question->$name = hotpot_string_id($data);
2208 break;
2210 case 'correct':
2211 case 'ignored':
2212 case 'wrong':
2213 $response->$name = hotpot_string_ids($data);
2214 break;
2216 case 'score':
2217 case 'weighting':
2218 case 'hints':
2219 case 'clues':
2220 case 'checks':
2221 $response->$name = intval($data);
2222 break;
2225 } else { // attempt details
2227 // adjust field name and value
2228 hotpot_adjust_response_field($quiztype, $question, $num='', $name, $data);
2230 // add $data to the attempt details
2231 if ($name=='penalties') {
2232 $attempt->$name = intval($data);
2237 $i++;
2238 } // end while
2240 // add the final question and response, if any
2241 hotpot_add_response($attempt, $question, $response);
2243 function hotpot_add_response(&$attempt, &$question, &$response) {
2244 global $db, $next_url;
2246 $loopcount = 1;
2248 $looping = isset($question) && isset($question->name) && isset($response);
2249 while ($looping) {
2251 if ($loopcount==1) {
2252 $questionname = $question->name;
2255 $question->md5key = md5($question->name);
2256 if (!$question->id = get_field('hotpot_questions', 'id', 'hotpot', $attempt->hotpot, 'md5key', $question->md5key, 'name', $question->name)) {
2257 // add question record
2258 if (!$question->id = insert_record('hotpot_questions', $question)) {
2259 error("Could not add question record (attempt_id=$attempt->id): ".$db->ErrorMsg(), $next_url);
2263 if (record_exists('hotpot_responses', 'attempt', $attempt->id, 'question', $question->id)) {
2264 // there is already a response to this question for this attempt
2265 // probably because this quiz has two questions with the same text
2266 // e.g. Which one of these answers is correct?
2268 // To workaround this, we create new question names
2269 // e.g. Which one of these answers is correct? (2)
2270 // until we get a question name for which there is no response yet on this attempt
2272 $loopcount++;
2273 $question->name = "$questionname ($loopcount)";
2275 // This method fails to correctly identify questions in
2276 // quizzes which allow questions to be shuffled or omitted.
2277 // As yet, there is no workaround for such cases.
2279 } else {
2280 $response->question = $question->id;
2282 // add response record
2283 if(!$response->id = insert_record('hotpot_responses', $response)) {
2284 error("Could not add response record (attempt_id=$attempt->id, question_id=$question->id): ".$db->ErrorMsg(), $next_url);
2287 // we can stop looping now
2288 $looping = false;
2290 } // end while
2292 function hotpot_adjust_response_field($quiztype, &$question, &$num, &$name, &$data) {
2293 switch ($quiztype) {
2294 case 'jbc':
2295 $question->type = HOTPOT_JCB;
2296 switch ($name) {
2297 case 'right':
2298 $name = 'correct';
2299 break;
2301 break;
2302 case 'jcloze':
2303 $question->type = HOTPOT_JCLOZE;
2304 if (is_numeric($num)) {
2305 $question->name = $num;
2307 switch ($name) {
2308 case 'penalties':
2309 if (is_numeric($num)) {
2310 $name = 'checks';
2311 if (is_numeric($data)) {
2312 $data++;
2315 break;
2316 case 'clue_shown':
2317 $name = 'clues';
2318 $data = ($data=='YES' ? 1 : 0);
2319 break;
2320 case 'clue_text':
2321 $name = 'text';
2322 break;
2324 break;
2325 case 'jcross':
2326 $question->type = HOTPOT_JCROSS;
2327 $question->name = $num;
2328 switch ($name) {
2329 case '': // HotPot v2.0.x
2330 $name = 'correct';
2331 break;
2332 case 'clue':
2333 $name = 'text';
2334 break;
2336 break;
2337 case 'jmatch':
2338 $question->type = HOTPOT_JMATCH;
2339 switch ($name) {
2340 case 'attempts':
2341 $name = 'penalties';
2342 if (is_numeric($data) && $data>0) {
2343 $data--;
2345 break;
2346 case 'lhs':
2347 $name = 'name';
2348 break;
2349 case 'rhs':
2350 $name = 'correct';
2351 break;
2353 break;
2354 case 'jmix':
2355 $question->type = HOTPOT_JMIX;
2356 $question->name = $num;
2357 switch ($name) {
2358 // keep these in for "restore" of courses
2359 // which were backed up with HotPot v2.0.x
2360 case 'wrongguesses':
2361 $name = 'checks';
2362 if (is_numeric($data)) {
2363 $data++;
2365 break;
2366 case 'right':
2367 $name = 'correct';
2368 break;
2370 break;
2371 break;
2372 case 'jquiz':
2373 switch ($name) {
2374 case 'type':
2375 $data = HOTPOT_JQUIZ;
2376 switch ($data) {
2377 case 'multiple-choice':
2378 $data .= '.'.HOTPOT_JQUIZ_MULTICHOICE;
2379 break;
2380 case 'short-answer':
2381 $data .= '.'.HOTPOT_JQUIZ_SHORTANSWER;
2382 break;
2383 case 'hybrid':
2384 $data .= '.'.HOTPOT_JQUIZ_HYBRID;
2385 break;
2386 case 'multi-select':
2387 $data .= '.'.HOTPOT_JQUIZ_MULTISELECT;
2388 case 'n/a':
2389 default:
2390 // do nothing more
2391 break;
2393 break;
2394 case 'question':
2395 $name = 'name';
2396 break;
2398 break;
2400 case 'rhubarb':
2401 $question->type = HOTPOT_TEXTOYS_RHUBARB;
2402 if (empty($question->name)) {
2403 $question->name = $num;
2405 break;
2407 case 'sequitur':
2408 $question->type = HOTPOT_TEXTOYS_SEQUITUR;
2409 break;
2412 function hotpot_string_ids($field_value) {
2413 $ids = array();
2414 $strings = explode(',', $field_value);
2415 foreach($strings as $str) {
2416 if ($id = hotpot_string_id($str)) {
2417 $ids[] = $id;
2420 return implode(',', $ids);
2422 function hotpot_string_id($str) {
2423 $id = '';
2424 if (isset($str) && $str<>'') {
2426 // get the id from the table if it is already there
2427 $md5key = md5($str);
2428 if (!$id = get_field('hotpot_strings', 'id', 'md5key', $md5key, 'string', $str)) {
2430 // create a string record
2431 $record = new stdClass();
2432 $record->string = $str;
2433 $record->md5key = $md5key;
2435 // try and add the new string record
2436 if (!$id = insert_record('hotpot_strings', $record)) {
2437 global $db;
2438 error("Could not add string record for '".htmlspecialchars($str)."': ".$db->ErrorMsg());
2442 return $id;
2445 function hotpot_get_view_actions() {
2446 return array('view','view all','report');
2449 function hotpot_get_post_actions() {
2450 return array('attempt','review','submit');
2453 if (!function_exists('file_get_contents')) {
2454 // add this function for php version<4.3
2455 function file_get_contents($filepath) {
2456 $contents = file($filepath);
2457 if (is_array($contents)) {
2458 $contents = implode('', $contents);
2460 return $contents;
2463 if (!function_exists('html_entity_decode')) {
2464 // add this function for php version<4.3
2465 function html_entity_decode($str) {
2466 $t = get_html_translation_table(HTML_ENTITIES);
2467 $t = array_flip($t);
2468 return strtr($str, $t);
2473 // required for Moodle 1.x
2474 if (!isset($CFG->pixpath)) {
2475 $CFG->pixpath = "$CFG->wwwroot/pix";
2478 if (!function_exists('fullname')) {
2479 // add this function for Moodle 1.x
2480 function fullname($user) {
2481 return "$user->firstname $user->lastname";
2484 if (!function_exists('get_user_preferences')) {
2485 // add this function for Moodle 1.x
2486 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
2487 return $default;
2490 if (!function_exists('set_user_preference')) {
2491 // add this function for Moodle 1.x
2492 function set_user_preference($name, $value, $otheruser=NULL) {
2493 return false;
2496 if (!function_exists('get_coursemodule_from_id')) {
2497 // add this function for Moodle < 1.5.4
2498 function get_coursemodule_from_id($modulename, $cmid, $courseid=0) {
2499 global $CFG;
2500 return get_record_sql("
2501 SELECT
2502 cm.*, m.name, md.name as modname
2503 FROM
2504 {$CFG->prefix}course_modules cm,
2505 {$CFG->prefix}modules md,
2506 {$CFG->prefix}$modulename m
2507 WHERE
2508 ".($courseid ? "cm.course = '$courseid' AND " : '')."
2509 cm.id = '$cmid' AND
2510 cm.instance = m.id AND
2511 md.name = '$modulename' AND
2512 md.id = cm.module
2516 if (!function_exists('get_coursemodule_from_instance')) {
2517 // add this function for Moodle < 1.5.4
2518 function get_coursemodule_from_instance($modulename, $instance, $courseid=0) {
2519 global $CFG;
2520 return get_record_sql("
2521 SELECT
2522 cm.*, m.name, md.name as modname
2523 FROM
2524 {$CFG->prefix}course_modules cm,
2525 {$CFG->prefix}modules md,
2526 {$CFG->prefix}$modulename m
2527 WHERE
2528 ".($courseid ? "cm.course = '$courseid' AND" : '')."
2529 cm.instance = m.id AND
2530 md.name = '$modulename' AND
2531 md.id = cm.module AND
2532 m.id = '$instance'
2536 function hotpot_utf8_to_html_entity($char) {
2537 // http://www.zend.com/codex.php?id=835&single=1
2539 // array used to figure what number to decrement from character order value
2540 // according to number of characters used to map unicode to ascii by utf-8
2541 static $HOTPOT_UTF8_DECREMENT = array(
2542 1=>0, 2=>192, 3=>224, 4=>240
2545 // the number of bits to shift each character by
2546 static $HOTPOT_UTF8_SHIFT = array(
2547 1=>array(0=>0),
2548 2=>array(0=>6, 1=>0),
2549 3=>array(0=>12, 1=>6, 2=>0),
2550 4=>array(0=>18, 1=>12, 2=>6, 3=>0)
2553 $dec = 0;
2554 $len = strlen($char);
2555 for ($pos=0; $pos<$len; $pos++) {
2556 $ord = ord ($char{$pos});
2557 $ord -= ($pos ? 128 : $HOTPOT_UTF8_DECREMENT[$len]);
2558 $dec += ($ord << $HOTPOT_UTF8_SHIFT[$len][$pos]);
2560 return '&#x'.sprintf('%04X', $dec).';';
2563 function hotpot_print_show_links($course, $location, $reference, $actions='', $spacer=' &nbsp; ', $new_window=false, $return=false) {
2564 global $CFG;
2565 if (is_string($actions)) {
2566 if (empty($actions)) {
2567 $actions = 'showxmlsource,showxmltree,showhtmlsource';
2569 $actions = explode(',', $actions);
2571 $strenterafilename = get_string('enterafilename', 'hotpot');
2572 $html = <<<END_OF_SCRIPT
2573 <script type="text/javascript">
2574 //<![CDATA[
2575 function setLink(lnk) {
2576 var form = null;
2577 if (document.forms['mform1']) {
2578 var form = document.forms['mform1'];
2579 } else if (document.forms['form']) {
2580 var form = document.forms['form'];
2582 return setLinkAttribute(lnk, 'reference', form) && setLinkAttribute(lnk, 'location', form);
2584 function setLinkAttribute(lnk, name, form) {
2585 // set link attribute value using
2586 // f(orm) name and e(lement) name
2588 var r = true; // result
2590 var obj = (form) ? form.elements[name] : null;
2591 if (obj) {
2592 r = false;
2593 var v = getObjValue(obj);
2594 if (v=='') {
2595 alert('$strenterafilename');
2596 } else {
2597 var s = lnk.href;
2598 var i = s.indexOf('?');
2599 if (i>=0) {
2600 i = s.indexOf(name+'=', i+1);
2601 if (i>=0) {
2602 i += name.length+1;
2603 var ii = s.indexOf('&', i);
2604 if (ii<0) {
2605 ii = s.length;
2607 lnk.href = s.substring(0, i) + v + s.substring(ii);
2608 r = true;
2613 return r;
2615 function getObjValue(obj) {
2616 var v = ''; // the value
2617 var t = (obj && obj.type) ? obj.type : "";
2618 if (t=="text" || t=="textarea" || t=="hidden") {
2619 v = obj.value;
2620 } else if (t=="select-one" || t=="select-multiple") {
2621 var l = obj.options.length;
2622 for (var i=0; i<l; i++) {
2623 if (obj.options[i].selected) {
2624 v += (v=="" ? "" : ",") + obj.options[i].value;
2628 return v;
2630 function getDir(s) {
2631 if (s.charAt(0)!='/') {
2632 s = '/' + s;
2634 var i = s.lastIndexOf('/');
2635 return s.substring(0, i);
2637 //]]>
2638 </script>
2639 END_OF_SCRIPT;
2641 foreach ($actions as $action) {
2642 $html .= $spacer
2643 . '<a href="'
2644 . $CFG->wwwroot.'/mod/hotpot/show.php'
2645 . '?course='.$course.'&amp;location='.$location.'&amp;reference='.urlencode($reference).'&amp;action='.$action
2646 . '"'
2647 . ' onclick="return setLink(this);"'
2648 . ($new_window ? ' target="_blank"' : '')
2649 . '>'.get_string($action, 'hotpot').'</a>'
2652 $html = '<span class="helplink">'.$html.'</span>';
2653 if ($return) {
2654 return $html;
2655 } else {
2656 print $html;
2661 * Returns all other caps used in module
2663 function hotpot_get_extra_capabilities() {
2664 return array('moodle/site:accessallgroups');
2668 * This function is used by the reset_course_userdata function in moodlelib.
2669 * This function will remove all attempts from hotpot quizzes in the specified course.
2670 * @param $data the data submitted from the reset course.
2671 * @return array status array
2673 function hotpot_reset_userdata($data) {
2674 global $CFG;
2675 require_once($CFG->libdir.'/filelib.php');
2677 $status = array();
2679 if (!empty($data->reset_hotpot_deleteallattempts)) {
2681 $hotpotids = "SELECT h.id FROM {$CFG->prefix}hotpot h WHERE h.course={$data->courseid}";
2682 $attemptids = "SELECT a.id FROM {$CFG->prefix}hotpot_attempts a WHERE a.hotpot in ($hotpotids)";
2684 delete_records_select('hotpot_responses', "attempt in ($attemptids)");
2685 delete_records_select('hotpot_details', "attempt in ($attemptids)");
2686 delete_records_select('hotpot_attempts', "hotpot IN ($hotpotids)");
2688 $status[] = array('component' => get_string('modulenameplural', 'hotpot'),
2689 'item' => get_string('deleteallattempts', 'hotpot'),
2690 'error' => false);
2693 return $status;
2697 * Called by course/reset.php
2698 * @param $mform form passed by reference
2700 function hotpot_reset_course_form_definition(&$mform) {
2701 $mform->addElement('header', 'hotpotheader', get_string('modulenameplural', 'hotpot'));
2702 $mform->addElement('checkbox', 'reset_hotpot_deleteallattempts', get_string('deleteallattempts', 'hotpot'));
2706 * Course reset form defaults.
2708 function hotpot_reset_course_form_defaults($course) {
2709 return array('reset_hotpot_deleteallattempts' => 1);
2713 * Tells if files in moddata are trusted and can be served without XSS protection.
2714 * @return bool true if file can be submitted by teacher only (trusted), false otherwise
2716 function hotpot_is_moddata_trusted() {
2717 return true;