PageRenderTime 79ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/hotpot/lib.php

https://bitbucket.org/ceu/moodle_demo
PHP | 2724 lines | 2065 code | 344 blank | 315 comment | 360 complexity | 7153cb35f1f596cfbf3cf05a9839527b MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1
  1. <?PHP // $Id: lib.php,v 1.79.2.27 2012/05/19 11:04:52 moodlerobot Exp $
  2. //////////////////////////////////
  3. /// CONFIGURATION settings
  4. if (!isset($CFG->hotpot_showtimes)) {
  5. set_config("hotpot_showtimes", 0);
  6. }
  7. if (!isset($CFG->hotpot_excelencodings)) {
  8. set_config("hotpot_excelencodings", "");
  9. }
  10. //////////////////////////////////
  11. /// CONSTANTS and GLOBAL VARIABLES
  12. $CFG->hotpotroot = "$CFG->dirroot/mod/hotpot";
  13. $CFG->hotpottemplate = "$CFG->hotpotroot/template";
  14. if (!empty($_SERVER['HTTP_USER_AGENT'])) {
  15. $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']);
  16. } else {
  17. $CFG->hotpotismobile = false;
  18. }
  19. define("HOTPOT_JS", "$CFG->wwwroot/mod/hotpot/hotpot-full.js");
  20. define("HOTPOT_NO", "0");
  21. define("HOTPOT_YES", "1");
  22. define ("HOTPOT_TEXTSOURCE_QUIZ", "0");
  23. define ("HOTPOT_TEXTSOURCE_FILENAME", "1");
  24. define ("HOTPOT_TEXTSOURCE_FILEPATH", "2");
  25. define ("HOTPOT_TEXTSOURCE_SPECIFIC", "3");
  26. define("HOTPOT_LOCATION_COURSEFILES", "0");
  27. define("HOTPOT_LOCATION_SITEFILES", "1");
  28. $HOTPOT_LOCATION = array (
  29. HOTPOT_LOCATION_COURSEFILES => get_string("coursefiles"),
  30. HOTPOT_LOCATION_SITEFILES => get_string("sitefiles"),
  31. );
  32. define("HOTPOT_OUTPUTFORMAT_BEST", "1");
  33. define("HOTPOT_OUTPUTFORMAT_V3", "10");
  34. define("HOTPOT_OUTPUTFORMAT_V4", "11");
  35. define("HOTPOT_OUTPUTFORMAT_V5", "12");
  36. define("HOTPOT_OUTPUTFORMAT_V5_PLUS", "13");
  37. define("HOTPOT_OUTPUTFORMAT_V6", "14");
  38. define("HOTPOT_OUTPUTFORMAT_V6_PLUS", "15");
  39. define("HOTPOT_OUTPUTFORMAT_FLASH", "20");
  40. define("HOTPOT_OUTPUTFORMAT_MOBILE", "30");
  41. $HOTPOT_OUTPUTFORMAT = array (
  42. HOTPOT_OUTPUTFORMAT_BEST => get_string("outputformat_best", "hotpot"),
  43. HOTPOT_OUTPUTFORMAT_V6_PLUS => get_string("outputformat_v6_plus", "hotpot"),
  44. HOTPOT_OUTPUTFORMAT_V6 => get_string("outputformat_v6", "hotpot"),
  45. HOTPOT_OUTPUTFORMAT_V5_PLUS => get_string("outputformat_v5_plus", "hotpot"),
  46. HOTPOT_OUTPUTFORMAT_V5 => get_string("outputformat_v5", "hotpot"),
  47. HOTPOT_OUTPUTFORMAT_V4 => get_string("outputformat_v4", "hotpot"),
  48. HOTPOT_OUTPUTFORMAT_V3 => get_string("outputformat_v3", "hotpot"),
  49. HOTPOT_OUTPUTFORMAT_FLASH => get_string("outputformat_flash", "hotpot"),
  50. HOTPOT_OUTPUTFORMAT_MOBILE => get_string("outputformat_mobile", "hotpot"),
  51. );
  52. $HOTPOT_OUTPUTFORMAT_DIR = array (
  53. HOTPOT_OUTPUTFORMAT_V6_PLUS => 'v6',
  54. HOTPOT_OUTPUTFORMAT_V6 => 'v6',
  55. HOTPOT_OUTPUTFORMAT_V5_PLUS => 'v5',
  56. HOTPOT_OUTPUTFORMAT_V5 => 'v5',
  57. HOTPOT_OUTPUTFORMAT_V4 => 'v4',
  58. HOTPOT_OUTPUTFORMAT_V3 => 'v3',
  59. HOTPOT_OUTPUTFORMAT_FLASH => 'flash',
  60. HOTPOT_OUTPUTFORMAT_MOBILE => 'mobile',
  61. );
  62. foreach ($HOTPOT_OUTPUTFORMAT_DIR as $format=>$dir) {
  63. if (is_file("$CFG->hotpottemplate/$dir.php") && is_dir("$CFG->hotpottemplate/$dir")) {
  64. // do nothing ($format is available)
  65. } else {
  66. // $format is not available, so remove it
  67. unset($HOTPOT_OUTPUTFORMAT[$format]);
  68. unset($HOTPOT_OUTPUTFORMAT_DIR[$format]);
  69. }
  70. }
  71. define("HOTPOT_NAVIGATION_BAR", "1");
  72. define("HOTPOT_NAVIGATION_FRAME", "2");
  73. define("HOTPOT_NAVIGATION_IFRAME", "3");
  74. define("HOTPOT_NAVIGATION_BUTTONS", "4");
  75. define("HOTPOT_NAVIGATION_GIVEUP", "5");
  76. define("HOTPOT_NAVIGATION_NONE", "6");
  77. $HOTPOT_NAVIGATION = array (
  78. HOTPOT_NAVIGATION_BAR => get_string("navigation_bar", "hotpot"),
  79. HOTPOT_NAVIGATION_FRAME => get_string("navigation_frame", "hotpot"),
  80. HOTPOT_NAVIGATION_IFRAME => get_string("navigation_iframe", "hotpot"),
  81. HOTPOT_NAVIGATION_BUTTONS => get_string("navigation_buttons", "hotpot"),
  82. HOTPOT_NAVIGATION_GIVEUP => get_string("navigation_give_up", "hotpot"),
  83. HOTPOT_NAVIGATION_NONE => get_string("navigation_none", "hotpot"),
  84. );
  85. define("HOTPOT_JCB", "1");
  86. define("HOTPOT_JCLOZE", "2");
  87. define("HOTPOT_JCROSS", "3");
  88. define("HOTPOT_JMATCH", "4");
  89. define("HOTPOT_JMIX", "5");
  90. define("HOTPOT_JQUIZ", "6");
  91. define("HOTPOT_TEXTOYS_RHUBARB", "7");
  92. define("HOTPOT_TEXTOYS_SEQUITUR", "8");
  93. $HOTPOT_QUIZTYPE = array(
  94. HOTPOT_JCB => 'JCB',
  95. HOTPOT_JCLOZE => 'JCloze',
  96. HOTPOT_JCROSS => 'JCross',
  97. HOTPOT_JMATCH => 'JMatch',
  98. HOTPOT_JMIX => 'JMix',
  99. HOTPOT_JQUIZ => 'JQuiz',
  100. HOTPOT_TEXTOYS_RHUBARB => 'Rhubarb',
  101. HOTPOT_TEXTOYS_SEQUITUR => 'Sequitur'
  102. );
  103. define("HOTPOT_JQUIZ_MULTICHOICE", "1");
  104. define("HOTPOT_JQUIZ_SHORTANSWER", "2");
  105. define("HOTPOT_JQUIZ_HYBRID", "3");
  106. define("HOTPOT_JQUIZ_MULTISELECT", "4");
  107. define("HOTPOT_GRADEMETHOD_HIGHEST", "1");
  108. define("HOTPOT_GRADEMETHOD_AVERAGE", "2");
  109. define("HOTPOT_GRADEMETHOD_FIRST", "3");
  110. define("HOTPOT_GRADEMETHOD_LAST", "4");
  111. $HOTPOT_GRADEMETHOD = array (
  112. HOTPOT_GRADEMETHOD_HIGHEST => get_string("gradehighest", "quiz"),
  113. HOTPOT_GRADEMETHOD_AVERAGE => get_string("gradeaverage", "quiz"),
  114. HOTPOT_GRADEMETHOD_FIRST => get_string("attemptfirst", "quiz"),
  115. HOTPOT_GRADEMETHOD_LAST => get_string("attemptlast", "quiz"),
  116. );
  117. define("HOTPOT_STATUS_INPROGRESS", "1");
  118. define("HOTPOT_STATUS_TIMEDOUT", "2");
  119. define("HOTPOT_STATUS_ABANDONED", "3");
  120. define("HOTPOT_STATUS_COMPLETED", "4");
  121. $HOTPOT_STATUS = array (
  122. HOTPOT_STATUS_INPROGRESS => get_string("inprogress", "hotpot"),
  123. HOTPOT_STATUS_TIMEDOUT => get_string("timedout", "hotpot"),
  124. HOTPOT_STATUS_ABANDONED => get_string("abandoned", "hotpot"),
  125. HOTPOT_STATUS_COMPLETED => get_string("completed", "hotpot"),
  126. );
  127. define("HOTPOT_FEEDBACK_NONE", "0");
  128. define("HOTPOT_FEEDBACK_WEBPAGE", "1");
  129. define("HOTPOT_FEEDBACK_FORMMAIL", "2");
  130. define("HOTPOT_FEEDBACK_MOODLEFORUM", "3");
  131. define("HOTPOT_FEEDBACK_MOODLEMESSAGING", "4");
  132. $HOTPOT_FEEDBACK = array (
  133. HOTPOT_FEEDBACK_NONE => get_string("feedbacknone", "hotpot"),
  134. HOTPOT_FEEDBACK_WEBPAGE => get_string("feedbackwebpage", "hotpot"),
  135. HOTPOT_FEEDBACK_FORMMAIL => get_string("feedbackformmail", "hotpot"),
  136. HOTPOT_FEEDBACK_MOODLEFORUM => get_string("feedbackmoodleforum", "hotpot"),
  137. HOTPOT_FEEDBACK_MOODLEMESSAGING => get_string("feedbackmoodlemessaging", "hotpot"),
  138. );
  139. if (empty($CFG->messaging)) { // Moodle 1.4 (and less)
  140. unset($HOTPOT_FEEDBACK[HOTPOT_FEEDBACK_MOODLEMESSAGING]);
  141. }
  142. define("HOTPOT_DISPLAYNEXT_QUIZ", "0");
  143. define("HOTPOT_DISPLAYNEXT_COURSE", "1");
  144. define("HOTPOT_DISPLAYNEXT_INDEX", "2");
  145. /**
  146. * If start and end date for the quiz are more than this many seconds apart
  147. * they will be represented by two separate events in the calendar
  148. */
  149. define("HOTPOT_MAX_EVENT_LENGTH", "432000"); // 5 days maximum
  150. //////////////////////////////////
  151. /// CORE FUNCTIONS
  152. // possible return values:
  153. // false:
  154. // display moderr.html (if exists) OR "Could not update" and return to couse view
  155. // string:
  156. // display as error message and return to course view
  157. // true (or non-zero number):
  158. // continue to $hotpot->redirect (if set) OR hotpot/view.php (to displsay quiz)
  159. // $hotpot is an object containing the values of the form in mod.html
  160. // i.e. all the fields in the 'hotpot' table, plus the following:
  161. // $hotpot->course : an id in the 'course' table
  162. // $hotpot->coursemodule : an id in the 'course_modules' table
  163. // $hotpot->section : an id in the 'course_sections' table
  164. // $hotpot->module : an id in the 'modules' table
  165. // $hotpot->modulename : always 'hotpot'
  166. // $hotpot->instance : an id in the 'hotpot' table
  167. // $hotpot->mode : 'add' or 'update'
  168. // $hotpot->sesskey : unique string required for Moodle's session management
  169. function hotpot_add_instance(&$hotpot) {
  170. if (hotpot_set_form_values($hotpot)) {
  171. if ($result = insert_record('hotpot', $hotpot)) {
  172. $hotpot->id = $result;
  173. hotpot_update_events($hotpot);
  174. hotpot_grade_item_update(stripslashes_recursive($hotpot));
  175. }
  176. } else {
  177. $result= false;
  178. }
  179. return $result;
  180. }
  181. function hotpot_update_instance(&$hotpot) {
  182. if (hotpot_set_form_values($hotpot)) {
  183. $hotpot->id = $hotpot->instance;
  184. if ($result = update_record('hotpot', $hotpot)) {
  185. hotpot_update_events($hotpot);
  186. //hotpot_grade_item_update(stripslashes_recursive($hotpot));
  187. hotpot_update_grades(stripslashes_recursive($hotpot));
  188. }
  189. } else {
  190. $result= false;
  191. }
  192. return $result;
  193. }
  194. function hotpot_update_events($hotpot) {
  195. // remove any previous calendar events for this hotpot
  196. delete_records('event', 'modulename', 'hotpot', 'instance', $hotpot->id);
  197. $event = new stdClass();
  198. $event->description = addslashes($hotpot->summary);
  199. $event->courseid = $hotpot->course;
  200. $event->groupid = 0;
  201. $event->userid = 0;
  202. $event->modulename = 'hotpot';
  203. $event->instance = $hotpot->id;
  204. $event->timestart = $hotpot->timeopen;
  205. if ($cm = get_coursemodule_from_id('hotpot', $hotpot->id)) {
  206. $event->visible = hotpot_is_visible($cm);
  207. } else {
  208. $event->visible = 1;
  209. }
  210. if ($hotpot->timeclose && $hotpot->timeopen) {
  211. // we have both a start and an end date
  212. $event->eventtype = 'open';
  213. $event->timeduration = ($hotpot->timeclose - $hotpot->timeopen);
  214. if ($event->timeduration > HOTPOT_MAX_EVENT_LENGTH) { /// Long durations create two events
  215. $event->name = addslashes($hotpot->name).' ('.get_string('hotpotopens', 'hotpot').')';
  216. $event->timeduration = 0;
  217. add_event($event);
  218. $event->timestart = $hotpot->timeclose;
  219. $event->eventtype = 'close';
  220. $event->name = addslashes($hotpot->name).' ('.get_string('hotpotcloses', 'hotpot').')';
  221. unset($event->id);
  222. add_event($event);
  223. } else { // single event with duration
  224. $event->name = $hotpot->name;
  225. add_event($event);
  226. }
  227. } elseif ($hotpot->timeopen) { // only an open date
  228. $event->name = addslashes($hotpot->name).' ('.get_string('hotpotopens', 'hotpot').')';
  229. $event->eventtype = 'open';
  230. $event->timeduration = 0;
  231. add_event($event);
  232. } elseif ($hotpot->timeclose) { // only a closing date
  233. $event->name = addslashes($hotpot->name).' ('.get_string('hotpotcloses', 'hotpot').')';
  234. $event->timestart = $hotpot->timeclose;
  235. $event->eventtype = 'close';
  236. $event->timeduration = 0;
  237. add_event($event);
  238. }
  239. }
  240. function hotpot_set_form_values(&$hotpot) {
  241. $ok = true;
  242. $hotpot->errors = array(); // these will be reported by moderr.html
  243. if (empty($hotpot->reference)) {
  244. $ok = false;
  245. $hotpot->errors['reference']= get_string('error_nofilename', 'hotpot');
  246. }
  247. if (empty($hotpot->studentfeedbackurl) || $hotpot->studentfeedbackurl=='http://') {
  248. $hotpot->studentfeedbackurl = '';
  249. switch ($hotpot->studentfeedback) {
  250. case HOTPOT_FEEDBACK_WEBPAGE:
  251. $ok = false;
  252. $hotpot->errors['studentfeedbackurl']= get_string('error_nofeedbackurlwebpage', 'hotpot');
  253. break;
  254. case HOTPOT_FEEDBACK_FORMMAIL:
  255. $ok = false;
  256. $hotpot->errors['studentfeedbackurl']= get_string('error_nofeedbackurlformmail', 'hotpot');
  257. break;
  258. }
  259. }
  260. $time = time();
  261. $hotpot->timecreated = $time;
  262. $hotpot->timemodified = $time;
  263. if (empty($hotpot->mode)) {
  264. // moodle 1.9 (from mod_form.lib)
  265. if ($hotpot->add) {
  266. $hotpot->mode = 'add';
  267. } else if ($hotpot->update) {
  268. $hotpot->mode = 'update';
  269. } else {
  270. $hotpot->mode = '';
  271. }
  272. }
  273. if ($hotpot->quizchain==HOTPOT_YES) {
  274. switch ($hotpot->mode) {
  275. case 'add':
  276. $ok = hotpot_add_chain($hotpot);
  277. break;
  278. case 'update':
  279. $ok = hotpot_update_chain($hotpot);
  280. break;
  281. }
  282. } else { // $hotpot->quizchain==HOTPOT_NO
  283. hotpot_set_name_summary_reference($hotpot);
  284. }
  285. if (isset($hotpot->displaynext)) {
  286. switch ($hotpot->displaynext) {
  287. // N.B. redirection only works for Moodle 1.5+
  288. case HOTPOT_DISPLAYNEXT_COURSE:
  289. $hotpot->redirect = true;
  290. $hotpot->redirecturl = "view.php?id=$hotpot->course";
  291. break;
  292. case HOTPOT_DISPLAYNEXT_INDEX:
  293. $hotpot->redirect = true;
  294. $hotpot->redirecturl = "../mod/hotpot/index.php?id=$hotpot->course";
  295. break;
  296. default:
  297. // use Moodle default action (i.e. go on to display the hotpot quiz)
  298. }
  299. } else {
  300. $hotpot->displaynext = HOTPOT_DISPLAYNEXT_QUIZ;
  301. }
  302. // if ($ok && $hotpot->setdefaults) {
  303. if ($ok) {
  304. set_user_preference('hotpot_timeopen', $hotpot->timeopen);
  305. set_user_preference('hotpot_timeclose', $hotpot->timeclose);
  306. set_user_preference('hotpot_navigation', $hotpot->navigation);
  307. set_user_preference('hotpot_outputformat', $hotpot->outputformat);
  308. set_user_preference('hotpot_studentfeedback', $hotpot->studentfeedback);
  309. set_user_preference('hotpot_studentfeedbackurl', $hotpot->studentfeedbackurl);
  310. set_user_preference('hotpot_forceplugins', $hotpot->forceplugins);
  311. set_user_preference('hotpot_shownextquiz', $hotpot->shownextquiz);
  312. set_user_preference('hotpot_review', $hotpot->review);
  313. set_user_preference('hotpot_grade', $hotpot->grade);
  314. set_user_preference('hotpot_grademethod', $hotpot->grademethod);
  315. set_user_preference('hotpot_attempts', $hotpot->attempts);
  316. set_user_preference('hotpot_subnet', $hotpot->subnet);
  317. set_user_preference('hotpot_displaynext', $hotpot->displaynext);
  318. if ($hotpot->mode=='add') {
  319. set_user_preference('hotpot_quizchain', $hotpot->quizchain);
  320. set_user_preference('hotpot_namesource', $hotpot->namesource);
  321. set_user_preference('hotpot_summarysource', $hotpot->summarysource);
  322. }
  323. }
  324. return $ok;
  325. }
  326. function hotpot_get_chain(&$cm) {
  327. // get details of course_modules in this section
  328. $course_module_ids = get_field('course_sections', 'sequence', 'id', $cm->section);
  329. if (empty($course_module_ids)) {
  330. $hotpot_modules = array();
  331. } else {
  332. $hotpot_modules = get_records_select('course_modules', "id IN ($course_module_ids) AND module=$cm->module");
  333. if (empty($hotpot_modules)) {
  334. $hotpot_modules = array();
  335. }
  336. }
  337. // get ids of hotpot modules in this section
  338. $ids = array();
  339. foreach ($hotpot_modules as $hotpot_module) {
  340. $ids[] = $hotpot_module->instance;
  341. }
  342. // get details of hotpots in this section
  343. if (empty($ids)) {
  344. $hotpots = array();
  345. } else {
  346. $hotpots = get_records_list('hotpot', 'id', implode(',', $ids));
  347. }
  348. $found = false;
  349. $chain = array();
  350. // loop through course_modules in this section
  351. $ids = explode(',', $course_module_ids);
  352. foreach ($ids as $id) {
  353. // check this course_module is a hotpot activity
  354. if (isset($hotpot_modules[$id])) {
  355. // store details of this course module and hotpot activity
  356. $hotpot_id = $hotpot_modules[$id]->instance;
  357. $chain[$id] = &$hotpot_modules[$id];
  358. $chain[$id]->hotpot = &$hotpots[$hotpot_id];
  359. // set $found, if this is the course module we're looking for
  360. if (isset($cm->coursemodule)) {
  361. if ($id==$cm->coursemodule) {
  362. $found = true;
  363. }
  364. } else {
  365. if ($id==$cm->id) {
  366. $found = true;
  367. }
  368. }
  369. // is this the end of a chain
  370. if (empty($hotpots[$hotpot_id]->shownextquiz)) {
  371. if ($found) {
  372. break; // out of loop
  373. } else {
  374. // restart chain (target cm has not been found yet)
  375. $chain = array();
  376. }
  377. }
  378. }
  379. } // end foreach $ids
  380. return $found ? $chain : false;
  381. }
  382. function hotpot_is_visible(&$cm) {
  383. global $CFG, $COURSE;
  384. // check grouping
  385. $modulecontext = get_context_instance(CONTEXT_MODULE, $cm->id);
  386. if (empty($CFG->enablegroupings) || empty($cm->groupmembersonly) || has_capability('moodle/site:accessallgroups', $modulecontext)) {
  387. // groupings not applicable
  388. } else if (!isguestuser() && groups_has_membership($cm)) {
  389. // user is in one of the groups in the allowed grouping
  390. } else {
  391. // user is not in the required grouping and does not have sufficiently privileges to view this hotpot activity
  392. return false;
  393. }
  394. // check if user can view hidden activities
  395. if (isset($COURSE->context)) {
  396. $coursecontext = &$COURSE->context;
  397. } else {
  398. $coursecontext = get_context_instance(CONTEXT_COURSE, $cm->course);
  399. }
  400. if (has_capability('moodle/course:viewhiddenactivities', $coursecontext)) {
  401. return true; // user can view hidden activities
  402. }
  403. if (!isset($cm->sectionvisible)) {
  404. if (! $section = get_record('course_sections', 'id', $cm->section)) {
  405. error('Course module record contains invalid section');
  406. }
  407. $cm->sectionvisible = $section->visible;
  408. }
  409. if (empty($cm->sectionvisible)) {
  410. $visible = HOTPOT_NO;
  411. } else {
  412. $visible = HOTPOT_YES;
  413. if (empty($cm->visible)) {
  414. if ($chain = hotpot_get_chain($cm)) {
  415. $startofchain = array_shift($chain);
  416. $visible = $startofchain->visible;
  417. }
  418. }
  419. }
  420. return $visible;
  421. }
  422. function hotpot_add_chain(&$hotpot) {
  423. /// add a chain of hotpot actiivities
  424. global $CFG, $course;
  425. $ok = true;
  426. $hotpot->names = array();
  427. $hotpot->summaries = array();
  428. $hotpot->references = array();
  429. $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
  430. if (isset($xml_quiz->error)) {
  431. $hotpot->errors['reference'] = $xml_quiz->error;
  432. $ok = false;
  433. } else if (is_dir($xml_quiz->filepath)) {
  434. // get list of hotpot files in this folder
  435. if ($dh = @opendir($xml_quiz->filepath)) {
  436. while (false !== ($file = @readdir($dh))) {
  437. if (preg_match('/\.(jbc|jcl|jcw|jmt|jmx|jqz|htm|html)$/', $file)) {
  438. $hotpot->references[] = "$xml_quiz->reference/$file";
  439. }
  440. }
  441. closedir($dh);
  442. // get titles
  443. foreach ($hotpot->references as $i=>$reference) {
  444. $filepath = $xml_quiz->fileroot.'/'.$reference;
  445. hotpot_get_titles_and_next_ex($hotpot, $filepath);
  446. $hotpot->names[$i] = $hotpot->exercisetitle;
  447. $hotpot->summaries[$i] = $hotpot->exercisesubtitle;
  448. }
  449. } else {
  450. $ok = false;
  451. $hotpot->errors['reference'] = get_string('error_couldnotopenfolder', 'hotpot', $hotpot->reference);
  452. }
  453. } else if (is_file($xml_quiz->filepath)) {
  454. $filerootlength = strlen($xml_quiz->fileroot) + 1;
  455. while ($xml_quiz->filepath) {
  456. hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath, true);
  457. $hotpot->names[] = $hotpot->exercisetitle;
  458. $hotpot->summaries[] = $hotpot->exercisesubtitle;
  459. $hotpot->references[] = substr($xml_quiz->filepath, $filerootlength);
  460. if ($hotpot->nextexercise) {
  461. $filepath = $xml_quiz->fileroot.'/'.$xml_quiz->filesubdir.$hotpot->nextexercise;
  462. // check file is not already in chain
  463. $reference = substr($filepath, $filerootlength);
  464. if (in_array($reference, $hotpot->references)) {
  465. $filepath = '';
  466. }
  467. } else {
  468. $filepath = '';
  469. }
  470. if ($filepath && file_exists($filepath) && is_file($filepath) && is_readable($filepath)) {
  471. $xml_quiz->filepath = $filepath;
  472. } else {
  473. $xml_quiz->filepath = false; // finish while loop
  474. }
  475. } // end while
  476. } else {
  477. $ok = false;
  478. $hotpot->errors['reference'] = get_string('error_notfileorfolder', 'hotpot', $hotpot->reference);
  479. }
  480. if (empty($hotpot->references) && empty($hotpot->errors['reference'])) {
  481. $ok = false;
  482. $hotpot->errors['reference'] = get_string('error_noquizzesfound', 'hotpot', $hotpot->reference);
  483. }
  484. if ($ok) {
  485. $hotpot->visible = HOTPOT_YES;
  486. if (trim($hotpot->name)=='') {
  487. $hotpot->name = get_string("modulename", $hotpot->modulename);
  488. }
  489. $hotpot->specificname = $hotpot->name;
  490. $hotpot->specificsummary = $hotpot->summary;
  491. // add all except last activity in chain
  492. $i_max = count($hotpot->references)-1;
  493. for ($i=0; $i<$i_max; $i++) {
  494. hotpot_set_name_summary_reference($hotpot, $i);
  495. $hotpot->reference = addslashes($hotpot->reference);
  496. if (!$hotpot->instance = insert_record("hotpot", $hotpot)) {
  497. error("Could not add a new instance of $hotpot->modulename", "view.php?id=$hotpot->course");
  498. }
  499. // store (hotpot table) id of start of chain
  500. if ($i==0) {
  501. $hotpot->startofchain = $hotpot->instance;
  502. }
  503. if (isset($course->groupmode)) {
  504. $hotpot->groupmode = $course->groupmode;
  505. }
  506. if (! $hotpot->coursemodule = add_course_module($hotpot)) {
  507. error("Could not add a new course module");
  508. }
  509. if (! $sectionid = add_mod_to_section($hotpot) ) {
  510. error("Could not add the new course module to that section");
  511. }
  512. if (! set_field("course_modules", "section", $sectionid, "id", $hotpot->coursemodule)) {
  513. error("Could not update the course module with the correct section");
  514. }
  515. add_to_log($hotpot->course, "course", "add mod",
  516. "../mod/$hotpot->modulename/view.php?id=$hotpot->coursemodule",
  517. "$hotpot->modulename $hotpot->instance"
  518. );
  519. add_to_log($hotpot->course, $hotpot->modulename, "add",
  520. "view.php?id=$hotpot->coursemodule",
  521. "$hotpot->instance", $hotpot->coursemodule
  522. );
  523. // hide tail of chain
  524. if ($hotpot->shownextquiz==HOTPOT_YES) {
  525. $hotpot->visible = HOTPOT_NO;
  526. }
  527. } // end for ($hotpot->references)
  528. // settings for final activity in chain
  529. hotpot_set_name_summary_reference($hotpot, $i);
  530. $hotpot->reference = addslashes($hotpot->references[$i]);
  531. $hotpot->shownextquiz = HOTPOT_NO;
  532. if (isset($hotpot->startofchain)) {
  533. // redirection only works for Moodle 1.5+
  534. $hotpot->redirect = true;
  535. $hotpot->redirecturl = "$CFG->wwwroot/mod/hotpot/view.php?hp=$hotpot->startofchain";
  536. }
  537. } // end if $ok
  538. return $ok;
  539. }
  540. function hotpot_set_name_summary_reference(&$hotpot, $chain_index=NULL) {
  541. $xml_quiz = NULL;
  542. $textfields = array('name', 'summary');
  543. foreach ($textfields as $textfield) {
  544. $textsource = $textfield.'source';
  545. // are we adding a chain?
  546. if (isset($chain_index)) {
  547. switch ($hotpot->$textsource) {
  548. case HOTPOT_TEXTSOURCE_QUIZ:
  549. if ($textfield=='name') {
  550. $hotpot->exercisetitle = $hotpot->names[$chain_index];
  551. } else if ($textfield=='summary') {
  552. $hotpot->exercisesubtitle = $hotpot->summaries[$chain_index];
  553. }
  554. break;
  555. case HOTPOT_TEXTSOURCE_SPECIFIC:
  556. $specifictext = 'specific'.$textfield;
  557. if (empty($hotpot->$specifictext) && trim($hotpot->$specifictext)=='') {
  558. $hotpot->$textfield = '';
  559. } else {
  560. $hotpot->$textfield = $hotpot->$specifictext.' ('.($chain_index+1).')';
  561. }
  562. break;
  563. }
  564. $hotpot->reference = $hotpot->references[$chain_index];
  565. }
  566. if ($hotpot->$textsource==HOTPOT_TEXTSOURCE_QUIZ) {
  567. if (empty($xml_quiz) && !isset($chain_index)) {
  568. $xml_quiz = new hotpot_xml_quiz($hotpot, false, false, false, false, false);
  569. hotpot_get_titles_and_next_ex($hotpot, $xml_quiz->filepath);
  570. }
  571. if ($textfield=='name') {
  572. $hotpot->$textfield = addslashes($hotpot->exercisetitle);
  573. } else if ($textfield=='summary') {
  574. $hotpot->$textfield = addslashes($hotpot->exercisesubtitle);
  575. }
  576. }
  577. switch ($hotpot->$textsource) {
  578. case HOTPOT_TEXTSOURCE_FILENAME:
  579. $hotpot->$textfield = basename($hotpot->reference);
  580. break;
  581. case HOTPOT_TEXTSOURCE_FILEPATH:
  582. $hotpot->$textfield = '';
  583. // continue to next lines
  584. default:
  585. if (empty($hotpot->$textfield)) {
  586. $hotpot->$textfield = str_replace('/', ' ', $hotpot->reference);
  587. }
  588. } // end switch
  589. } // end foreach
  590. }
  591. function hotpot_get_titles_and_next_ex(&$hotpot, $filepath, $get_next=false) {
  592. $hotpot->exercisetitle = '';
  593. $hotpot->exercisesubtitle = '';
  594. $hotpot->nextexercise = '';
  595. // read the quiz file source
  596. if ($source = file_get_contents($filepath)) {
  597. $next = '';
  598. $title = '';
  599. $subtitle = '';
  600. if (preg_match('|\.html?$|', $filepath)) {
  601. // html file
  602. if (preg_match('|<h2[^>]*class="ExerciseTitle"[^>]*>(.*?)</h2>|is', $source, $matches)) {
  603. $title = trim(strip_tags($matches[1]));
  604. }
  605. if (empty($title)) {
  606. if (preg_match('|<title[^>]*>(.*?)</title>|is', $source, $matches)) {
  607. $title = trim(strip_tags($matches[1]));
  608. }
  609. }
  610. if (preg_match('|<h3[^>]*class="ExerciseSubtitle"[^>]*>(.*?)</h3>|is', $source, $matches)) {
  611. $subtitle = trim(strip_tags($matches[1]));
  612. }
  613. if ($get_next) {
  614. if (preg_match('|<div[^>]*class="NavButtonBar"[^>]*>(.*?)</div>|is', $source, $matches)) {
  615. $navbuttonbar = $matches[1];
  616. if (preg_match_all('|<button[^>]*onclick="'."location='([^']*)'".'[^"]*"[^>]*>|is', $navbuttonbar, $matches)) {
  617. $lastbutton = count($matches[0])-1;
  618. $next = $matches[1][$lastbutton];
  619. }
  620. }
  621. }
  622. } else {
  623. // xml file (...maybe)
  624. $xml_tree = new hotpot_xml_tree($source);
  625. $xml_tree->filetype = '';
  626. $keys = array_keys($xml_tree->xml);
  627. foreach ($keys as $key) {
  628. if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
  629. $xml_tree->filetype = 'xml';
  630. $xml_tree->xml_root = "['$key']['#']";
  631. $xml_tree->quiztype = strtolower($matches[2]);
  632. break;
  633. }
  634. }
  635. if ($xml_tree->filetype=='xml') {
  636. $title = strip_tags($xml_tree->xml_value('data,title'));
  637. $subtitle = $xml_tree->xml_value('hotpot-config-file,'.$xml_tree->quiztype.',exercise-subtitle');
  638. if ($get_next) {
  639. $include = $xml_tree->xml_value('hotpot-config-file,global,include-next-ex');
  640. if (!empty($include)) {
  641. $next = $xml_tree->xml_value("hotpot-config-file,$xml_tree->quiztype,next-ex-url");
  642. if (is_array($next)) {
  643. $next = $next[0]; // in case "next-ex-url" was repeated in the xml file
  644. }
  645. }
  646. }
  647. }
  648. }
  649. $hotpot->nextexercise = $next;
  650. $hotpot->exercisetitle = (empty($title) || is_array($title)) ? basename($filepath) : $title;
  651. $hotpot->exercisesubtitle = (empty($subtitle) || is_array($subtitle)) ? $hotpot->exercisetitle : $subtitle;
  652. }
  653. }
  654. function hotpot_get_all_instances_in_course($modulename, $course) {
  655. /// called from index.php
  656. global $CFG;
  657. $instances = array();
  658. if (isset($CFG->release) && substr($CFG->release, 0, 3)>=1.2) {
  659. $groupmode = 'cm.groupmode,';
  660. } else {
  661. $groupmode = '';
  662. }
  663. $query = "
  664. SELECT
  665. cm.id AS coursemodule,
  666. cm.course AS course,
  667. cm.module AS module,
  668. cm.instance AS instance,
  669. -- cm.section AS section,
  670. cm.visible AS visible,
  671. $groupmode
  672. -- cs.section AS sectionnumber,
  673. cs.section AS section,
  674. cs.sequence AS sequence,
  675. cs.visible AS sectionvisible,
  676. thismodule.*
  677. FROM
  678. {$CFG->prefix}course_modules cm,
  679. {$CFG->prefix}course_sections cs,
  680. {$CFG->prefix}modules m,
  681. {$CFG->prefix}$modulename thismodule
  682. WHERE
  683. m.name = '$modulename' AND
  684. m.id = cm.module AND
  685. cm.course = '$course->id' AND
  686. cm.section = cs.id AND
  687. cm.instance = thismodule.id
  688. ";
  689. if ($rawmods = get_records_sql($query)) {
  690. // cache $isteacher setting
  691. $isteacher = has_capability('mod/hotpot:viewreport', get_context_instance(CONTEXT_COURSE, $course->id));
  692. $explodesection = array();
  693. $order = array();
  694. foreach ($rawmods as $rawmod) {
  695. if (empty($explodesection[$rawmod->section])) {
  696. $explodesection[$rawmod->section] = true;
  697. $coursemodules = explode(',', $rawmod->sequence);
  698. foreach ($coursemodules as $i=>$coursemodule) {
  699. $order[$coursemodule] = sprintf('%d.%04d', $rawmod->section, $i);
  700. }
  701. }
  702. if ($isteacher) {
  703. $visible = true;
  704. } else if ($modulename=='hotpot') {
  705. $visible = hotpot_is_visible($rawmod);
  706. } else {
  707. $visible = $rawmod->visible;
  708. }
  709. if ($visible) {
  710. $instances[$order[$rawmod->coursemodule]] = $rawmod;
  711. }
  712. } // end foreach $modinfo
  713. ksort($instances);
  714. $instances = array_values($instances);
  715. }
  716. return $instances;
  717. }
  718. function hotpot_update_chain(&$hotpot) {
  719. /// update a chain of hotpot actiivities
  720. $ok = true;
  721. if ($hotpot_modules = hotpot_get_chain($hotpot)) {
  722. // skip updating of these fields
  723. $skipfields = array('id', 'course', 'name', 'reference', 'summary', 'shownextquiz');
  724. $fields = array();
  725. foreach ($hotpot_modules as $hotpot_module) {
  726. if ($hotpot->instance==$hotpot_module->id) {
  727. // don't need to update this hotpot
  728. } else {
  729. // shortcut to hotpot record
  730. $thishotpot = &$hotpot_module->hotpot;
  731. // get a list of fields to update (first time only)
  732. if (empty($fields)) {
  733. $fields = array_keys(get_object_vars($thishotpot));
  734. }
  735. // assume update is NOT required
  736. $require_update = false;
  737. // update field values (except $skipfields)
  738. foreach($fields as $field) {
  739. if (in_array($field, $skipfields) || $thishotpot->$field==$hotpot->$field) {
  740. // update not required for this field
  741. } else {
  742. $require_update = true;
  743. $thishotpot->$field = $hotpot->$field;
  744. }
  745. }
  746. // update $thishotpot, if required
  747. if ($require_update && !update_record("hotpot", $thishotpot)) {
  748. error("Could not update the $hotpot->modulename", "view.php?id=$hotpot->course");
  749. }
  750. }
  751. } // end foreach $ids
  752. }
  753. return $ok;
  754. }
  755. function hotpot_delete_instance($id) {
  756. /// Given an ID of an instance of this module,
  757. /// this function will permanently delete the instance
  758. /// and any data that depends on it.
  759. if (! $hotpot = get_record("hotpot", "id", $id)) {
  760. return false;
  761. }
  762. if (! delete_records("hotpot", "id", "$id")) {
  763. return false;
  764. }
  765. delete_records("hotpot_questions", "hotpot", "$id");
  766. if ($attempts = get_records_select("hotpot_attempts", "hotpot='$id'")) {
  767. $ids = implode(',', array_keys($attempts));
  768. delete_records_select("hotpot_attempts", "id IN ($ids)");
  769. delete_records_select("hotpot_details", "attempt IN ($ids)");
  770. delete_records_select("hotpot_responses", "attempt IN ($ids)");
  771. }
  772. // remove calendar events for this hotpot
  773. delete_records('event', 'modulename', 'hotpot', 'instance', $id);
  774. // remove grade item for this hotpot
  775. hotpot_grade_item_delete($hotpot);
  776. return true;
  777. }
  778. function hotpot_delete_and_notify($table, $select, $strtable) {
  779. $count = max(0, count_records_select($table, $select));
  780. if ($count) {
  781. delete_records_select($table, $select);
  782. $count -= max(0, count_records_select($table, $select));
  783. if ($count) {
  784. notify(get_string('deleted')." $count x $strtable");
  785. }
  786. }
  787. }
  788. function hotpot_user_complete($course, $user, $mod, $hotpot) {
  789. /// Print a detailed representation of what a user has done with
  790. /// a given particular instance of this module, for user activity reports.
  791. $report = hotpot_user_outline($course, $user, $mod, $hotpot);
  792. if (empty($report)) {
  793. print get_string("noactivity", "hotpot");
  794. } else {
  795. $date = userdate($report->time, get_string('strftimerecentfull'));
  796. print $report->info.' '.get_string('mostrecently').': '.$date;
  797. }
  798. return true;
  799. }
  800. function hotpot_user_outline($course, $user, $mod, $hotpot) {
  801. /// Return a small object with summary information about what a
  802. /// user has done with a given particular instance of this module
  803. /// Used for user activity reports.
  804. /// $report->time = the time they did it
  805. /// $report->info = a short text description
  806. $report = NULL;
  807. if ($records = get_records_select("hotpot_attempts", "hotpot='$hotpot->id' AND userid='$user->id'", "timestart ASC", "*")) {
  808. $report = new stdClass();
  809. $scores = array();
  810. foreach ($records as $record){
  811. if (empty($report->time)) {
  812. $report->time = $record->timestart;
  813. }
  814. $scores[] = hotpot_format_score($record);
  815. }
  816. if (empty($scores)) {
  817. $report->time = 0;
  818. $report->info = get_string('noactivity', 'hotpot');
  819. } else {
  820. $report->info = get_string('score', 'quiz').': '.implode(', ', $scores);
  821. }
  822. }
  823. return $report;
  824. }
  825. function hotpot_format_score($record, $undefined='&nbsp;') {
  826. if (isset($record->score)) {
  827. $score = $record->score;
  828. } else {
  829. $score = $undefined;
  830. }
  831. return $score;
  832. }
  833. function hotpot_format_status($record, $undefined='&nbsp;') {
  834. global $HOTPOT_STATUS;
  835. if (isset($record->status) || isset($HOTPOT_STATUS[$record->status])) {
  836. $status = $HOTPOT_STATUS[$record->status];
  837. } else {
  838. $status = $undefined;
  839. }
  840. return $status;
  841. }
  842. function hotpot_print_recent_activity($course, $isteacher, $timestart) {
  843. /// Given a course and a time, this module should find recent activity
  844. /// that has occurred in hotpot activities and print it out.
  845. /// Return true if there was output, or false is there was none.
  846. global $CFG;
  847. $result = false;
  848. $records = get_records_sql("
  849. SELECT
  850. h.id AS id,
  851. h.name AS name,
  852. COUNT(*) AS count_attempts
  853. FROM
  854. {$CFG->prefix}hotpot h,
  855. {$CFG->prefix}hotpot_attempts a
  856. WHERE
  857. h.course = $course->id
  858. AND h.id = a.hotpot
  859. AND a.id = a.clickreportid
  860. AND a.starttime > $timestart
  861. GROUP BY
  862. h.id, h.name
  863. ");
  864. // note that PostGreSQL requires h.name in the GROUP BY clause
  865. if($records) {
  866. $names = array();
  867. foreach ($records as $id => $record){
  868. if ($cm = get_coursemodule_from_instance('hotpot', $record->id, $course->id)) {
  869. $context = get_context_instance(CONTEXT_MODULE, $cm->id);
  870. if (has_capability('mod/hotpot:viewreport', $context)) {
  871. $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$id";
  872. $name = '&nbsp;<a href="'.$href.'">'.$record->name.'</a>';
  873. if ($record->count_attempts > 1) {
  874. $name .= " ($record->count_attempts)";
  875. }
  876. $names[] = $name;
  877. }
  878. }
  879. }
  880. if (count($names) > 0) {
  881. print_headline(get_string('modulenameplural', 'hotpot').':');
  882. if ($CFG->version >= 2005050500) { // Moodle 1.5+
  883. echo '<div class="head"><div class="name">'.implode('<br />', $names).'</div></div>';
  884. } else { // Moodle 1.4.x (or less)
  885. echo '<font size="1">'.implode('<br />', $names).'</font>';
  886. }
  887. $result = true;
  888. }
  889. }
  890. return $result; // True if anything was printed, otherwise false
  891. }
  892. function hotpot_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $cmid="", $userid="", $groupid="") {
  893. // Returns all quizzes since a given time.
  894. global $CFG;
  895. // If $cmid or $userid are specified, then this restricts the results
  896. $cm_select = empty($cmid) ? "" : " AND cm.id = '$cmid'";
  897. $user_select = empty($userid) ? "" : " AND u.id = '$userid'";
  898. $records = get_records_sql("
  899. SELECT
  900. a.*,
  901. h.name, h.course,
  902. cm.instance, cm.section,
  903. u.firstname, u.lastname, u.picture
  904. FROM
  905. {$CFG->prefix}hotpot_attempts a,
  906. {$CFG->prefix}hotpot h,
  907. {$CFG->prefix}course_modules cm,
  908. {$CFG->prefix}user u
  909. WHERE
  910. a.timefinish > '$sincetime'
  911. AND a.id = a.clickreportid
  912. AND a.userid = u.id $user_select
  913. AND a.hotpot = h.id $cm_select
  914. AND cm.instance = h.id
  915. AND cm.course = '$courseid'
  916. AND h.course = cm.course
  917. ORDER BY
  918. a.timefinish ASC
  919. ");
  920. if (!empty($records)) {
  921. foreach ($records as $record) {
  922. if (empty($groupid) || groups_is_member($groupid, $record->userid)) {
  923. unset($activity);
  924. $activity->type = "hotpot";
  925. $activity->defaultindex = $index;
  926. $activity->instance = $record->hotpot;
  927. $activity->name = $record->name;
  928. $activity->section = $record->section;
  929. $activity->content->attemptid = $record->id;
  930. $activity->content->attempt = $record->attempt;
  931. $activity->content->score = $record->score;
  932. $activity->content->timestart = $record->timestart;
  933. $activity->content->timefinish = $record->timefinish;
  934. $activity->user->userid = $record->userid;
  935. $activity->user->fullname = fullname($record);
  936. $activity->user->picture = $record->picture;
  937. $activity->timestamp = $record->timefinish;
  938. $activities[] = $activity;
  939. $index++;
  940. }
  941. } // end foreach
  942. }
  943. }
  944. function hotpot_print_recent_mod_activity($activity, $courseid, $detail=false) {
  945. /// Basically, this function prints the results of "hotpot_get_recent_activity"
  946. global $CFG, $THEME, $USER;
  947. if (isset($THEME->cellcontent2)) {
  948. $bgcolor = ' bgcolor="'.$THEME->cellcontent2.'"';
  949. } else {
  950. $bgcolor = '';
  951. }
  952. if (is_object($courseid) && isset($courseid->id)) {
  953. $courseid = $courseid->id; // shouldn't happen !!
  954. }
  955. print '<table border="0" cellpadding="3" cellspacing="0">';
  956. print '<tr><td'.$bgcolor.' class="forumpostpicture" width="35" valign="top">';
  957. print_user_picture($activity->user->userid, $courseid, $activity->user->picture);
  958. print '</td><td width="100%"><font size="2">';
  959. if ($detail) {
  960. // activity icon
  961. $src = "$CFG->modpixpath/$activity->type/icon.gif";
  962. print '<img src="'.$src.'" class="icon" alt="'.$activity->type.'" /> ';
  963. // link to activity
  964. $href = "$CFG->wwwroot/mod/hotpot/view.php?hp=$activity->instance";
  965. print '<a href="'.$href.'">'.$activity->name.'</a> - ';
  966. }
  967. if (has_capability('mod/hotpot:viewreport',get_context_instance(CONTEXT_COURSE, $courseid))) {
  968. // score (with link to attempt details)
  969. $href = "$CFG->wwwroot/mod/hotpot/review.php?hp=$activity->instance&attempt=".$activity->content->attemptid;
  970. print '<a href="'.$href.'">('.hotpot_format_score($activity->content).')</a> ';
  971. // attempt number
  972. print get_string('attempt', 'quiz').' - '.$activity->content->attempt.'<br />';
  973. }
  974. // link to user
  975. $href = "$CFG->wwwroot/user/view.php?id=$activity->user->userid&course=$courseid";
  976. print '<a href="'.$href.'">'.$activity->user->fullname.'</a> ';
  977. // time and date
  978. print ' - ' . userdate($activity->timestamp);
  979. // duration
  980. $duration = format_time($activity->content->timestart - $activity->content->timefinish);
  981. print " &nbsp; ($duration)";
  982. print "</font></td></tr>";
  983. print "</table>";
  984. }
  985. function hotpot_cron () {
  986. /// Function to be run periodically according to the moodle cron
  987. /// This function searches for things that need to be done, such
  988. /// as sending out mail, toggling flags etc ...
  989. global $CFG;
  990. return true;
  991. }
  992. function hotpot_grades($hotpotid) {
  993. /// Must return an array of grades for a given instance of this module,
  994. /// indexed by user. It also returns a maximum allowed grade.
  995. $hotpot = get_record('hotpot', 'id', $hotpotid);
  996. $return->grades = hotpot_get_grades($hotpot);
  997. $return->maxgrade = $hotpot->grade;
  998. return $return;
  999. }
  1000. function hotpot_get_grades($hotpot, $user_ids='') {
  1001. global $CFG;
  1002. $grades = array();
  1003. $weighting = $hotpot->grade / 100;
  1004. $precision = hotpot_get_precision($hotpot);
  1005. // set the SQL string to determine the $grade
  1006. $grade = "";
  1007. switch ($hotpot->grademethod) {
  1008. case HOTPOT_GRADEMETHOD_HIGHEST:
  1009. $grade = "ROUND(MAX(score) * $weighting, $precision) AS grade";
  1010. break;
  1011. case HOTPOT_GRADEMETHOD_AVERAGE:
  1012. // the 'AVG' function skips abandoned quizzes, so use SUM(score)/COUNT(id)
  1013. $grade = "ROUND(SUM(score)/COUNT(id) * $weighting, $precision) AS grade";
  1014. break;
  1015. case HOTPOT_GRADEMETHOD_FIRST:
  1016. $grade = "ROUND(score * $weighting, $precision)";
  1017. $grade = sql_concat('timestart', "'_'", $grade);
  1018. $grade = "MIN($grade) AS grade";
  1019. break;
  1020. case HOTPOT_GRADEMETHOD_LAST:
  1021. $grade = "ROUND(score * $weighting, $precision)";
  1022. $grade = sql_concat('timestart', "'_'", $grade);
  1023. $grade = "MAX($grade) AS grade";
  1024. break;
  1025. }
  1026. if ($grade) {
  1027. $userid_condition = empty($user_ids) ? '' : "AND userid IN ($user_ids) ";
  1028. $grades = get_records_sql_menu("
  1029. SELECT userid, $grade
  1030. FROM {$CFG->prefix}hotpot_attempts
  1031. WHERE timefinish>0 AND hotpot='$hotpot->id' $userid_condition
  1032. GROUP BY userid
  1033. ");
  1034. if ($grades) {
  1035. if ($hotpot->grademethod==HOTPOT_GRADEMETHOD_FIRST || $hotpot->grademethod==HOTPOT_GRADEMETHOD_LAST) {
  1036. // remove left hand characters in $grade (up to and including the underscore)
  1037. foreach ($grades as $userid=>$grade) {
  1038. $grades[$userid] = substr($grades[$userid], strpos($grades[$userid], '_')+1);
  1039. }
  1040. }
  1041. }
  1042. }
  1043. return $grades;
  1044. }
  1045. function hotpot_get_precision(&$hotpot) {
  1046. return ($hotpot->grademethod==HOTPOT_GRADEMETHOD_AVERAGE || $hotpot->grade<100) ? 1 : 0;
  1047. }
  1048. /**
  1049. * Return grade for given user or all users.
  1050. *
  1051. * @param object $hotpot
  1052. * @param int $userid optional user id, 0 means all users
  1053. * @return array array of grades, false if none
  1054. */
  1055. function hotpot_get_user_grades($hotpot, $userid=0) {
  1056. $grades = array();
  1057. if ($hotpotgrades = hotpot_get_grades($hotpot, $userid)) {
  1058. foreach ($hotpotgrades as $hotpotuserid => $hotpotgrade) {
  1059. $grades[$hotpotuserid] = new stdClass();
  1060. $grades[$hotpotuserid]->id = $hotpotuserid;
  1061. $grades[$hotpotuserid]->userid = $hotpotuserid;
  1062. $grades[$hotpotuserid]->rawgrade = $hotpotgrade;
  1063. }
  1064. }
  1065. if (count($grades)) {
  1066. return $grades;
  1067. } else {
  1068. return false;
  1069. }
  1070. }
  1071. /**
  1072. * Update grades in central gradebook
  1073. * this function is called from db/upgrade.php
  1074. * it is initially called with no arguments, which forces it to get a list of all hotpots
  1075. * it then iterates through the hotpots, calling itself to create a grade record for each hotpot
  1076. *
  1077. * @param object $hotpot null means all hotpots
  1078. * @param int $userid specific user only, 0 means all users
  1079. */
  1080. function hotpot_update_grades($hotpot=null, $userid=0, $nullifnone=true) {
  1081. global $CFG;
  1082. if (! function_exists('grade_update')) {
  1083. require_once($CFG->libdir.'/gradelib.php');
  1084. }
  1085. if (is_null($hotpot)) {
  1086. // update (=create) grades for all hotpots
  1087. $sql = "
  1088. SELECT h.*, cm.idnumber as cmidnumber
  1089. FROM {$CFG->prefix}hotpot h, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
  1090. WHERE m.name='hotpot' AND m.id=cm.module AND cm.instance=h.id"
  1091. ;
  1092. if ($rs = get_recordset_sql($sql)) {
  1093. while ($hotpot = rs_fetch_next_record($rs)) {
  1094. hotpot_update_grades($hotpot, 0, false);
  1095. }
  1096. rs_close($rs);
  1097. }
  1098. } else {
  1099. // update (=create) grade for a single hotpot
  1100. if ($grades = hotpot_get_user_grades($hotpot, $userid)) {
  1101. hotpot_grade_item_update($hotpot, $grades);
  1102. } else if ($userid && $nullifnone) {
  1103. // no grades for this user, but we must force the creation of a "null" grade record
  1104. $grade = new object();
  1105. $grade->userid = $userid;
  1106. $grade->rawgrade = null;
  1107. hotpot_grade_item_update($hotpot, $grade);
  1108. } else {
  1109. // no grades and no userid
  1110. hotpot_grade_item_update($hotpot);
  1111. }
  1112. }
  1113. }
  1114. /**
  1115. * Update/create grade item for given hotpot
  1116. *
  1117. * @param object $hotpot object with extra cmidnumber
  1118. * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
  1119. * @return object grade_item
  1120. */
  1121. function hotpot_grade_item_update($hotpot, $grades=null) {
  1122. global $CFG;
  1123. if (! function_exists('grade_update')) {
  1124. require_once($CFG->libdir.'/gradelib.php');
  1125. }
  1126. $params = array('itemname' => $hotpot->name);
  1127. if (array_key_exists('cmidnumber', $hotpot)) {
  1128. //cmidnumber may not be always present
  1129. $params['idnumber'] = $hotpot->cmidnumber;
  1130. }
  1131. if ($hotpot->grade > 0) {
  1132. $params['gradetype'] = GRADE_TYPE_VALUE;
  1133. $params['grademax'] = $hotpot->grade;
  1134. $params['grademin'] = 0;
  1135. } else {
  1136. $params['gradetype'] = GRADE_TYPE_NONE;
  1137. // Note: when adding a new activity, a gradeitem will *not*
  1138. // be created in the grade book if gradetype==GRADE_TYPE_NONE
  1139. // A gradeitem will be created later if gradetype changes to GRADE_TYPE_VALUE
  1140. // However, the gradeitem will *not* be deleted if the activity's
  1141. // gradetype changes back from GRADE_TYPE_VALUE to GRADE_TYPE_NONE
  1142. // Therefore, we give the user the ability to force the removal of empty gradeitems
  1143. if (! empty($hotpot->removegradeitem)) {
  1144. $params['deleted'] = true;
  1145. }
  1146. }
  1147. return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, $grades, $params);
  1148. }
  1149. /**
  1150. * Delete grade item for given hotpot
  1151. *
  1152. * @param object $hotpot object
  1153. * @return object grade_item
  1154. */
  1155. function hotpot_grade_item_delete($hotpot) {
  1156. global $CFG;
  1157. if (! function_exists('grade_update')) {
  1158. require_once($CFG->libdir.'/gradelib.php');
  1159. }
  1160. return grade_update('mod/hotpot', $hotpot->course, 'mod', 'hotpot', $hotpot->id, 0, null, array('deleted'=>1));
  1161. }
  1162. function hotpot_get_participants($hotpotid) {
  1163. //Must return an array of user ids who are participants
  1164. //for a given instance of hotpot. Must include every user involved
  1165. //in the instance, independient of his role (student, teacher, admin...)
  1166. //See other modules as example.
  1167. global $CFG;
  1168. return get_records_sql("
  1169. SELECT DISTINCT
  1170. u.id, u.id
  1171. FROM
  1172. {$CFG->prefix}user u,
  1173. {$CFG->prefix}hotpot_attempts a
  1174. WHERE
  1175. u.id = a.userid
  1176. AND a.hotpot = '$hotpotid'
  1177. ");
  1178. }
  1179. function hotpot_scale_used ($hotpotid, $scaleid) {
  1180. //This function returns if a scale is being used by one hotpot
  1181. //it it has support for grading and scales. Commented code should be
  1182. //modified if necessary. See forum, glossary or journal modules
  1183. //as reference.
  1184. $report = false;
  1185. //$rec = get_record("hotpot","id","$hotpotid","scale","-$scaleid");
  1186. //
  1187. //if (!empty($rec) && !empty($scaleid)) {
  1188. // $report = true;
  1189. //}
  1190. return $report;
  1191. }
  1192. /**
  1193. * Checks if scale is being used by any instance of hotpot
  1194. *
  1195. * This is used to find out if scale used anywhere
  1196. * @param $scaleid int
  1197. * @return boolean True if the scale is used by any hotpot
  1198. */
  1199. function hotpot_scale_used_anywhere($scaleid) {
  1200. return false;
  1201. }
  1202. //////////////////////////////////////////////////////////
  1203. /// Any other hotpot functions go here.
  1204. /// Each of them must have a name that starts with hotpot
  1205. function hotpot_add_attempt($hotpotid) {
  1206. global $db, $CFG, $USER;
  1207. // get start time of this attempt
  1208. $time = time();
  1209. // set all previous "in progress" attempts at this quiz to "abandoned"
  1210. if ($attempts = get_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id' AND status='".HOTPOT_STATUS_INPROGRESS."'")) {
  1211. foreach ($attempts as $attempt) {
  1212. if ($attempt->timefinish==0) {
  1213. $attempt->timefinish = $time;
  1214. }
  1215. if ($attempt->clickreportid==0) {
  1216. $attempt->clickreportid = $attempt->id;
  1217. }
  1218. $attempt->status = HOTPOT_STATUS_ABANDONED;
  1219. update_record('hotpot_attempts', $attempt);
  1220. }
  1221. }
  1222. // create and add new attempt record
  1223. $attempt = new stdClass();
  1224. $attempt->hotpot = $hotpotid;
  1225. $attempt->userid = $USER->id;
  1226. $attempt->attempt = hotpot_get_next_attempt($hotpotid);
  1227. $attempt->timestart = $time;
  1228. return insert_record("hotpot_attempts", $attempt);
  1229. }
  1230. function hotpot_get_next_attempt($hotpotid) {
  1231. global $USER;
  1232. // get max attempt so far
  1233. $i = count_records_select('hotpot_attempts', "hotpot='$hotpotid' AND userid='$USER->id'", 'MAX(attempt)');
  1234. return empty($i) ? 1 : ($i+1);
  1235. }
  1236. function hotpot_get_question_name($question) {
  1237. $name = '';
  1238. if (isset($question->text)) {
  1239. $name = hotpot_strings($question->text);
  1240. }
  1241. if (empty($name)) {
  1242. $name = $question->name;
  1243. }
  1244. return $name;
  1245. }
  1246. function hotpot_strings($ids) {
  1247. // array of ids of empty strings
  1248. static $HOTPOT_EMPTYSTRINGS;
  1249. if (!isset($HOTPOT_EMPTYSTRINGS)) { // first time only
  1250. // get ids of empty strings
  1251. $emptystrings = get_records_select('hotpot_strings', 'LENGTH(TRIM(string))=0');
  1252. $HOTPOT_EMPTYSTRINGS = empty($emptystrings) ? array() : array_keys($emptystrings);
  1253. }
  1254. $strings = array();
  1255. if (!empty($ids)) {
  1256. $ids = explode(',', $ids);
  1257. foreach ($ids as $id) {
  1258. if (!in_array($id, $HOTPOT_EMPTYSTRINGS)) {
  1259. $strings[] = hotpot_string($id);
  1260. }
  1261. }
  1262. }
  1263. return implode(',', $strings);
  1264. }
  1265. function hotpot_string($id) {
  1266. return get_field('hotpot_strings', 'string', 'id', $id);
  1267. }
  1268. //////////////////////////////////////////////////////////////////////////////////////
  1269. /// the class definitions to handle XML trees
  1270. // get the standard XML parser supplied with Moodle
  1271. require_once("$CFG->libdir/xmlize.php");
  1272. // get the default class for hotpot quiz templates
  1273. require_once("$CFG->hotpottemplate/default.php");
  1274. class hotpot_xml_tree {
  1275. function hotpot_xml_tree($str, $xml_root='') {
  1276. if (empty($str)) {
  1277. $this->xml = array();
  1278. } else {
  1279. if (empty($CFG->unicodedb)) {
  1280. $str = utf8_encode($str);
  1281. }
  1282. $this->xml = xmlize($str, 0);
  1283. }
  1284. $this->xml_root = $xml_root;
  1285. }
  1286. function xml_value($tags, $more_tags="[0]['#']") {
  1287. $tags = empty($tags) ? '' : "['".str_replace(",", "'][0]['#']['", $tags)."']";
  1288. eval('$value = &$this->xml'.$this->xml_root.$tags.$more_tags.';');
  1289. if (is_string($value)) {
  1290. if (empty($CFG->unicodedb)) {
  1291. $value = utf8_decode($value);
  1292. }
  1293. // decode angle brackets
  1294. $value = strtr($value, array('&#x003C;'=>'<', '&#x003E;'=>'>', '&#x0026;'=>'&'));
  1295. // remove white space between <table>, <ul|OL|DL> and <OBJECT|EMBED> parts
  1296. // (so it doesn't get converted to <br />)
  1297. $htmltags = '('
  1298. . 'TABLE|\\/?CAPTION|\\/?COL|\\/?COLGROUP|\\/?TBODY|\\/?TFOOT|\\/?THEAD|\\/?TD|\\/?TH|\\/?TR'
  1299. . '|OL|UL|\\/?LI'
  1300. . '|DL|\\/?DT|\\/?DD'
  1301. . '|EMBED|OBJECT|APPLET|\\/?PARAM'
  1302. //. '|SELECT|\\/?OPTION'
  1303. //. '|FIELDSET|\\/?LEGEND'
  1304. //. '|FRAMESET|\\/?FRAME'
  1305. . ')'
  1306. ;
  1307. $space = '(?:\s|(?:<br[^>]*>))+';
  1308. $search = '/(<'.$htmltags.'[^>]*'.'>)'.$space.'(?='.'<)/is';
  1309. $value = preg_replace($search, '\\1', $value);
  1310. // replace remaining newlines with <br />
  1311. $value = str_replace("\n", '<br />', $value);
  1312. // encode unicode characters as HTML entities
  1313. // (in particular, accented charaters that have not been encoded by HP)
  1314. // unicode characters can be detected by checking the hex value of a character
  1315. // 00 - 7F : ascii char (roman alphabet + punctuation)
  1316. // 80 - BF : byte 2, 3 or 4 of a unicode char
  1317. // C0 - DF : 1st byte of 2-byte char
  1318. // E0 - EF : 1st byte of 3-byte char
  1319. // F0 - FF : 1st byte of 4-byte char
  1320. // if the string doesn't match the above, it might be
  1321. // 80 - FF : single-byte, non-ascii char
  1322. $search = '/'.'[\xc0-\xdf][\x80-\xbf]'.'|'.'[\xe0-\xef][\x80-\xbf]{2}'.'|'.'[\xf0-\xff][\x80-\xbf]{3}'.'|'.'[\x80-\xff]'.'/';
  1323. $value = preg_replace_callback($search, array(&$this, 'xml_value_callback'), $value);
  1324. }
  1325. return $value;
  1326. }
  1327. function xml_value_callback(&$matches) {
  1328. return hotpot_utf8_to_html_entity($matches[0]);
  1329. }
  1330. function xml_values($tags) {
  1331. $i = 0;
  1332. $values = array();
  1333. while ($value = $this->xml_value($tags, "[$i]['#']")) {
  1334. $values[$i++] = $value;
  1335. }
  1336. return $values;
  1337. }
  1338. function obj_value(&$obj, $name) {
  1339. return is_object($obj) ? @$obj->$name : (is_array($obj) ? @$obj[$name] : NULL);
  1340. }
  1341. function encode_cdata(&$str, $tag) {
  1342. static $ILLEGAL_STRINGS = array(
  1343. "\r\n" => '&lt;br /&gt;',
  1344. "\r" => '&lt;br /&gt;',
  1345. "\n" => '&lt;br /&gt;',
  1346. '[' => '&#91;',
  1347. ']' => '&#93;'
  1348. );
  1349. // extract the $tag from the $str(ing), if possible
  1350. $pattern = '|(^.*<'.$tag.'[^>]*)(>.*<)(/'.$tag.'>.*$)|is';
  1351. if (preg_match($pattern, $str, $matches)) {
  1352. // encode problematic CDATA chars and strings
  1353. $matches[2] = strtr($matches[2], $ILLEGAL_STRINGS);
  1354. // if there are any ampersands in "open text"
  1355. // surround them by CDATA start and end markers
  1356. // (and convert HTML entities to plain text)
  1357. $search = '/(?<=>)'.'[^<]*&[^<]*'.'(?=<)/';
  1358. $matches[2] = preg_replace_callback($search, array(&$this, 'encode_cdata_callback'), $matches[2]);
  1359. $str = $matches[1].$matches[2].$matches[3];
  1360. }
  1361. }
  1362. function encode_cdata_callback(&$matches) {
  1363. static $HTML_ENTITIES = array(
  1364. '&apos;' => "'",
  1365. '&quot;' => '"',
  1366. '&lt;' => '<',
  1367. '&gt;' => '>',
  1368. '&amp;' => '&',
  1369. );
  1370. return '<![CDATA['.strtr($matches[0], $HTML_ENTITIES).']]>';
  1371. }
  1372. }
  1373. class hotpot_xml_quiz extends hotpot_xml_tree {
  1374. // constructor function
  1375. function hotpot_xml_quiz(&$obj, $read_file=true, $parse_xml=true, $convert_urls=true, $report_errors=true, $create_html=true) {
  1376. // obj can be the $_GET array or a form object/array
  1377. global $CFG, $HOTPOT_OUTPUTFORMAT, $HOTPOT_OUTPUTFORMAT_DIR;
  1378. // check xmlize functions are available
  1379. if (! function_exists("xmlize")) {
  1380. error('xmlize functions are not available');
  1381. }
  1382. $this->read_file = $read_file;
  1383. $this->parse_xml = $parse_xml;
  1384. $this->convert_urls = $convert_urls;
  1385. $this->report_errors = $report_errors;
  1386. $this->create_html = $create_html;
  1387. // extract fields from $obj
  1388. // course : the course id
  1389. // reference : the filename within the files folder
  1390. // location : "site" files folder or "course" files folder
  1391. // navigation : type of navigation required in quiz
  1392. // forceplugins : force Moodle compatible media players
  1393. $this->course = $this->obj_value($obj, 'course');
  1394. $this->reference = $this->obj_value($obj, 'reference');
  1395. $this->location = $this->obj_value($obj, 'location');
  1396. $this->navigation = $this->obj_value($obj, 'navigation');
  1397. $this->forceplugins = $this->obj_value($obj, 'forceplugins');
  1398. // can't continue if there is no course or reference
  1399. if (empty($this->course) || empty($this->reference)) {
  1400. $this->error = get_string('error_nocourseorfilename', 'hotpot');
  1401. if ($this->report_errors) {
  1402. error($this->error);
  1403. }
  1404. return;
  1405. }
  1406. $this->course_homeurl = "$CFG->wwwroot/course/view.php?id=$this->course";
  1407. // set filedir, filename and filepath
  1408. switch ($this->location) {
  1409. case HOTPOT_LOCATION_SITEFILES:
  1410. $site = get_site();
  1411. $this->filedir = $site->id;
  1412. break;
  1413. case HOTPOT_LOCATION_COURSEFILES:
  1414. default:
  1415. $this->filedir = $this->course;
  1416. break;
  1417. }
  1418. $this->filesubdir = dirname($this->reference);
  1419. if ($this->filesubdir=='.') {
  1420. $this->filesubdir = '';
  1421. }
  1422. if ($this->filesubdir) {
  1423. $this->filesubdir .= '/';
  1424. }
  1425. $this->filename = basename($this->reference);
  1426. $this->fileroot = "$CFG->dataroot/$this->filedir";
  1427. $this->filepath = "$this->fileroot/$this->reference";
  1428. // read the file, if required
  1429. if ($this->read_file) {
  1430. if (!file_exists($this->filepath) || !is_readable($this->filepath)) {
  1431. $this->error = get_string('error_couldnotopensourcefile', 'hotpot', $this->filepath);
  1432. if ($this->report_errors) {
  1433. error($this->error, $this->course_homeurl);
  1434. }
  1435. return;
  1436. }
  1437. // read in the XML source
  1438. $this->source = file_get_contents($this->filepath);
  1439. // convert relative URLs to absolute URLs
  1440. if ($this->convert_urls) {
  1441. $this->hotpot_convert_relative_urls($this->source);
  1442. }
  1443. $this->html = '';
  1444. $this->quiztype = '';
  1445. $this->outputformat = 0;
  1446. // is this an html file?
  1447. if (preg_match('|\.html?$|', $this->filename)) {
  1448. $this->filetype = 'html';
  1449. $this->html = &$this->source;
  1450. // relative URLs in stylesheets
  1451. $search = '/'.'(<style[^>]*>)'.'(.*?)'.'(<\/style>)'.'/is';
  1452. $this->source = preg_replace_callback($search, array(&$this, 'callback_stylesheets_urls'), $this->source);
  1453. // relative URLs in "PreloadImages(...);"
  1454. $search = '/'.'(?<='.'PreloadImages'.'\('.')'."([^)]+?)".'(?='.'\);'.')'.'/is';
  1455. $this->source = preg_replace_callback($search, array(&$this, 'callback_preloadimages_urls'), $this->source);
  1456. // relative URLs in <button class="NavButton" ... onclick="location='...'">
  1457. $search = '/'.'(?<='.'onclick="'."location='".')'."([^']*)".'(?='."'; return false;".'")'.'/is';
  1458. $this->source = preg_replace_callback($search, array(&$this, 'callback_navbutton_url'), $this->source);
  1459. // relative URLs in <a ... onclick="window.open('...')...">...</a>
  1460. $search = '/'.'(?<='.'onclick="'."window.open\\('".')'."([^']*)".'(?='."'\\);return false;".'")'.'/is';
  1461. $this->source = preg_replace_callback($search, array(&$this, 'callback_url'), $this->source);
  1462. } else {
  1463. // relative URLs in <a ... onclick="window.open('...')...">...</a>
  1464. $search = '/'.'(?<='.'onclick=&quot;'."window.open\\(&apos;".')'."(.*?)".'(?='."&apos;\\);return false;".'&quot;)'.'/is';
  1465. $this->source = preg_replace_callback($search, array(&$this, 'callback_url'), $this->source);
  1466. if ($this->parse_xml) {
  1467. $this->filetype = 'xml';
  1468. // encode "gap fill" text in JCloze exercise
  1469. $this->encode_cdata($this->source, 'gap-fill');
  1470. // convert source to xml tree
  1471. $this->hotpot_xml_tree($this->source);
  1472. $keys = array_keys($this->xml);
  1473. foreach ($keys as $key) {
  1474. if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
  1475. $this->quiztype = strtolower($matches[2]);
  1476. $this->xml_root = "['$key']['#']";
  1477. break;
  1478. }
  1479. }
  1480. }
  1481. if ($this->create_html) {
  1482. // set the real output format from the requested output format
  1483. $this->real_outputformat = $this->obj_value($obj, 'outputformat');
  1484. $this->draganddrop = '';
  1485. if (
  1486. empty($this->real_outputformat) ||
  1487. $this->real_outputformat==HOTPOT_OUTPUTFORMAT_BEST ||
  1488. empty($HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat])
  1489. ) {
  1490. if ($CFG->hotpotismobile && isset($HOTPOT_OUTPUTFORMAT_DIR[HOTPOT_OUTPUTFORMAT_MOBILE])) {
  1491. $this->real_outputformat = HOTPOT_OUTPUTFORMAT_MOBILE;
  1492. } else { // PC
  1493. if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
  1494. $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6_PLUS;
  1495. } else {
  1496. $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
  1497. }
  1498. }
  1499. }
  1500. if ($this->real_outputformat==HOTPOT_OUTPUTFORMAT_V6_PLUS) {
  1501. if ($this->quiztype=='jmatch' || $this->quiztype=='jmix') {
  1502. $this->draganddrop = 'd'; // prefix for templates (can also be "f" ?)
  1503. }
  1504. $this->real_outputformat = HOTPOT_OUTPUTFORMAT_V6;
  1505. }
  1506. // set path(s) to template
  1507. $this->template_dir = $HOTPOT_OUTPUTFORMAT_DIR[$this->real_outputformat];
  1508. $this->template_dirpath = $CFG->hotpottemplate.'/'.$this->template_dir;
  1509. $this->template_filepath = $CFG->hotpottemplate.'/'.$this->template_dir.'.php';
  1510. // check template class exists
  1511. if (!file_exists($this->template_filepath) || !is_readable($this->template_filepath)) {
  1512. $this->error = get_string('error_couldnotopentemplate', 'hotpot', $this->template_dir);
  1513. if ($this->report_errors) {
  1514. error($this->error, $this->course_homeurl);
  1515. }
  1516. return;
  1517. }
  1518. // get default and output-specfic template classes
  1519. include($this->template_filepath);
  1520. // create html (using the template for the specified output format)
  1521. $this->template = new hotpot_xml_quiz_template($this);
  1522. $this->html = &$this->template->html;
  1523. } // end $this->create_html
  1524. } // end if html/xml file
  1525. } // end if $this->read_file
  1526. } // end constructor function
  1527. function callback_stylesheets_urls(&$matches) {
  1528. return $matches[1].hotpot_convert_stylesheets_urls($this->get_baseurl(), $this->reference , $matches[2], false).$matches[3];
  1529. }
  1530. function callback_preloadimages_urls(&$matches) {
  1531. return hotpot_convert_preloadimages_urls($this->get_baseurl(), $this->reference, $matches[1], false);
  1532. }
  1533. function callback_navbutton_url(&$matches) {
  1534. return hotpot_convert_navbutton_url($this->get_baseurl(), $this->reference, $matches[1], $this->course, false);
  1535. }
  1536. function callback_url(&$matches) {
  1537. return hotpot_convert_url($this->get_baseurl(), $this->reference, $matches[1], false);
  1538. }
  1539. function callback_relative_url(&$matches) {
  1540. return hotpot_convert_relative_url($this->get_baseurl(), $this->reference, $matches[1], $matches[6], $matches[7], false);
  1541. }
  1542. function hotpot_convert_relative_urls(&$str) {
  1543. $tagopen = '(?:(<)|(&lt;)|(&amp;#x003C;))'; // left angle bracket
  1544. $tagclose = '(?(2)>|(?(3)&gt;|(?(4)&amp;#x003E;)))'; // right angle bracket (to match left angle bracket)
  1545. $space = '\s+'; // at least one space
  1546. $anychar = '(?:[^>]*?)'; // any character
  1547. $quoteopen = '("|&quot;|&amp;quot;)'; // open quote
  1548. $quoteclose = '\\5'; // close quote (to match open quote)
  1549. $tags = array('script'=>'src', 'link'=>'href', 'a'=>'href','img'=>'src','param'=>'value', 'object'=>'data', 'embed'=>'src', '(?:table|th|td)' =>'background');
  1550. foreach ($tags as $tag=>$attribute) {
  1551. if ($tag=='param') {
  1552. $url = '\S+?\.\S+?'; // must include a filename and have no spaces
  1553. } else {
  1554. $url = '.*?';
  1555. }
  1556. $search = "/($tagopen$tag$space$anychar$attribute=$quoteopen)($url)($quoteclose$anychar$tagclose)/is";
  1557. $str = preg_replace_callback($search, array(&$this, 'callback_relative_url'), $str);
  1558. }
  1559. }
  1560. function get_baseurl() {
  1561. // set the url base (first time only)
  1562. if (!isset($this->baseurl)) {
  1563. global $CFG;
  1564. require_once($CFG->libdir.'/filelib.php');
  1565. $this->baseurl = get_file_url($this->filedir).'/';
  1566. }
  1567. return $this->baseurl;
  1568. }
  1569. // insert forms and messages
  1570. function remove_nav_buttons() {
  1571. $search = '/<!-- Begin(Top|Bottom)NavButtons -->(.*?)<!-- End(Top|Bottom)NavButtons -->/s';
  1572. $this->html = preg_replace($search, '', $this->html);
  1573. }
  1574. function insert_script($src=HOTPOT_JS) {
  1575. $script = '<script src="'.$src.'" type="text/javascript"></script>'."\n";
  1576. $this->html = preg_replace('|</head>|i', $script.'</head>', $this->html, 1);
  1577. }
  1578. function insert_submission_form($attemptid, $startblock, $endblock, $keep_contents=false, $targetframe='') {
  1579. $form_id = 'store';
  1580. $form_fields = ''
  1581. . '<fieldset style="display:none">'
  1582. . '<input type="hidden" name="attemptid" value="'.$attemptid.'" />'
  1583. . '<input type="hidden" name="starttime" value="" />'
  1584. . '<input type="hidden" name="endtime" value="" />'
  1585. . '<input type="hidden" name="mark" value="" />'
  1586. . '<input type="hidden" name="detail" value="" />'
  1587. . '<input type="hidden" name="status" value="" />'
  1588. . '</fieldset>'
  1589. ;
  1590. $this->insert_form($startblock, $endblock, $form_id, $form_fields, $keep_contents, false, $targetframe);
  1591. }
  1592. function insert_giveup_form($attemptid, $startblock, $endblock, $keep_contents=false) {
  1593. $form_id = ''; // no <form> tag will be generated
  1594. $form_fields = ''
  1595. . '<button onclick="Finish('.HOTPOT_STATUS_ABANDONED.')" class="FuncButton" '
  1596. . 'onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" '
  1597. . 'onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" '
  1598. . 'onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">'
  1599. . get_string('giveup', 'hotpot').'</button>'
  1600. ;
  1601. $this->insert_form($startblock, $endblock, $form_id, $form_fields, $keep_contents, true);
  1602. }
  1603. function insert_form($startblock, $endblock, $form_id, $form_fields, $keep_contents, $center=false, $targetframe='') {
  1604. global $CFG;
  1605. $search = '/('.preg_quote($startblock, '/').')(.*?)('.preg_quote($endblock, '/').')/s';
  1606. $replace = $form_fields;
  1607. if ($keep_contents) {
  1608. $replace .= '\\2';
  1609. }
  1610. if ($targetframe) {
  1611. $frametarget = ' onsubmit="'."this.target='$targetframe';".'"';
  1612. } else if (! empty($CFG->framename)) {
  1613. $frametarget = ' onsubmit="'."this.target='$CFG->framename';".'"';
  1614. } else if (! empty($CFG->frametarget)) {
  1615. $frametarget = $CFG->frametarget;
  1616. } else {
  1617. $frametarget = '';
  1618. }
  1619. if ($form_id) {
  1620. $replace = '<form action="'.$CFG->wwwroot.'/mod/hotpot/attempt.php" method="post" id="'.$form_id.'"'.$frametarget.'>'.$replace.'</form>';
  1621. }
  1622. if ($center) {
  1623. $replace = '<div style="margin-left:auto; margin-right:auto; text-align: center;">'.$replace.'</div>';
  1624. }
  1625. $replace = '\\1'.$replace.'\\3';
  1626. $this->html = preg_replace($search, $replace, $this->html, 1);
  1627. }
  1628. function insert_message($start_str, $message, $color='red', $align='center') {
  1629. $message = '<p align="'.$align.'" style="text-align:'.$align.'"><b><font color="'.$color.'">'.$message."</font></b></p>\n";
  1630. $this->html = preg_replace('|'.preg_quote($start_str).'|', $start_str.$message, $this->html, 1);
  1631. }
  1632. function adjust_media_urls() {
  1633. if ($this->forceplugins) {
  1634. // make sure the Moodle media plugin is available
  1635. global $CFG;
  1636. //include_once "$CFG->dirroot/filter/mediaplugin/filter.php";
  1637. include_once "$CFG->dirroot/mod/hotpot/mediaplayers/moodle/filter.php";
  1638. $space = '\s(?:.+\s)?';
  1639. $quote = '["'."']?"; // single, double, or no quote
  1640. // patterns to media files types and paths
  1641. $filetypes = "avi|mpeg|mpg|mp3|mov|wmv|flv";
  1642. if ($CFG->filter_mediaplugin_enable_swf) {
  1643. $filetypes .= '|swf';
  1644. }
  1645. $filepath = '[^"'."']*".'\\.(?:'.$filetypes.')[^"'."']*";
  1646. $tagopen = '(?:(<)|(\\\\u003C))'; // left angle-bracket (uses two parenthese)
  1647. $tagchars = '(?(1)[^>]|(?(2).(?!\\\\u003E)))*?'; // string of chars inside the tag
  1648. $tagclose = '(?(1)>|(?(2)\\\\u003E))'; // right angle-bracket (to match the left one)
  1649. $tagreopen = '(?(1)<|(?(2)\\\\u003C))'; // another left angle-bracket (to match the first one)
  1650. // pattern to match <param> tags which contain the file path
  1651. $param_names = 'movie|src|url|flashvars';
  1652. // wmp : url
  1653. // quicktime : src
  1654. // realplayer : src
  1655. // flash : movie, flashvars
  1656. $param_url = '/'.$tagopen.'param'.'\s'.$tagchars.'name="(?:'.$param_names.')"'.$tagchars.'value="('.$filepath.')"'.$tagchars.$tagclose.'/is';
  1657. // pattern to match <a> tags which link to multimedia files
  1658. $link_url = '/'.$tagopen.'a'.'\s'.$tagchars.'href="('.$filepath.')"'.$tagchars.$tagclose.'.*?'.$tagreopen.'\/a'.$tagclose.'/is';
  1659. // extract <object> tags
  1660. $object_tag = '/'.$tagopen.'object'.'\s'.$tagchars.$tagclose.'(.*?)'.'(?:'.$tagreopen.'\/object'.$tagclose.')+/is';
  1661. preg_match_all($object_tag, $this->html, $objects);
  1662. $i_max = count($objects[0]);
  1663. for ($i=0; $i<$i_max; $i++) {
  1664. // extract URL from <param> or <a>
  1665. $url = '';
  1666. if (preg_match($param_url, $objects[3][$i], $matches) || preg_match($link_url, $objects[3][$i], $matches)) {
  1667. $url = $matches[3];
  1668. }
  1669. if ($url) {
  1670. // strip inner tags (e.g. <embed>)
  1671. $txt = preg_replace("/$tagopen.*?$tagclose/", '', $objects[3][$i]);
  1672. // if url is in the query string, remove the leading characters
  1673. $url = preg_replace('/^([^=]+=[^&]*&)*[^=]+=(http:[^&]*)$/', '$2', $url, 1);
  1674. $link = '<a href="'.$url.'">'.$txt.'</a>';
  1675. $new_object = hotpot_mediaplayer_moodle($this, $link);
  1676. $new_object = str_replace($link, '', $new_object);
  1677. $new_object = str_replace('&amp;', '&', $new_object);
  1678. $this->html = str_replace($objects[0][$i], $new_object, $this->html);
  1679. }
  1680. }
  1681. }
  1682. }
  1683. } // end class
  1684. function hotpot_stripslashes($str) {
  1685. // strip slashes from double quotes, single quotes and back slashes
  1686. // the slashes were added by preg_replace() when using the "e" modifier
  1687. static $escapedchars = array('\\\\', '\\"', "\\'");
  1688. static $unescapedchars = array('\\', '"', "'");
  1689. return str_replace($escapedchars, $unescapedchars, $str);
  1690. }
  1691. function hotpot_convert_stylesheets_urls($baseurl, $reference, $css, $stripslashes=true) {
  1692. if ($stripslashes) {
  1693. $css = hotpot_stripslashes($css);
  1694. }
  1695. $search = '/(?<=url\()'.'(?:.+?)'.'(?=\))/is';
  1696. if (preg_match_all($search, $css, $matches, PREG_OFFSET_CAPTURE)) {
  1697. $i_max = count($matches[0]) - 1;
  1698. for ($i=$i_max; $i>=0; $i--) {
  1699. $match = $matches[0][$i][0];
  1700. $start = $matches[0][$i][1];
  1701. $replace = hotpot_convert_url($baseurl, $reference, $match, false);
  1702. $css = substr_replace($css, $replace, $start, strlen($match));
  1703. }
  1704. }
  1705. return $css;
  1706. }
  1707. function hotpot_convert_preloadimages_urls($baseurl, $reference, $urls, $stripslashes=true) {
  1708. if ($stripslashes) {
  1709. $urls = hotpot_stripslashes($urls);
  1710. }
  1711. $search = '|(?<=["'."'])(?:[^,'".'"]*?)(?=["'."'])|is";
  1712. if (preg_match_all($search, $urls, $matches, PREG_OFFSET_CAPTURE)) {
  1713. $i_max = count($matches[0]) - 1;
  1714. for ($i=$i_max; $i>=0; $i--) {
  1715. $match = $matches[0][$i][0];
  1716. $start = $matches[0][$i][1];
  1717. $replace = hotpot_convert_url($baseurl, $reference, $match, false);
  1718. $urls = substr_replace($urls, $replace, $start, strlen($match));
  1719. }
  1720. }
  1721. return $urls;
  1722. }
  1723. function hotpot_convert_navbutton_url($baseurl, $reference, $url, $course, $stripslashes=true) {
  1724. global $CFG;
  1725. if ($stripslashes) {
  1726. $url = hotpot_stripslashes($url);
  1727. }
  1728. $url = hotpot_convert_url($baseurl, $reference, $url, false);
  1729. // is this a $url for another hotpot in this course ?
  1730. if (preg_match("/^".preg_quote($baseurl, '/')."(.*)$/", $url, $matches)) {
  1731. if ($records = get_records_select('hotpot', "course='$course' AND reference='".$matches[1]."'")) {
  1732. $ids = array_keys($records);
  1733. $url = "$CFG->wwwroot/mod/hotpot/view.php?hp=".$ids[0];
  1734. }
  1735. }
  1736. return $url;
  1737. }
  1738. function hotpot_convert_relative_url($baseurl, $reference, $opentag, $url, $closetag, $stripslashes=true) {
  1739. if ($stripslashes) {
  1740. $opentag = hotpot_stripslashes($opentag);
  1741. $url = hotpot_stripslashes($url);
  1742. $closetag = hotpot_stripslashes($closetag);
  1743. }
  1744. // catch <PARAM name="FlashVars" value="TheSound=soundfile.mp3">
  1745. // ampersands can appear as "&", "&amp;" or "&amp;#x0026;amp;"
  1746. if (preg_match('/^'.'\w+=[^&]+'.'('.'&((amp;#x0026;)?amp;)?'.'\w+=[^&]+)*'.'$/', $url)) {
  1747. $query = $url;
  1748. $url = '';
  1749. $fragment = '';
  1750. // parse the $url into $matches
  1751. // [1] path
  1752. // [2] query string, if any
  1753. // [3] anchor fragment, if any
  1754. } else if (preg_match('/^'.'([^?]*)'.'((?:\\?[^#]*)?)'.'((?:#.*)?)'.'$/', $url, $matches)) {
  1755. $url = $matches[1];
  1756. $query = $matches[2];
  1757. $fragment = $matches[3];
  1758. // these appears to be no query or fragment in this url
  1759. } else {
  1760. $query = '';
  1761. $fragment = '';
  1762. }
  1763. if ($url) {
  1764. $url = hotpot_convert_url($baseurl, $reference, $url, false);
  1765. }
  1766. if ($query) {
  1767. $search = '/'.'(file|src|thesound|mp3)='."([^&]+)".'/is';
  1768. if (preg_match_all($search, $query, $matches, PREG_OFFSET_CAPTURE)) {
  1769. $i_max = count($matches[0]) - 1;
  1770. for ($i=$i_max; $i>=0; $i--) {
  1771. $match = $matches[2][$i][0];
  1772. $start = $matches[2][$i][1];
  1773. $replace = hotpot_convert_url($baseurl, $reference, $match, false);
  1774. $query = substr_replace($query, $replace, $start, strlen($match));
  1775. }
  1776. }
  1777. }
  1778. $url = $opentag.$url.$query.$fragment.$closetag;
  1779. return $url;
  1780. }
  1781. function hotpot_convert_url($baseurl, $reference, $url, $stripslashes=true) {
  1782. // maintain a cache of converted urls
  1783. static $HOTPOT_RELATIVE_URLS = array();
  1784. if ($stripslashes) {
  1785. $url = hotpot_stripslashes($url);
  1786. }
  1787. // is this an absolute url? (or javascript pseudo url)
  1788. if (preg_match('%^(http://|https://|/|javascript:)%i', $url)) {
  1789. // do nothing
  1790. // has this relative url already been converted?
  1791. } else if (isset($HOTPOT_RELATIVE_URLS[$url])) {
  1792. $url = $HOTPOT_RELATIVE_URLS[$url];
  1793. } else {
  1794. $relativeurl = $url;
  1795. // get the subdirectory, $dir, of the quiz $reference
  1796. $dir = dirname($reference);
  1797. // allow for leading "./" and "../"
  1798. while (preg_match('|^(\.{1,2})/(.*)$|', $url, $matches)) {
  1799. if ($matches[1]=='..') {
  1800. $dir = dirname($dir);
  1801. }
  1802. $url = $matches[2];
  1803. }
  1804. // add subdirectory, $dir, to $baseurl, if necessary
  1805. if ($dir && $dir<>'.') {
  1806. $baseurl .= "$dir/";
  1807. }
  1808. // prefix $url with $baseurl
  1809. $url = "$baseurl$url";
  1810. // add url to cache
  1811. $HOTPOT_RELATIVE_URLS[$relativeurl] = $url;
  1812. }
  1813. return $url;
  1814. }
  1815. // ===================================================
  1816. // function for adding attempt questions and responses
  1817. // ===================================================
  1818. function hotpot_add_attempt_details(&$attempt) {
  1819. // encode ampersands so that HTML entities are preserved in the XML parser
  1820. // N.B. ampersands inside <![CDATA[ ]]> blocks do NOT need to be encoded
  1821. $old = &$attempt->details; // shortcut to "old" details
  1822. $new = '';
  1823. $str_start = 0;
  1824. while (($cdata_start = strpos($old, '<![CDATA[', $str_start)) && ($cdata_end = strpos($old, ']]>', $cdata_start))) {
  1825. $cdata_end += 3;
  1826. $new .= str_replace('&', '&amp;', substr($old, $str_start, $cdata_start-$str_start)).substr($old, $cdata_start, $cdata_end-$cdata_start);
  1827. $str_start = $cdata_end;
  1828. }
  1829. $new .= str_replace('&', '&amp;', substr($old, $str_start));
  1830. unset($old);
  1831. // parse the attempt details as xml
  1832. $details = new hotpot_xml_tree($new, "['hpjsresult']['#']");
  1833. $num = -1;
  1834. $q_num = -1;
  1835. $question = NULL;
  1836. $reponse = NULL;
  1837. $i = 0;
  1838. $tags = 'fields,field';
  1839. while (($field="[$i]['#']") && $details->xml_value($tags, $field)) {
  1840. $name = $details->xml_value($tags, $field."['fieldname'][0]['#']");
  1841. $data = $details->xml_value($tags, $field."['fielddata'][0]['#']");
  1842. // parse the field name into $matches
  1843. // [1] quiz type
  1844. // [2] attempt detail name
  1845. if (preg_match('/^(\w+?)_(\w+)$/', $name, $matches)) {
  1846. $quiztype = strtolower($matches[1]);
  1847. $name = strtolower($matches[2]);
  1848. // parse the attempt detail $name into $matches
  1849. // [1] question number
  1850. // [2] question detail name
  1851. if (preg_match('/^q(\d+)_(\w+)$/', $name, $matches)) {
  1852. $num = $matches[1];
  1853. $name = strtolower($matches[2]);
  1854. $data = addslashes($data);
  1855. // adjust JCross question numbers
  1856. if (preg_match('/^(across|down)(.*)$/', $name, $matches)) {
  1857. $num .= '_'.$matches[1]; // e.g. 01_across, 02_down
  1858. $name = $matches[2];
  1859. if (substr($name, 0, 1)=='_') {
  1860. $name = substr($name, 1); // remove leading '_'
  1861. }
  1862. }
  1863. // is this a new question (or the first one)?
  1864. if ($q_num<>$num) {
  1865. // add previous question and response, if any
  1866. hotpot_add_response($attempt, $question, $response);
  1867. // initialize question object
  1868. $question = NULL;
  1869. $question->name = '';
  1870. $question->text = '';
  1871. $question->hotpot = $attempt->hotpot;
  1872. // initialize response object
  1873. $response = NULL;
  1874. $response->attempt = $attempt->id;
  1875. // update question number
  1876. $q_num = $num;
  1877. }
  1878. // adjust field name and value, and set question type
  1879. // (may not be necessary one day)
  1880. hotpot_adjust_response_field($quiztype, $question, $num, $name, $data);
  1881. // add $data to the question/response details
  1882. switch ($name) {
  1883. case 'name':
  1884. case 'type':
  1885. $question->$name = $data;
  1886. break;
  1887. case 'text':
  1888. $question->$name = hotpot_string_id($data);
  1889. break;
  1890. case 'correct':
  1891. case 'ignored':
  1892. case 'wrong':
  1893. $response->$name = hotpot_string_ids($data);
  1894. break;
  1895. case 'score':
  1896. case 'weighting':
  1897. case 'hints':
  1898. case 'clues':
  1899. case 'checks':
  1900. $response->$name = intval($data);
  1901. break;
  1902. }
  1903. } else { // attempt details
  1904. // adjust field name and value
  1905. hotpot_adjust_response_field($quiztype, $question, $num='', $name, $data);
  1906. // add $data to the attempt details
  1907. if ($name=='penalties') {
  1908. $attempt->$name = intval($data);
  1909. }
  1910. }
  1911. }
  1912. $i++;
  1913. } // end while
  1914. // add the final question and response, if any
  1915. hotpot_add_response($attempt, $question, $response);
  1916. }
  1917. function hotpot_add_response(&$attempt, &$question, &$response) {
  1918. global $db, $next_url;
  1919. $loopcount = 1;
  1920. $looping = isset($question) && isset($question->name) && isset($response);
  1921. while ($looping) {
  1922. if ($loopcount==1) {
  1923. $questionname = $question->name;
  1924. }
  1925. $question->md5key = md5($question->name);
  1926. if (!$question->id = get_field('hotpot_questions', 'id', 'hotpot', $attempt->hotpot, 'md5key', $question->md5key, 'name', $question->name)) {
  1927. // add question record
  1928. if (!$question->id = insert_record('hotpot_questions', $question)) {
  1929. error("Could not add question record (attempt_id=$attempt->id): ".$db->ErrorMsg(), $next_url);
  1930. }
  1931. }
  1932. if (record_exists('hotpot_responses', 'attempt', $attempt->id, 'question', $question->id)) {
  1933. // there is already a response to this question for this attempt
  1934. // probably because this quiz has two questions with the same text
  1935. // e.g. Which one of these answers is correct?
  1936. // To workaround this, we create new question names
  1937. // e.g. Which one of these answers is correct? (2)
  1938. // until we get a question name for which there is no response yet on this attempt
  1939. $loopcount++;
  1940. $question->name = "$questionname ($loopcount)";
  1941. // This method fails to correctly identify questions in
  1942. // quizzes which allow questions to be shuffled or omitted.
  1943. // As yet, there is no workaround for such cases.
  1944. } else {
  1945. $response->question = $question->id;
  1946. // add response record
  1947. if(!$response->id = insert_record('hotpot_responses', $response)) {
  1948. error("Could not add response record (attempt_id=$attempt->id, question_id=$question->id): ".$db->ErrorMsg(), $next_url);
  1949. }
  1950. // we can stop looping now
  1951. $looping = false;
  1952. }
  1953. } // end while
  1954. }
  1955. function hotpot_adjust_response_field($quiztype, &$question, &$num, &$name, &$data) {
  1956. switch ($quiztype) {
  1957. case 'jbc':
  1958. $question->type = HOTPOT_JCB;
  1959. switch ($name) {
  1960. case 'right':
  1961. $name = 'correct';
  1962. break;
  1963. }
  1964. break;
  1965. case 'jcloze':
  1966. $question->type = HOTPOT_JCLOZE;
  1967. if (is_numeric($num)) {
  1968. $question->name = $num;
  1969. }
  1970. switch ($name) {
  1971. case 'penalties':
  1972. if (is_numeric($num)) {
  1973. $name = 'checks';
  1974. if (is_numeric($data)) {
  1975. $data++;
  1976. }
  1977. }
  1978. break;
  1979. case 'clue_shown':
  1980. $name = 'clues';
  1981. $data = ($data=='YES' ? 1 : 0);
  1982. break;
  1983. case 'clue_text':
  1984. $name = 'text';
  1985. break;
  1986. }
  1987. break;
  1988. case 'jcross':
  1989. $question->type = HOTPOT_JCROSS;
  1990. $question->name = $num;
  1991. switch ($name) {
  1992. case '': // HotPot v2.0.x
  1993. $name = 'correct';
  1994. break;
  1995. case 'clue':
  1996. $name = 'text';
  1997. break;
  1998. }
  1999. break;
  2000. case 'jmatch':
  2001. $question->type = HOTPOT_JMATCH;
  2002. switch ($name) {
  2003. case 'attempts':
  2004. $name = 'penalties';
  2005. if (is_numeric($data) && $data>0) {
  2006. $data--;
  2007. }
  2008. break;
  2009. case 'lhs':
  2010. $name = 'name';
  2011. break;
  2012. case 'rhs':
  2013. $name = 'correct';
  2014. break;
  2015. }
  2016. break;
  2017. case 'jmix':
  2018. $question->type = HOTPOT_JMIX;
  2019. $question->name = $num;
  2020. switch ($name) {
  2021. // keep these in for "restore" of courses
  2022. // which were backed up with HotPot v2.0.x
  2023. case 'wrongguesses':
  2024. $name = 'checks';
  2025. if (is_numeric($data)) {
  2026. $data++;
  2027. }
  2028. break;
  2029. case 'right':
  2030. $name = 'correct';
  2031. break;
  2032. }
  2033. break;
  2034. break;
  2035. case 'jquiz':
  2036. switch ($name) {
  2037. case 'type':
  2038. $data = HOTPOT_JQUIZ;
  2039. switch ($data) {
  2040. case 'multiple-choice':
  2041. $data .= '.'.HOTPOT_JQUIZ_MULTICHOICE;
  2042. break;
  2043. case 'short-answer':
  2044. $data .= '.'.HOTPOT_JQUIZ_SHORTANSWER;
  2045. break;
  2046. case 'hybrid':
  2047. $data .= '.'.HOTPOT_JQUIZ_HYBRID;
  2048. break;
  2049. case 'multi-select':
  2050. $data .= '.'.HOTPOT_JQUIZ_MULTISELECT;
  2051. case 'n/a':
  2052. default:
  2053. // do nothing more
  2054. break;
  2055. }
  2056. break;
  2057. case 'question':
  2058. $name = 'name';
  2059. break;
  2060. }
  2061. break;
  2062. case 'rhubarb':
  2063. $question->type = HOTPOT_TEXTOYS_RHUBARB;
  2064. if (empty($question->name)) {
  2065. $question->name = $num;
  2066. }
  2067. break;
  2068. case 'sequitur':
  2069. $question->type = HOTPOT_TEXTOYS_SEQUITUR;
  2070. break;
  2071. }
  2072. }
  2073. function hotpot_string_ids($field_value) {
  2074. $ids = array();
  2075. $strings = explode(',', $field_value);
  2076. foreach($strings as $str) {
  2077. if ($id = hotpot_string_id($str)) {
  2078. $ids[] = $id;
  2079. }
  2080. }
  2081. return implode(',', $ids);
  2082. }
  2083. function hotpot_string_id($str) {
  2084. $id = '';
  2085. if (isset($str) && $str<>'') {
  2086. // get the id from the table if it is already there
  2087. $md5key = md5($str);
  2088. if (!$id = get_field('hotpot_strings', 'id', 'md5key', $md5key, 'string', $str)) {
  2089. // create a string record
  2090. $record = new stdClass();
  2091. $record->string = $str;
  2092. $record->md5key = $md5key;
  2093. // try and add the new string record
  2094. if (!$id = insert_record('hotpot_strings', $record)) {
  2095. global $db;
  2096. error("Could not add string record for '".htmlspecialchars($str)."': ".$db->ErrorMsg());
  2097. }
  2098. }
  2099. }
  2100. return $id;
  2101. }
  2102. function hotpot_get_view_actions() {
  2103. return array('view','view all','report');
  2104. }
  2105. function hotpot_get_post_actions() {
  2106. return array('attempt','review','submit');
  2107. }
  2108. if (!function_exists('file_get_contents')) {
  2109. // add this function for php version<4.3
  2110. function file_get_contents($filepath) {
  2111. $contents = file($filepath);
  2112. if (is_array($contents)) {
  2113. $contents = implode('', $contents);
  2114. }
  2115. return $contents;
  2116. }
  2117. }
  2118. if (!function_exists('html_entity_decode')) {
  2119. // add this function for php version<4.3
  2120. function html_entity_decode($str) {
  2121. $t = get_html_translation_table(HTML_ENTITIES);
  2122. $t = array_flip($t);
  2123. return strtr($str, $t);
  2124. }
  2125. }
  2126. // required for Moodle 1.x
  2127. if (!isset($CFG->pixpath)) {
  2128. $CFG->pixpath = "$CFG->wwwroot/pix";
  2129. }
  2130. if (!function_exists('fullname')) {
  2131. // add this function for Moodle 1.x
  2132. function fullname($user) {
  2133. return "$user->firstname $user->lastname";
  2134. }
  2135. }
  2136. if (!function_exists('get_user_preferences')) {
  2137. // add this function for Moodle 1.x
  2138. function get_user_preferences($name=NULL, $default=NULL, $userid=NULL) {
  2139. return $default;
  2140. }
  2141. }
  2142. if (!function_exists('set_user_preference')) {
  2143. // add this function for Moodle 1.x
  2144. function set_user_preference($name, $value, $otheruser=NULL) {
  2145. return false;
  2146. }
  2147. }
  2148. if (!function_exists('get_coursemodule_from_id')) {
  2149. // add this function for Moodle < 1.5.4
  2150. function get_coursemodule_from_id($modulename, $cmid, $courseid=0) {
  2151. global $CFG;
  2152. return get_record_sql("
  2153. SELECT
  2154. cm.*, m.name, md.name as modname
  2155. FROM
  2156. {$CFG->prefix}course_modules cm,
  2157. {$CFG->prefix}modules md,
  2158. {$CFG->prefix}$modulename m
  2159. WHERE
  2160. ".($courseid ? "cm.course = '$courseid' AND " : '')."
  2161. cm.id = '$cmid' AND
  2162. cm.instance = m.id AND
  2163. md.name = '$modulename' AND
  2164. md.id = cm.module
  2165. ");
  2166. }
  2167. }
  2168. if (!function_exists('get_coursemodule_from_instance')) {
  2169. // add this function for Moodle < 1.5.4
  2170. function get_coursemodule_from_instance($modulename, $instance, $courseid=0) {
  2171. global $CFG;
  2172. return get_record_sql("
  2173. SELECT
  2174. cm.*, m.name, md.name as modname
  2175. FROM
  2176. {$CFG->prefix}course_modules cm,
  2177. {$CFG->prefix}modules md,
  2178. {$CFG->prefix}$modulename m
  2179. WHERE
  2180. ".($courseid ? "cm.course = '$courseid' AND" : '')."
  2181. cm.instance = m.id AND
  2182. md.name = '$modulename' AND
  2183. md.id = cm.module AND
  2184. m.id = '$instance'
  2185. ");
  2186. }
  2187. }
  2188. function hotpot_utf8_to_html_entity($char) {
  2189. // http://www.zend.com/codex.php?id=835&single=1
  2190. // array used to figure what number to decrement from character order value
  2191. // according to number of characters used to map unicode to ascii by utf-8
  2192. static $HOTPOT_UTF8_DECREMENT = array(
  2193. 1=>0, 2=>192, 3=>224, 4=>240
  2194. );
  2195. // the number of bits to shift each character by
  2196. static $HOTPOT_UTF8_SHIFT = array(
  2197. 1=>array(0=>0),
  2198. 2=>array(0=>6, 1=>0),
  2199. 3=>array(0=>12, 1=>6, 2=>0),
  2200. 4=>array(0=>18, 1=>12, 2=>6, 3=>0)
  2201. );
  2202. $dec = 0;
  2203. $len = strlen($char);
  2204. for ($pos=0; $pos<$len; $pos++) {
  2205. $ord = ord ($char{$pos});
  2206. $ord -= ($pos ? 128 : $HOTPOT_UTF8_DECREMENT[$len]);
  2207. $dec += ($ord << $HOTPOT_UTF8_SHIFT[$len][$pos]);
  2208. }
  2209. return '&#x'.sprintf('%04X', $dec).';';
  2210. }
  2211. function hotpot_print_show_links($course, $location, $reference, $actions='', $spacer=' &nbsp; ', $new_window=false, $return=false) {
  2212. global $CFG;
  2213. if (is_string($actions)) {
  2214. if (empty($actions)) {
  2215. $actions = 'showxmlsource,showxmltree,showhtmlsource';
  2216. }
  2217. $actions = explode(',', $actions);
  2218. }
  2219. $strenterafilename = get_string('enterafilename', 'hotpot');
  2220. $html = <<<END_OF_SCRIPT
  2221. <script type="text/javascript">
  2222. //<![CDATA[
  2223. function setLink(lnk) {
  2224. var form = null;
  2225. if (document.forms['mform1']) {
  2226. var form = document.forms['mform1'];
  2227. } else if (document.forms['form']) {
  2228. var form = document.forms['form'];
  2229. }
  2230. return setLinkAttribute(lnk, 'reference', form) && setLinkAttribute(lnk, 'location', form);
  2231. }
  2232. function setLinkAttribute(lnk, name, form) {
  2233. // set link attribute value using
  2234. // f(orm) name and e(lement) name
  2235. var r = true; // result
  2236. var obj = (form) ? form.elements[name] : null;
  2237. if (obj) {
  2238. r = false;
  2239. var v = getObjValue(obj);
  2240. if (v=='') {
  2241. alert('$strenterafilename');
  2242. } else {
  2243. var s = lnk.href;
  2244. var i = s.indexOf('?');
  2245. if (i>=0) {
  2246. i = s.indexOf(name+'=', i+1);
  2247. if (i>=0) {
  2248. i += name.length+1;
  2249. var ii = s.indexOf('&', i);
  2250. if (ii<0) {
  2251. ii = s.length;
  2252. }
  2253. lnk.href = s.substring(0, i) + v + s.substring(ii);
  2254. r = true;
  2255. }
  2256. }
  2257. }
  2258. }
  2259. return r;
  2260. }
  2261. function getObjValue(obj) {
  2262. var v = ''; // the value
  2263. var t = (obj && obj.type) ? obj.type : "";
  2264. if (t=="text" || t=="textarea" || t=="hidden") {
  2265. v = obj.value;
  2266. } else if (t=="select-one" || t=="select-multiple") {
  2267. var l = obj.options.length;
  2268. for (var i=0; i<l; i++) {
  2269. if (obj.options[i].selected) {
  2270. v += (v=="" ? "" : ",") + obj.options[i].value;
  2271. }
  2272. }
  2273. }
  2274. return v;
  2275. }
  2276. function getDir(s) {
  2277. if (s.charAt(0)!='/') {
  2278. s = '/' + s;
  2279. }
  2280. var i = s.lastIndexOf('/');
  2281. return s.substring(0, i);
  2282. }
  2283. //]]>
  2284. </script>
  2285. END_OF_SCRIPT;
  2286. foreach ($actions as $action) {
  2287. $html .= $spacer
  2288. . '<a href="'
  2289. . $CFG->wwwroot.'/mod/hotpot/show.php'
  2290. . '?course='.$course.'&amp;location='.$location.'&amp;reference='.urlencode($reference).'&amp;action='.$action
  2291. . '"'
  2292. . ' onclick="return setLink(this);"'
  2293. . ($new_window ? ' target="_blank"' : '')
  2294. . '>'.get_string($action, 'hotpot').'</a>'
  2295. ;
  2296. }
  2297. $html = '<span class="helplink">'.$html.'</span>';
  2298. if ($return) {
  2299. return $html;
  2300. } else {
  2301. print $html;
  2302. }
  2303. }
  2304. /**
  2305. * Returns all other caps used in module
  2306. */
  2307. function hotpot_get_extra_capabilities() {
  2308. return array('moodle/site:accessallgroups');
  2309. }
  2310. /**
  2311. * This function is used by the reset_course_userdata function in moodlelib.
  2312. * This function will remove all attempts from hotpot quizzes in the specified course.
  2313. * @param $data the data submitted from the reset course.
  2314. * @return array status array
  2315. */
  2316. function hotpot_reset_userdata($data) {
  2317. global $CFG;
  2318. require_once($CFG->libdir.'/filelib.php');
  2319. $status = array();
  2320. if (!empty($data->reset_hotpot_deleteallattempts)) {
  2321. $hotpotids = "SELECT h.id FROM {$CFG->prefix}hotpot h WHERE h.course={$data->courseid}";
  2322. $attemptids = "SELECT a.id FROM {$CFG->prefix}hotpot_attempts a WHERE a.hotpot in ($hotpotids)";
  2323. delete_records_select('hotpot_responses', "attempt in ($attemptids)");
  2324. delete_records_select('hotpot_details', "attempt in ($attemptids)");
  2325. delete_records_select('hotpot_attempts', "hotpot IN ($hotpotids)");
  2326. $status[] = array('component' => get_string('modulenameplural', 'hotpot'),
  2327. 'item' => get_string('deleteallattempts', 'hotpot'),
  2328. 'error' => false);
  2329. }
  2330. return $status;
  2331. }
  2332. /**
  2333. * Called by course/reset.php
  2334. * @param $mform form passed by reference
  2335. */
  2336. function hotpot_reset_course_form_definition(&$mform) {
  2337. $mform->addElement('header', 'hotpotheader', get_string('modulenameplural', 'hotpot'));
  2338. $mform->addElement('checkbox', 'reset_hotpot_deleteallattempts', get_string('deleteallattempts', 'hotpot'));
  2339. }
  2340. /**
  2341. * Course reset form defaults.
  2342. */
  2343. function hotpot_reset_course_form_defaults($course) {
  2344. return array('reset_hotpot_deleteallattempts' => 1);
  2345. }
  2346. /**
  2347. * Tells if files in moddata are trusted and can be served without XSS protection.
  2348. * @return bool true if file can be submitted by teacher only (trusted), false otherwise
  2349. */
  2350. function hotpot_is_moddata_trusted() {
  2351. return true;
  2352. }
  2353. ?>