| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?PHP // $Id: lib.php,v 1.79.2.15 2008/07/24 21:58:05 skodak Exp $ 2 3 ////////////////////////////////// 4 /// CONFIGURATION settings 5 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 } 12 13 ////////////////////////////////// 14 /// CONSTANTS and GLOBAL VARIABLES 15 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 } 23 24 define("HOTPOT_JS", "$CFG->wwwroot/mod/hotpot/hotpot-full.js"); 25 26 define("HOTPOT_NO", "0"); 27 define("HOTPOT_YES", "1"); 28 29 define ("HOTPOT_TEXTSOURCE_QUIZ", "0"); 30 define ("HOTPOT_TEXTSOURCE_FILENAME", "1"); 31 define ("HOTPOT_TEXTSOURCE_FILEPATH", "2"); 32 define ("HOTPOT_TEXTSOURCE_SPECIFIC", "3"); 33 34 define("HOTPOT_LOCATION_COURSEFILES", "0"); 35 define("HOTPOT_LOCATION_SITEFILES", "1"); 36 37 $HOTPOT_LOCATION = array ( 38 HOTPOT_LOCATION_COURSEFILES => get_string("coursefiles"), 39 HOTPOT_LOCATION_SITEFILES => get_string("sitefiles"), 40 ); 41 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"); 51 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"); 88 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 ); 97 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"); 106 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 ); 117 118 define("HOTPOT_JQUIZ_MULTICHOICE", "1"); 119 define("HOTPOT_JQUIZ_SHORTANSWER", "2"); 120 define("HOTPOT_JQUIZ_HYBRID", "3"); 121 define("HOTPOT_JQUIZ_MULTISELECT", "4"); 122 123 define("HOTPOT_GRADEMETHOD_HIGHEST", "1"); 124 define("HOTPOT_GRADEMETHOD_AVERAGE", "2"); 125 define("HOTPOT_GRADEMETHOD_FIRST", "3"); 126 define("HOTPOT_GRADEMETHOD_LAST", "4"); 127 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 ); 134 135 define("HOTPOT_STATUS_INPROGRESS", "1"); 136 define("HOTPOT_STATUS_TIMEDOUT", "2"); 137 define("HOTPOT_STATUS_ABANDONED", "3"); 138 define("HOTPOT_STATUS_COMPLETED", "4"); 139 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 ); 146 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"); 152 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]); 162 } 163 164 define("HOTPOT_DISPLAYNEXT_QUIZ", "0"); 165 define("HOTPOT_DISPLAYNEXT_COURSE", "1"); 166 define("HOTPOT_DISPLAYNEXT_INDEX", "2"); 167 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 173 174 ////////////////////////////////// 175 /// CORE FUNCTIONS 176 177 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) 185 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 196 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; 208 } 209 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; 221 } 222 function hotpot_update_events($hotpot) { 223 224 // remove any previous calendar events for this hotpot 225 delete_records('event', 'modulename', 'hotpot', 'instance', $hotpot->id); 226 227 $event = new stdClass(); 228 $event->description = addslashes($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 } 240 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); 245 246 if ($event->timeduration > HOTPOT_MAX_EVENT_LENGTH) { /// Long durations create two events 247 248 $event->name = addslashes($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 = addslashes($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 } 273 } 274 275 function hotpot_set_form_values(&$hotpot) { 276 $ok = true; 277 $hotpot->errors = array(); // these will be reported by moderr.html 278 279 if (empty($hotpot->reference)) { 280 $ok = false; 281 $hotpot->errors['reference']= get_string('error_nofilename', 'hotpot'); 282 } 283 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 } 297 298 $time = time(); 299 $hotpot->timecreated = $time; 300 $hotpot->timemodified = $time; 301 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 } 324 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 } 342 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; 366 } 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(); 372 } else { 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(); 376 } 377 } 378 379 // get ids of hotpot modules in this section 380 $ids = array(); 381 foreach ($hotpot_modules as $hotpot_module) { 382 $ids[] = $hotpot_module->instance; 383 } 384 385 // get details of hotpots in this section 386 if (empty($ids)) { 387 $hotpots = array(); 388 } else { 389 $hotpots = get_records_list('hotpot', 'id', implode(',', $ids)); 390 } 391 392 $found = false; 393 $chain = array(); 394 395 // loop through course_modules in this section 396 $ids = explode(',', $course_module_ids); 397 foreach ($ids as $id) { 398 399 // check this course_module is a hotpot activity 400 if (isset($hotpot_modules[$id])) { 401 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]; 406 407 // set $found, if this is the course module we're looking for 408 if (isset($cm->coursemodule)) { 409 if ($id==$cm->coursemodule) { 410 $found = true; 411 } 412 } else { 413 if ($id==$cm->id) { 414 $found = true; 415 } 416 } 417 418 // is this the end of a chain 419 if (empty($hotpots[$hotpot_id]->shownextquiz)) { 420 if ($found) { 421 break; // out of loop 422 } else { 423 // restart chain (target cm has not been found yet) 424 $chain = array(); 425 } 426 } 427 } 428 } // end foreach $ids 429 430 return $found ? $chain : false; 431 } 432 function hotpot_is_visible(&$cm) { 433 global $CFG, $COURSE; 434 435 // check grouping 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 441 } else { 442 // user is not in the required grouping and does not have sufficiently privileges to view this hotpot activity 443 return false; 444 } 445 446 // check if user can view hidden activities 447 if (isset($COURSE->context)) { 448 $coursecontext = &$COURSE->context; 449 } else { 450 $coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course); 451 } 452 if (has_capability('moodle/course:viewhiddenactivities', $coursecontext)) { 453 return true; // user can view hidden activities 454 } 455 456 if (!isset($cm->sectionvisible)) { 457 if (! $section = get_record('course_sections', 'id', $cm->section)) { 458 error('Course module record contains invalid section'); 459 } 460 $cm->sectionvisible = $section->visible; 461 } 462 if (empty($cm->sectionvisible)) { 463 $visible = HOTPOT_NO; 464 } else { 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; 470 } 471 } 472 } 473 return $visible; 474 } 475 function hotpot_add_chain(&$hotpot) { 476 /// add a chain of hotpot actiivities 477 478 global $CFG, $course; 479 480 $ok = true; 481 $hotpot->names = array(); 482 $hotpot->summaries = array(); 483 $hotpot->references = array(); 484 485 $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false); 486 487 if (isset($xml_quiz->error)) { 488 $hotpot->errors['reference'] = $xml_quiz->error; 489 $ok = false; 490 491 } else if (is_dir($xml_quiz->filepath)) { 492 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"; 498 } 499 } 500 closedir($dh); 501 502 // get titles 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; 508 } 509 510 } else { 511 $ok = false; 512 $hotpot->errors['reference'] = get_string('error_couldnotopenfolder', 'hotpot', $hotpot->reference); 513 } 514 515 } else if (is_file($xml_quiz->filepath)) { 516 517 $filerootlength = strlen($xml_quiz->fileroot) + 1; 518 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); 524 525 if ($hotpot->nextexercise) { 526 $filepath = $xml_quiz->fileroot.'/'.$xml_quiz->filesubdir.$hotpot->nextexercise; 527 528 // check file is not already in chain 529 $reference = substr($filepath, $filerootlength); 530 if (in_array($reference, $hotpot->references)) { 531 $filepath = ''; 532 } 533 } else { 534 $filepath = ''; 535 } 536 if ($filepath && file_exists($filepath) && is_file($filepath) && is_readable($filepath)) { 537 $xml_quiz->filepath = $filepath; 538 } else { 539 $xml_quiz->filepath = false; // finish while loop 540 } 541 } // end while 542 543 } else { 544 $ok = false; 545 $hotpot->errors['reference'] = get_string('error_notfileorfolder', 'hotpot', $hotpot->reference); 546 } 547 548 if (empty($hotpot->references) && empty($hotpot->errors['reference'])) { 549 $ok = false; 550 $hotpot->errors['reference'] = get_string('error_noquizzesfound', 'hotpot', $hotpot->reference); 551 } 552 553 if ($ok) { 554 $hotpot->visible = HOTPOT_YES; 555 556 if (trim($hotpot->name)=='') { 557 $hotpot->name = get_string("modulename", $hotpot->modulename); 558 } 559 $hotpot->specificname = $hotpot->name; 560 $hotpot->specificsummary = $hotpot->summary; 561 562 // add all except last activity in chain 563 564 $i_max = count($hotpot->references)-1; 565 for ($i=0; $i<$i_max; $i++) { 566 567 hotpot_set_name_summary_reference($hotpot, $i); 568 $hotpot->reference = addslashes($hotpot->reference); 569 570 if (!$hotpot->instance = insert_record("hotpot", $hotpot)) { 571 error("Could not add a new instance of $hotpot->modulename", "view.php?id=$hotpot->course"); 572 } 573 574 // store (hotpot table) id of start of chain 575 if ($i==0) { 576 $hotpot->startofchain = $hotpot->instance; 577 } 578 579 if (isset($course->groupmode)) { 580 $hotpot->groupmode = $course->groupmode; 581 } 582 583 if (! $hotpot->coursemodule = add_course_module($hotpot)) { 584 error("Could not add a new course module"); 585 } 586 if (! $sectionid = add_mod_to_section($hotpot) ) { 587 error("Could not add the new course module to that section"); 588 } 589 590 if (! set_field("course_modules", "section", $sectionid, "id", $hotpot->coursemodule)) { 591 error("Could not update the course module with the correct section"); 592 } 593 594 add_to_log($hotpot->course, "course", "add mod", 595 "../mod/$hotpot->modulename/view.php?id=$hotpot->coursemodule", 596 "$hotpot->modulename $hotpot->instance" 597 ); 598 add_to_log($hotpot->course, $hotpot->modulename, "add", 599 "view.php?id=$hotpot->coursemodule", 600 "$hotpot->instance", $hotpot->coursemodule 601 ); 602 603 // hide tail of chain 604 if ($hotpot->shownextquiz==HOTPOT_YES) { 605 $hotpot->visible = HOTPOT_NO; 606 } 607 } // end for ($hotpot->references) 608 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; 613 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"; 618 } 619 } // end if $ok 620 621 return $ok; 622 } 623 function hotpot_set_name_summary_reference(&$hotpot, $chain_index=NULL) { 624 625 $xml_quiz = NULL; 626 627 $textfields = array('name', 'summary'); 628 foreach ($textfields as $textfield) { 629 630 $textsource = $textfield.'source'; 631 632 // are we adding a chain? 633 if (isset($chain_index)) { 634 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]; 641 } 642 break; 643 case HOTPOT_TEXTSOURCE_SPECIFIC: 644 $specifictext = 'specific'.$textfield; 645 if (empty($hotpot->$specifictext) && trim($hotpot->$specifictext)=='') { 646 $hotpot->$textfield = ''; 647 } else { 648 $hotpot->$textfield = $hotpot->$specifictext.' ('.($chain_index+1).')'; 649 } 650 break; 651 } 652 $hotpot->reference = $hotpot->references[$chain_index]; 653 } 654 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); 659 } 660 if ($textfield=='name') { 661 $hotpot->$textfield = addslashes($hotpot->exercisetitle); 662 } else if ($textfield=='summary') { 663 $hotpot->$textfield = addslashes($hotpot->exercisesubtitle); 664 } 665 } 666 switch ($hotpot->$textsource) { 667 case HOTPOT_TEXTSOURCE_FILENAME: 668 $hotpot->$textfield = basename($hotpot->reference); 669 break; 670 case HOTPOT_TEXTSOURCE_FILEPATH: 671 $hotpot->$textfield = ''; 672 // continue to next lines 673 default: 674 if (empty($hotpot->$textfield)) { 675 $hotpot->$textfield = str_replace('/', ' ', $hotpot->reference); 676 } 677 } // end switch 678 } // end foreach 679 } 680 function hotpot_get_titles_and_next_ex(&$hotpot, $filepath, $get_next=false) { 681 682 $hotpot->exercisetitle = ''; 683 $hotpot->exercisesubtitle = ''; 684 $hotpot->nextexercise = ''; 685 686 // read the quiz file source 687 if ($source = file_get_contents($filepath)) { 688 689 $next = ''; 690 $title = ''; 691 $subtitle = ''; 692 693 if (preg_match('|\.html?$|', $filepath)) { 694 // html file 695 if (preg_match('|<h2[^>]*class="ExerciseTitle"[^>]*>(.*?)</h2>|is', $source, $matches)) { 696 $title = trim(strip_tags($matches[1])); 697 } 698 if (empty($title)) { 699 if (preg_match('|<title[^>]*>(.*?)</title>|is', $source, $matches)) { 700 $title = trim(strip_tags($matches[1])); 701 } 702 } 703 if (preg_match('|<h3[^>]*class="ExerciseSubtitle"[^>]*>(.*?)</h3>|is', $source, $matches)) { 704 $subtitle = trim(strip_tags($matches[1])); 705 } 706 if ($get_next) { 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]; 712 } 713 } 714 } 715 716 } else { 717 // xml file (...maybe) 718 $xml_tree = new hotpot_xml_tree($source); 719 $xml_tree->filetype = ''; 720 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]); 727 break; 728 } 729 } 730 if ($xml_tree->filetype=='xml') { 731 732 $title = strip_tags($xml_tree->xml_value('data,title')); 733 $subtitle = $xml_tree->xml_value('hotpot-config-file,'.$xml_tree->quiztype.',exercise-subtitle'); 734 735 if ($get_next) { 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 741 } 742 } 743 } 744 } 745 } 746 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; 750 } 751 } 752 function hotpot_get_all_instances_in_course($modulename, $course) { 753 /// called from index.php 754 755 global $CFG; 756 $instances = array(); 757 758 if (isset($CFG->release) && substr($CFG->release, 0, 3)>=1.2) { 759 $groupmode = 'cm.groupmode,'; 760 } else { 761 $groupmode = ''; 762 } 763 764 $query = " 765 SELECT 766 cm.id AS coursemodule, 767 cm.course AS course, 768 cm.module AS module, 769 cm.instance AS instance, 770 -- cm.section AS section, 771 cm.visible AS visible, 772 $groupmode 773 -- cs.section AS sectionnumber, 774 cs.section AS section, 775 cs.sequence AS sequence, 776 cs.visible AS sectionvisible, 777 thismodule.* 778 FROM 779 {$CFG->prefix}course_modules cm, 780 {$CFG->prefix}course_sections cs, 781 {$CFG->prefix}modules m, 782 {$CFG->prefix}$modulename thismodule 783 WHERE 784 m.name = '$modulename' AND 785 m.id = cm.module AND 786 cm.course = '$course->id' AND 787 cm.section = cs.id AND 788 cm.instance = thismodule.id 789 "; 790 if ($rawmods = get_records_sql($query)) { 791 792 // cache $isteacher setting 793 794 $isteacher = has_capability('mod/hotpot:viewreport', get_context_instance(CONTEXT_COURSE, $course->id)); 795 796 $explodesection = array(); 797 $order = array(); 798 799 foreach ($rawmods as $rawmod) { 800 801 if (empty($explodesection[$rawmod->section])) { 802 $explodesection[$rawmod->section] = true; 803 804 $coursemodules = explode(',', $rawmod->sequence); 805 foreach ($coursemodules as $i=>$coursemodule) { 806 $order[$coursemodule] = sprintf('%d.%04d', $rawmod->section, $i); 807 } 808 } 809 810 if ($isteacher) { 811 $visible = true; 812 } else if ($modulename=='hotpot') { 813 $visible = hotpot_is_visible($rawmod); 814 } else { 815 $visible = $rawmod->visible; 816 } 817 818 if ($visible) { 819 $instances[$order[$rawmod->coursemodule]] = $rawmod; 820 } 821 822 } // end foreach $modinfo 823 824 ksort($instances); 825 $instances = array_values($instances); 826 } 827 828 return $instances; 829 } 830 831 function hotpot_update_chain(&$hotpot) { 832 /// update a chain of hotpot actiivities 833 834 $ok = true; 835 if ($hotpot_modules = hotpot_get_chain($hotpot)) { 836 837 // skip updating of these fields 838 $skipfields = array('id', 'course', 'name', 'reference', 'summary', 'shownextquiz'); 839 $fields = array(); 840 841 foreach ($hotpot_modules as $hotpot_module) { 842 843 if ($hotpot->instance==$hotpot_module->id) { 844 // don't need to update this hotpot 845 846 } else { 847 // shortcut to hotpot record 848 $thishotpot = &$hotpot_module->hotpot; 849 850 // get a list of fields to update (first time only) 851 if (empty($fields)) { 852 $fields = array_keys(get_object_vars($thishotpot)); 853 } 854 855 // assume update is NOT required 856 $require_update = false; 857 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 862 } else { 863 $require_update = true; 864 $thishotpot->$field = $hotpot->$field; 865 } 866 } 867 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"); 871 } 872 } 873 } // end foreach $ids 874 } 875 return $ok; 876 } 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. 881 882 if (! $hotpot = get_record("hotpot", "id", $id)) { 883 return false; 884 } 885 886 if (! delete_records("hotpot", "id", "$id")) { 887 return false; 888 } 889 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)"); 896 } 897 898 // remove calendar events for this hotpot 899 delete_records('event', 'modulename', 'hotpot', 'instance', $id); 900 901 // remove grade item for this hotpot 902 hotpot_grade_item_delete($hotpot); 903 904 return true; 905 } 906 function hotpot_delete_and_notify($table, $select, $strtable) { 907 $count = max(0, count_records_select($table, $select)); 908 if ($count) { 909 delete_records_select($table, $select); 910 $count -= max(0, count_records_select($table, $select)); 911 if ($count) { 912 notify(get_string('deleted')." $count x $strtable"); 913 } 914 } 915 } 916 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. 920 921 $report = hotpot_user_outline($course, $user, $mod, $hotpot); 922 if (empty($report)) { 923 print get_string("noactivity", "hotpot"); 924 } else { 925 $date = userdate($report->time, get_string('strftimerecentfull')); 926 print $report->info.' '.get_string('mostrecently').': '.$date; 927 } 928 return true; 929 } 930 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 937 938 $report = NULL; 939 if ($records = get_records_select("hotpot_attempts", "hotpot='$hotpot->id' AND userid='$user->id'", "timestart ASC", "*")) { 940 $report = new stdClass(); 941 $scores = array(); 942 foreach ($records as $record){ 943 if (empty($report->time)) { 944 $report->time = $record->timestart; 945 } 946 $scores[] = hotpot_format_score($record); 947 } 948 if (empty($scores)) { 949 $report->time = 0; 950 $report->info = get_string('noactivity', 'hotpot'); 951 } else { 952 $report->info = get_string('score', 'quiz').': '.implode(', ', $scores); 953 } 954 } 955 return $report; 956 } 957 958 function hotpot_format_score($record, $undefined=' ') { 959 if (isset($record->score)) { 960 $score = $record->score; 961 } else { 962 $score = $undefined; 963 } 964 return $score; 965 } 966 967 function hotpot_format_status($record, $undefined=' ') { 968 global $HOTPOT_STATUS; 969 970 if (isset($record->status) || isset($HOTPOT_STATUS[$record->status])) { 971 $status = $HOTPOT_STATUS[$record->status]; 972 } else { 973 $status = $undefined; 974 } 975 return $status; 976 } 977 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. 982 983 global $CFG; 984 $result = false; 985 986 $records = get_records_sql(" 987 SELECT 988 h.id AS id, 989 h.name AS name, 990 COUNT(*) AS count_attempts 991 FROM 992 {$CFG->prefix}hotpot h, 993 {$CFG->prefix}hotpot_attempts a 994 WHERE 995 h.course = $course->id 996 AND h.id = a.hotpot 997 AND a.id = a.clickreportid 998 AND a.starttime > $timestart 999 GROUP BY 1000 h.id, h.name 1001 "); 1002 // note that PostGreSQL requires h.name in the GROUP BY clause 1003 1004 if($records) { 1005 $names = array(); 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); 1009 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)"; 1015 } 1016 $names[] = $name; 1017 } 1018 } 1019 } 1020 if (count($names) > 0) { 1021 print_headline(get_string('modulenameplural', 'hotpot').':'); 1022 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>'; 1027 } 1028 $result = true; 1029 } 1030 } 1031 return $result; // True if anything was printed, otherwise false 1032 } 1033 1034 function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") { 1035 // Returns all quizzes since a given time. 1036 1037 global $CFG; 1038 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'"; 1042 1043 $records = get_records_sql(" 1044 SELECT 1045 a.*, 1046 h.name, h.course, 1047 cm.instance, cm.section, 1048 u.firstname, u.lastname, u.picture 1049 FROM 1050 {$CFG->prefix}hotpot_attempts a, 1051 {$CFG->prefix}hotpot h, 1052 {$CFG->prefix}course_modules cm, 1053 {$CFG->prefix}user u 1054 WHERE 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 1062 ORDER BY 1063 a.timefinish ASC 1064 "); 1065 1066 if (!empty($records)) { 1067 foreach ($records as $record) { 1068 if (empty($groupid) || groups_is_member($groupid, $record->userid)) { 1069 1070 unset($activity); 1071 1072 $activity->type = "hotpot"; 1073 $activity->defaultindex = $index; 1074 $activity->instance = $record->hotpot; 1075 1076 $activity->name = $record->name; 1077 $activity->section = $record->section; 1078 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; 1084 1085 $activity->user->userid = $record->userid; 1086 $activity->user->fullname = fullname($record); 1087 $activity->user->picture = $record->picture; 1088 1089 $activity->timestamp = $record->timefinish; 1090 1091 $activities[] = $activity; 1092 1093 $index++; 1094 } 1095 } // end foreach 1096 } 1097 } 1098 1099 function hotpot_print_recent_mod_activity($activity, $course, $detail=false) { 1100 /// Basically, this function prints the results of "hotpot_get_recent_activity" 1101 1102 global $CFG, $THEME, $USER; 1103 1104 if (isset($THEME->cellcontent2)) { 1105 $bgcolor = ' bgcolor="'.$THEME->cellcontent2.'"'; 1106 } else { 1107 $bgcolor = ''; 1108 } 1109 1110 print '<table border="0" cellpadding="3" cellspacing="0">'; 1111 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">'; 1115 1116 if ($detail) { 1117 // activity icon 1118 $src = "$CFG->modpixpath/$activity->type/icon.gif"; 1119 print '<img src="'.$src.'" class="icon" alt="'.$activity->type.'" /> '; 1120 1121 // link to activity 1122 $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$activity->instance"; 1123 print '<a href="'.$href.'">'.$activity->name.'</a> - '; 1124 } 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> '; 1129 1130 // attempt number 1131 print get_string('attempt', 'quiz').' - '.$activity->content->attempt.'<br />'; 1132 } 1133 1134 // link to user 1135 $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$course"; 1136 print '<a href="'.$href.'">'.$activity->user->fullname.'</a> '; 1137 1138 // time and date 1139 print ' - ' . userdate($activity->timestamp); 1140 1141 // duration 1142 $duration = format_time($activity->content->timestart - $activity->content->timefinish); 1143 print " ($duration)"; 1144 1145 print "</font></td></tr>"; 1146 print "</table>"; 1147 } 1148 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 ... 1153 1154 global $CFG; 1155 1156 return true; 1157 } 1158 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. 1162 1163 $hotpot = get_record('hotpot', 'id', $hotpotid); 1164 $return->grades = hotpot_get_grades($hotpot); 1165 $return->maxgrade = $hotpot->grade; 1166 1167 return $return; 1168 } 1169 function hotpot_get_grades($hotpot, $user_ids='') { 1170 global $CFG; 1171 1172 $grades = array(); 1173 1174 $weighting = $hotpot->grade / 100; 1175 $precision = hotpot_get_precision($hotpot); 1176 1177 // set the SQL string to determine the $grade 1178 $grade = ""; 1179 switch ($hotpot->grademethod) { 1180 case HOTPOT_GRADEMETHOD_HIGHEST: 1181 $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade"; 1182 break; 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"; 1186 break; 1187 case HOTPOT_GRADEMETHOD_FIRST: 1188 $grade = "ROUND(score * $weighting, $precision)"; 1189 $grade = sql_concat('timestart', "'_'", $grade); 1190 $grade = "MIN($grade) AS grade"; 1191 break; 1192 case HOTPOT_GRADEMETHOD_LAST: 1193 $grade = "ROUND(score * $weighting, $precision)"; 1194 $grade = sql_concat('timestart', "'_'", $grade); 1195 $grade = "MAX($grade) AS grade"; 1196 break; 1197 } 1198 1199 if ($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 1205 GROUP BY userid 1206 "); 1207 if ($grades) { 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); 1212 } 1213 } 1214 } 1215 } 1216 1217 return $grades; 1218 } 1219 function hotpot_get_precision(&$hotpot) { 1220 return ($hotpot->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0; 1221 } 1222 1223 /** 1224 * Return grade for given user or all users. 1225 * 1226 * @param object $hotpot 1227 * @param int $userid optional user id, 0 means all users 1228 * @return array array of grades, false if none 1229 */ 1230 function hotpot_get_user_grades($hotpot, $userid=0) { 1231 $grades = array(); 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; 1238 } 1239 } 1240 if (count($grades)) { 1241 return $grades; 1242 } else { 1243 return false; 1244 } 1245 } 1246 1247 /** 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 1252 * 1253 * @param object $hotpot null means all hotpots 1254 * @param int $userid specific user only, 0 means all users 1255 */ 1256 function hotpot_update_grades($hotpot=null, $userid=0, $nullifnone=true) { 1257 global $CFG; 1258 if (! function_exists('grade_update')) { 1259 require_once($CFG->libdir.'/gradelib.php'); 1260 } 1261 if (is_null($hotpot)) { 1262 // update (=create) grades for all hotpots 1263 $sql = " 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" 1267 ; 1268 if ($rs = get_recordset_sql($sql)) { 1269 while ($hotpot = rs_fetch_next_record($rs)) { 1270 hotpot_update_grades($hotpot, 0, false); 1271 } 1272 rs_close($rs); 1273 } 1274 } else { 1275 // update (=create) grade for a single hotpot 1276 if ($grades = hotpot_get_user_grades($hotpot, $userid)) { 1277 hotpot_grade_item_update($hotpot, $grades); 1278 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); 1285 1286 } else { 1287 // no grades and no userid 1288 hotpot_grade_item_update($hotpot); 1289 } 1290 } 1291 } 1292 1293 /** 1294 * Update/create grade item for given hotpot 1295 * 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 1299 */ 1300 function hotpot_grade_item_update($hotpot, $grades=null) { 1301 global $CFG; 1302 if (! function_exists('grade_update')) { 1303 require_once($CFG->libdir.'/gradelib.php'); 1304 } 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; 1309 } 1310 if ($hotpot->grade > 0) { 1311 $params['gradetype'] = GRADE_TYPE_VALUE; 1312 $params['grademax'] = $hotpot->grade; 1313 $params['grademin'] = 0; 1314 1315 } else { 1316 $params['gradetype'] = GRADE_TYPE_NONE; 1317 } 1318 return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, $grades, $params); 1319 } 1320 1321 /** 1322 * Delete grade item for given hotpot 1323 * 1324 * @param object $hotpot object 1325 * @return object grade_item 1326 */ 1327 function hotpot_grade_item_delete($hotpot) { 1328 global $CFG; 1329 if (! function_exists('grade_update')) { 1330 require_once($CFG->libdir.'/gradelib.php'); 1331 } 1332 return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, null, array('deleted'=>1)); 1333 } 1334 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. 1340 global $CFG; 1341 1342 return get_records_sql(" 1343 SELECT DISTINCT 1344 u.id, u.id 1345 FROM 1346 {$CFG->prefix}user u, 1347 {$CFG->prefix}hotpot_attempts a 1348 WHERE 1349 u.id = a.userid 1350 AND a.hotpot = '$hotpotid' 1351 "); 1352 } 1353 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 1358 //as reference. 1359 1360 $report = false; 1361 1362 //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid"); 1363 // 1364 //if (!empty($rec) && !empty($scaleid)) { 1365 // $report = true; 1366 //} 1367 1368 return $report; 1369 } 1370 1371 /** 1372 * Checks if scale is being used by any instance of hotpot 1373 * 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 1377 */ 1378 function hotpot_scale_used_anywhere($scaleid) { 1379 return false; 1380 } 1381 1382 ////////////////////////////////////////////////////////// 1383 /// Any other hotpot functions go here. 1384 /// Each of them must have a name that starts with hotpot 1385 1386 1387 function hotpot_add_attempt($hotpotid) { 1388 global $db, $CFG, $USER; 1389 1390 // get start time of this attempt 1391 $time = time(); 1392 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; 1398 } 1399 if ($attempt->clickreportid==0) { 1400 $attempt->clickreportid = $attempt->id; 1401 } 1402 $attempt->status = HOTPOT_STATUS_ABANDONED; 1403 update_record('hotpot_attempts', $attempt); 1404 } 1405 } 1406 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; 1413 1414 return insert_record("hotpot_attempts", $attempt); 1415 } 1416 function hotpot_get_next_attempt($hotpotid) { 1417 global $USER; 1418 1419 // get max attempt so far 1420 $i = count_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id'", 'MAX(attempt)'); 1421 1422 return empty($i) ? 1 : ($i+1); 1423 } 1424 function hotpot_get_question_name($question) { 1425 $name = ''; 1426 if (isset($question->text)) { 1427 $name = hotpot_strings($question->text); 1428 } 1429 if (empty($name)) { 1430 $name = $question->name; 1431 } 1432 return $name; 1433 } 1434 function hotpot_strings($ids) { 1435 1436 // array of ids of empty strings 1437 static $HOTPOT_EMPTYSTRINGS; 1438 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); 1443 } 1444 1445 $strings = array(); 1446 if (!empty($ids)) { 1447 $ids = explode(',', $ids); 1448 foreach ($ids as $id) { 1449 if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) { 1450 $strings[] = hotpot_string($id); 1451 } 1452 } 1453 } 1454 return implode(',', $strings); 1455 } 1456 function hotpot_string($id) { 1457 return get_field('hotpot_strings', 'string', 'id', $id); 1458 } 1459 1460 ////////////////////////////////////////////////////////////////////////////////////// 1461 /// the class definitions to handle XML trees 1462 1463 // get the standard XML parser supplied with Moodle 1464 require_once("$CFG->libdir/xmlize.php"); 1465 1466 // get the default class for hotpot quiz templates 1467 require_once("$CFG->hotpottemplate/default.php"); 1468 1469 class hotpot_xml_tree { 1470 function hotpot_xml_tree($str, $xml_root='') { 1471 if (empty($str)) { 1472 $this->xml = array(); 1473 } else { 1474 if (empty($CFG->unicodedb)) { 1475 $str = utf8_encode($str); 1476 } 1477 $this->xml = xmlize($str, 0); 1478 } 1479 $this->xml_root = $xml_root; 1480 } 1481 function xml_value($tags, $more_tags="[0]['#']") { 1482 1483 $tags = empty($tags) ? '' : "['".str_replace(",", "'][0]['#']['", $tags)."']"; 1484 eval('$value = &$this->xml'.$this->xml_root.$tags.$more_tags.';'); 1485 1486 if (is_string($value)) { 1487 if (empty($CFG->unicodedb)) { 1488 $value = utf8_decode($value); 1489 } 1490 1491 // decode angle brackets 1492 $value = strtr($value, array('<'=>'<', '>'=>'>', '&'=>'&')); 1493 1494 // remove white space between <table>, <ul|OL|DL> and <OBJECT|EMBED> parts 1495 // (so it doesn't get converted to <br />) 1496 $htmltags = '(' 1497 . 'TABLE|/?CAPTION|/?COL|/?COLGROUP|/?TBODY|/?TFOOT|/?THEAD|/?TD|/?TH|/?TR' 1498 . '|OL|UL|/?LI' 1499 . '|DL|/?DT|/?DD' 1500 . '|EMBED|OBJECT|APPLET|/?PARAM' 1501 //. '|SELECT|/?OPTION' 1502 //. '|FIELDSET|/?LEGEND' 1503 //. '|FRAMESET|/?FRAME' 1504 . ')' 1505 ; 1506 1507 $space = '(\s|(<br[^>]*>))+'; 1508 $search = '#(<'.$htmltags.'[^>]*'.'>)'.$space.'(?='.'<)#is'; 1509 $value = preg_replace($search, '\\1', $value); 1510 1511 // replace remaining newlines with <br /> 1512 $value = str_replace("\n", '<br />', $value); 1513 1514 // encode unicode characters as HTML entities 1515 // (in particular, accented charaters that have not been encoded by HP) 1516 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); 1527 } 1528 return $value; 1529 } 1530 function xml_values($tags) { 1531 $i = 0; 1532 $values = array(); 1533 while ($value = $this->xml_value($tags, "[$i]['#']")) { 1534 $values[$i++] = $value; 1535 } 1536 return $values; 1537 } 1538 function obj_value(&$obj, $name) { 1539 return is_object($obj) ? @$obj->$name : (is_array($obj) ? @$obj[$name] : NULL); 1540 } 1541 function encode_cdata(&$str, $tag) { 1542 1543 // conversion tables 1544 static $HTML_ENTITIES = array( 1545 ''' => "'", 1546 '"' => '"', 1547 '<' => '<', 1548 '>' => '>', 1549 '&' => '&', 1550 ); 1551 static $ILLEGAL_STRINGS = array( 1552 "\r\n" => '<br />', 1553 "\r" => '<br />', 1554 "\n" => '<br />', 1555 '[' => '[', 1556 ']' => ']' 1557 ); 1558 1559 // extract the $tag from the $str(ing), if possible 1560 $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is'; 1561 if (preg_match($pattern, $str, $matches)) { 1562 1563 // encode problematic CDATA chars and strings 1564 $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS); 1565 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]); 1572 1573 $str = $matches[1].$matches[2].$matches[3]; 1574 } 1575 } 1576 } 1577 1578 class hotpot_xml_quiz extends hotpot_xml_tree { 1579 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 1583 1584 global $CFG, $HOTPOT_OUTPUTFORMAT, $HOTPOT_OUTPUTFORMAT_DIR; 1585 1586 // check xmlize functions are available 1587 if (! function_exists("xmlize")) { 1588 error('xmlize functions are not available'); 1589 } 1590 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; 1596 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'); 1608 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); 1614 } 1615 return; 1616 } 1617 1618 $this->course_homeurl = "$CFG->wwwroot/course/view.php?id=$this->course"; 1619 1620 // set filedir, filename and filepath 1621 switch ($this->location) { 1622 case HOTPOT_LOCATION_SITEFILES: 1623 $site = get_site(); 1624 $this->filedir = $site->id; 1625 break; 1626 1627 case HOTPOT_LOCATION_COURSEFILES: 1628 default: 1629 $this->filedir = $this->course; 1630 break; 1631 } 1632 $this->filesubdir = dirname($this->reference); 1633 if ($this->filesubdir=='.') { 1634 $this->filesubdir = ''; 1635 } 1636 if ($this->filesubdir) { 1637 $this->filesubdir .= '/'; 1638 } 1639 $this->filename = basename($this->reference); 1640 $this->fileroot = "$CFG->dataroot/$this->filedir"; 1641 $this->filepath = "$this->fileroot/$this->reference"; 1642 1643 // read the file, if required 1644 if ($this->read_file) { 1645 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); 1650 } 1651 return; 1652 } 1653 1654 // read in the XML source 1655 $this->source = file_get_contents($this->filepath); 1656 1657 // convert relative URLs to absolute URLs 1658 if ($this->convert_urls) { 1659 $this->hotpot_convert_relative_urls($this->source); 1660 } 1661 1662 $this->html = ''; 1663 $this->quiztype = ''; 1664 $this->outputformat = 0; 1665 1666 // is this an html file? 1667 if (preg_match('|\.html?$|', $this->filename)) { 1668 1669 $this->filetype = 'html'; 1670 $this->html = &$this->source; 1671 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); 1676 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); 1681 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); 1686 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); 1691 1692 } else { 1693 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); 1698 1699 if ($this->parse_xml) { 1700 1701 $this->filetype = 'xml'; 1702 1703 // encode "gap fill" text in JCloze exercise 1704 $this->encode_cdata($this->source, 'gap-fill'); 1705 1706 // convert source to xml tree 1707 $this->hotpot_xml_tree($this->source); 1708 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']['#']"; 1714 break; 1715 } 1716 } 1717 } 1718 1719 if ($this->create_html) { 1720 1721 // set the real output format from the requested output format 1722 $this->real_outputformat = $this->obj_value($obj, 'outputformat'); 1723 $this->draganddrop = ''; 1724 if ( 1725 empty($this->real_outputformat) || 1726 $this->real_outputformat==HOTPOT_OUTPUTFORMAT_BEST || 1727 empty($HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat]) 1728 ) { 1729 if ($CFG->hotpotismobile && isset($HOTPOT_OUTPUTFORMAT_DIR[HOTPOT_OUTPUTFORMAT_MOBILE])) { 1730 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_MOBILE; 1731 } else { // PC 1732 if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') { 1733 $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS; 1734 } else { 1735