MDL-14679 fixed references to mod.html
[moodle.git] / mod / hotpot / lib.php
1 <?PHP  // $Id$
3 //////////////////////////////////
4 /// CONFIGURATION settings
6 if (!isset($CFG->hotpot_showtimes)) {
7     set_config("hotpot_showtimes", 0);
8 }
9 if (!isset($CFG->hotpot_excelencodings)) {
10     set_config("hotpot_excelencodings", "");
11 }
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;
22 }
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"),
40 );
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"),
62 );
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',
72 );
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]);
80     }
81 }
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"),
96 );
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'
116 );
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"),
133 );
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"),
145 );
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"),
159 );
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");
168 /**
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
171  */
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_form.php
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));
203         }
204     } else {
205         $result=  false;
206     }
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         }
217     } else {
218         $result=  false;
219     }
220     return $result;
222 function hotpot_update_events($hotpot) {
224     // remove any previous calendar events for this hotpot
225     delete_records('event', 'modulename', 'hotpot', 'instance', $hotpot->id);
227     $event = new stdClass();
228     $event->description = $hotpot->summary;
229     $event->courseid    = $hotpot->course;
230     $event->groupid     = 0;
231     $event->userid      = 0;
232     $event->modulename  = 'hotpot';
233     $event->instance    = $hotpot->id;
234     $event->timestart   = $hotpot->timeopen;
235     if ($cm = get_coursemodule_from_id('hotpot', $hotpot->id)) {
236         $event->visible = hotpot_is_visible($cm);
237     } else {
238         $event->visible = 1;
239     }
241     if ($hotpot->timeclose && $hotpot->timeopen) {
242         // we have both a start and an end date
243         $event->eventtype   = 'open';
244         $event->timeduration = ($hotpot->timeclose - $hotpot->timeopen);
246         if ($event->timeduration > HOTPOT_MAX_EVENT_LENGTH) {  /// Long durations create two events
247     
248             $event->name          = $hotpot->name.' ('.get_string('hotpotopens', 'hotpot').')';
249             $event->timeduration  = 0;
250             add_event($event);
251     
252             $event->timestart    = $hotpot->timeclose;
253             $event->eventtype    = 'close';
254             $event->name         = $hotpot->name.' ('.get_string('hotpotcloses', 'hotpot').')';
255             unset($event->id);
256             add_event($event);
257         } else { // single event with duration
258             $event->name        = $hotpot->name;
259             add_event($event);
260         }
261     } elseif ($hotpot->timeopen) { // only an open date
262         $event->name          = addslashes($hotpot->name).' ('.get_string('hotpotopens', 'hotpot').')';
263         $event->eventtype   = 'open';
264         $event->timeduration = 0;
265         add_event($event);
266     } elseif ($hotpot->timeclose) { // only a closing date
267         $event->name         = addslashes($hotpot->name).' ('.get_string('hotpotcloses', 'hotpot').')';
268         $event->timestart    = $hotpot->timeclose;
269         $event->eventtype    = 'close';
270         $event->timeduration = 0;
271         add_event($event);
272     }
275 function hotpot_set_form_values(&$hotpot) {
276     $ok = true;
277     $hotpot->errors = array(); // these will be reported by moderr.html
279     if (empty($hotpot->reference)) {
280         $ok = false;
281         $hotpot->errors['reference']= get_string('error_nofilename', 'hotpot');
282     }
284     if (empty($hotpot->studentfeedbackurl) || $hotpot->studentfeedbackurl=='http://') {
285         $hotpot->studentfeedbackurl = '';
286         switch ($hotpot->studentfeedback) {
287             case HOTPOT_FEEDBACK_WEBPAGE:
288                 $ok = false;
289                 $hotpot->errors['studentfeedbackurl']= get_string('error_nofeedbackurlwebpage', 'hotpot');
290             break;
291             case HOTPOT_FEEDBACK_FORMMAIL:
292                 $ok = false;
293                 $hotpot->errors['studentfeedbackurl']= get_string('error_nofeedbackurlformmail', 'hotpot');
294             break;
295         }
296     }
298     $time = time();
299     $hotpot->timecreated = $time;
300     $hotpot->timemodified = $time;
302     if (empty($hotpot->mode)) {
303         // moodle 1.9 (from mod_form.lib)
304         if ($hotpot->add) {
305             $hotpot->mode = 'add';
306         } else if ($hotpot->update) {
307             $hotpot->mode = 'update';
308         } else {
309             $hotpot->mode = '';
310         }
311     }
312     if ($hotpot->quizchain==HOTPOT_YES) {
313         switch ($hotpot->mode) {
314             case 'add':
315                 $ok = hotpot_add_chain($hotpot);
316             break;
317             case 'update':
318                 $ok = hotpot_update_chain($hotpot);
319             break;
320         }
321     } else { // $hotpot->quizchain==HOTPOT_NO
322         hotpot_set_name_summary_reference($hotpot);
323     }
325     if (isset($hotpot->displaynext)) {
326         switch ($hotpot->displaynext) {
327             // N.B. redirection only works for Moodle 1.5+
328             case HOTPOT_DISPLAYNEXT_COURSE:
329                 $hotpot->redirect = true;
330                 $hotpot->redirecturl = "view.php?id=$hotpot->course";
331                 break;
332             case HOTPOT_DISPLAYNEXT_INDEX:
333                 $hotpot->redirect = true;
334                 $hotpot->redirecturl = "../mod/hotpot/index.php?id=$hotpot->course";
335                 break;
336             default:
337                 // use Moodle default action (i.e. go on to display the hotpot quiz)
338         }
339     } else {
340         $hotpot->displaynext = HOTPOT_DISPLAYNEXT_QUIZ;
341     }
343     // if ($ok && $hotpot->setdefaults) {
344     if ($ok) {
345         set_user_preference('hotpot_timeopen', $hotpot->timeopen);
346         set_user_preference('hotpot_timeclose', $hotpot->timeclose);
347         set_user_preference('hotpot_navigation', $hotpot->navigation);
348         set_user_preference('hotpot_outputformat', $hotpot->outputformat);
349         set_user_preference('hotpot_studentfeedback', $hotpot->studentfeedback);
350         set_user_preference('hotpot_studentfeedbackurl', $hotpot->studentfeedbackurl);
351         set_user_preference('hotpot_forceplugins', $hotpot->forceplugins);
352         set_user_preference('hotpot_shownextquiz', $hotpot->shownextquiz);
353         set_user_preference('hotpot_review', $hotpot->review);
354         set_user_preference('hotpot_grade', $hotpot->grade);
355         set_user_preference('hotpot_grademethod', $hotpot->grademethod);
356         set_user_preference('hotpot_attempts', $hotpot->attempts);
357         set_user_preference('hotpot_subnet', $hotpot->subnet);
358         set_user_preference('hotpot_displaynext', $hotpot->displaynext);
359         if ($hotpot->mode=='add') {
360             set_user_preference('hotpot_quizchain', $hotpot->quizchain);
361             set_user_preference('hotpot_namesource', $hotpot->namesource);
362             set_user_preference('hotpot_summarysource', $hotpot->summarysource);
363         }
364     }
365     return $ok;
367 function hotpot_get_chain(&$cm) {
368     global $DB;
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();
377         }
378     }
380     // get ids of hotpot modules in this section
381     $ids = array();
382     foreach ($hotpot_modules as $hotpot_module) {
383         $ids[] = $hotpot_module->instance;
384     }
386     // get details of hotpots in this section
387     if (empty($ids)) {
388         $hotpots = array();
389     } else {
390         $hotpots = $DB->get_records_list('hotpot', 'id', $ids);
391     }
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;
412                 }
413             } else {
414                 if ($id==$cm->id) {
415                     $found = true;
416                 }
417             }
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();
426                 }
427             }
428         }
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;
445     }
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);
452     }
453     if (has_capability('moodle/course:viewhiddenactivities', $coursecontext)) {
454         return true; // user can view hidden activities
455     }
457     if (!isset($cm->sectionvisible)) {
458         if (! $section = get_record('course_sections', 'id', $cm->section)) {
459             print_error('invalidsection');
460         }
461         $cm->sectionvisible = $section->visible;
462     }
464     if (empty($cm->sectionvisible)) {
465         $visible = HOTPOT_NO;
466     } else {
467         $visible = HOTPOT_YES;
468         if (empty($cm->visible)) {
469             if ($chain = hotpot_get_chain($cm)) {
470                 $startofchain = array_shift($chain);
471                 $visible = $startofchain->visible;
472             }
473         }
474     }
475     return $visible;
477 function hotpot_add_chain(&$hotpot) {
478 /// add a chain of hotpot actiivities
480     global $CFG, $course;
482     $ok = true;
483     $hotpot->names = array();
484     $hotpot->summaries = array();
485     $hotpot->references = array();
487     $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
489     if (isset($xml_quiz->error)) {
490         $hotpot->errors['reference'] = $xml_quiz->error;
491         $ok = false;
493     } else if (is_dir($xml_quiz->filepath)) {
495         // get list of hotpot files in this folder
496         if ($dh = @opendir($xml_quiz->filepath)) {
497             while (false !== ($file = @readdir($dh))) {
498                 if (preg_match('/\.(jbc|jcl|jcw|jmt|jmx|jqz|htm|html)$/', $file)) {
499                     $hotpot->references[] = "$xml_quiz->reference/$file";
500                 }
501             }
502             closedir($dh);
504             // get titles
505             foreach ($hotpot->references as $i=>$reference) {
506                 $filepath = $xml_quiz->fileroot.'/'.$reference;
507                 hotpot_get_titles_and_next_ex($hotpot, $filepath);
508                 $hotpot->names[$i] = $hotpot->exercisetitle;
509                 $hotpot->summaries[$i] = $hotpot->exercisesubtitle;
510             }
512         } else {
513             $ok = false;
514             $hotpot->errors['reference'] = get_string('error_couldnotopenfolder', 'hotpot', $hotpot->reference);
515         }
517     } else if (is_file($xml_quiz->filepath)) {
519         $filerootlength = strlen($xml_quiz->fileroot) + 1;
521         while ($xml_quiz->filepath) {
522             hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath, true);
523             $hotpot->names[] = $hotpot->exercisetitle;
524             $hotpot->summaries[] = $hotpot->exercisesubtitle;
525             $hotpot->references[] = substr($xml_quiz->filepath, $filerootlength);
527             if ($hotpot->nextexercise) {
528                 $filepath = $xml_quiz->fileroot.'/'.$xml_quiz->filesubdir.$hotpot->nextexercise;
530                 // check file is not already in chain
531                 $reference = substr($filepath, $filerootlength);
532                 if (in_array($reference, $hotpot->references)) {
533                     $filepath = '';
534                 }
535             } else {
536                 $filepath = '';
537             }
538             if ($filepath && file_exists($filepath) && is_file($filepath) && is_readable($filepath)) {
539                 $xml_quiz->filepath = $filepath;
540             } else {
541                 $xml_quiz->filepath = false; // finish while loop
542             }
543         } // end while
545     } else {
546         $ok = false;
547         $hotpot->errors['reference'] = get_string('error_notfileorfolder', 'hotpot', $hotpot->reference);
548     }
550     if (empty($hotpot->references) && empty($hotpot->errors['reference'])) {
551         $ok = false;
552         $hotpot->errors['reference'] = get_string('error_noquizzesfound', 'hotpot', $hotpot->reference);
553     }
555     if ($ok) {
556         $hotpot->visible = HOTPOT_YES;
558         if (trim($hotpot->name)=='') {
559             $hotpot->name = get_string("modulename", $hotpot->modulename);
560         }
561         $hotpot->specificname = $hotpot->name;
562         $hotpot->specificsummary = $hotpot->summary;
564         // add all except last activity in chain
566         $i_max = count($hotpot->references)-1;
567         for ($i=0; $i<$i_max; $i++) {
569             hotpot_set_name_summary_reference($hotpot, $i);
570             $hotpot->reference = addslashes($hotpot->reference);
572             if (!$hotpot->instance = insert_record("hotpot", $hotpot)) {
573                 print_error('cannotaddnewinstance', '', 'view.php?id='.$hotpot->course, $hotpot->modulename);
574             }
576             // store (hotpot table) id of start of chain
577             if ($i==0) {
578                 $hotpot->startofchain = $hotpot->instance;
579             }
581             if (isset($course->groupmode)) {
582                 $hotpot->groupmode = $course->groupmode;
583             }
585             if (! $hotpot->coursemodule = add_course_module($hotpot)) {
586                 print_error('cannotaddcoursemodule');
587             }
588             if (! $sectionid = add_mod_to_section($hotpot) ) {
589                 print_error('cannotaddcoursemoduletosection');
590             }
592             if (! set_field('course_modules', 'section', $sectionid, "id", $hotpot->coursemodule)) {
593                 print_error('cannotupdatecoursemodule');
594             }
596             add_to_log($hotpot->course, "course", "add mod",
597                 "../mod/$hotpot->modulename/view.php?id=$hotpot->coursemodule",
598                 "$hotpot->modulename $hotpot->instance"
599             );
600             add_to_log($hotpot->course, $hotpot->modulename, "add",
601                 "view.php?id=$hotpot->coursemodule",
602                 "$hotpot->instance", $hotpot->coursemodule
603             );
605             // hide tail of chain
606             if ($hotpot->shownextquiz==HOTPOT_YES) {
607                 $hotpot->visible = HOTPOT_NO;
608             }
609         } // end for ($hotpot->references)
611         // settings for final activity in chain
612         hotpot_set_name_summary_reference($hotpot, $i);
613         $hotpot->reference = addslashes($hotpot->references[$i]);
614         $hotpot->shownextquiz = HOTPOT_NO;
616         if (isset($hotpot->startofchain)) {
617             // redirection only works for Moodle 1.5+
618             $hotpot->redirect = true;
619             $hotpot->redirecturl = "$CFG->wwwroot/mod/hotpot/view.php?hp=$hotpot->startofchain";
620         }
621     } // end if $ok
623     return $ok;
625 function hotpot_set_name_summary_reference(&$hotpot, $chain_index=NULL) {
627     $xml_quiz = NULL;
629     $textfields = array('name', 'summary');
630     foreach ($textfields as $textfield) {
632         $textsource = $textfield.'source';
634         // are we adding a chain?
635         if (isset($chain_index)) {
637             switch ($hotpot->$textsource) {
638                 case HOTPOT_TEXTSOURCE_QUIZ:
639                     if ($textfield=='name') {
640                         $hotpot->exercisetitle = $hotpot->names[$chain_index];
641                     } else if ($textfield=='summary') {
642                         $hotpot->exercisesubtitle = $hotpot->summaries[$chain_index];
643                     }
644                     break;
645                 case HOTPOT_TEXTSOURCE_SPECIFIC:
646                     $specifictext = 'specific'.$textfield;
647                     if (empty($hotpot->$specifictext) && trim($hotpot->$specifictext)=='') {
648                         $hotpot->$textfield = '';
649                     } else {
650                         $hotpot->$textfield = $hotpot->$specifictext.' ('.($chain_index+1).')';
651                     }
652                     break;
653             }
654             $hotpot->reference = $hotpot->references[$chain_index];
655         }
657         if ($hotpot->$textsource==HOTPOT_TEXTSOURCE_QUIZ) {
658             if (empty($xml_quiz) && !isset($chain_index)) {
659                 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
660                 hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath);
661             }
662             if ($textfield=='name') {
663                 $hotpot->$textfield = addslashes($hotpot->exercisetitle);
664             } else if ($textfield=='summary') {
665                 $hotpot->$textfield = addslashes($hotpot->exercisesubtitle);
666             }
667         }
668         switch ($hotpot->$textsource) {
669             case HOTPOT_TEXTSOURCE_FILENAME:
670                 $hotpot->$textfield = basename($hotpot->reference);
671                 break;
672             case HOTPOT_TEXTSOURCE_FILEPATH:
673                 $hotpot->$textfield = '';
674                 // continue to next lines
675             default:
676                 if (empty($hotpot->$textfield)) {
677                     $hotpot->$textfield = str_replace('/', ' ', $hotpot->reference);
678                 }
679         } // end switch
680     } // end foreach
682 function hotpot_get_titles_and_next_ex(&$hotpot, $filepath, $get_next=false) {
684     $hotpot->exercisetitle = '';
685     $hotpot->exercisesubtitle = '';
686     $hotpot->nextexercise = '';
688     // read the quiz file source
689     if ($source = file_get_contents($filepath)) {
691         $next = '';
692         $title = '';
693         $subtitle = '';
695         if (preg_match('|\.html?$|', $filepath)) {
696             // html file
697             if (preg_match('|<h2[^>]*class="ExerciseTitle"[^>]*>(.*?)</h2>|is', $source, $matches)) {
698                 $title = trim(strip_tags($matches[1]));
699             }
700             if (empty($title)) {
701                 if (preg_match('|<title[^>]*>(.*?)</title>|is', $source, $matches)) {
702                     $title = trim(strip_tags($matches[1]));
703                 }
704             }
705             if (preg_match('|<h3[^>]*class="ExerciseSubtitle"[^>]*>(.*?)</h3>|is', $source, $matches)) {
706                 $subtitle = trim(strip_tags($matches[1]));
707             }
708             if ($get_next) {
709                 if (preg_match('|<div[^>]*class="NavButtonBar"[^>]*>(.*?)</div>|is', $source, $matches)) {
710                     $navbuttonbar = $matches[1];
711                     if (preg_match_all('|<button[^>]*class="NavButton"[^>]*onclick="'."location='([^']*)'".'[^"]*"[^>]*>|is', $navbuttonbar, $matches)) {
712                         $lastbutton = count($matches[0])-1;
713                         $next = $matches[1][$lastbutton];
714                     }
715                 }
716             }
718         } else {
719             // xml file (...maybe)
720             $xml_tree = new hotpot_xml_tree($source);
721             $xml_tree->filetype = '';
723             $keys = array_keys($xml_tree->xml);
724             foreach ($keys as $key) {
725                 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
726                     $xml_tree->filetype = 'xml';
727                     $xml_tree->xml_root = "['$key']['#']";
728                     $xml_tree->quiztype = strtolower($matches[2]);
729                     break;
730                 }
731             }
732             if ($xml_tree->filetype=='xml') {
734                 $title = strip_tags($xml_tree->xml_value('data,title'));
735                 $subtitle = $xml_tree->xml_value('hotpot-config-file,'.$xml_tree->quiztype.',exercise-subtitle');
737                 if ($get_next) {
738                     $include = $xml_tree->xml_value('hotpot-config-file,global,include-next-ex');
739                     if (!empty($include)) {
740                         $next = $xml_tree->xml_value("hotpot-config-file,$xml_tree->quiztype,next-ex-url");
741                         if (is_array($next)) {
742                             $next = $next[0]; // in case "next-ex-url" was repeated in the xml file
743                         }
744                     }
745                 }
746             }
747         }
749         $hotpot->nextexercise = $next;
750         $hotpot->exercisetitle = (empty($title) || is_array($title)) ? basename($filepath) : $title;
751         $hotpot->exercisesubtitle = (empty($subtitle) || is_array($subtitle)) ? $hotpot->exercisetitle : $subtitle;
752     }
754 function hotpot_get_all_instances_in_course($modulename, $course) {
755 /// called from index.php
757     global $CFG;
758     $instances = array();
760     if (isset($CFG->release) && substr($CFG->release, 0, 3)>=1.2) {
761         $groupmode = 'cm.groupmode,';
762     } else {
763         $groupmode = '';
764     }
766     $query = "
767         SELECT
768             cm.id AS coursemodule,
769             cm.course AS course,
770             cm.module AS module,
771             cm.instance AS instance,
772             -- cm.section AS section,
773             cm.visible AS visible,
774             $groupmode
775             -- cs.section AS sectionnumber,
776             cs.section AS section,
777             cs.sequence AS sequence,
778             cs.visible AS sectionvisible,
779             thismodule.*
780         FROM
781             {$CFG->prefix}course_modules cm,
782             {$CFG->prefix}course_sections cs,
783             {$CFG->prefix}modules m,
784             {$CFG->prefix}$modulename thismodule
785         WHERE
786             m.name = '$modulename' AND
787             m.id = cm.module AND
788             cm.course = '$course->id' AND
789             cm.section = cs.id AND
790             cm.instance = thismodule.id
791     ";
792     if ($rawmods = get_records_sql($query)) {
794         // cache $isteacher setting
795         
796         $isteacher = has_capability('mod/hotpot:viewreport', get_context_instance(CONTEXT_COURSE, $course->id));
798         $explodesection = array();
799         $order = array();
801         foreach ($rawmods as $rawmod) {
803             if (empty($explodesection[$rawmod->section])) {
804                 $explodesection[$rawmod->section] = true;
806                 $coursemodules = explode(',', $rawmod->sequence);
807                 foreach ($coursemodules as $i=>$coursemodule) {
808                     $order[$coursemodule] = sprintf('%d.%04d', $rawmod->section, $i);
809                 }
810             }
812             if ($isteacher) {
813                 $visible = true;
814             } else if ($modulename=='hotpot') {
815                 $visible = hotpot_is_visible($rawmod);
816             } else {
817                 $visible = $rawmod->visible;
818             }
820             if ($visible) {
821                 $instances[$order[$rawmod->coursemodule]] = $rawmod;
822             }
824         } // end foreach $modinfo
826         ksort($instances);
827         $instances = array_values($instances);
828     }
830     return $instances;
833 function hotpot_update_chain(&$hotpot) {
834 /// update a chain of hotpot actiivities
836     $ok = true;
837     if ($hotpot_modules = hotpot_get_chain($hotpot)) {
839         // skip updating of these fields
840         $skipfields = array('id', 'course', 'name', 'reference', 'summary', 'shownextquiz');
841         $fields = array();
843         foreach ($hotpot_modules as $hotpot_module) {
845             if ($hotpot->instance==$hotpot_module->id) {
846                 // don't need to update this hotpot
848             } else {
849                 // shortcut to hotpot record
850                 $thishotpot = &$hotpot_module->hotpot;
852                 // get a list of fields to update (first time only)
853                 if (empty($fields)) {
854                     $fields = array_keys(get_object_vars($thishotpot));
855                 }
857                 // assume update is NOT required
858                 $require_update = false;
860                 // update field values (except $skipfields)
861                 foreach($fields as $field) {
862                     if (in_array($field, $skipfields) || $thishotpot->$field==$hotpot->$field) {
863                         // update not required for this field
864                     } else {
865                         $require_update = true;
866                         $thishotpot->$field = $hotpot->$field;
867                     }
868                 }
870                 // update $thishotpot, if required
871                 if ($require_update && !update_record("hotpot", $thishotpot)) {
872                     print_error('cannotupdatemod', '',
873                             'view.php?id='.$hotpot->course, $hotpot->modulename);
874                 }
875             }
876         } // end foreach $ids
877     }
878     return $ok;
880 function hotpot_delete_instance($id) {
881 /// Given an ID of an instance of this module,
882 /// this function will permanently delete the instance
883 /// and any data that depends on it.
885     if (! $hotpot = get_record("hotpot", "id", $id)) {
886         return false;
887     }
889     if (! delete_records("hotpot", "id", "$id")) {
890         return false;
891     }
893     delete_records("hotpot_questions", "hotpot", "$id");
894     if ($attempts = get_records_select("hotpot_attempts", "hotpot='$id'")) {
895         $ids = implode(',', array_keys($attempts));
896         delete_records_select("hotpot_attempts",  "id IN ($ids)");
897         delete_records_select("hotpot_details",   "attempt IN ($ids)");
898         delete_records_select("hotpot_responses", "attempt IN ($ids)");
899     }
901      // remove calendar events for this hotpot
902     delete_records('event', 'modulename', 'hotpot', 'instance', $id);
904      // remove grade item for this hotpot
905     hotpot_grade_item_delete($hotpot);
907     return true;
909 function hotpot_delete_and_notify($table, $select, $strtable) {
910     $count = max(0, count_records_select($table, $select));
911     if ($count) {
912         delete_records_select($table, $select);
913         $count -= max(0, count_records_select($table, $select));
914         if ($count) {
915             notify(get_string('deleted')." $count x $strtable");
916         }
917     }
920 function hotpot_user_complete($course, $user, $mod, $hotpot) {
921 /// Print a detailed representation of what a  user has done with
922 /// a given particular instance of this module, for user activity reports.
924     $report = hotpot_user_outline($course, $user, $mod, $hotpot);
925     if (empty($report)) {
926         print get_string("noactivity", "hotpot");
927     } else {
928         $date = userdate($report->time, get_string('strftimerecentfull'));
929         print $report->info.' '.get_string('mostrecently').': '.$date;
930     }
931     return true;
934 function hotpot_user_outline($course, $user, $mod, $hotpot) {
935 /// Return a small object with summary information about what a
936 /// user has done with a given particular instance of this module
937 /// Used for user activity reports.
938 /// $report->time = the time they did it
939 /// $report->info = a short text description
941     $report = NULL;
942     if ($records = get_records_select("hotpot_attempts", "hotpot='$hotpot->id' AND userid='$user->id'", "timestart ASC", "*")) {
943         $report = new stdClass();
944         $scores = array();
945         foreach ($records as $record){
946             if (empty($report->time)) {
947                 $report->time = $record->timestart;
948             }
949             $scores[] = hotpot_format_score($record);
950         }
951         if (empty($scores)) {
952             $report->time = 0;
953             $report->info = get_string('noactivity', 'hotpot');
954         } else {
955             $report->info = get_string('score', 'quiz').': '.implode(', ', $scores);
956         }
957     }
958     return $report;
961 function hotpot_format_score($record, $undefined='&nbsp;') {
962     if (isset($record->score)) {
963         $score = $record->score;
964     } else {
965         $score = $undefined;
966     }
967     return $score;
970 function hotpot_format_status($record, $undefined='&nbsp;') {
971     global $HOTPOT_STATUS;
973     if (isset($record->status) || isset($HOTPOT_STATUS[$record->status])) {
974         $status = $HOTPOT_STATUS[$record->status];
975     } else {
976         $status = $undefined;
977     }
978     return $status;
981 function hotpot_print_recent_activity($course, $isteacher, $timestart) {
982 /// Given a course and a time, this module should find recent activity
983 /// that has occurred in hotpot activities and print it out.
984 /// Return true if there was output, or false is there was none.
986     global $CFG;
987     $result = false;
989     $records = get_records_sql("
990         SELECT
991             h.id AS id,
992             h.name AS name,
993             COUNT(*) AS count_attempts
994         FROM
995             {$CFG->prefix}hotpot h,
996             {$CFG->prefix}hotpot_attempts a
997         WHERE
998             h.course = $course->id
999             AND h.id = a.hotpot
1000             AND a.id = a.clickreportid
1001             AND a.starttime > $timestart
1002         GROUP BY
1003             h.id, h.name
1004     ");
1005     // note that PostGreSQL requires h.name in the GROUP BY clause
1007     if($records) {
1008         $names = array();
1009         foreach ($records as $id => $record){
1010             if ($cm = get_coursemodule_from_instance('hotpot', $record->id, $course->id)) {
1011                 $context = get_context_instance(CONTEXT_MODULE, $cm->id);
1012                 
1013                 if (has_capability('mod/hotpot:viewreport', $context)) {
1014                     $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$id";
1015                     $name = '&nbsp;<a href="'.$href.'">'.$record->name.'</a>';
1016                     if ($record->count_attempts > 1) {
1017                         $name .= " ($record->count_attempts)";
1018                     }
1019                     $names[] = $name;
1020                 }
1021             }
1022         }
1023         if (count($names) > 0) {
1024             print_headline(get_string('modulenameplural', 'hotpot').':');
1026             if ($CFG->version >= 2005050500) { // Moodle 1.5+
1027                 echo '<div class="head"><div class="name">'.implode('<br />', $names).'</div></div>';
1028             } else { // Moodle 1.4.x (or less)
1029                 echo '<font size="1">'.implode('<br />', $names).'</font>';
1030             }
1031             $result = true;
1032         }
1033     }
1034     return $result;  //  True if anything was printed, otherwise false
1037 function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") {
1038 // Returns all quizzes since a given time.
1040     global $CFG;
1042     // If $cmid or $userid are specified, then this restricts the results
1043     $cm_select = empty($cmid) ? "" : " AND cm.id = '$cmid'";
1044     $user_select = empty($userid) ? "" : " AND u.id = '$userid'";
1046     $records = get_records_sql("
1047         SELECT
1048             a.*,
1049             h.name, h.course,
1050             cm.instance, cm.section,
1051             u.firstname, u.lastname, u.picture
1052         FROM
1053             {$CFG->prefix}hotpot_attempts a,
1054             {$CFG->prefix}hotpot h,
1055             {$CFG->prefix}course_modules cm,
1056             {$CFG->prefix}user u
1057         WHERE
1058             a.timefinish > '$sincetime'
1059             AND a.id = a.clickreportid
1060             AND a.userid = u.id $user_select
1061             AND a.hotpot = h.id $cm_select
1062             AND cm.instance = h.id
1063             AND cm.course = '$courseid'
1064             AND h.course = cm.course
1065         ORDER BY
1066             a.timefinish ASC
1067     ");
1069     if (!empty($records)) {
1070         foreach ($records as $record) {
1071             if (empty($groupid) || groups_is_member($groupid, $record->userid)) {
1073                 unset($activity);
1075                 $activity->type = "hotpot";
1076                 $activity->defaultindex = $index;
1077                 $activity->instance = $record->hotpot;
1079                 $activity->name = $record->name;
1080                 $activity->section = $record->section;
1082                 $activity->content->attemptid = $record->id;
1083                 $activity->content->attempt = $record->attempt;
1084                 $activity->content->score = $record->score;
1085                 $activity->content->timestart = $record->timestart;
1086                 $activity->content->timefinish = $record->timefinish;
1088                 $activity->user->userid = $record->userid;
1089                 $activity->user->fullname = fullname($record);
1090                 $activity->user->picture = $record->picture;
1092                 $activity->timestamp = $record->timefinish;
1094                 $activities[] = $activity;
1096                 $index++;
1097             }
1098         } // end foreach
1099     }
1102 function hotpot_print_recent_mod_activity($activity, $course, $detail=false) {
1103 /// Basically, this function prints the results of "hotpot_get_recent_activity"
1105     global $CFG, $THEME, $USER;
1107     if (isset($THEME->cellcontent2)) {
1108         $bgcolor =  ' bgcolor="'.$THEME->cellcontent2.'"';
1109     } else {
1110         $bgcolor = '';
1111     }
1113     print '<table border="0" cellpadding="3" cellspacing="0">';
1115     print '<tr><td'.$bgcolor.' class="forumpostpicture" width="35" valign="top">';
1116     print_user_picture($activity->user->userid, $course, $activity->user->picture);
1117     print '</td><td width="100%"><font size="2">';
1119     if ($detail) {
1120         // activity icon
1121         $src = "$CFG->modpixpath/$activity->type/icon.gif";
1122         print '<img src="'.$src.'" class="icon" alt="'.$activity->type.'" /> ';
1124         // link to activity
1125         $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$activity->instance";
1126         print '<a href="'.$href.'">'.$activity->name.'</a> - ';
1127     }
1128     if (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE, $course))) {
1129         // score (with link to attempt details)
1130         $href = "$CFG->wwwroot/mod/hotpot/review.php?hp=$activity->instance&attempt=".$activity->content->attemptid;
1131         print '<a href="'.$href.'">('.hotpot_format_score($activity->content).')</a> ';
1133         // attempt number
1134         print get_string('attempt', 'quiz').' - '.$activity->content->attempt.'<br />';
1135     }
1137     // link to user
1138     $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$course";
1139     print '<a href="'.$href.'">'.$activity->user->fullname.'</a> ';
1141     // time and date
1142     print ' - ' . userdate($activity->timestamp);
1144     // duration
1145     $duration = format_time($activity->content->timestart - $activity->content->timefinish);
1146     print " &nbsp; ($duration)";
1148     print "</font></td></tr>";
1149     print "</table>";
1152 function hotpot_cron () {
1153 /// Function to be run periodically according to the moodle cron
1154 /// This function searches for things that need to be done, such
1155 /// as sending out mail, toggling flags etc ...
1157     global $CFG;
1159     return true;
1162 function hotpot_grades($hotpotid) {
1163 /// Must return an array of grades for a given instance of this module,
1164 /// indexed by user.  It also returns a maximum allowed grade.
1166     $hotpot = get_record('hotpot', 'id', $hotpotid);
1167     $return->grades = hotpot_get_grades($hotpot);
1168     $return->maxgrade = $hotpot->grade;
1170     return $return;
1172 function hotpot_get_grades($hotpot, $user_ids='') {
1173     global $CFG, $DB;
1175     $grades = array();
1177     $weighting = $hotpot->grade / 100;
1178     $precision = hotpot_get_precision($hotpot);
1180     // set the SQL string to determine the $grade
1181     $grade = "";
1182     switch ($hotpot->grademethod) {
1183         case HOTPOT_GRADEMETHOD_HIGHEST:
1184             $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade";
1185             break;
1186         case HOTPOT_GRADEMETHOD_AVERAGE:
1187             // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id)
1188             $grade = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade";
1189             break;
1190         case HOTPOT_GRADEMETHOD_FIRST:
1191             $grade = "ROUND(score * $weighting, $precision)";
1192             $grade = sql_concat('timestart', "'_'", $grade);
1193             $grade = "MIN($grade) AS grade";
1194             break;
1195         case HOTPOT_GRADEMETHOD_LAST:
1196             $grade = "ROUND(score * $weighting, $precision)";
1197             $grade = sql_concat('timestart', "'_'", $grade);
1198             $grade = "MAX($grade) AS grade";
1199             break;
1200     }
1202     if ($grade) {
1203         $userid_condition = empty($user_ids) ? '' : "AND userid IN ($user_ids) ";
1204         $grades = $DB->get_records_sql_menu("
1205             SELECT userid, $grade
1206             FROM {hotpot_attempts}
1207             WHERE timefinish>0 AND hotpot=:hid $userid_condition
1208             GROUP BY userid
1209         ", array('hid'=>$hotpot->id));
1210         if ($grades) {
1211             if ($hotpot->grademethod==HOTPOT_GRADEMETHOD_FIRST || $hotpot->grademethod==HOTPOT_GRADEMETHOD_LAST) {
1212                 // remove left hand characters in $grade (up to and including the underscore)
1213                 foreach ($grades as $userid=>$grade) {
1214                     $grades[$userid] = substr($grades[$userid], strpos($grades[$userid], '_')+1);
1215                 }
1216             }
1217         }
1218     }
1220     return $grades;
1222 function hotpot_get_precision(&$hotpot) {
1223     return ($hotpot->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0;
1226 /**
1227  * Return grade for given user or all users.
1228  *
1229  * @param object $hotpot
1230  * @param int $userid optional user id, 0 means all users
1231  * @return array array of grades, false if none
1232  */
1233 function hotpot_get_user_grades($hotpot, $userid=0) {
1234     $grades = array();
1235     if ($hotpotgrades = hotpot_get_grades($hotpot, $userid)) {
1236         foreach ($hotpotgrades as $hotpotuserid => $hotpotgrade) {
1237             $grades[$hotpotuserid] = new stdClass();
1238             $grades[$hotpotuserid]->id        = $hotpotuserid;
1239             $grades[$hotpotuserid]->userid    = $hotpotuserid;
1240             $grades[$hotpotuserid]->rawgrade  = $hotpotgrade;
1241         }
1242     }
1243     if (count($grades)) {
1244         return $grades;
1245     } else {
1246         return false;
1247     }
1250 /**
1251  * Update grades in central gradebook
1252  * this function is called from db/upgrade.php
1253  *     it is initially called with no arguments, which forces it to get a list of all hotpots
1254  *     it then iterates through the hotpots, calling itself to create a grade record for each hotpot
1255  *
1256  * @param object $hotpot null means all hotpots
1257  * @param int $userid specific user only, 0 means all users
1258  */
1259 function hotpot_update_grades($hotpot=null, $userid=0, $nullifnone=true) {
1260     global $CFG;
1261     if (! function_exists('grade_update')) {
1262         require_once($CFG->libdir.'/gradelib.php');
1263     }
1264     if (is_null($hotpot)) {
1265         // update (=create) grades for all hotpots
1266         $sql = "
1267             SELECT h.*, cm.idnumber as cmidnumber
1268             FROM {$CFG->prefix}hotpot h, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
1269             WHERE m.name='hotpot' AND m.id=cm.module AND cm.instance=h.id"
1270         ;
1271         if ($rs = get_recordset_sql($sql)) {
1272             while ($hotpot = rs_fetch_next_record($rs)) {
1273                 hotpot_update_grades($hotpot, 0, false);
1274             }
1275             rs_close($rs);
1276         }
1277     } else {
1278         // update (=create) grade for a single hotpot
1279         if ($grades = hotpot_get_user_grades($hotpot, $userid)) {
1280             hotpot_grade_item_update($hotpot, $grades);
1282         } else if ($userid && $nullifnone) {
1283             // no grades for this user, but we must force the creation of a "null" grade record
1284             $grade = new object();
1285             $grade->userid   = $userid;
1286             $grade->rawgrade = null;
1287             hotpot_grade_item_update($hotpot, $grade);
1289         } else {
1290             // no grades and no userid
1291             hotpot_grade_item_update($hotpot);
1292         }
1293     }
1296 /**
1297  * Update/create grade item for given hotpot
1298  *
1299  * @param object $hotpot object with extra cmidnumber
1300  * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
1301  * @return object grade_item
1302  */
1303 function hotpot_grade_item_update($hotpot, $grades=null) {
1304     global $CFG;
1305     if (! function_exists('grade_update')) {
1306         require_once($CFG->libdir.'/gradelib.php');
1307     }
1308     $params = array('itemname' => $hotpot->name);
1309     if (array_key_exists('cmidnumber', $hotpot)) {
1310         //cmidnumber may not be always present
1311         $params['idnumber'] = $hotpot->cmidnumber;
1312     }
1313     if ($hotpot->grade > 0) {
1314         $params['gradetype'] = GRADE_TYPE_VALUE;
1315         $params['grademax']  = $hotpot->grade;
1316         $params['grademin']  = 0;
1318     } else {
1319         $params['gradetype'] = GRADE_TYPE_NONE;
1320     }
1321     return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, $grades, $params);
1324 /**
1325  * Delete grade item for given hotpot
1326  *
1327  * @param object $hotpot object
1328  * @return object grade_item
1329  */
1330 function hotpot_grade_item_delete($hotpot) {
1331     global $CFG;
1332     if (! function_exists('grade_update')) {
1333         require_once($CFG->libdir.'/gradelib.php');
1334     }
1335     return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, null, array('deleted'=>1));
1338 function hotpot_get_participants($hotpotid) {
1339 //Must return an array of user ids who are participants
1340 //for a given instance of hotpot. Must include every user involved
1341 //in the instance, independient of his role (student, teacher, admin...)
1342 //See other modules as example.
1343     global $CFG;
1345     return get_records_sql("
1346         SELECT DISTINCT
1347             u.id, u.id
1348         FROM
1349             {$CFG->prefix}user u,
1350             {$CFG->prefix}hotpot_attempts a
1351         WHERE
1352             u.id = a.userid
1353             AND a.hotpot = '$hotpotid'
1354     ");
1357 function hotpot_scale_used ($hotpotid, $scaleid) {
1358 //This function returns if a scale is being used by one hotpot
1359 //it it has support for grading and scales. Commented code should be
1360 //modified if necessary. See forum, glossary or journal modules
1361 //as reference.
1363     $report = false;
1365     //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid");
1366     //
1367     //if (!empty($rec)  && !empty($scaleid)) {
1368     //  $report = true;
1369     //}
1371     return $report;
1374 /**
1375  * Checks if scale is being used by any instance of hotpot
1376  *
1377  * This is used to find out if scale used anywhere
1378  * @param $scaleid int
1379  * @return boolean True if the scale is used by any hotpot
1380  */
1381 function hotpot_scale_used_anywhere($scaleid) {
1382  return false;
1385 //////////////////////////////////////////////////////////
1386 /// Any other hotpot functions go here.
1387 /// Each of them must have a name that starts with hotpot
1390 function hotpot_add_attempt($hotpotid) {
1391     global $DB, $CFG, $USER;
1393     // get start time of this attempt
1394     $time = time();
1396     // set all previous "in progress" attempts at this quiz to "abandoned"
1397     if ($attempts = get_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id' AND status='".HOTPOT_STATUS_INPROGRESS."'")) {
1398         foreach ($attempts as $attempt) {
1399             if ($attempt->timefinish==0) {
1400                 $attempt->timefinish = $time;
1401             }
1402             if ($attempt->clickreportid==0) {
1403                 $attempt->clickreportid = $attempt->id;
1404             }
1405             $attempt->status = HOTPOT_STATUS_ABANDONED;
1406             update_record('hotpot_attempts', $attempt);
1407         }
1408     }    
1410     // create and add new attempt record
1411     $attempt = new stdClass();
1412     $attempt->hotpot = $hotpotid;
1413     $attempt->userid = $USER->id;
1414     $attempt->attempt = hotpot_get_next_attempt($hotpotid);
1415     $attempt->timestart = $time;
1417     return insert_record("hotpot_attempts", $attempt);
1419 function hotpot_get_next_attempt($hotpotid) {
1420     global $USER;
1422     // get max attempt so far
1423     $i = count_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id'", 'MAX(attempt)');
1425     return empty($i) ? 1 : ($i+1);
1427 function hotpot_get_question_name($question) {
1428     $name = '';
1429     if (isset($question->text)) {
1430         $name = hotpot_strings($question->text);
1431     }
1432     if (empty($name)) {
1433         $name = $question->name;
1434     }
1435     return $name;
1437 function hotpot_strings($ids) {
1439     // array of ids of empty strings
1440     static $HOTPOT_EMPTYSTRINGS;
1442     if (!isset($HOTPOT_EMPTYSTRINGS)) { // first time only
1443         // get ids of empty strings
1444         $emptystrings = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0');
1445         $HOTPOT_EMPTYSTRINGS = empty($emptystrings) ? array() : array_keys($emptystrings);
1446     }
1448     $strings = array();
1449     if (!empty($ids)) {
1450         $ids = explode(',', $ids);
1451         foreach ($ids as $id) {
1452             if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) {
1453                 $strings[] = hotpot_string($id);
1454             }
1455         }
1456     }
1457     return implode(',', $strings);
1459 function hotpot_string($id) {
1460     return get_field('hotpot_strings', 'string', 'id', $id);
1463 //////////////////////////////////////////////////////////////////////////////////////
1464 /// the class definitions to handle XML trees
1466 // get the standard XML parser supplied with Moodle
1467 require_once("$CFG->libdir/xmlize.php");
1469 // get the default class for hotpot quiz templates
1470 require_once("$CFG->hotpottemplate/default.php");
1472 class hotpot_xml_tree {
1473     function hotpot_xml_tree($str, $xml_root='') {
1474         if (empty($str)) {
1475             $this->xml =  array();
1476         } else {
1477             if (empty($CFG->unicodedb)) {
1478                 $str = utf8_encode($str);
1479             }
1480             $this->xml =  xmlize($str, 0);
1481         }
1482         $this->xml_root = $xml_root;
1483     }
1484     function xml_value($tags, $more_tags="[0]['#']") {
1486         $tags = empty($tags) ? '' : "['".str_replace(",", "'][0]['#']['", $tags)."']";
1487         eval('$value = &$this->xml'.$this->xml_root.$tags.$more_tags.';');
1489         if (is_string($value)) {
1490             if (empty($CFG->unicodedb)) {
1491                 $value = utf8_decode($value);
1492             }
1494             // decode angle brackets
1495             $value = strtr($value, array('&#x003C;'=>'<', '&#x003E;'=>'>', '&#x0026;'=>'&'));
1497             // remove white space between <table>, <ul|OL|DL> and <OBJECT|EMBED> parts
1498             // (so it doesn't get converted to <br />)
1499             $htmltags = '('
1500             .   'TABLE|/?CAPTION|/?COL|/?COLGROUP|/?TBODY|/?TFOOT|/?THEAD|/?TD|/?TH|/?TR'
1501             .   '|OL|UL|/?LI'
1502             .   '|DL|/?DT|/?DD'
1503             .   '|EMBED|OBJECT|APPLET|/?PARAM'
1504             //. '|SELECT|/?OPTION'
1505             //. '|FIELDSET|/?LEGEND'
1506             //. '|FRAMESET|/?FRAME'
1507             .   ')'
1508             ;
1510             $space = '(\s|(<br[^>]*>))+';
1511             $search = '#(<'.$htmltags.'[^>]*'.'>)'.$space.'(?='.'<)#is';
1512             $value = preg_replace($search, '\\1', $value);
1514             // replace remaining newlines with <br />
1515             $value = str_replace("\n", '<br />', $value);
1517             // encode unicode characters as HTML entities
1518             // (in particular, accented charaters that have not been encoded by HP)
1520             // unicode characters can be detected by checking the hex value of a character
1521             //  00 - 7F : ascii char (roman alphabet + punctuation)
1522             //  80 - BF : byte 2, 3 or 4 of a unicode char
1523             //  C0 - DF : 1st byte of 2-byte char
1524             //  E0 - EF : 1st byte of 3-byte char
1525             //  F0 - FF : 1st byte of 4-byte char
1526             // if the string doesn't match the above, it might be
1527             //  80 - FF : single-byte, non-ascii char
1528             $search = '#('.'[\xc0-\xdf][\x80-\xbf]'.'|'.'[\xe0-\xef][\x80-\xbf]{2}'.'|'.'[\xf0-\xff][\x80-\xbf]{3}'.'|'.'[\x80-\xff]'.')#se';
1529             $value = preg_replace($search, "hotpot_utf8_to_html_entity('\\1')", $value);
1530         }
1531         return $value;
1532     }
1533     function xml_values($tags) {
1534         $i = 0;
1535         $values = array();
1536         while ($value = $this->xml_value($tags, "[$i]['#']")) {
1537             $values[$i++] = $value;
1538         }
1539         return $values;
1540     }
1541     function obj_value(&$obj, $name) {
1542         return is_object($obj) ? @$obj->$name : (is_array($obj) ? @$obj[$name] : NULL);
1543     }
1544     function encode_cdata(&$str, $tag) {
1546         // conversion tables
1547         static $HTML_ENTITIES = array(
1548             '&apos;' => "'",
1549             '&quot;' => '"',
1550             '&lt;'   => '<',
1551             '&gt;'   => '>',
1552             '&amp;'  => '&',
1553         );
1554         static $ILLEGAL_STRINGS = array(
1555             "\r\n"  => '&lt;br /&gt;',
1556             "\r"    => '&lt;br /&gt;',
1557             "\n"    => '&lt;br /&gt;',
1558             '['     => '&#91;',
1559             ']'     => '&#93;'
1560         );
1562         // extract the $tag from the $str(ing), if possible
1563         $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is';
1564         if (preg_match($pattern, $str, $matches)) {
1566             // encode problematic CDATA chars and strings
1567             $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS);
1569             // if there are any ampersands in "open text"
1570             // surround them by CDATA start and end markers
1571             // (and convert HTML entities to plain text)
1572             $search = '/>([^<]*&[^<]*)</e';
1573             $replace = '"><![CDATA[".strtr("$1", $HTML_ENTITIES)."]]><"';
1574             $matches[2] = preg_replace($search, $replace, $matches[2]);
1576             $str = $matches[1].$matches[2].$matches[3];
1577         }
1578     }
1581 class hotpot_xml_quiz extends hotpot_xml_tree {
1583     // constructor function
1584     function hotpot_xml_quiz(&$obj, $read_file=true, $parse_xml=true, $convert_urls=true, $report_errors=true, $create_html=true) {
1585         // obj can be the $_GET array or a form object/array
1587         global $CFG, $HOTPOT_OUTPUTFORMAT, $HOTPOT_OUTPUTFORMAT_DIR;
1589         // check xmlize functions are available
1590         if (! function_exists("xmlize")) {
1591             print_error('xmlizeunavailable', 'debug');
1592         }
1594         $this->read_file = $read_file;
1595         $this->parse_xml = $parse_xml;
1596         $this->convert_urls = $convert_urls;
1597         $this->report_errors = $report_errors;
1598         $this->create_html = $create_html;
1600         // extract fields from $obj
1601         //  course     : the course id
1602         //  reference   : the filename within the files folder
1603         //  location     : "site" files folder or "course" files folder
1604         //  navigation   : type of navigation required in quiz
1605         //  forceplugins : force Moodle compatible media players
1606         $this->course = $this->obj_value($obj, 'course');
1607         $this->reference = $this->obj_value($obj, 'reference');
1608         $this->location = $this->obj_value($obj, 'location');
1609         $this->navigation = $this->obj_value($obj, 'navigation');
1610         $this->forceplugins = $this->obj_value($obj, 'forceplugins');
1612         // can't continue if there is no course or reference
1613         if (empty($this->course) || empty($this->reference)) {
1614             $this->error = get_string('error_nocourseorfilename', 'hotpot');
1615             if ($this->report_errors) {
1616                 print_error($this->error);
1617             }
1618             return;
1619         }
1621         $this->course_homeurl = "$CFG->wwwroot/course/view.php?id=$this->course";
1623         // set filedir, filename and filepath
1624         switch ($this->location) {
1625             case HOTPOT_LOCATION_SITEFILES:
1626                 $site = get_site();
1627                 $this->filedir = $site->id;
1628                 break;
1630             case HOTPOT_LOCATION_COURSEFILES:
1631             default:
1632                 $this->filedir = $this->course;
1633                 break;
1634         }
1635         $this->filesubdir = dirname($this->reference);
1636         if ($this->filesubdir=='.') {
1637             $this->filesubdir = '';
1638         }
1639         if ($this->filesubdir) {
1640             $this->filesubdir .= '/';
1641         }
1642         $this->filename = basename($this->reference);
1643         $this->fileroot = "$CFG->dataroot/$this->filedir";
1644         $this->filepath = "$this->fileroot/$this->reference";
1646         // read the file, if required
1647         if ($this->read_file) {
1649             if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
1650                 $this->error = get_string('error_couldnotopensourcefile', 'hotpot', $this->filepath);
1651                 if ($this->report_errors) {
1652                     print_error($this->error, '', $this->course_homeurl);
1653                 }
1654                 return;
1655             }
1657             // read in the XML source
1658             $this->source = file_get_contents($this->filepath);
1660             // convert relative URLs to absolute URLs
1661             if ($this->convert_urls) {
1662                 $this->hotpot_convert_relative_urls($this->source);
1663             }
1665             $this->html = '';
1666             $this->quiztype = '';
1667             $this->outputformat = 0;
1669             // is this an html file?
1670             if (preg_match('|\.html?$|', $this->filename)) {
1672                 $this->filetype = 'html';
1673                 $this->html = &$this->source;
1675                 // relative URLs in stylesheets
1676                 $search = '|'.'(<style[^>]*>)'.'(.*?)'.'(</style>)'.'|ise';
1677                 $replace = "hotpot_stripslashes('\\1').hotpot_convert_stylesheets_urls('".$this->get_baseurl()."','".$this->reference."','\\2'.'\\3')";
1678                 $this->source = preg_replace($search, $replace, $this->source);
1680                 // relative URLs in "PreloadImages(...);"
1681                 $search = '|'.'(?<='.'PreloadImages'.'\('.')'."([^)]+?)".'(?='.'\);'.')'.'|se';
1682                 $replace = "hotpot_convert_preloadimages_urls('".$this->get_baseurl()."','".$this->reference."','\\1')";
1683                 $this->source = preg_replace($search, $replace, $this->source);
1685                 // relative URLs in <button class="NavButton" ... onclick="location='...'">
1686                 $search = '|'.'(?<='.'onclick="'."location='".')'."([^']*)".'(?='."'; return false;".'")'.'|ise';
1687                 $replace = "hotpot_convert_navbutton_url('".$this->get_baseurl()."','".$this->reference."','\\1','".$this->course."')";
1688                 $this->source = preg_replace($search, $replace, $this->source);
1690                 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1691                 $search = '|'.'(?<='.'onclick="'."window.open\\('".')'."([^']*)".'(?='."'\\);return false;".'")'.'|ise';
1692                 $replace = "hotpot_convert_url('".$this->get_baseurl()."','".$this->reference."','\\1')";
1693                 $this->source = preg_replace($search, $replace, $this->source);
1695             } else {
1697                 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1698                 $search = '|'.'(?<='.'onclick=&quot;'."window.open\\(&apos;".')'."(.*?)".'(?='."&apos;\\);return false;".'&quot;)'.'|ise';
1699                 $replace = "hotpot_convert_url('".$this->get_baseurl()."','".$this->reference."','\\1')";
1700                 $this->source = preg_replace($search, $replace, $this->source);
1702                 if ($this->parse_xml) {
1704                     $this->filetype = 'xml';
1706                     // encode "gap fill" text in JCloze exercise
1707                     $this->encode_cdata($this->source, 'gap-fill');
1709                     // convert source to xml tree
1710                     $this->hotpot_xml_tree($this->source);
1712                     $keys = array_keys($this->xml);
1713                     foreach ($keys as $key) {
1714                         if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
1715                             $this->quiztype = strtolower($matches[2]);
1716                             $this->xml_root = "['$key']['#']";
1717                             break;
1718                         }
1719                     }
1720                 }
1722                 if ($this->create_html) {
1724                     // set the real output format from the requested output format
1725                     $this->real_outputformat = $this->obj_value($obj, 'outputformat');
1726                     $this->draganddrop = '';
1727                     if (
1728                         empty($this->real_outputformat) ||
1729                         $this->real_outputformat==HOTPOT_OUTPUTFORMAT_BEST ||
1730                         empty($HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat])
1731                     ) {
1732                         if ($CFG->hotpotismobile && isset($HOTPOT_OUTPUTFORMAT_DIR[HOTPOT_OUTPUTFORMAT_MOBILE])) {
1733                                 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_MOBILE;
1734                         } else { // PC
1735                             if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
1736                                 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS;
1737                             } else {
1738                                 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
1739                             }
1740                         }
1741                     }
1743                     if ($this->real_outputformat==HOTPOT_OUTPUTFORMAT_V6_PLUS) {
1744                         if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
1745                             $this->draganddrop = 'd'; // prefix for templates (can also be "f" ?)
1746                         }
1747                         $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
1748                     }
1750                     // set path(s) to template
1751                     $this->template_dir = $HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat];
1752                     $this->template_dirpath = $CFG->hotpottemplate.'/'.$this->template_dir;
1753                     $this->template_filepath = $CFG->hotpottemplate.'/'.$this->template_dir.'.php';
1755                     // check template class exists
1756                     if (!file_exists($this->template_filepath) || !is_readable($this->template_filepath)) {
1757                         $this->error = get_string('error_couldnotopentemplate', 'hotpot', $this->template_dir);
1758                         if ($this->report_errors) {
1759                             print_error($this->error, '', $this->course_homeurl);
1760                         }
1761                         return;
1762                     }
1764                     // get default and output-specfic template classes
1765                     include($this->template_filepath);
1767                     // create html (using the template for the specified output format)
1768                     $this->template = new hotpot_xml_quiz_template($this);
1769                     $this->html = &$this->template->html;
1771                 } // end $this->create_html
1772             } // end if html/xml file
1773         } // end if $this->read_file
1774     } // end constructor function
1776     function hotpot_convert_relative_urls(&$str) {
1777         $tagopen = '(?:(<)|(&lt;)|(&amp;#x003C;))'; // left angle bracket
1778         $tagclose = '(?(2)>|(?(3)&gt;|(?(4)&amp;#x003E;)))'; //  right angle bracket (to match left angle bracket)
1780         $space = '\s+'; // at least one space
1781         $anychar = '(?:[^>]*?)'; // any character
1783         $quoteopen = '("|&quot;|&amp;quot;)'; // open quote
1784         $quoteclose = '\\5'; //  close quote (to match open quote)
1786         $replace = "hotpot_convert_relative_url('".$this->get_baseurl()."', '".$this->reference."', '\\1', '\\6', '\\7')";
1788         $tags = array('script'=>'src', 'link'=>'href', 'a'=>'href','img'=>'src','param'=>'value', 'object'=>'data', 'embed'=>'src');
1789         foreach ($tags as $tag=>$attribute) {
1790             if ($tag=='param') {
1791                 $url = '\S+?\.\S+?'; // must include a filename and have no spaces
1792             } else {
1793                 $url = '.*?';
1794             }
1795             $search = "%($tagopen$tag$space$anychar$attribute=$quoteopen)($url)($quoteclose$anychar$tagclose)%ise";
1796             $str = preg_replace($search, $replace, $str);
1797         }
1798     }
1800     function get_baseurl() {
1801         // set the url base (first time only)
1802         if (!isset($this->baseurl)) {
1803             global $CFG;
1804             if ($CFG->slasharguments) {
1805                 $this->baseurl = "$CFG->wwwroot/file.php/$this->filedir/";
1806             } else {
1807                 $this->baseurl = "$CFG->wwwroot/file.php?file=/$this->filedir/";
1808             }
1809         }
1810         return $this->baseurl;
1811     }
1814     // insert forms and messages
1816     function remove_nav_buttons() {
1817         $search = '#<!-- Begin(Top|Bottom)NavButtons -->(.*?)<!-- End(Top|Bottom)NavButtons -->#s';
1818         $this->html = preg_replace($search, '', $this->html);
1819     }
1820     function insert_script($src=HOTPOT_JS) {
1821         $script = '<script src="'.$src.'" type="text/javascript"></script>'."\n";
1822         $this->html = preg_replace('|</head>|i', $script.'</head>', $this->html, 1);
1823     }
1824     function insert_submission_form($attemptid, $startblock, $endblock, $keep_contents=false, $targetframe='') {
1825         $form_name = 'store';
1826         $form_fields = ''
1827         .   '<input type="hidden" name="attemptid" value="'.$attemptid.'" />'
1828         .   '<input type="hidden" name="starttime" value="" />'
1829         .   '<input type="hidden" name="endtime" value="" />'
1830         .   '<input type="hidden" name="mark" value="" />'
1831         .   '<input type="hidden" name="detail" value="" />'
1832         .   '<input type="hidden" name="status" value="" />'
1833         ;
1834         $this->insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, false, $targetframe);
1835     }
1836     function insert_giveup_form($attemptid, $startblock, $endblock, $keep_contents=false) {
1837         $form_name = ''; // no <form> tag will be generated
1838         $form_fields = ''
1839         .   '<button onclick="Finish('.HOTPOT_STATUS_ABANDONED.')" class="FuncButton" '
1840         .   'onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" '
1841         .   'onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" '
1842         .   'onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">'
1843         .   get_string('giveup', 'hotpot').'</button>'
1844         ;
1845         $this->insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, true);
1846     }
1847     function insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, $center=false, $targetframe='') {
1848         global $CFG;
1849         $search = '#('.preg_quote($startblock).')(.*?)('.preg_quote($endblock).')#s';
1850         $replace = $form_fields;
1851         if ($keep_contents) {
1852             $replace .= '\\2';
1853         }
1854         if ($targetframe) {
1855             $frametarget = ' target="'.$targetframe.'"';
1856         } else {
1857             $frametarget = $CFG->frametarget;
1858         }
1859         if ($form_name) {
1860             $replace = '<form action="'.$CFG->wwwroot.'/mod/hotpot/attempt.php" method="post" name="'.$form_name.'"'.$frametarget.'>'.$replace.'</form>';
1861         }
1862         if ($center) {
1863             $replace = '<div style="margin-left:auto; margin-right:auto; text-align: center;">'.$replace.'</div>';
1864         }
1865         $replace = '\\1'.$replace.'\\3';
1866         $this->html = preg_replace($search, $replace, $this->html, 1);
1867     }
1868     function insert_message($start_str, $message, $color='red', $align='center') {
1869         $message = '<p align="'.$align.'" style="text-align:'.$align.'"><b><font color="'.$color.'">'.$message."</font></b></p>\n";
1870         $this->html = preg_replace('|'.preg_quote($start_str).'|', $start_str.$message, $this->html, 1);
1871     }
1873     function adjust_media_urls() {
1875         if ($this->forceplugins) {
1877             // make sure the Moodle media plugin is available
1878             global $CFG;
1879             //include_once "$CFG->dirroot/filter/mediaplugin/filter.php";
1880             include_once "$CFG->dirroot/mod/hotpot/mediaplayers/moodle/filter.php";
1882             // exclude swf files from the filter
1883             //$CFG->filter_mediaplugin_ignore_swf = true;
1885             $space = '\s(?:.+\s)?';
1886             $quote = '["'."']?"; // single, double, or no quote
1888             // patterns to media files types and paths
1889             $filetype = "avi|mpeg|mpg|mp3|mov|wmv";
1890             $filepath = ".*?\.($filetype)";
1892             $tagopen = '(?:(<)|(\\\\u003C))'; // left angle-bracket (uses two parenthese)
1893             $tagclose = '(?(1)>|(?(2)\\\\u003E))'; // right angle-bracket (to match the left one)
1894             $tagreopen = '(?(1)<|(?(2)\\\\u003C))'; // another left angle-bracket (to match the first one)
1896             // pattern to match <PARAM> tags which contain the file path
1897             //  wmp        : url
1898             //  quicktime  : src
1899             //  realplayer : src
1900             //  flash      : movie (doesn't need replacing)
1901             $param_url = "/{$tagopen}param{$space}name=$quote(?:movie|src|url)$quote{$space}value=$quote($filepath)$quote.*?$tagclose/is";
1903             // pattern to match <a> tags which link to multimedia files
1904             $link_url = "/{$tagopen}a{$space}href=$quote($filepath)$quote.*?$tagclose.*?$tagreopen\/A$tagclose/is";
1906             // extract <object> tags
1907             preg_match_all("/{$tagopen}object\s.*?{$tagclose}(.*?)(?:{$tagreopen}\/object{$tagclose})+/is", $this->html, $objects);
1909             $i_max = count($objects[0]);
1910             for ($i=0; $i<$i_max; $i++) {
1912                 // extract URL from <PARAM> or <A> 
1913                 $url = '';
1914                 if (preg_match($param_url, $objects[3][$i], $matches) || preg_match($link_url, $objects[3][$i], $matches)) {
1915                     $url = $matches[3];
1916                 }
1918                 if ($url) {
1919                     // strip inner tags (e.g. <embed>)
1920                     $txt = preg_replace("/$tagopen.*?$tagclose/", '', $objects[3][$i]);
1922                     // if url is in the query string, remove the leading characters
1923                     $url = preg_replace('/^[^?]*\?([^=]+=[^&]*&)*[^=]+=([^&]*)$/', '$2', $url, 1);
1924                     $link = '<a href="'.$url.'">'.$txt.'</a>';
1926                     $new_object = hotpot_mediaplayer_moodle($this, $link);
1927                     $new_object = str_replace($link, '', $new_object);
1928                     $new_object = str_replace('&amp;', '&', $new_object);
1930                     $this->html = str_replace($objects[0][$i], $new_object, $this->html);
1931                 }
1932             }
1933         }
1934     }
1936 } // end class
1938 function hotpot_stripslashes($str) {
1939     // strip slashes from  double quotes, single quotes and  back slashes
1940     // the slashes were added by preg_replace() when using the "e" modifier
1941     static $escapedchars = array('\\\\', '\\"', "\\'");
1942     static $unescapedchars = array('\\', '"', "'");
1943     return str_replace($escapedchars, $unescapedchars, $str);
1945 function hotpot_convert_stylesheets_urls($baseurl, $reference, $css, $stripslashes=true) {
1946     if ($stripslashes) {
1947         $css = hotpot_stripslashes($css);
1948     }
1949     $search = '|'.'(?<='.'url'.'\('.')'."(.+?)".'(?='.'\)'.')'.'|ise';
1950     $replace = "hotpot_convert_url('".$baseurl."','".$reference."','\\1')";
1951     return preg_replace($search, $replace, $css);
1953 function hotpot_convert_preloadimages_urls($baseurl, $reference, $urls, $stripslashes=true) {
1954     if ($stripslashes) {
1955         $urls = hotpot_stripslashes($urls);
1956     }
1957     $search = '|(?<=["'."'])([^,'".'"]*?)(?=["'."'])|ise";
1958     $replace = "hotpot_convert_url('".$baseurl."','".$reference."','\\1')";
1959     return preg_replace($search, $replace, $urls);
1961 function hotpot_convert_navbutton_url($baseurl, $reference, $url, $course, $stripslashes=true) {
1962     global $CFG;
1964     if ($stripslashes) {
1965         $url = hotpot_stripslashes($url);
1966     }
1967     $url = hotpot_convert_url($baseurl, $reference, $url, false);
1969     // is this a $url for another hotpot in this course ?
1970     if (preg_match("|^$baseurl(.*)$|", $url, $matches)) {
1971         if ($records = get_records_select('hotpot', "course='$course' AND reference='".$matches[1]."'")) {
1972             $ids = array_keys($records);
1973             $url = "$CFG->wwwroot/mod/hotpot/view.php?hp=".$ids[0];
1974         }
1975     }
1977     return $url;
1980 function hotpot_convert_relative_url($baseurl, $reference, $opentag, $url, $closetag, $stripslashes=true) {
1981     if ($stripslashes) {
1982         $opentag = hotpot_stripslashes($opentag);
1983         $url = hotpot_stripslashes($url);
1984         $closetag = hotpot_stripslashes($closetag);
1985     }
1987     // catch <PARAM name="FlashVars" value="TheSound=soundfile.mp3">
1988     //  ampersands can appear as "&", "&amp;" or "&amp;#x0026;amp;"
1989     if (preg_match('|^'.'\w+=[^&]+'.'('.'&((amp;#x0026;)?amp;)?'.'\w+=[^&]+)*'.'$|', $url)) {
1990         $query = $url;
1991         $url = '';
1992         $fragment = '';
1994     // parse the $url into $matches
1995     //  [1] path
1996     //  [2] query string, if any
1997     //  [3] anchor fragment, if any
1998     } else if (preg_match('|^'.'([^?]*)'.'((?:\\?[^#]*)?)'.'((?:#.*)?)'.'$|', $url, $matches)) {
1999         $url = $matches[1];
2000         $query = $matches[2];
2001         $fragment = $matches[3];
2003     // these appears to be no query or fragment in this url
2004     } else {
2005         $query = '';
2006         $fragment = '';
2007     }
2009     if ($url) {
2010         $url = hotpot_convert_url($baseurl, $reference, $url, false);
2011     }
2013     if ($query) {
2014         $search = '#'.'(file|src|thesound|mp3)='."([^&]+)".'#ise';
2015         $replace = "'\\1='.hotpot_convert_url('".$baseurl."','".$reference."','\\2')";
2016         $query = preg_replace($search, $replace, $query);
2017     }
2019     $url = $opentag.$url.$query.$fragment.$closetag;
2021     return $url;
2024 function hotpot_convert_url($baseurl, $reference, $url, $stripslashes=true) {
2025     // maintain a cache of converted urls
2026     static $HOTPOT_RELATIVE_URLS = array();
2028     if ($stripslashes) {
2029         $url = hotpot_stripslashes($url);
2030     }
2032     // is this an absolute url? (or javascript pseudo url)
2033     if (preg_match('%^(http://|/|javascript:)%i', $url)) {
2034         // do nothing
2036     // has this relative url already been converted?
2037     } else if (isset($HOTPOT_RELATIVE_URLS[$url])) {
2038         $url = $HOTPOT_RELATIVE_URLS[$url];
2040     } else {
2041         $relativeurl = $url;
2043         // get the subdirectory, $dir, of the quiz $reference
2044         $dir = dirname($reference);
2046         // allow for leading "./" and "../"
2047         while (preg_match('|^(\.{1,2})/(.*)$|', $url, $matches)) {
2048             if ($matches[1]=='..') {
2049                 $dir = dirname($dir);
2050             }
2051             $url = $matches[2];
2052         }
2054         // add subdirectory, $dir, to $baseurl, if necessary
2055         if ($dir && $dir<>'.') {
2056             $baseurl .= "$dir/";
2057         }
2059         // prefix $url with $baseurl
2060         $url = "$baseurl$url";
2062         // add url to cache
2063         $HOTPOT_RELATIVE_URLS[$relativeurl] = $url;
2064     }
2065     return $url;
2068 // ===================================================
2069 // function for adding attempt questions and responses
2070 // ===================================================
2072 function hotpot_add_attempt_details(&$attempt) {
2074     // encode ampersands so that HTML entities are preserved in the XML parser
2075     // N.B. ampersands inside <![CDATA[ ]]> blocks do NOT need to be encoded
2077     $old = &$attempt->details; // shortcut to "old" details
2078     $new = '';
2079     $str_start = 0;
2080     while (($cdata_start = strpos($old, '<![CDATA[', $str_start)) && ($cdata_end = strpos($old, ']]>', $cdata_start))) {
2081         $cdata_end += 3;
2082         $new .= str_replace('&', '&amp;', substr($old, $str_start, $cdata_start-$str_start)).substr($old, $cdata_start, $cdata_end-$cdata_start);
2083         $str_start = $cdata_end;
2084     }
2085     $new .= str_replace('&', '&amp;', substr($old, $str_start));
2086     unset($old);
2088     // parse the attempt details as xml
2089     $details = new hotpot_xml_tree($new, "['hpjsresult']['#']");
2091     $num = -1;
2092     $q_num = -1;
2093     $question = NULL;
2094     $reponse = NULL;
2096     $i = 0;
2097     $tags = 'fields,field';
2099     while (($field="[$i]['#']") && $details->xml_value($tags, $field)) {
2101         $name = $details->xml_value($tags, $field."['fieldname'][0]['#']");
2102         $data = $details->xml_value($tags, $field."['fielddata'][0]['#']");
2104         // parse the field name into $matches
2105         //  [1] quiz type
2106         //  [2] attempt detail name
2107         if (preg_match('/^(\w+?)_(\w+)$/', $name, $matches)) {
2108             $quiztype = strtolower($matches[1]);
2109             $name = strtolower($matches[2]);
2111             // parse the attempt detail $name into $matches
2112             //  [1] question number
2113             //  [2] question detail name
2114             if (preg_match('/^q(\d+)_(\w+)$/', $name, $matches)) {
2115                 $num = $matches[1];
2116                 $name = strtolower($matches[2]);
2117                 $data = addslashes($data);
2119                 // adjust JCross question numbers
2120                 if (preg_match('/^(across|down)(.*)$/', $name, $matches)) {
2121                     $num .= '_'.$matches[1]; // e.g. 01_across, 02_down
2122                     $name = $matches[2];
2123                     if (substr($name, 0, 1)=='_') {
2124                         $name = substr($name, 1); // remove leading '_'
2125                     }
2126                 }
2128                 // is this a new question (or the first one)?
2129                 if ($q_num<>$num) {
2131                     // add previous question and response, if any
2132                     hotpot_add_response($attempt, $question, $response);
2134                     // initialize question object
2135                     $question = NULL;
2136                     $question->name = '';
2137                     $question->text = '';
2138                     $question->hotpot = $attempt->hotpot;
2140                     // initialize response object
2141                     $response = NULL;
2142                     $response->attempt = $attempt->id;
2144                     // update question number
2145                     $q_num = $num;
2146                 }
2148                 // adjust field name and value, and set question type
2149                 // (may not be necessary one day)
2150                 hotpot_adjust_response_field($quiztype, $question, $num, $name, $data);
2152                 // add $data to the question/response details
2153                 switch ($name) {
2154                     case 'name':
2155                     case 'type':
2156                         $question->$name = $data;
2157                         break;
2158                     case 'text':
2159                         $question->$name = hotpot_string_id($data);
2160                         break;
2162                     case 'correct':
2163                     case 'ignored':
2164                     case 'wrong':
2165                         $response->$name = hotpot_string_ids($data);
2166                         break;
2168                     case 'score':
2169                     case 'weighting':
2170                     case 'hints':
2171                     case 'clues':
2172                     case 'checks':
2173                         $response->$name = intval($data);
2174                         break;
2175                 }
2177             } else { // attempt details
2179                 // adjust field name and value
2180                 hotpot_adjust_response_field($quiztype, $question, $num='', $name, $data);
2182                 // add $data to the attempt details
2183                 if ($name=='penalties') {
2184                     $attempt->$name = intval($data);
2185                 }
2186             }
2187         }
2189         $i++;
2190     } // end while
2192     // add the final question and response, if any
2193     hotpot_add_response($attempt, $question, $response);
2195 function hotpot_add_response(&$attempt, &$question, &$response) {
2196     global $DB, $next_url;
2198     $loopcount = 1;
2200     $looping = isset($question) && isset($question->name) && isset($response);
2201     while ($looping) {
2203         if ($loopcount==1) {
2204             $questionname = $question->name;
2205         }
2207         $question->md5key = md5($question->name);
2208         if (!$question->id = get_field('hotpot_questions', 'id', 'hotpot', $attempt->hotpot, 'md5key', $question->md5key, 'name', $question->name)) {
2209             // add question record
2210             if (!$question->id = insert_record('hotpot_questions', $question)) {
2211                 print_error("Could not add question record (attempt_id=$attempt->id): ".$DB->get_last_error(), '', $next_url);
2212             }
2213         }
2215         if (record_exists('hotpot_responses', 'attempt', $attempt->id, 'question', $question->id)) {
2216             // there is already a response to this question for this attempt
2217             // probably because this quiz has two questions with the same text
2218             //  e.g. Which one of these answers is correct?
2220             // To workaround this, we create new question names
2221             //  e.g. Which one of these answers is correct? (2)
2222             // until we get a question name for which there is no response yet on this attempt
2224             $loopcount++;
2225             $question->name = "$questionname ($loopcount)";
2227             // This method fails to correctly identify questions in
2228             // quizzes which allow questions to be shuffled or omitted.
2229             // As yet, there is no workaround for such cases.
2231         } else {
2232             $response->question = $question->id;
2234             // add response record
2235             if(!$response->id = insert_record('hotpot_responses', $response)) {
2236                 print_error("Could not add response record (attempt_id=$attempt->id, question_id=$question->id): ".$DB->get_last_error(), '', $next_url);
2237             }
2239             // we can stop looping now
2240             $looping = false;
2241         }
2242     } // end while
2244 function hotpot_adjust_response_field($quiztype, &$question, &$num, &$name, &$data) {
2245     switch ($quiztype) {
2246         case 'jbc':
2247             $question->type = HOTPOT_JCB;
2248             switch ($name) {
2249                 case 'right':
2250                     $name = 'correct';
2251                 break;
2252             }
2253             break;
2254         case 'jcloze':
2255             $question->type = HOTPOT_JCLOZE;
2256             if (is_numeric($num)) {
2257                 $question->name = $num;
2258             }
2259             switch ($name) {
2260                 case 'penalties':
2261                     if (is_numeric($num)) {
2262                         $name = 'checks';
2263                         if (is_numeric($data)) {
2264                             $data++;
2265                         }
2266                     }
2267                     break;
2268                 case 'clue_shown':
2269                     $name = 'clues';
2270                     $data = ($data=='YES' ? 1 : 0);
2271                     break;
2272                 case 'clue_text':
2273                     $name = 'text';
2274                     break;
2275             }
2276             break;
2277         case 'jcross':
2278             $question->type = HOTPOT_JCROSS;
2279             $question->name = $num;
2280             switch ($name) {
2281                 case '': // HotPot v2.0.x
2282                     $name = 'correct';
2283                     break;
2284                 case 'clue':
2285                     $name = 'text';
2286                     break;
2287             }
2288             break;
2289         case 'jmatch':
2290             $question->type = HOTPOT_JMATCH;
2291             switch ($name) {
2292                 case 'attempts':
2293                     $name = 'penalties';
2294                     if (is_numeric($data) && $data>0) {
2295                         $data--;
2296                     }
2297                 break;
2298                 case 'lhs':
2299                     $name = 'name';
2300                 break;
2301                 case 'rhs':
2302                     $name = 'correct';
2303                 break;
2304             }
2305             break;
2306         case 'jmix':
2307             $question->type = HOTPOT_JMIX;
2308             $question->name = $num;
2309             switch ($name) {
2310                 // keep these in for "restore" of courses
2311                 // which were backed up with HotPot v2.0.x
2312                 case 'wrongguesses':
2313                     $name = 'checks';
2314                     if (is_numeric($data)) {
2315                         $data++;
2316                     }
2317                 break;
2318                 case 'right':
2319                     $name = 'correct';
2320                 break;
2321             }
2322             break;
2323             break;
2324         case 'jquiz':
2325             switch ($name) {
2326                 case 'type':
2327                     $data = HOTPOT_JQUIZ;
2328                     switch ($data) {
2329                         case 'multiple-choice':
2330                             $data .= '.'.HOTPOT_JQUIZ_MULTICHOICE;
2331                         break;
2332                         case 'short-answer':
2333                             $data .= '.'.HOTPOT_JQUIZ_SHORTANSWER;
2334                         break;
2335                         case 'hybrid':
2336                             $data .= '.'.HOTPOT_JQUIZ_HYBRID;
2337                         break;
2338                         case 'multi-select':
2339                             $data .= '.'.HOTPOT_JQUIZ_MULTISELECT;
2340                         case 'n/a':
2341                         default:
2342                             // do nothing more
2343                         break;
2344                     }
2345                 break;
2346                 case 'question':
2347                     $name = 'name';
2348                 break;
2349             }
2350             break;
2352         case 'rhubarb':
2353             $question->type = HOTPOT_TEXTOYS_RHUBARB;
2354             if (empty($question->name)) {
2355                 $question->name = $num;
2356             }
2357             break;
2359         case 'sequitur':
2360             $question->type = HOTPOT_TEXTOYS_SEQUITUR;
2361             break;
2362     }
2364 function hotpot_string_ids($field_value) {
2365     $ids = array();
2366     $strings = explode(',', $field_value);
2367     foreach($strings as $str) {
2368         if ($id = hotpot_string_id($str)) {
2369             $ids[] = $id;
2370         }
2371     }
2372     return implode(',', $ids);
2374 function hotpot_string_id($str) {
2375     $id = '';
2376     if (isset($str) && $str<>'') {
2378         // get the id from the table if it is already there
2379         $md5key = md5($str);
2380         if (!$id = get_field('hotpot_strings', 'id', 'md5key', $md5key, 'string', $str)) {
2382             // create a string record
2383             $record = new stdClass();
2384             $record->string = $str;
2385             $record->md5key = $md5key;
2387             // try and add the new string record
2388             if (!$id = insert_record('hotpot_strings', $record)) {
2389                 global $DB;
2390                 print_error("Could not add string record for '".htmlspecialchars($str)."': ".$DB->get_last_error());
2391             }
2392         }
2393     }
2394     return $id;
2397 function hotpot_get_view_actions() {
2398     return array('view','view all','report');
2401 function hotpot_get_post_actions() {
2402     return array('attempt','review','submit');
2405 function hotpot_utf8_to_html_entity($char) {
2406     // http://www.zend.com/codex.php?id=835&single=1
2408     // array used to figure what number to decrement from character order value
2409     // according to number of characters used to map unicode to ascii by utf-8
2410     static $HOTPOT_UTF8_DECREMENT = array(
2411         1=>0, 2=>192, 3=>224, 4=>240
2412     );
2414     // the number of bits to shift each character by
2415     static $HOTPOT_UTF8_SHIFT = array(
2416         1=>array(0=>0),
2417         2=>array(0=>6,  1=>0),
2418         3=>array(0=>12, 1=>6,  2=>0),
2419         4=>array(0=>18, 1=>12, 2=>6, 3=>0)
2420     );
2422     $dec = 0;
2423     $len = strlen($char);
2424     for ($pos=0; $pos<$len; $pos++) {
2425         $ord = ord ($char{$pos});
2426         $ord -= ($pos ? 128 : $HOTPOT_UTF8_DECREMENT[$len]);
2427         $dec += ($ord << $HOTPOT_UTF8_SHIFT[$len][$pos]);
2428     }
2429     return '&#x'.sprintf('%04X', $dec).';';
2432 function hotpot_print_show_links($course, $location, $reference, $actions='', $spacer=' &nbsp; ', $new_window=false, $return=false) {
2433     global $CFG;
2434     if (is_string($actions)) {
2435         if (empty($actions)) {
2436             $actions = 'showxmlsource,showxmltree,showhtmlsource';
2437         }
2438         $actions = explode(',', $actions);
2439     }
2440     $strenterafilename = get_string('enterafilename', 'hotpot');
2441     $html = <<<END_OF_SCRIPT
2442 <script type="text/javascript">
2443 //<![CDATA[
2444     function setLink(lnk) {
2445         var form = null;
2446         if (document.forms['mform1']) {
2447             var form = document.forms['mform1'];
2448         } else if (document.forms['form']) {
2449             var form = document.forms['form'];
2450         }
2451         return setLinkAttribute(lnk, 'reference', form) && setLinkAttribute(lnk, 'location', form);
2452     }
2453     function setLinkAttribute(lnk, name, form) {
2454         // set link attribute value using
2455         // f(orm) name and e(lement) name
2457         var r = true; // result
2459         var obj = (form) ? form.elements[name] : null;
2460         if (obj) {
2461             r = false;
2462             var v = getObjValue(obj);
2463             if (v=='') {
2464                 alert('$strenterafilename');
2465             } else {
2466                 var s = lnk.href;
2467                 var i = s.indexOf('?');
2468                 if (i>=0) {
2469                     i = s.indexOf(name+'=', i+1);
2470                     if (i>=0) {
2471                         i += name.length+1;
2472                         var ii = s.indexOf('&', i);
2473                         if (ii<0) {
2474                             ii = s.length;
2475                         }
2476                         lnk.href = s.substring(0, i) + v + s.substring(ii);
2477                         r = true;
2478                     }
2479                 }
2480             }
2481         }
2482         return r;
2483     }
2484     function getObjValue(obj) {
2485         var v = ''; // the value
2486         var t = (obj && obj.type) ? obj.type : "";
2487         if (t=="text" || t=="textarea" || t=="hidden") {
2488             v = obj.value;
2489         } else if (t=="select-one" || t=="select-multiple") {
2490             var l = obj.options.length;
2491             for (var i=0; i<l; i++) {
2492                 if (obj.options[i].selected) {
2493                     v += (v=="" ? "" : ",") + obj.options[i].value;
2494                 }
2495             }
2496         }
2497         return v;
2498     }
2499     function getDir(s) {
2500         if (s.charAt(0)!='/') {
2501             s = '/' + s;
2502         }
2503         var i = s.lastIndexOf('/');
2504         return s.substring(0, i);
2505     }
2506 //]]>
2507 </script>
2508 END_OF_SCRIPT;
2510     foreach ($actions as $action) {
2511         $html .= $spacer
2512         .   '<a href="'
2513         .           $CFG->wwwroot.'/mod/hotpot/show.php'
2514         .           '?course='.$course.'&amp;location='.$location.'&amp;reference='.urlencode($reference).'&amp;action='.$action
2515         .       '"'
2516         .       ' onclick="return setLink(this);"'
2517         .       ($new_window ? ' target="_blank"' : '')
2518         .   '>'.get_string($action, 'hotpot').'</a>'
2519         ;
2520     }
2521     $html = '<span class="helplink">'.$html.'</span>';
2522     if ($return) {
2523         return $html;
2524     } else {
2525         print $html;
2526     }
2529 ?>