3 //////////////////////////////////
4 /// CONFIGURATION settings
6 if (!isset($CFG->hotpot_showtimes
)) {
7 set_config("hotpot_showtimes", 0);
9 if (!isset($CFG->hotpot_excelencodings
)) {
10 set_config("hotpot_excelencodings", "");
13 //////////////////////////////////
14 /// CONSTANTS and GLOBAL VARIABLES
16 $CFG->hotpotroot
= "$CFG->dirroot/mod/hotpot";
17 $CFG->hotpottemplate
= "$CFG->hotpotroot/template";
18 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
19 $CFG->hotpotismobile
= preg_match('/Alcatel|ATTWS|DoCoMo|Doris|Hutc3G|J-PHONE|Java|KDDI|KGT|LGE|MOT|Nokia|portalmmm|ReqwirelessWeb|SAGEM|SHARP|SIE-|SonyEricsson|Teleport|UP\.Browser|UPG1|Wapagsim/', $_SERVER['HTTP_USER_AGENT']);
21 $CFG->hotpotismobile
= false;
24 define("HOTPOT_JS", "$CFG->wwwroot/mod/hotpot/hotpot-full.js");
26 define("HOTPOT_NO", "0");
27 define("HOTPOT_YES", "1");
29 define ("HOTPOT_TEXTSOURCE_QUIZ", "0");
30 define ("HOTPOT_TEXTSOURCE_FILENAME", "1");
31 define ("HOTPOT_TEXTSOURCE_FILEPATH", "2");
32 define ("HOTPOT_TEXTSOURCE_SPECIFIC", "3");
34 define("HOTPOT_LOCATION_COURSEFILES", "0");
35 define("HOTPOT_LOCATION_SITEFILES", "1");
37 $HOTPOT_LOCATION = array (
38 HOTPOT_LOCATION_COURSEFILES
=> get_string("coursefiles"),
39 HOTPOT_LOCATION_SITEFILES
=> get_string("sitefiles"),
42 define("HOTPOT_OUTPUTFORMAT_BEST", "1");
43 define("HOTPOT_OUTPUTFORMAT_V3", "10");
44 define("HOTPOT_OUTPUTFORMAT_V4", "11");
45 define("HOTPOT_OUTPUTFORMAT_V5", "12");
46 define("HOTPOT_OUTPUTFORMAT_V5_PLUS", "13");
47 define("HOTPOT_OUTPUTFORMAT_V6", "14");
48 define("HOTPOT_OUTPUTFORMAT_V6_PLUS", "15");
49 define("HOTPOT_OUTPUTFORMAT_FLASH", "20");
50 define("HOTPOT_OUTPUTFORMAT_MOBILE", "30");
52 $HOTPOT_OUTPUTFORMAT = array (
53 HOTPOT_OUTPUTFORMAT_BEST
=> get_string("outputformat_best", "hotpot"),
54 HOTPOT_OUTPUTFORMAT_V6_PLUS
=> get_string("outputformat_v6_plus", "hotpot"),
55 HOTPOT_OUTPUTFORMAT_V6
=> get_string("outputformat_v6", "hotpot"),
56 HOTPOT_OUTPUTFORMAT_V5_PLUS
=> get_string("outputformat_v5_plus", "hotpot"),
57 HOTPOT_OUTPUTFORMAT_V5
=> get_string("outputformat_v5", "hotpot"),
58 HOTPOT_OUTPUTFORMAT_V4
=> get_string("outputformat_v4", "hotpot"),
59 HOTPOT_OUTPUTFORMAT_V3
=> get_string("outputformat_v3", "hotpot"),
60 HOTPOT_OUTPUTFORMAT_FLASH
=> get_string("outputformat_flash", "hotpot"),
61 HOTPOT_OUTPUTFORMAT_MOBILE
=> get_string("outputformat_mobile", "hotpot"),
63 $HOTPOT_OUTPUTFORMAT_DIR = array (
64 HOTPOT_OUTPUTFORMAT_V6_PLUS
=> 'v6',
65 HOTPOT_OUTPUTFORMAT_V6
=> 'v6',
66 HOTPOT_OUTPUTFORMAT_V5_PLUS
=> 'v5',
67 HOTPOT_OUTPUTFORMAT_V5
=> 'v5',
68 HOTPOT_OUTPUTFORMAT_V4
=> 'v4',
69 HOTPOT_OUTPUTFORMAT_V3
=> 'v3',
70 HOTPOT_OUTPUTFORMAT_FLASH
=> 'flash',
71 HOTPOT_OUTPUTFORMAT_MOBILE
=> 'mobile',
73 foreach ($HOTPOT_OUTPUTFORMAT_DIR as $format=>$dir) {
74 if (is_file("$CFG->hotpottemplate/$dir.php") && is_dir("$CFG->hotpottemplate/$dir")) {
75 // do nothing ($format is available)
77 // $format is not available, so remove it
78 unset($HOTPOT_OUTPUTFORMAT[$format]);
79 unset($HOTPOT_OUTPUTFORMAT_DIR[$format]);
82 define("HOTPOT_NAVIGATION_BAR", "1");
83 define("HOTPOT_NAVIGATION_FRAME", "2");
84 define("HOTPOT_NAVIGATION_IFRAME", "3");
85 define("HOTPOT_NAVIGATION_BUTTONS", "4");
86 define("HOTPOT_NAVIGATION_GIVEUP", "5");
87 define("HOTPOT_NAVIGATION_NONE", "6");
89 $HOTPOT_NAVIGATION = array (
90 HOTPOT_NAVIGATION_BAR
=> get_string("navigation_bar", "hotpot"),
91 HOTPOT_NAVIGATION_FRAME
=> get_string("navigation_frame", "hotpot"),
92 HOTPOT_NAVIGATION_IFRAME
=> get_string("navigation_iframe", "hotpot"),
93 HOTPOT_NAVIGATION_BUTTONS
=> get_string("navigation_buttons", "hotpot"),
94 HOTPOT_NAVIGATION_GIVEUP
=> get_string("navigation_give_up", "hotpot"),
95 HOTPOT_NAVIGATION_NONE
=> get_string("navigation_none", "hotpot"),
98 define("HOTPOT_JCB", "1");
99 define("HOTPOT_JCLOZE", "2");
100 define("HOTPOT_JCROSS", "3");
101 define("HOTPOT_JMATCH", "4");
102 define("HOTPOT_JMIX", "5");
103 define("HOTPOT_JQUIZ", "6");
104 define("HOTPOT_TEXTOYS_RHUBARB", "7");
105 define("HOTPOT_TEXTOYS_SEQUITUR", "8");
107 $HOTPOT_QUIZTYPE = array(
109 HOTPOT_JCLOZE
=> 'JCloze',
110 HOTPOT_JCROSS
=> 'JCross',
111 HOTPOT_JMATCH
=> 'JMatch',
112 HOTPOT_JMIX
=> 'JMix',
113 HOTPOT_JQUIZ
=> 'JQuiz',
114 HOTPOT_TEXTOYS_RHUBARB
=> 'Rhubarb',
115 HOTPOT_TEXTOYS_SEQUITUR
=> 'Sequitur'
118 define("HOTPOT_JQUIZ_MULTICHOICE", "1");
119 define("HOTPOT_JQUIZ_SHORTANSWER", "2");
120 define("HOTPOT_JQUIZ_HYBRID", "3");
121 define("HOTPOT_JQUIZ_MULTISELECT", "4");
123 define("HOTPOT_GRADEMETHOD_HIGHEST", "1");
124 define("HOTPOT_GRADEMETHOD_AVERAGE", "2");
125 define("HOTPOT_GRADEMETHOD_FIRST", "3");
126 define("HOTPOT_GRADEMETHOD_LAST", "4");
128 $HOTPOT_GRADEMETHOD = array (
129 HOTPOT_GRADEMETHOD_HIGHEST
=> get_string("gradehighest", "quiz"),
130 HOTPOT_GRADEMETHOD_AVERAGE
=> get_string("gradeaverage", "quiz"),
131 HOTPOT_GRADEMETHOD_FIRST
=> get_string("attemptfirst", "quiz"),
132 HOTPOT_GRADEMETHOD_LAST
=> get_string("attemptlast", "quiz"),
135 define("HOTPOT_STATUS_INPROGRESS", "1");
136 define("HOTPOT_STATUS_TIMEDOUT", "2");
137 define("HOTPOT_STATUS_ABANDONED", "3");
138 define("HOTPOT_STATUS_COMPLETED", "4");
140 $HOTPOT_STATUS = array (
141 HOTPOT_STATUS_INPROGRESS
=> get_string("inprogress", "hotpot"),
142 HOTPOT_STATUS_TIMEDOUT
=> get_string("timedout", "hotpot"),
143 HOTPOT_STATUS_ABANDONED
=> get_string("abandoned", "hotpot"),
144 HOTPOT_STATUS_COMPLETED
=> get_string("completed", "hotpot"),
147 define("HOTPOT_FEEDBACK_NONE", "0");
148 define("HOTPOT_FEEDBACK_WEBPAGE", "1");
149 define("HOTPOT_FEEDBACK_FORMMAIL", "2");
150 define("HOTPOT_FEEDBACK_MOODLEFORUM", "3");
151 define("HOTPOT_FEEDBACK_MOODLEMESSAGING", "4");
153 $HOTPOT_FEEDBACK = array (
154 HOTPOT_FEEDBACK_NONE
=> get_string("feedbacknone", "hotpot"),
155 HOTPOT_FEEDBACK_WEBPAGE
=> get_string("feedbackwebpage", "hotpot"),
156 HOTPOT_FEEDBACK_FORMMAIL
=> get_string("feedbackformmail", "hotpot"),
157 HOTPOT_FEEDBACK_MOODLEFORUM
=> get_string("feedbackmoodleforum", "hotpot"),
158 HOTPOT_FEEDBACK_MOODLEMESSAGING
=> get_string("feedbackmoodlemessaging", "hotpot"),
160 if (empty($CFG->messaging
)) { // Moodle 1.4 (and less)
161 unset($HOTPOT_FEEDBACK[HOTPOT_FEEDBACK_MOODLEMESSAGING
]);
164 define("HOTPOT_DISPLAYNEXT_QUIZ", "0");
165 define("HOTPOT_DISPLAYNEXT_COURSE", "1");
166 define("HOTPOT_DISPLAYNEXT_INDEX", "2");
169 * If start and end date for the quiz are more than this many seconds apart
170 * they will be represented by two separate events in the calendar
172 define("HOTPOT_MAX_EVENT_LENGTH", "432000"); // 5 days maximum
174 //////////////////////////////////
178 // possible return values:
180 // display moderr.html (if exists) OR "Could not update" and return to couse view
182 // display as error message and return to course view
183 // true (or non-zero number):
184 // continue to $hotpot->redirect (if set) OR hotpot/view.php (to displsay quiz)
186 // $hotpot is an object containing the values of the form in mod.html
187 // i.e. all the fields in the 'hotpot' table, plus the following:
188 // $hotpot->course : an id in the 'course' table
189 // $hotpot->coursemodule : an id in the 'course_modules' table
190 // $hotpot->section : an id in the 'course_sections' table
191 // $hotpot->module : an id in the 'modules' table
192 // $hotpot->modulename : always 'hotpot'
193 // $hotpot->instance : an id in the 'hotpot' table
194 // $hotpot->mode : 'add' or 'update'
195 // $hotpot->sesskey : unique string required for Moodle's session management
197 function hotpot_add_instance(&$hotpot) {
198 if (hotpot_set_form_values($hotpot)) {
199 if ($result = insert_record('hotpot', $hotpot)) {
200 $hotpot->id
= $result;
201 hotpot_update_events($hotpot);
202 hotpot_grade_item_update(stripslashes_recursive($hotpot));
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));
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
= addslashes($hotpot->summary
);
229 $event->courseid
= $hotpot->course
;
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);
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
248 $event->name
= addslashes($hotpot->name
).' ('.get_string('hotpotopens', 'hotpot').')';
249 $event->timeduration
= 0;
252 $event->timestart
= $hotpot->timeclose
;
253 $event->eventtype
= 'close';
254 $event->name
= addslashes($hotpot->name
).' ('.get_string('hotpotcloses', 'hotpot').')';
257 } else { // single event with duration
258 $event->name
= $hotpot->name
;
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;
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;
275 function hotpot_set_form_values(&$hotpot) {
277 $hotpot->errors
= array(); // these will be reported by moderr.html
279 if (empty($hotpot->reference
)) {
281 $hotpot->errors
['reference']= get_string('error_nofilename', 'hotpot');
284 if (empty($hotpot->studentfeedbackurl
) ||
$hotpot->studentfeedbackurl
=='http://') {
285 $hotpot->studentfeedbackurl
= '';
286 switch ($hotpot->studentfeedback
) {
287 case HOTPOT_FEEDBACK_WEBPAGE
:
289 $hotpot->errors
['studentfeedbackurl']= get_string('error_nofeedbackurlwebpage', 'hotpot');
291 case HOTPOT_FEEDBACK_FORMMAIL
:
293 $hotpot->errors
['studentfeedbackurl']= get_string('error_nofeedbackurlformmail', 'hotpot');
299 $hotpot->timecreated
= $time;
300 $hotpot->timemodified
= $time;
302 if (empty($hotpot->mode
)) {
303 // moodle 1.9 (from mod_form.lib)
305 $hotpot->mode
= 'add';
306 } else if ($hotpot->update
) {
307 $hotpot->mode
= 'update';
312 if ($hotpot->quizchain
==HOTPOT_YES
) {
313 switch ($hotpot->mode
) {
315 $ok = hotpot_add_chain($hotpot);
318 $ok = hotpot_update_chain($hotpot);
321 } else { // $hotpot->quizchain==HOTPOT_NO
322 hotpot_set_name_summary_reference($hotpot);
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";
332 case HOTPOT_DISPLAYNEXT_INDEX
:
333 $hotpot->redirect
= true;
334 $hotpot->redirecturl
= "../mod/hotpot/index.php?id=$hotpot->course";
337 // use Moodle default action (i.e. go on to display the hotpot quiz)
340 $hotpot->displaynext
= HOTPOT_DISPLAYNEXT_QUIZ
;
343 // if ($ok && $hotpot->setdefaults) {
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
);
367 function hotpot_get_chain(&$cm) {
368 // get details of course_modules in this section
369 $course_module_ids = get_field('course_sections', 'sequence', 'id', $cm->section
);
370 if (empty($course_module_ids)) {
371 $hotpot_modules = array();
373 $hotpot_modules = get_records_select('course_modules', "id IN ($course_module_ids) AND module=$cm->module");
374 if (empty($hotpot_modules)) {
375 $hotpot_modules = array();
379 // get ids of hotpot modules in this section
381 foreach ($hotpot_modules as $hotpot_module) {
382 $ids[] = $hotpot_module->instance
;
385 // get details of hotpots in this section
389 $hotpots = get_records_list('hotpot', 'id', implode(',', $ids));
395 // loop through course_modules in this section
396 $ids = explode(',', $course_module_ids);
397 foreach ($ids as $id) {
399 // check this course_module is a hotpot activity
400 if (isset($hotpot_modules[$id])) {
402 // store details of this course module and hotpot activity
403 $hotpot_id = $hotpot_modules[$id]->instance
;
404 $chain[$id] = &$hotpot_modules[$id];
405 $chain[$id]->hotpot
= &$hotpots[$hotpot_id];
407 // set $found, if this is the course module we're looking for
408 if (isset($cm->coursemodule
)) {
409 if ($id==$cm->coursemodule
) {
418 // is this the end of a chain
419 if (empty($hotpots[$hotpot_id]->shownextquiz
)) {
421 break; // out of loop
423 // restart chain (target cm has not been found yet)
428 } // end foreach $ids
430 return $found ?
$chain : false;
432 function hotpot_is_visible(&$cm) {
433 global $CFG, $COURSE;
436 $modulecontext = get_context_instance(CONTEXT_MODULE
, $cm->id
);
437 if (empty($CFG->enablegroupings
) ||
empty($cm->groupmembersonly
) ||
has_capability('moodle/site:accessallgroups', $modulecontext)) {
438 // groupings not applicable
439 } else if (!isguestuser() && groups_has_membership($cm)) {
440 // user is in one of the groups in the allowed grouping
442 // user is not in the required grouping and does not have sufficiently privileges to view this hotpot activity
446 // check if user can view hidden activities
447 if (isset($COURSE->context
)) {
448 $coursecontext = &$COURSE->context
;
450 $coursecontext = get_context_instance(CONTEXT_COURSE
, $cm->course
);
452 if (has_capability('moodle/course:viewhiddenactivities', $coursecontext)) {
453 return true; // user can view hidden activities
456 if (!isset($cm->sectionvisible
)) {
457 if (! $section = get_record('course_sections', 'id', $cm->section
)) {
458 error('Course module record contains invalid section');
460 $cm->sectionvisible
= $section->visible
;
462 if (empty($cm->sectionvisible
)) {
463 $visible = HOTPOT_NO
;
465 $visible = HOTPOT_YES
;
466 if (empty($cm->visible
)) {
467 if ($chain = hotpot_get_chain($cm)) {
468 $startofchain = array_shift($chain);
469 $visible = $startofchain->visible
;
475 function hotpot_add_chain(&$hotpot) {
476 /// add a chain of hotpot actiivities
478 global $CFG, $course;
481 $hotpot->names
= array();
482 $hotpot->summaries
= array();
483 $hotpot->references
= array();
485 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
487 if (isset($xml_quiz->error
)) {
488 $hotpot->errors
['reference'] = $xml_quiz->error
;
491 } else if (is_dir($xml_quiz->filepath
)) {
493 // get list of hotpot files in this folder
494 if ($dh = @opendir
($xml_quiz->filepath
)) {
495 while (false !== ($file = @readdir
($dh))) {
496 if (preg_match('/\.(jbc|jcl|jcw|jmt|jmx|jqz|htm|html)$/', $file)) {
497 $hotpot->references
[] = "$xml_quiz->reference/$file";
503 foreach ($hotpot->references
as $i=>$reference) {
504 $filepath = $xml_quiz->fileroot
.'/'.$reference;
505 hotpot_get_titles_and_next_ex($hotpot, $filepath);
506 $hotpot->names
[$i] = $hotpot->exercisetitle
;
507 $hotpot->summaries
[$i] = $hotpot->exercisesubtitle
;
512 $hotpot->errors
['reference'] = get_string('error_couldnotopenfolder', 'hotpot', $hotpot->reference
);
515 } else if (is_file($xml_quiz->filepath
)) {
517 $filerootlength = strlen($xml_quiz->fileroot
) +
1;
519 while ($xml_quiz->filepath
) {
520 hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath
, true);
521 $hotpot->names
[] = $hotpot->exercisetitle
;
522 $hotpot->summaries
[] = $hotpot->exercisesubtitle
;
523 $hotpot->references
[] = substr($xml_quiz->filepath
, $filerootlength);
525 if ($hotpot->nextexercise
) {
526 $filepath = $xml_quiz->fileroot
.'/'.$xml_quiz->filesubdir
.$hotpot->nextexercise
;
528 // check file is not already in chain
529 $reference = substr($filepath, $filerootlength);
530 if (in_array($reference, $hotpot->references
)) {
536 if ($filepath && file_exists($filepath) && is_file($filepath) && is_readable($filepath)) {
537 $xml_quiz->filepath
= $filepath;
539 $xml_quiz->filepath
= false; // finish while loop
545 $hotpot->errors
['reference'] = get_string('error_notfileorfolder', 'hotpot', $hotpot->reference
);
548 if (empty($hotpot->references
) && empty($hotpot->errors
['reference'])) {
550 $hotpot->errors
['reference'] = get_string('error_noquizzesfound', 'hotpot', $hotpot->reference
);
554 $hotpot->visible
= HOTPOT_YES
;
556 if (trim($hotpot->name
)=='') {
557 $hotpot->name
= get_string("modulename", $hotpot->modulename
);
559 $hotpot->specificname
= $hotpot->name
;
560 $hotpot->specificsummary
= $hotpot->summary
;
562 // add all except last activity in chain
564 $i_max = count($hotpot->references
)-1;
565 for ($i=0; $i<$i_max; $i++
) {
567 hotpot_set_name_summary_reference($hotpot, $i);
568 $hotpot->reference
= addslashes($hotpot->reference
);
570 if (!$hotpot->instance
= insert_record("hotpot", $hotpot)) {
571 error("Could not add a new instance of $hotpot->modulename", "view.php?id=$hotpot->course");
574 // store (hotpot table) id of start of chain
576 $hotpot->startofchain
= $hotpot->instance
;
579 if (isset($course->groupmode
)) {
580 $hotpot->groupmode
= $course->groupmode
;
583 if (! $hotpot->coursemodule
= add_course_module($hotpot)) {
584 error("Could not add a new course module");
586 if (! $sectionid = add_mod_to_section($hotpot) ) {
587 error("Could not add the new course module to that section");
590 if (! set_field("course_modules", "section", $sectionid, "id", $hotpot->coursemodule
)) {
591 error("Could not update the course module with the correct section");
594 add_to_log($hotpot->course
, "course", "add mod",
595 "../mod/$hotpot->modulename/view.php?id=$hotpot->coursemodule",
596 "$hotpot->modulename $hotpot->instance"
598 add_to_log($hotpot->course
, $hotpot->modulename
, "add",
599 "view.php?id=$hotpot->coursemodule",
600 "$hotpot->instance", $hotpot->coursemodule
603 // hide tail of chain
604 if ($hotpot->shownextquiz
==HOTPOT_YES
) {
605 $hotpot->visible
= HOTPOT_NO
;
607 } // end for ($hotpot->references)
609 // settings for final activity in chain
610 hotpot_set_name_summary_reference($hotpot, $i);
611 $hotpot->reference
= addslashes($hotpot->references
[$i]);
612 $hotpot->shownextquiz
= HOTPOT_NO
;
614 if (isset($hotpot->startofchain
)) {
615 // redirection only works for Moodle 1.5+
616 $hotpot->redirect
= true;
617 $hotpot->redirecturl
= "$CFG->wwwroot/mod/hotpot/view.php?hp=$hotpot->startofchain";
623 function hotpot_set_name_summary_reference(&$hotpot, $chain_index=NULL) {
627 $textfields = array('name', 'summary');
628 foreach ($textfields as $textfield) {
630 $textsource = $textfield.'source';
632 // are we adding a chain?
633 if (isset($chain_index)) {
635 switch ($hotpot->$textsource) {
636 case HOTPOT_TEXTSOURCE_QUIZ
:
637 if ($textfield=='name') {
638 $hotpot->exercisetitle
= $hotpot->names
[$chain_index];
639 } else if ($textfield=='summary') {
640 $hotpot->exercisesubtitle
= $hotpot->summaries
[$chain_index];
643 case HOTPOT_TEXTSOURCE_SPECIFIC
:
644 $specifictext = 'specific'.$textfield;
645 if (empty($hotpot->$specifictext) && trim($hotpot->$specifictext)=='') {
646 $hotpot->$textfield = '';
648 $hotpot->$textfield = $hotpot->$specifictext.' ('.($chain_index+
1).')';
652 $hotpot->reference
= $hotpot->references
[$chain_index];
655 if ($hotpot->$textsource==HOTPOT_TEXTSOURCE_QUIZ
) {
656 if (empty($xml_quiz) && !isset($chain_index)) {
657 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
658 hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath
);
660 if ($textfield=='name') {
661 $hotpot->$textfield = addslashes($hotpot->exercisetitle
);
662 } else if ($textfield=='summary') {
663 $hotpot->$textfield = addslashes($hotpot->exercisesubtitle
);
666 switch ($hotpot->$textsource) {
667 case HOTPOT_TEXTSOURCE_FILENAME
:
668 $hotpot->$textfield = basename($hotpot->reference
);
670 case HOTPOT_TEXTSOURCE_FILEPATH
:
671 $hotpot->$textfield = '';
672 // continue to next lines
674 if (empty($hotpot->$textfield)) {
675 $hotpot->$textfield = str_replace('/', ' ', $hotpot->reference
);
680 function hotpot_get_titles_and_next_ex(&$hotpot, $filepath, $get_next=false) {
682 $hotpot->exercisetitle
= '';
683 $hotpot->exercisesubtitle
= '';
684 $hotpot->nextexercise
= '';
686 // read the quiz file source
687 if ($source = file_get_contents($filepath)) {
693 if (preg_match('|\.html?$|', $filepath)) {
695 if (preg_match('|<h2[^>]*class="ExerciseTitle"[^>]*>(.*?)</h2>|is', $source, $matches)) {
696 $title = trim(strip_tags($matches[1]));
699 if (preg_match('|<title[^>]*>(.*?)</title>|is', $source, $matches)) {
700 $title = trim(strip_tags($matches[1]));
703 if (preg_match('|<h3[^>]*class="ExerciseSubtitle"[^>]*>(.*?)</h3>|is', $source, $matches)) {
704 $subtitle = trim(strip_tags($matches[1]));
707 if (preg_match('|<div[^>]*class="NavButtonBar"[^>]*>(.*?)</div>|is', $source, $matches)) {
708 $navbuttonbar = $matches[1];
709 if (preg_match_all('|<button[^>]*class="NavButton"[^>]*onclick="'."location='([^']*)'".'[^"]*"[^>]*>|is', $navbuttonbar, $matches)) {
710 $lastbutton = count($matches[0])-1;
711 $next = $matches[1][$lastbutton];
717 // xml file (...maybe)
718 $xml_tree = new hotpot_xml_tree($source);
719 $xml_tree->filetype
= '';
721 $keys = array_keys($xml_tree->xml
);
722 foreach ($keys as $key) {
723 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
724 $xml_tree->filetype
= 'xml';
725 $xml_tree->xml_root
= "['$key']['#']";
726 $xml_tree->quiztype
= strtolower($matches[2]);
730 if ($xml_tree->filetype
=='xml') {
732 $title = strip_tags($xml_tree->xml_value('data,title'));
733 $subtitle = $xml_tree->xml_value('hotpot-config-file,'.$xml_tree->quiztype
.',exercise-subtitle');
736 $include = $xml_tree->xml_value('hotpot-config-file,global,include-next-ex');
737 if (!empty($include)) {
738 $next = $xml_tree->xml_value("hotpot-config-file,$xml_tree->quiztype,next-ex-url");
739 if (is_array($next)) {
740 $next = $next[0]; // in case "next-ex-url" was repeated in the xml file
747 $hotpot->nextexercise
= $next;
748 $hotpot->exercisetitle
= (empty($title) ||
is_array($title)) ?
basename($filepath) : $title;
749 $hotpot->exercisesubtitle
= (empty($subtitle) ||
is_array($subtitle)) ?
$hotpot->exercisetitle
: $subtitle;
752 function hotpot_get_all_instances_in_course($modulename, $course) {
753 /// called from index.php
756 $instances = array();
758 if (isset($CFG->release
) && substr($CFG->release
, 0, 3)>=1.2) {
759 $groupmode = 'cm.groupmode,';
766 cm.id AS coursemodule,
769 cm.instance AS instance,
770 -- cm.section AS section,
771 cm.visible AS visible,
773 -- cs.section AS sectionnumber,
774 cs.section AS section,
775 cs.sequence AS sequence,
776 cs.visible AS sectionvisible,
779 {$CFG->prefix}course_modules cm,
780 {$CFG->prefix}course_sections cs,
781 {$CFG->prefix}modules m,
782 {$CFG->prefix}$modulename thismodule
784 m.name = '$modulename' AND
786 cm.course = '$course->id' AND
787 cm.section = cs.id AND
788 cm.instance = thismodule.id
790 if ($rawmods = get_records_sql($query)) {
792 // cache $isteacher setting
794 $isteacher = has_capability('mod/hotpot:viewreport', get_context_instance(CONTEXT_COURSE
, $course->id
));
796 $explodesection = array();
799 foreach ($rawmods as $rawmod) {
801 if (empty($explodesection[$rawmod->section
])) {
802 $explodesection[$rawmod->section
] = true;
804 $coursemodules = explode(',', $rawmod->sequence
);
805 foreach ($coursemodules as $i=>$coursemodule) {
806 $order[$coursemodule] = sprintf('%d.%04d', $rawmod->section
, $i);
812 } else if ($modulename=='hotpot') {
813 $visible = hotpot_is_visible($rawmod);
815 $visible = $rawmod->visible
;
819 $instances[$order[$rawmod->coursemodule
]] = $rawmod;
822 } // end foreach $modinfo
825 $instances = array_values($instances);
831 function hotpot_update_chain(&$hotpot) {
832 /// update a chain of hotpot actiivities
835 if ($hotpot_modules = hotpot_get_chain($hotpot)) {
837 // skip updating of these fields
838 $skipfields = array('id', 'course', 'name', 'reference', 'summary', 'shownextquiz');
841 foreach ($hotpot_modules as $hotpot_module) {
843 if ($hotpot->instance
==$hotpot_module->id
) {
844 // don't need to update this hotpot
847 // shortcut to hotpot record
848 $thishotpot = &$hotpot_module->hotpot
;
850 // get a list of fields to update (first time only)
851 if (empty($fields)) {
852 $fields = array_keys(get_object_vars($thishotpot));
855 // assume update is NOT required
856 $require_update = false;
858 // update field values (except $skipfields)
859 foreach($fields as $field) {
860 if (in_array($field, $skipfields) ||
$thishotpot->$field==$hotpot->$field) {
861 // update not required for this field
863 $require_update = true;
864 $thishotpot->$field = $hotpot->$field;
868 // update $thishotpot, if required
869 if ($require_update && !update_record("hotpot", $thishotpot)) {
870 error("Could not update the $hotpot->modulename", "view.php?id=$hotpot->course");
873 } // end foreach $ids
877 function hotpot_delete_instance($id) {
878 /// Given an ID of an instance of this module,
879 /// this function will permanently delete the instance
880 /// and any data that depends on it.
882 if (! $hotpot = get_record("hotpot", "id", $id)) {
886 if (! delete_records("hotpot", "id", "$id")) {
890 delete_records("hotpot_questions", "hotpot", "$id");
891 if ($attempts = get_records_select("hotpot_attempts", "hotpot='$id'")) {
892 $ids = implode(',', array_keys($attempts));
893 delete_records_select("hotpot_attempts", "id IN ($ids)");
894 delete_records_select("hotpot_details", "attempt IN ($ids)");
895 delete_records_select("hotpot_responses", "attempt IN ($ids)");
898 // remove calendar events for this hotpot
899 delete_records('event', 'modulename', 'hotpot', 'instance', $id);
901 // remove grade item for this hotpot
902 hotpot_grade_item_delete($hotpot);
906 function hotpot_delete_and_notify($table, $select, $strtable) {
907 $count = max(0, count_records_select($table, $select));
909 delete_records_select($table, $select);
910 $count -= max(0, count_records_select($table, $select));
912 notify(get_string('deleted')." $count x $strtable");
917 function hotpot_user_complete($course, $user, $mod, $hotpot) {
918 /// Print a detailed representation of what a user has done with
919 /// a given particular instance of this module, for user activity reports.
921 $report = hotpot_user_outline($course, $user, $mod, $hotpot);
922 if (empty($report)) {
923 print get_string("noactivity", "hotpot");
925 $date = userdate($report->time
, get_string('strftimerecentfull'));
926 print $report->info
.' '.get_string('mostrecently').': '.$date;
931 function hotpot_user_outline($course, $user, $mod, $hotpot) {
932 /// Return a small object with summary information about what a
933 /// user has done with a given particular instance of this module
934 /// Used for user activity reports.
935 /// $report->time = the time they did it
936 /// $report->info = a short text description
939 if ($records = get_records_select("hotpot_attempts", "hotpot='$hotpot->id' AND userid='$user->id'", "timestart ASC", "*")) {
940 $report = new stdClass();
942 foreach ($records as $record){
943 if (empty($report->time
)) {
944 $report->time
= $record->timestart
;
946 $scores[] = hotpot_format_score($record);
948 if (empty($scores)) {
950 $report->info
= get_string('noactivity', 'hotpot');
952 $report->info
= get_string('score', 'quiz').': '.implode(', ', $scores);
958 function hotpot_format_score($record, $undefined=' ') {
959 if (isset($record->score
)) {
960 $score = $record->score
;
967 function hotpot_format_status($record, $undefined=' ') {
968 global $HOTPOT_STATUS;
970 if (isset($record->status
) ||
isset($HOTPOT_STATUS[$record->status
])) {
971 $status = $HOTPOT_STATUS[$record->status
];
973 $status = $undefined;
978 function hotpot_print_recent_activity($course, $isteacher, $timestart) {
979 /// Given a course and a time, this module should find recent activity
980 /// that has occurred in hotpot activities and print it out.
981 /// Return true if there was output, or false is there was none.
986 $records = get_records_sql("
990 COUNT(*) AS count_attempts
992 {$CFG->prefix}hotpot h,
993 {$CFG->prefix}hotpot_attempts a
995 h.course = $course->id
997 AND a.id = a.clickreportid
998 AND a.starttime > $timestart
1002 // note that PostGreSQL requires h.name in the GROUP BY clause
1006 foreach ($records as $id => $record){
1007 if ($cm = get_coursemodule_from_instance('hotpot', $record->id
, $course->id
)) {
1008 $context = get_context_instance(CONTEXT_MODULE
, $cm->id
);
1010 if (has_capability('mod/hotpot:viewreport', $context)) {
1011 $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$id";
1012 $name = ' <a href="'.$href.'">'.$record->name
.'</a>';
1013 if ($record->count_attempts
> 1) {
1014 $name .= " ($record->count_attempts)";
1020 if (count($names) > 0) {
1021 print_headline(get_string('modulenameplural', 'hotpot').':');
1023 if ($CFG->version
>= 2005050500) { // Moodle 1.5+
1024 echo '<div class="head"><div class="name">'.implode('<br />', $names).'</div></div>';
1025 } else { // Moodle 1.4.x (or less)
1026 echo '<font size="1">'.implode('<br />', $names).'</font>';
1031 return $result; // True if anything was printed, otherwise false
1034 function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") {
1035 // Returns all quizzes since a given time.
1039 // If $cmid or $userid are specified, then this restricts the results
1040 $cm_select = empty($cmid) ?
"" : " AND cm.id = '$cmid'";
1041 $user_select = empty($userid) ?
"" : " AND u.id = '$userid'";
1043 $records = get_records_sql("
1047 cm.instance, cm.section,
1048 u.firstname, u.lastname, u.picture
1050 {$CFG->prefix}hotpot_attempts a,
1051 {$CFG->prefix}hotpot h,
1052 {$CFG->prefix}course_modules cm,
1053 {$CFG->prefix}user u
1055 a.timefinish > '$sincetime'
1056 AND a.id = a.clickreportid
1057 AND a.userid = u.id $user_select
1058 AND a.hotpot = h.id $cm_select
1059 AND cm.instance = h.id
1060 AND cm.course = '$courseid'
1061 AND h.course = cm.course
1066 if (!empty($records)) {
1067 foreach ($records as $record) {
1068 if (empty($groupid) ||
groups_is_member($groupid, $record->userid
)) {
1072 $activity->type
= "hotpot";
1073 $activity->defaultindex
= $index;
1074 $activity->instance
= $record->hotpot
;
1076 $activity->name
= $record->name
;
1077 $activity->section
= $record->section
;
1079 $activity->content
->attemptid
= $record->id
;
1080 $activity->content
->attempt
= $record->attempt
;
1081 $activity->content
->score
= $record->score
;
1082 $activity->content
->timestart
= $record->timestart
;
1083 $activity->content
->timefinish
= $record->timefinish
;
1085 $activity->user
->userid
= $record->userid
;
1086 $activity->user
->fullname
= fullname($record);
1087 $activity->user
->picture
= $record->picture
;
1089 $activity->timestamp
= $record->timefinish
;
1091 $activities[] = $activity;
1099 function hotpot_print_recent_mod_activity($activity, $course, $detail=false) {
1100 /// Basically, this function prints the results of "hotpot_get_recent_activity"
1102 global $CFG, $THEME, $USER;
1104 if (isset($THEME->cellcontent2
)) {
1105 $bgcolor = ' bgcolor="'.$THEME->cellcontent2
.'"';
1110 print '<table border="0" cellpadding="3" cellspacing="0">';
1112 print '<tr><td'.$bgcolor.' class="forumpostpicture" width="35" valign="top">';
1113 print_user_picture($activity->user
->userid
, $course, $activity->user
->picture
);
1114 print '</td><td width="100%"><font size="2">';
1118 $src = "$CFG->modpixpath/$activity->type/icon.gif";
1119 print '<img src="'.$src.'" class="icon" alt="'.$activity->type
.'" /> ';
1122 $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$activity->instance";
1123 print '<a href="'.$href.'">'.$activity->name
.'</a> - ';
1125 if (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE
, $course))) {
1126 // score (with link to attempt details)
1127 $href = "$CFG->wwwroot/mod/hotpot/review.php?hp=$activity->instance&attempt=".$activity->content
->attemptid
;
1128 print '<a href="'.$href.'">('.hotpot_format_score($activity->content
).')</a> ';
1131 print get_string('attempt', 'quiz').' - '.$activity->content
->attempt
.'<br />';
1135 $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$course";
1136 print '<a href="'.$href.'">'.$activity->user
->fullname
.'</a> ';
1139 print ' - ' . userdate($activity->timestamp
);
1142 $duration = format_time($activity->content
->timestart
- $activity->content
->timefinish
);
1143 print " ($duration)";
1145 print "</font></td></tr>";
1149 function hotpot_cron () {
1150 /// Function to be run periodically according to the moodle cron
1151 /// This function searches for things that need to be done, such
1152 /// as sending out mail, toggling flags etc ...
1159 function hotpot_grades($hotpotid) {
1160 /// Must return an array of grades for a given instance of this module,
1161 /// indexed by user. It also returns a maximum allowed grade.
1163 $hotpot = get_record('hotpot', 'id', $hotpotid);
1164 $return->grades
= hotpot_get_grades($hotpot);
1165 $return->maxgrade
= $hotpot->grade
;
1169 function hotpot_get_grades($hotpot, $user_ids='') {
1174 $weighting = $hotpot->grade
/ 100;
1175 $precision = hotpot_get_precision($hotpot);
1177 // set the SQL string to determine the $grade
1179 switch ($hotpot->grademethod
) {
1180 case HOTPOT_GRADEMETHOD_HIGHEST
:
1181 $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade";
1183 case HOTPOT_GRADEMETHOD_AVERAGE
:
1184 // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id)
1185 $grade = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade";
1187 case HOTPOT_GRADEMETHOD_FIRST
:
1188 $grade = "ROUND(score * $weighting, $precision)";
1189 $grade = sql_concat('timestart', "'_'", $grade);
1190 $grade = "MIN($grade) AS grade";
1192 case HOTPOT_GRADEMETHOD_LAST
:
1193 $grade = "ROUND(score * $weighting, $precision)";
1194 $grade = sql_concat('timestart', "'_'", $grade);
1195 $grade = "MAX($grade) AS grade";
1200 $userid_condition = empty($user_ids) ?
'' : "AND userid IN ($user_ids) ";
1201 $grades = get_records_sql_menu("
1202 SELECT userid, $grade
1203 FROM {$CFG->prefix}hotpot_attempts
1204 WHERE timefinish>0 AND hotpot='$hotpot->id' $userid_condition
1208 if ($hotpot->grademethod
==HOTPOT_GRADEMETHOD_FIRST ||
$hotpot->grademethod
==HOTPOT_GRADEMETHOD_LAST
) {
1209 // remove left hand characters in $grade (up to and including the underscore)
1210 foreach ($grades as $userid=>$grade) {
1211 $grades[$userid] = substr($grades[$userid], strpos($grades[$userid], '_')+
1);
1219 function hotpot_get_precision(&$hotpot) {
1220 return ($hotpot->grademethod
==HOTPOT_GRADEMETHOD_AVERAGE ||
$hotpot->grade
<100) ?
1 : 0;
1224 * Return grade for given user or all users.
1226 * @param object $hotpot
1227 * @param int $userid optional user id, 0 means all users
1228 * @return array array of grades, false if none
1230 function hotpot_get_user_grades($hotpot, $userid=0) {
1232 if ($hotpotgrades = hotpot_get_grades($hotpot, $userid)) {
1233 foreach ($hotpotgrades as $hotpotuserid => $hotpotgrade) {
1234 $grades[$hotpotuserid] = new stdClass();
1235 $grades[$hotpotuserid]->id
= $hotpotuserid;
1236 $grades[$hotpotuserid]->userid
= $hotpotuserid;
1237 $grades[$hotpotuserid]->rawgrade
= $hotpotgrade;
1240 if (count($grades)) {
1248 * Update grades in central gradebook
1249 * this function is called from db/upgrade.php
1250 * it is initially called with no arguments, which forces it to get a list of all hotpots
1251 * it then iterates through the hotpots, calling itself to create a grade record for each hotpot
1253 * @param object $hotpot null means all hotpots
1254 * @param int $userid specific user only, 0 means all users
1256 function hotpot_update_grades($hotpot=null, $userid=0, $nullifnone=true) {
1258 if (! function_exists('grade_update')) {
1259 require_once($CFG->libdir
.'/gradelib.php');
1261 if (is_null($hotpot)) {
1262 // update (=create) grades for all hotpots
1264 SELECT h.*, cm.idnumber as cmidnumber
1265 FROM {$CFG->prefix}hotpot h, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
1266 WHERE m.name='hotpot' AND m.id=cm.module AND cm.instance=h.id"
1268 if ($rs = get_recordset_sql($sql)) {
1269 while ($hotpot = rs_fetch_next_record($rs)) {
1270 hotpot_update_grades($hotpot, 0, false);
1275 // update (=create) grade for a single hotpot
1276 if ($grades = hotpot_get_user_grades($hotpot, $userid)) {
1277 hotpot_grade_item_update($hotpot, $grades);
1279 } else if ($userid && $nullifnone) {
1280 // no grades for this user, but we must force the creation of a "null" grade record
1281 $grade = new object();
1282 $grade->userid
= $userid;
1283 $grade->rawgrade
= null;
1284 hotpot_grade_item_update($hotpot, $grade);
1287 // no grades and no userid
1288 hotpot_grade_item_update($hotpot);
1294 * Update/create grade item for given hotpot
1296 * @param object $hotpot object with extra cmidnumber
1297 * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
1298 * @return object grade_item
1300 function hotpot_grade_item_update($hotpot, $grades=null) {
1302 if (! function_exists('grade_update')) {
1303 require_once($CFG->libdir
.'/gradelib.php');
1305 $params = array('itemname' => $hotpot->name
);
1306 if (array_key_exists('cmidnumber', $hotpot)) {
1307 //cmidnumber may not be always present
1308 $params['idnumber'] = $hotpot->cmidnumber
;
1310 if ($hotpot->grade
> 0) {
1311 $params['gradetype'] = GRADE_TYPE_VALUE
;
1312 $params['grademax'] = $hotpot->grade
;
1313 $params['grademin'] = 0;
1316 $params['gradetype'] = GRADE_TYPE_NONE
;
1318 return grade_update('mod/hotpot', $hotpot->course
, 'mod', 'hotpot', $hotpot->id
, 0, $grades, $params);
1322 * Delete grade item for given hotpot
1324 * @param object $hotpot object
1325 * @return object grade_item
1327 function hotpot_grade_item_delete($hotpot) {
1329 if (! function_exists('grade_update')) {
1330 require_once($CFG->libdir
.'/gradelib.php');
1332 return grade_update('mod/hotpot', $hotpot->course
, 'mod', 'hotpot', $hotpot->id
, 0, null, array('deleted'=>1));
1335 function hotpot_get_participants($hotpotid) {
1336 //Must return an array of user ids who are participants
1337 //for a given instance of hotpot. Must include every user involved
1338 //in the instance, independient of his role (student, teacher, admin...)
1339 //See other modules as example.
1342 return get_records_sql("
1346 {$CFG->prefix}user u,
1347 {$CFG->prefix}hotpot_attempts a
1350 AND a.hotpot = '$hotpotid'
1354 function hotpot_scale_used ($hotpotid, $scaleid) {
1355 //This function returns if a scale is being used by one hotpot
1356 //it it has support for grading and scales. Commented code should be
1357 //modified if necessary. See forum, glossary or journal modules
1362 //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid");
1364 //if (!empty($rec) && !empty($scaleid)) {
1372 * Checks if scale is being used by any instance of hotpot
1374 * This is used to find out if scale used anywhere
1375 * @param $scaleid int
1376 * @return boolean True if the scale is used by any hotpot
1378 function hotpot_scale_used_anywhere($scaleid) {
1382 //////////////////////////////////////////////////////////
1383 /// Any other hotpot functions go here.
1384 /// Each of them must have a name that starts with hotpot
1387 function hotpot_add_attempt($hotpotid) {
1388 global $db, $CFG, $USER;
1390 // get start time of this attempt
1393 // set all previous "in progress" attempts at this quiz to "abandoned"
1394 if ($attempts = get_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id' AND status='".HOTPOT_STATUS_INPROGRESS
."'")) {
1395 foreach ($attempts as $attempt) {
1396 if ($attempt->timefinish
==0) {
1397 $attempt->timefinish
= $time;
1399 if ($attempt->clickreportid
==0) {
1400 $attempt->clickreportid
= $attempt->id
;
1402 $attempt->status
= HOTPOT_STATUS_ABANDONED
;
1403 update_record('hotpot_attempts', $attempt);
1407 // create and add new attempt record
1408 $attempt = new stdClass();
1409 $attempt->hotpot
= $hotpotid;
1410 $attempt->userid
= $USER->id
;
1411 $attempt->attempt
= hotpot_get_next_attempt($hotpotid);
1412 $attempt->timestart
= $time;
1414 return insert_record("hotpot_attempts", $attempt);
1416 function hotpot_get_next_attempt($hotpotid) {
1419 // get max attempt so far
1420 $i = count_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id'", 'MAX(attempt)');
1422 return empty($i) ?
1 : ($i+
1);
1424 function hotpot_get_question_name($question) {
1426 if (isset($question->text
)) {
1427 $name = hotpot_strings($question->text
);
1430 $name = $question->name
;
1434 function hotpot_strings($ids) {
1436 // array of ids of empty strings
1437 static $HOTPOT_EMPTYSTRINGS;
1439 if (!isset($HOTPOT_EMPTYSTRINGS)) { // first time only
1440 // get ids of empty strings
1441 $emptystrings = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0');
1442 $HOTPOT_EMPTYSTRINGS = empty($emptystrings) ?
array() : array_keys($emptystrings);
1447 $ids = explode(',', $ids);
1448 foreach ($ids as $id) {
1449 if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) {
1450 $strings[] = hotpot_string($id);
1454 return implode(',', $strings);
1456 function hotpot_string($id) {
1457 return get_field('hotpot_strings', 'string', 'id', $id);
1460 //////////////////////////////////////////////////////////////////////////////////////
1461 /// the class definitions to handle XML trees
1463 // get the standard XML parser supplied with Moodle
1464 require_once("$CFG->libdir/xmlize.php");
1466 // get the default class for hotpot quiz templates
1467 require_once("$CFG->hotpottemplate/default.php");
1469 class hotpot_xml_tree
{
1470 function hotpot_xml_tree($str, $xml_root='') {
1472 $this->xml
= array();
1474 if (empty($CFG->unicodedb
)) {
1475 $str = utf8_encode($str);
1477 $this->xml
= xmlize($str, 0);
1479 $this->xml_root
= $xml_root;
1481 function xml_value($tags, $more_tags="[0]['#']") {
1483 $tags = empty($tags) ?
'' : "['".str_replace(",", "'][0]['#']['", $tags)."']";
1484 eval('$value = &$this->xml'.$this->xml_root
.$tags.$more_tags.';');
1486 if (is_string($value)) {
1487 if (empty($CFG->unicodedb
)) {
1488 $value = utf8_decode($value);
1491 // decode angle brackets
1492 $value = strtr($value, array('<'=>'<', '>'=>'>', '&'=>'&'));
1494 // remove white space between <table>, <ul|OL|DL> and <OBJECT|EMBED> parts
1495 // (so it doesn't get converted to <br />)
1497 . 'TABLE|/?CAPTION|/?COL|/?COLGROUP|/?TBODY|/?TFOOT|/?THEAD|/?TD|/?TH|/?TR'
1500 . '|EMBED|OBJECT|APPLET|/?PARAM'
1501 //. '|SELECT|/?OPTION'
1502 //. '|FIELDSET|/?LEGEND'
1503 //. '|FRAMESET|/?FRAME'
1507 $space = '(\s|(<br[^>]*>))+';
1508 $search = '#(<'.$htmltags.'[^>]*'.'>)'.$space.'(?='.'<)#is';
1509 $value = preg_replace($search, '\\1', $value);
1511 // replace remaining newlines with <br />
1512 $value = str_replace("\n", '<br />', $value);
1514 // encode unicode characters as HTML entities
1515 // (in particular, accented charaters that have not been encoded by HP)
1517 // unicode characters can be detected by checking the hex value of a character
1518 // 00 - 7F : ascii char (roman alphabet + punctuation)
1519 // 80 - BF : byte 2, 3 or 4 of a unicode char
1520 // C0 - DF : 1st byte of 2-byte char
1521 // E0 - EF : 1st byte of 3-byte char
1522 // F0 - FF : 1st byte of 4-byte char
1523 // if the string doesn't match the above, it might be
1524 // 80 - FF : single-byte, non-ascii char
1525 $search = '#('.'[\xc0-\xdf][\x80-\xbf]'.'|'.'[\xe0-\xef][\x80-\xbf]{2}'.'|'.'[\xf0-\xff][\x80-\xbf]{3}'.'|'.'[\x80-\xff]'.')#se';
1526 $value = preg_replace($search, "hotpot_utf8_to_html_entity('\\1')", $value);
1530 function xml_values($tags) {
1533 while ($value = $this->xml_value($tags, "[$i]['#']")) {
1534 $values[$i++
] = $value;
1538 function obj_value(&$obj, $name) {
1539 return is_object($obj) ?
@$obj->$name : (is_array($obj) ?
@$obj[$name] : NULL);
1541 function encode_cdata(&$str, $tag) {
1543 // conversion tables
1544 static $HTML_ENTITIES = array(
1551 static $ILLEGAL_STRINGS = array(
1552 "\r\n" => '<br />',
1553 "\r" => '<br />',
1554 "\n" => '<br />',
1559 // extract the $tag from the $str(ing), if possible
1560 $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is';
1561 if (preg_match($pattern, $str, $matches)) {
1563 // encode problematic CDATA chars and strings
1564 $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS);
1566 // if there are any ampersands in "open text"
1567 // surround them by CDATA start and end markers
1568 // (and convert HTML entities to plain text)
1569 $search = '/>([^<]*&[^<]*)</e';
1570 $replace = '"><![CDATA[".strtr("$1", $HTML_ENTITIES)."]]><"';
1571 $matches[2] = preg_replace($search, $replace, $matches[2]);
1573 $str = $matches[1].$matches[2].$matches[3];
1578 class hotpot_xml_quiz
extends hotpot_xml_tree
{
1580 // constructor function
1581 function hotpot_xml_quiz(&$obj, $read_file=true, $parse_xml=true, $convert_urls=true, $report_errors=true, $create_html=true) {
1582 // obj can be the $_GET array or a form object/array
1584 global $CFG, $HOTPOT_OUTPUTFORMAT, $HOTPOT_OUTPUTFORMAT_DIR;
1586 // check xmlize functions are available
1587 if (! function_exists("xmlize")) {
1588 error('xmlize functions are not available');
1591 $this->read_file
= $read_file;
1592 $this->parse_xml
= $parse_xml;
1593 $this->convert_urls
= $convert_urls;
1594 $this->report_errors
= $report_errors;
1595 $this->create_html
= $create_html;
1597 // extract fields from $obj
1598 // course : the course id
1599 // reference : the filename within the files folder
1600 // location : "site" files folder or "course" files folder
1601 // navigation : type of navigation required in quiz
1602 // forceplugins : force Moodle compatible media players
1603 $this->course
= $this->obj_value($obj, 'course');
1604 $this->reference
= $this->obj_value($obj, 'reference');
1605 $this->location
= $this->obj_value($obj, 'location');
1606 $this->navigation
= $this->obj_value($obj, 'navigation');
1607 $this->forceplugins
= $this->obj_value($obj, 'forceplugins');
1609 // can't continue if there is no course or reference
1610 if (empty($this->course
) ||
empty($this->reference
)) {
1611 $this->error
= get_string('error_nocourseorfilename', 'hotpot');
1612 if ($this->report_errors
) {
1613 error($this->error
);
1618 $this->course_homeurl
= "$CFG->wwwroot/course/view.php?id=$this->course";
1620 // set filedir, filename and filepath
1621 switch ($this->location
) {
1622 case HOTPOT_LOCATION_SITEFILES
:
1624 $this->filedir
= $site->id
;
1627 case HOTPOT_LOCATION_COURSEFILES
:
1629 $this->filedir
= $this->course
;
1632 $this->filesubdir
= dirname($this->reference
);
1633 if ($this->filesubdir
=='.') {
1634 $this->filesubdir
= '';
1636 if ($this->filesubdir
) {
1637 $this->filesubdir
.= '/';
1639 $this->filename
= basename($this->reference
);
1640 $this->fileroot
= "$CFG->dataroot/$this->filedir";
1641 $this->filepath
= "$this->fileroot/$this->reference";
1643 // read the file, if required
1644 if ($this->read_file
) {
1646 if (!file_exists($this->filepath
) ||
!is_readable($this->filepath
)) {
1647 $this->error
= get_string('error_couldnotopensourcefile', 'hotpot', $this->filepath
);
1648 if ($this->report_errors
) {
1649 error($this->error
, $this->course_homeurl
);
1654 // read in the XML source
1655 $this->source
= file_get_contents($this->filepath
);
1657 // convert relative URLs to absolute URLs
1658 if ($this->convert_urls
) {
1659 $this->hotpot_convert_relative_urls($this->source
);
1663 $this->quiztype
= '';
1664 $this->outputformat
= 0;
1666 // is this an html file?
1667 if (preg_match('|\.html?$|', $this->filename
)) {
1669 $this->filetype
= 'html';
1670 $this->html
= &$this->source
;
1672 // relative URLs in stylesheets
1673 $search = '|'.'(<style[^>]*>)'.'(.*?)'.'(</style>)'.'|ise';
1674 $replace = "hotpot_stripslashes('\\1').hotpot_convert_stylesheets_urls('".$this->get_baseurl()."','".$this->reference
."','\\2'.'\\3')";
1675 $this->source
= preg_replace($search, $replace, $this->source
);
1677 // relative URLs in "PreloadImages(...);"
1678 $search = '|'.'(?<='.'PreloadImages'.'\('.')'."([^)]+?)".'(?='.'\);'.')'.'|se';
1679 $replace = "hotpot_convert_preloadimages_urls('".$this->get_baseurl()."','".$this->reference
."','\\1')";
1680 $this->source
= preg_replace($search, $replace, $this->source
);
1682 // relative URLs in <button class="NavButton" ... onclick="location='...'">
1683 $search = '|'.'(?<='.'onclick="'."location='".')'."([^']*)".'(?='."'; return false;".'")'.'|ise';
1684 $replace = "hotpot_convert_navbutton_url('".$this->get_baseurl()."','".$this->reference
."','\\1','".$this->course
."')";
1685 $this->source
= preg_replace($search, $replace, $this->source
);
1687 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1688 $search = '|'.'(?<='.'onclick="'."window.open\\('".')'."([^']*)".'(?='."'\\);return false;".'")'.'|ise';
1689 $replace = "hotpot_convert_url('".$this->get_baseurl()."','".$this->reference
."','\\1')";
1690 $this->source
= preg_replace($search, $replace, $this->source
);
1694 // relative URLs in <a ... onclick="window.open('...')...">...</a>
1695 $search = '|'.'(?<='.'onclick="'."window.open\\('".')'."(.*?)".'(?='."'\\);return false;".'")'.'|ise';
1696 $replace = "hotpot_convert_url('".$this->get_baseurl()."','".$this->reference
."','\\1')";
1697 $this->source
= preg_replace($search, $replace, $this->source
);
1699 if ($this->parse_xml
) {
1701 $this->filetype
= 'xml';
1703 // encode "gap fill" text in JCloze exercise
1704 $this->encode_cdata($this->source
, 'gap-fill');
1706 // convert source to xml tree
1707 $this->hotpot_xml_tree($this->source
);
1709 $keys = array_keys($this->xml
);
1710 foreach ($keys as $key) {
1711 if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
1712 $this->quiztype
= strtolower($matches[2]);
1713 $this->xml_root
= "['$key']['#']";
1719 if ($this->create_html
) {
1721 // set the real output format from the requested output format
1722 $this->real_outputformat
= $this->obj_value($obj, 'outputformat');
1723 $this->draganddrop
= '';
1725 empty($this->real_outputformat
) ||
1726 $this->real_outputformat
==HOTPOT_OUTPUTFORMAT_BEST ||
1727 empty($HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat
])
1729 if ($CFG->hotpotismobile
&& isset($HOTPOT_OUTPUTFORMAT_DIR[HOTPOT_OUTPUTFORMAT_MOBILE
])) {
1730 $this->real_outputformat
= HOTPOT_OUTPUTFORMAT_MOBILE
;
1732 if ($this->quiztype
=='jmatch' ||
$this->quiztype
=='jmix') {
1733 $this->real_outputformat
= HOTPOT_OUTPUTFORMAT_V6_PLUS
;
1735 $this->real_outputformat
= HOTPOT_OUTPUTFORMAT_V6
;
1740 if ($this->real_outputformat
==HOTPOT_OUTPUTFORMAT_V6_PLUS
) {
1741 if ($this->quiztype
=='jmatch' ||
$this->quiztype
=='jmix') {
1742 $this->draganddrop
= 'd'; // prefix for templates (can also be "f" ?)
1744 $this->real_outputformat
= HOTPOT_OUTPUTFORMAT_V6
;
1747 // set path(s) to template
1748 $this->template_dir
= $HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat
];
1749 $this->template_dirpath
= $CFG->hotpottemplate
.'/'.$this->template_dir
;
1750 $this->template_filepath
= $CFG->hotpottemplate
.'/'.$this->template_dir
.'.php';
1752 // check template class exists
1753 if (!file_exists($this->template_filepath
) ||
!is_readable($this->template_filepath
)) {
1754 $this->error
= get_string('error_couldnotopentemplate', 'hotpot', $this->template_dir
);
1755 if ($this->report_errors
) {
1756 error($this->error
, $this->course_homeurl
);
1761 // get default and output-specfic template classes
1762 include($this->template_filepath
);
1764 // create html (using the template for the specified output format)
1765 $this->template
= new hotpot_xml_quiz_template($this);
1766 $this->html
= &$this->template
->html
;
1768 } // end $this->create_html
1769 } // end if html/xml file
1770 } // end if $this->read_file
1771 } // end constructor function
1773 function hotpot_convert_relative_urls(&$str) {
1774 $tagopen = '(?:(<)|(<)|(&#x003C;))'; // left angle bracket
1775 $tagclose = '(?(2)>|(?(3)>|(?(4)&#x003E;)))'; // right angle bracket (to match left angle bracket)
1777 $space = '\s+'; // at least one space
1778 $anychar = '(?:[^>]*?)'; // any character
1780 $quoteopen = '("|"|&quot;)'; // open quote
1781 $quoteclose = '\\5'; // close quote (to match open quote)
1783 $replace = "hotpot_convert_relative_url('".$this->get_baseurl()."', '".$this->reference
."', '\\1', '\\6', '\\7')";
1785 $tags = array('script'=>'src', 'link'=>'href', 'a'=>'href','img'=>'src','param'=>'value', 'object'=>'data', 'embed'=>'src');
1786 foreach ($tags as $tag=>$attribute) {
1787 if ($tag=='param') {
1788 $url = '\S+?\.\S+?'; // must include a filename and have no spaces
1792 $search = "%($tagopen$tag$space$anychar$attribute=$quoteopen)($url)($quoteclose$anychar$tagclose)%ise";
1793 $str = preg_replace($search, $replace, $str);
1797 function get_baseurl() {
1798 // set the url base (first time only)
1799 if (!isset($this->baseurl
)) {
1801 require_once($CFG->libdir
.'/filelib.php');
1802 $this->baseurl
= get_file_url($this->filedir
).'/';
1804 return $this->baseurl
;
1808 // insert forms and messages
1810 function remove_nav_buttons() {
1811 $search = '#<!-- Begin(Top|Bottom)NavButtons -->(.*?)<!-- End(Top|Bottom)NavButtons -->#s';
1812 $this->html
= preg_replace($search, '', $this->html
);
1814 function insert_script($src=HOTPOT_JS
) {
1815 $script = '<script src="'.$src.'" type="text/javascript"></script>'."\n";
1816 $this->html
= preg_replace('|</head>|i', $script.'</head>', $this->html
, 1);
1818 function insert_submission_form($attemptid, $startblock, $endblock, $keep_contents=false, $targetframe='') {
1819 $form_name = 'store';
1821 . '<input type="hidden" name="attemptid" value="'.$attemptid.'" />'
1822 . '<input type="hidden" name="starttime" value="" />'
1823 . '<input type="hidden" name="endtime" value="" />'
1824 . '<input type="hidden" name="mark" value="" />'
1825 . '<input type="hidden" name="detail" value="" />'
1826 . '<input type="hidden" name="status" value="" />'
1828 $this->insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, false, $targetframe);
1830 function insert_giveup_form($attemptid, $startblock, $endblock, $keep_contents=false) {
1831 $form_name = ''; // no <form> tag will be generated
1833 . '<button onclick="Finish('.HOTPOT_STATUS_ABANDONED
.')" class="FuncButton" '
1834 . 'onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" '
1835 . 'onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" '
1836 . 'onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">'
1837 . get_string('giveup', 'hotpot').'</button>'
1839 $this->insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, true);
1841 function insert_form($startblock, $endblock, $form_name, $form_fields, $keep_contents, $center=false, $targetframe='') {
1843 $search = '#('.preg_quote($startblock).')(.*?)('.preg_quote($endblock).')#s';
1844 $replace = $form_fields;
1845 if ($keep_contents) {
1849 $frametarget = ' target="'.$targetframe.'"';
1851 $frametarget = $CFG->frametarget
;
1854 $replace = '<form action="'.$CFG->wwwroot
.'/mod/hotpot/attempt.php" method="post" name="'.$form_name.'"'.$frametarget.'>'.$replace.'</form>';
1857 $replace = '<div style="margin-left:auto; margin-right:auto; text-align: center;">'.$replace.'</div>';
1859 $replace = '\\1'.$replace.'\\3';
1860 $this->html
= preg_replace($search, $replace, $this->html
, 1);
1862 function insert_message($start_str, $message, $color='red', $align='center') {
1863 $message = '<p align="'.$align.'" style="text-align:'.$align.'"><b><font color="'.$color.'">'.$message."</font></b></p>\n";
1864 $this->html
= preg_replace('|'.preg_quote($start_str).'|', $start_str.$message, $this->html
, 1);
1867 function adjust_media_urls() {
1869 if ($this->forceplugins
) {
1871 // make sure the Moodle media plugin is available
1873 //include_once "$CFG->dirroot/filter/mediaplugin/filter.php";
1874 include_once "$CFG->dirroot/mod/hotpot/mediaplayers/moodle/filter.php";
1876 // exclude swf files from the filter
1877 //$CFG->filter_mediaplugin_ignore_swf = true;
1879 $space = '\s(?:.+\s)?';
1880 $quote = '["'."']?"; // single, double, or no quote
1882 // patterns to media files types and paths
1883 $filetype = "avi|mpeg|mpg|mp3|mov|wmv";
1884 $filepath = ".*?\.($filetype)";
1886 $tagopen = '(?:(<)|(\\\\u003C))'; // left angle-bracket (uses two parenthese)
1887 $tagclose = '(?(1)>|(?(2)\\\\u003E))'; // right angle-bracket (to match the left one)
1888 $tagreopen = '(?(1)<|(?(2)\\\\u003C))'; // another left angle-bracket (to match the first one)
1890 // pattern to match <PARAM> tags which contain the file path
1894 // flash : movie (doesn't need replacing)
1895 $param_url = "/{$tagopen}param{$space}name=$quote(?:movie|src|url)$quote{$space}value=$quote($filepath)$quote.*?$tagclose/is";
1897 // pattern to match <a> tags which link to multimedia files
1898 $link_url = "/{$tagopen}a{$space}href=$quote($filepath)$quote.*?$tagclose.*?$tagreopen\/A$tagclose/is";
1900 // extract <object> tags
1901 preg_match_all("/{$tagopen}object\s.*?{$tagclose}(.*?)(?:{$tagreopen}\/object{$tagclose})+/is", $this->html
, $objects);
1903 $i_max = count($objects[0]);
1904 for ($i=0; $i<$i_max; $i++
) {
1906 // extract URL from <PARAM> or <A>
1908 if (preg_match($param_url, $objects[3][$i], $matches) ||
preg_match($link_url, $objects[3][$i], $matches)) {
1913 // strip inner tags (e.g. <embed>)
1914 $txt = preg_replace("/$tagopen.*?$tagclose/", '', $objects[3][$i]);
1916 // if url is in the query string, remove the leading characters
1917 $url = preg_replace('/^[^?]*\?([^=]+=[^&]*&)*[^=]+=([^&]*)$/', '$2', $url, 1);
1918 $link = '<a href="'.$url.'">'.$txt.'</a>';
1920 $new_object = hotpot_mediaplayer_moodle($this, $link);
1921 $new_object = str_replace($link, '', $new_object);
1922 $new_object = str_replace('&', '&', $new_object);
1924 $this->html
= str_replace($objects[0][$i], $new_object, $this->html
);
1932 function hotpot_stripslashes($str) {
1933 // strip slashes from double quotes, single quotes and back slashes
1934 // the slashes were added by preg_replace() when using the "e" modifier
1935 static $escapedchars = array('\\\\', '\\"', "\\'");
1936 static $unescapedchars = array('\\', '"', "'");
1937 return str_replace($escapedchars, $unescapedchars, $str);
1939 function hotpot_convert_stylesheets_urls($baseurl, $reference, $css, $stripslashes=true) {
1940 if ($stripslashes) {
1941 $css = hotpot_stripslashes($css);
1943 $search = '|'.'(?<='.'url'.'\('.')'."(.+?)".'(?='.'\)'.')'.'|ise';
1944 $replace = "hotpot_convert_url('".$baseurl."','".$reference."','\\1')";
1945 return preg_replace($search, $replace, $css);
1947 function hotpot_convert_preloadimages_urls($baseurl, $reference, $urls, $stripslashes=true) {
1948 if ($stripslashes) {
1949 $urls = hotpot_stripslashes($urls);
1951 $search = '|(?<=["'."'])([^,'".'"]*?)(?=["'."'])|ise";
1952 $replace = "hotpot_convert_url('".$baseurl."','".$reference."','\\1')";
1953 return preg_replace($search, $replace, $urls);
1955 function hotpot_convert_navbutton_url($baseurl, $reference, $url, $course, $stripslashes=true) {
1958 if ($stripslashes) {
1959 $url = hotpot_stripslashes($url);
1961 $url = hotpot_convert_url($baseurl, $reference, $url, false);
1963 // is this a $url for another hotpot in this course ?
1964 if (preg_match("|^".preg_quote($baseurl)."(.*)$|", $url, $matches)) {
1965 if ($records = get_records_select('hotpot', "course='$course' AND reference='".$matches[1]."'")) {
1966 $ids = array_keys($records);
1967 $url = "$CFG->wwwroot/mod/hotpot/view.php?hp=".$ids[0];
1974 function hotpot_convert_relative_url($baseurl, $reference, $opentag, $url, $closetag, $stripslashes=true) {
1975 if ($stripslashes) {
1976 $opentag = hotpot_stripslashes($opentag);
1977 $url = hotpot_stripslashes($url);
1978 $closetag = hotpot_stripslashes($closetag);
1981 // catch <PARAM name="FlashVars" value="TheSound=soundfile.mp3">
1982 // ampersands can appear as "&", "&" or "&#x0026;amp;"
1983 if (preg_match('|^'.'\w+=[^&]+'.'('.'&((amp;#x0026;)?amp;)?'.'\w+=[^&]+)*'.'$|', $url)) {
1988 // parse the $url into $matches
1990 // [2] query string, if any
1991 // [3] anchor fragment, if any
1992 } else if (preg_match('|^'.'([^?]*)'.'((?:\\?[^#]*)?)'.'((?:#.*)?)'.'$|', $url, $matches)) {
1994 $query = $matches[2];
1995 $fragment = $matches[3];
1997 // these appears to be no query or fragment in this url
2004 $url = hotpot_convert_url($baseurl, $reference, $url, false);
2008 $search = '#'.'(file|src|thesound|mp3)='."([^&]+)".'#ise';
2009 $replace = "'\\1='.hotpot_convert_url('".$baseurl."','".$reference."','\\2')";
2010 $query = preg_replace($search, $replace, $query);
2013 $url = $opentag.$url.$query.$fragment.$closetag;
2018 function hotpot_convert_url($baseurl, $reference, $url, $stripslashes=true) {
2019 // maintain a cache of converted urls
2020 static $HOTPOT_RELATIVE_URLS = array();
2022 if ($stripslashes) {
2023 $url = hotpot_stripslashes($url);
2026 // is this an absolute url? (or javascript pseudo url)
2027 if (preg_match('%^(http://|/|javascript:)%i', $url)) {
2030 // has this relative url already been converted?
2031 } else if (isset($HOTPOT_RELATIVE_URLS[$url])) {
2032 $url = $HOTPOT_RELATIVE_URLS[$url];
2035 $relativeurl = $url;
2037 // get the subdirectory, $dir, of the quiz $reference
2038 $dir = dirname($reference);
2040 // allow for leading "./" and "../"
2041 while (preg_match('|^(\.{1,2})/(.*)$|', $url, $matches)) {
2042 if ($matches[1]=='..') {
2043 $dir = dirname($dir);
2048 // add subdirectory, $dir, to $baseurl, if necessary
2049 if ($dir && $dir<>'.') {
2050 $baseurl .= "$dir/";
2053 // prefix $url with $baseurl
2054 $url = "$baseurl$url";
2057 $HOTPOT_RELATIVE_URLS[$relativeurl] = $url;
2062 // ===================================================
2063 // function for adding attempt questions and responses
2064 // ===================================================
2066 function hotpot_add_attempt_details(&$attempt) {
2068 // encode ampersands so that HTML entities are preserved in the XML parser
2069 // N.B. ampersands inside <![CDATA[ ]]> blocks do NOT need to be encoded
2071 $old = &$attempt->details
; // shortcut to "old" details
2074 while (($cdata_start = strpos($old, '<![CDATA[', $str_start)) && ($cdata_end = strpos($old, ']]>', $cdata_start))) {
2076 $new .= str_replace('&', '&', substr($old, $str_start, $cdata_start-$str_start)).substr($old, $cdata_start, $cdata_end-$cdata_start);
2077 $str_start = $cdata_end;
2079 $new .= str_replace('&', '&', substr($old, $str_start));
2082 // parse the attempt details as xml
2083 $details = new hotpot_xml_tree($new, "['hpjsresult']['#']");
2091 $tags = 'fields,field';
2093 while (($field="[$i]['#']") && $details->xml_value($tags, $field)) {
2095 $name = $details->xml_value($tags, $field."['fieldname'][0]['#']");
2096 $data = $details->xml_value($tags, $field."['fielddata'][0]['#']");
2098 // parse the field name into $matches
2100 // [2] attempt detail name
2101 if (preg_match('/^(\w+?)_(\w+)$/', $name, $matches)) {
2102 $quiztype = strtolower($matches[1]);
2103 $name = strtolower($matches[2]);
2105 // parse the attempt detail $name into $matches
2106 // [1] question number
2107 // [2] question detail name
2108 if (preg_match('/^q(\d+)_(\w+)$/', $name, $matches)) {
2110 $name = strtolower($matches[2]);
2111 $data = addslashes($data);
2113 // adjust JCross question numbers
2114 if (preg_match('/^(across|down)(.*)$/', $name, $matches)) {
2115 $num .= '_'.$matches[1]; // e.g. 01_across, 02_down
2116 $name = $matches[2];
2117 if (substr($name, 0, 1)=='_') {
2118 $name = substr($name, 1); // remove leading '_'
2122 // is this a new question (or the first one)?
2125 // add previous question and response, if any
2126 hotpot_add_response($attempt, $question, $response);
2128 // initialize question object
2130 $question->name
= '';
2131 $question->text
= '';
2132 $question->hotpot
= $attempt->hotpot
;
2134 // initialize response object
2136 $response->attempt
= $attempt->id
;
2138 // update question number
2142 // adjust field name and value, and set question type
2143 // (may not be necessary one day)
2144 hotpot_adjust_response_field($quiztype, $question, $num, $name, $data);
2146 // add $data to the question/response details
2150 $question->$name = $data;
2153 $question->$name = hotpot_string_id($data);
2159 $response->$name = hotpot_string_ids($data);
2167 $response->$name = intval($data);
2171 } else { // attempt details
2173 // adjust field name and value
2174 hotpot_adjust_response_field($quiztype, $question, $num='', $name, $data);
2176 // add $data to the attempt details
2177 if ($name=='penalties') {
2178 $attempt->$name = intval($data);
2186 // add the final question and response, if any
2187 hotpot_add_response($attempt, $question, $response);
2189 function hotpot_add_response(&$attempt, &$question, &$response) {
2190 global $db, $next_url;
2194 $looping = isset($question) && isset($question->name
) && isset($response);
2197 if ($loopcount==1) {
2198 $questionname = $question->name
;
2201 $question->md5key
= md5($question->name
);
2202 if (!$question->id
= get_field('hotpot_questions', 'id', 'hotpot', $attempt->hotpot
, 'md5key', $question->md5key
, 'name', $question->name
)) {
2203 // add question record
2204 if (!$question->id
= insert_record('hotpot_questions', $question)) {
2205 error("Could not add question record (attempt_id=$attempt->id): ".$db->ErrorMsg(), $next_url);
2209 if (record_exists('hotpot_responses', 'attempt', $attempt->id
, 'question', $question->id
)) {
2210 // there is already a response to this question for this attempt
2211 // probably because this quiz has two questions with the same text
2212 // e.g. Which one of these answers is correct?
2214 // To workaround this, we create new question names
2215 // e.g. Which one of these answers is correct? (2)
2216 // until we get a question name for which there is no response yet on this attempt
2219 $question->name
= "$questionname ($loopcount)";
2221 // This method fails to correctly identify questions in
2222 // quizzes which allow questions to be shuffled or omitted.
2223 // As yet, there is no workaround for such cases.
2226 $response->question
= $question->id
;
2228 // add response record
2229 if(!$response->id
= insert_record('hotpot_responses', $response)) {
2230 error("Could not add response record (attempt_id=$attempt->id, question_id=$question->id): ".$db->ErrorMsg(), $next_url);
2233 // we can stop looping now
2238 function hotpot_adjust_response_field($quiztype, &$question, &$num, &$name, &$data) {
2239 switch ($quiztype) {
2241 $question->type
= HOTPOT_JCB
;
2249 $question->type
= HOTPOT_JCLOZE
;
2250 if (is_numeric($num)) {
2251 $question->name
= $num;
2255 if (is_numeric($num)) {
2257 if (is_numeric($data)) {
2264 $data = ($data=='YES' ?
1 : 0);
2272 $question->type
= HOTPOT_JCROSS
;
2273 $question->name
= $num;
2275 case '': // HotPot v2.0.x
2284 $question->type
= HOTPOT_JMATCH
;
2287 $name = 'penalties';
2288 if (is_numeric($data) && $data>0) {
2301 $question->type
= HOTPOT_JMIX
;
2302 $question->name
= $num;
2304 // keep these in for "restore" of courses
2305 // which were backed up with HotPot v2.0.x
2306 case 'wrongguesses':
2308 if (is_numeric($data)) {
2321 $data = HOTPOT_JQUIZ
;
2323 case 'multiple-choice':
2324 $data .= '.'.HOTPOT_JQUIZ_MULTICHOICE
;
2326 case 'short-answer':
2327 $data .= '.'.HOTPOT_JQUIZ_SHORTANSWER
;
2330 $data .= '.'.HOTPOT_JQUIZ_HYBRID
;
2332 case 'multi-select':
2333 $data .= '.'.HOTPOT_JQUIZ_MULTISELECT
;
2347 $question->type
= HOTPOT_TEXTOYS_RHUBARB
;
2348 if (empty($question->name
)) {
2349 $question->name
= $num;
2354 $question->type
= HOTPOT_TEXTOYS_SEQUITUR
;
2358 function hotpot_string_ids($field_value) {
2360 $strings = explode(',', $field_value);
2361 foreach($strings as $str) {
2362 if ($id = hotpot_string_id($str)) {
2366 return implode(',', $ids);
2368 function hotpot_string_id($str) {
2370 if (isset($str) && $str<>'') {
2372 // get the id from the table if it is already there
2373 $md5key = md5($str);
2374 if (!$id = get_field('hotpot_strings', 'id', 'md5key', $md5key, 'string', $str)) {
2376 // create a string record
2377 $record = new stdClass();
2378 $record->string = $str;
2379 $record->md5key
= $md5key;
2381 // try and add the new string record
2382 if (!$id = insert_record('hotpot_strings', $record)) {
2384 error("Could not add string record for '".htmlspecialchars($str)."': ".$db->ErrorMsg());
2391 function hotpot_get_view_actions() {
2392 return array('view','view all','report');
2395 function hotpot_get_post_actions() {
2396 return array('attempt','review','submit');
2399 if (!function_exists('file_get_contents')) {
2400 // add this function for php version<4.3
2401 function file_get_contents($filepath) {
2402 $contents = file($filepath);
2403 if (is_array($contents)) {
2404 $contents = implode('', $contents);
2409 if (!function_exists('html_entity_decode')) {
2410 // add this function for php version<4.3
2411 function html_entity_decode($str) {
2412 $t = get_html_translation_table(HTML_ENTITIES
);
2413 $t = array_flip($t);
2414 return strtr($str, $t);
2419 // required for Moodle 1.x
2420 if (!isset($CFG->pixpath
)) {
2421 $CFG->pixpath
= "$CFG->wwwroot/pix";
2424 if (!function_exists('fullname')) {
2425 // add this function for Moodle 1.x
2426 function fullname($user) {
2427 return "$user->firstname $user->lastname";
2430 if (!function_exists('get_user_preferences')) {
2431 // add this function for Moodle 1.x
2432 function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
2436 if (!function_exists('set_user_preference')) {
2437 // add this function for Moodle 1.x
2438 function set_user_preference($name, $value, $otheruser=NULL) {
2442 if (!function_exists('get_coursemodule_from_id')) {
2443 // add this function for Moodle < 1.5.4
2444 function get_coursemodule_from_id($modulename, $cmid, $courseid=0) {
2446 return get_record_sql("
2448 cm.*, m.name, md.name as modname
2450 {$CFG->prefix}course_modules cm,
2451 {$CFG->prefix}modules md,
2452 {$CFG->prefix}$modulename m
2454 ".($courseid ?
"cm.course = '$courseid' AND " : '')."
2456 cm.instance = m.id AND
2457 md.name = '$modulename' AND
2462 if (!function_exists('get_coursemodule_from_instance')) {
2463 // add this function for Moodle < 1.5.4
2464 function get_coursemodule_from_instance($modulename, $instance, $courseid=0) {
2466 return get_record_sql("
2468 cm.*, m.name, md.name as modname
2470 {$CFG->prefix}course_modules cm,
2471 {$CFG->prefix}modules md,
2472 {$CFG->prefix}$modulename m
2474 ".($courseid ?
"cm.course = '$courseid' AND" : '')."
2475 cm.instance = m.id AND
2476 md.name = '$modulename' AND
2477 md.id = cm.module AND
2482 function hotpot_utf8_to_html_entity($char) {
2483 // http://www.zend.com/codex.php?id=835&single=1
2485 // array used to figure what number to decrement from character order value
2486 // according to number of characters used to map unicode to ascii by utf-8
2487 static $HOTPOT_UTF8_DECREMENT = array(
2488 1=>0, 2=>192, 3=>224, 4=>240
2491 // the number of bits to shift each character by
2492 static $HOTPOT_UTF8_SHIFT = array(
2494 2=>array(0=>6, 1=>0),
2495 3=>array(0=>12, 1=>6, 2=>0),
2496 4=>array(0=>18, 1=>12, 2=>6, 3=>0)
2500 $len = strlen($char);
2501 for ($pos=0; $pos<$len; $pos++
) {
2502 $ord = ord ($char{$pos});
2503 $ord -= ($pos ?
128 : $HOTPOT_UTF8_DECREMENT[$len]);
2504 $dec +
= ($ord << $HOTPOT_UTF8_SHIFT[$len][$pos]);
2506 return '&#x'.sprintf('%04X', $dec).';';
2509 function hotpot_print_show_links($course, $location, $reference, $actions='', $spacer=' ', $new_window=false, $return=false) {
2511 if (is_string($actions)) {
2512 if (empty($actions)) {
2513 $actions = 'showxmlsource,showxmltree,showhtmlsource';
2515 $actions = explode(',', $actions);
2517 $strenterafilename = get_string('enterafilename', 'hotpot');
2518 $html = <<<END_OF_SCRIPT
2519 <script type="text/javascript">
2521 function setLink(lnk) {
2523 if (document.forms['mform1']) {
2524 var form = document.forms['mform1'];
2525 } else if (document.forms['form']) {
2526 var form = document.forms['form'];
2528 return setLinkAttribute(lnk, 'reference', form) && setLinkAttribute(lnk, 'location', form);
2530 function setLinkAttribute(lnk, name, form) {
2531 // set link attribute value using
2532 // f(orm) name and e(lement) name
2534 var r = true; // result
2536 var obj = (form) ? form.elements[name] : null;
2539 var v = getObjValue(obj);
2541 alert('$strenterafilename');
2544 var i = s.indexOf('?');
2546 i = s.indexOf(name+'=', i+1);
2549 var ii = s.indexOf('&', i);
2553 lnk.href = s.substring(0, i) + v + s.substring(ii);
2561 function getObjValue(obj) {
2562 var v = ''; // the value
2563 var t = (obj && obj.type) ? obj.type : "";
2564 if (t=="text" || t=="textarea" || t=="hidden") {
2566 } else if (t=="select-one" || t=="select-multiple") {
2567 var l = obj.options.length;
2568 for (var i=0; i<l; i++) {
2569 if (obj.options[i].selected) {
2570 v += (v=="" ? "" : ",") + obj.options[i].value;
2576 function getDir(s) {
2577 if (s.charAt(0)!='/') {
2580 var i = s.lastIndexOf('/');
2581 return s.substring(0, i);
2587 foreach ($actions as $action) {
2590 . $CFG->wwwroot.'/mod/hotpot/show.php'
2591 . '?course='.$course.'&location='.$location.'&reference='.urlencode($reference).'&action='.$action
2593 . ' onclick="return setLink(this);"'
2594 . ($new_window ? ' target="_blank"' : '')
2595 . '>'.get_string($action, 'hotpot').'</a>'
2598 $html = '<span class="helplink">'.$html.'</span>';
2607 * Returns all other caps used in module
2609 function hotpot_get_extra_capabilities() {
2610 return array('moodle/site:accessallgroups');