PageRenderTime 58ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/attempt/hp/6/jquiz/xml/v6/autoadvance/renderer.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 1133 lines | 900 code | 64 blank | 169 comment | 164 complexity | c2283a2e4d378ee8a6cc74aff74b3f7b MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Render an attempt at a HotPot quiz
  18. * Output format: hp_6_jquiz_xml_v6_autoadvance
  19. *
  20. * @package mod-hotpot
  21. * @copyright 2010 Gordon Bateson <gordon.bateson@gmail.com>
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. defined('MOODLE_INTERNAL') || die();
  25. // get parent class
  26. require_once($CFG->dirroot.'/mod/hotpot/attempt/hp/6/jquiz/xml/v6/renderer.php');
  27. /**
  28. * mod_hotpot_attempt_hp_6_jquiz_xml_v6_autoadvance_renderer
  29. *
  30. * @copyright 2010 Gordon Bateson
  31. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  32. * @since Moodle 2.0
  33. */
  34. class mod_hotpot_attempt_hp_6_jquiz_xml_v6_autoadvance_renderer extends mod_hotpot_attempt_hp_6_jquiz_xml_v6_renderer {
  35. /**
  36. * List of source types which this renderer can handle
  37. *
  38. * @return array of strings
  39. */
  40. public static function sourcetypes() {
  41. return array('hp_6_jquiz_xml');
  42. }
  43. /**
  44. * get_js_functionnames
  45. *
  46. * @return xxx
  47. */
  48. function get_js_functionnames() {
  49. // start list of function names
  50. $names = parent::get_js_functionnames();
  51. $names .= ($names ? ',' : '').'CompleteEmptyFeedback,ReplaceGuessBox,SetFocusToTextbox';
  52. return $names;
  53. }
  54. /**
  55. * fix_js_WriteToInstructions
  56. *
  57. * @param xxx $str (passed by reference)
  58. * @param xxx $start
  59. * @param xxx $length
  60. */
  61. function fix_js_WriteToInstructions(&$str, $start, $length) {
  62. $substr = substr($str, $start, $length);
  63. $search = "/(\s*)document\.getElementById\('InstructionsDiv'\)\.innerHTML = Feedback;/";
  64. $replace = "\\1"
  65. ."var AllDone = true;\\1"
  66. ."for (var QNum=0; QNum<State.length; QNum++){\\1"
  67. ." if (State[QNum]){\\1"
  68. ." if (State[QNum][0] < 0){\\1"
  69. ." AllDone = false;\\1"
  70. ." }\\1"
  71. ." }\\1"
  72. ."}\\1"
  73. ."if (AllDone) {\\1"
  74. ." var obj = document.getElementById('InstructionsDiv');\\1"
  75. ." if (obj) {\\1"
  76. ." obj.innerHTML = Feedback;\\1"
  77. ." obj.style.display = '';\\1"
  78. ." }\\1"
  79. ." var obj = document.getElementById('FeedbackDiv');\\1"
  80. ." if (obj && obj.style.display=='block') {\\1"
  81. ." var obj = document.getElementById('FeedbackContent');\\1"
  82. ." if (obj) {\\1"
  83. ." Feedback = obj.innerHTML + Feedback;;\\1"
  84. ." }\\1"
  85. ." }\\1"
  86. ." Finished = true;\\1"
  87. ." ShowMessage(Feedback);\\1"
  88. ."}"
  89. ;
  90. $substr = preg_replace($search, $replace, $substr, 1);
  91. parent::fix_js_WriteToInstructions($substr, 0, strlen($substr));
  92. $str = substr_replace($str, $substr, $start, $length);
  93. }
  94. /**
  95. * fix_js_StartUp
  96. *
  97. * @param xxx $str (passed by reference)
  98. * @param xxx $start
  99. * @param xxx $length
  100. */
  101. function fix_js_StartUp(&$str, $start, $length) {
  102. $substr = substr($str, $start, $length);
  103. // hide instructions, if they are not required
  104. $search = '/(\s*)strInstructions = document.getElementById[^;]*;/s';
  105. $replace = ''
  106. .'\\1'."var obj = document.getElementById('Instructions');"
  107. .'\\1'."if (obj==null || obj.innerHTML=='') {"
  108. .'\\1'." var obj = document.getElementById('InstructionsDiv');"
  109. .'\\1'." if (obj) {"
  110. .'\\1'." obj.style.display = 'none';"
  111. .'\\1'." }"
  112. .'\\1'."}"
  113. ;
  114. //$substr = preg_replace($search, $replace, $substr, 1);
  115. $insert = '';
  116. if ($this->expand_UserDefined1()) {
  117. $insert .= " AA_SetProgressBar();\n";
  118. }
  119. if ($this->expand_UserDefined2()) {
  120. $insert .= " setTimeout('AA_PlaySound(0,0)', 500);";
  121. }
  122. if ($insert) {
  123. $pos = strrpos($substr, '}');
  124. $substr = substr_replace($substr, $insert, $pos, 0);
  125. }
  126. // call the fix_js_StartUp() method on the parent object
  127. parent::fix_js_StartUp($substr, 0, strlen($substr));
  128. $str = substr_replace($str, $substr, $start, $length);
  129. }
  130. /**
  131. * fix_js_SetUpQuestions
  132. *
  133. * @param xxx $str (passed by reference)
  134. * @param xxx $start
  135. * @param xxx $length
  136. * @return xxx
  137. */
  138. function fix_js_SetUpQuestions(&$str, $start, $length) {
  139. global $CFG;
  140. $substr = substr($str, $start, $length);
  141. parent::fix_js_SetUpQuestions($substr, 0, strlen($substr));
  142. $dots = 'squares'; // default
  143. if ($param = clean_param($this->expand_UserDefined1(), PARAM_ALPHANUM)) {
  144. if (is_dir($CFG->dirroot."/mod/quizport/output/hp/6/jquiz/xml/v6/autoadvance/$param")) {
  145. $dots = $param;
  146. }
  147. }
  148. // assume there are no jmix questions
  149. $jmix = true;
  150. $search = 'Qs.appendChild(QList[i]);';
  151. if ($pos = strpos($substr, $search)) {
  152. $insert = "\n"
  153. ." SetUpJMixQuestion(i);\n"
  154. ;
  155. $substr = substr_replace($substr, $insert, $pos + strlen($search), 0);
  156. }
  157. // add progress bar
  158. if ($dots) {
  159. $search = '/\s*'.'SetQNumReadout\(\);/';
  160. $replace = "\n"
  161. ." var ProgressBar = document.createElement('div');\n"
  162. ." ProgressBar.setAttribute('id', 'ProgressBar');\n"
  163. ." ProgressBar.setAttribute(AA_className(), 'ProgressBar');\n"
  164. ."\n"
  165. ." // add feedback boxes and progess dots for each question\n"
  166. ." for (var i=0; i<QArray.length; i++){\n"
  167. ."\n"
  168. .' // remove bottom border (override class="QuizQuestion")'."\n"
  169. ." if (QArray[i]) {\n"
  170. ." QArray[i].style.borderWidth = '0px';\n"
  171. ." }\n"
  172. ."\n"
  173. ." if (ProgressBar.childNodes.length) {\n"
  174. ." // add arrow between progress dots\n"
  175. ." ProgressBar.appendChild(document.createTextNode(' '));\n"
  176. ." ProgressBar.appendChild(AA_ProgressArrow());\n"
  177. ." ProgressBar.appendChild(document.createTextNode(' '));\n"
  178. ." }\n"
  179. ." ProgressBar.appendChild(AA_ProgressDot(i));\n"
  180. ."\n"
  181. ." // AA_Add_FeedbackBox(i);\n"
  182. ." }\n"
  183. ." var OneByOneReadout = document.getElementById('OneByOneReadout');\n"
  184. ." if (OneByOneReadout) {\n"
  185. ." OneByOneReadout.parentNode.insertBefore(ProgressBar, OneByOneReadout);\n"
  186. ." OneByOneReadout.parentNode.removeChild(OneByOneReadout);\n"
  187. ." }\n"
  188. ." OneByOneReadout = null;\n"
  189. ." // hide the div containing ShowMethodButton, PrevQButton and NextQButton\n"
  190. ." var btn = document.getElementById('ShowMethodButton');\n"
  191. ." if (btn) {\n"
  192. ." btn.parentNode.style.display = 'none';\n"
  193. ." }\n"
  194. ." // activate first Progress dot\n"
  195. ." AA_SetProgressDot(0, 0);\n"
  196. ."\t"
  197. ;
  198. $substr = preg_replace($search, $replace, $substr, 1);
  199. }
  200. if ($jmix || $dots) {
  201. $substr .= "\n"
  202. ."function AA_isNonStandardIE() {\n"
  203. ." if (typeof(window.isNonStandardIE)=='undefined') {\n"
  204. ." if (navigator.appName=='Microsoft Internet Explorer' && (document.documentMode==null || document.documentMode<8)) {\n"
  205. ." // either IE8+ (in compatability mode) or IE7, IE6, IE5 ...\n"
  206. ." window.isNonStandardIE = true;\n"
  207. ." } else {\n"
  208. ." // Firefox, Safari, Opera, IE8+\n"
  209. ." window.isNonStandardIE = false;\n"
  210. ." }\n"
  211. ." }\n"
  212. ." return window.isNonStandardIE;\n"
  213. ."}\n"
  214. ."function AA_className() {\n"
  215. ." if (AA_isNonStandardIE()){\n"
  216. ." return 'className';\n"
  217. ." } else {\n"
  218. ." return 'class';\n"
  219. ." }\n"
  220. ."}\n"
  221. ."function AA_onclickAttribute(fn) {\n"
  222. ." if (AA_isNonStandardIE()){\n"
  223. ." return new Function(fn);\n"
  224. ." } else {\n"
  225. ." return fn; // just return the string\n"
  226. ." }\n"
  227. ."}\n"
  228. ;
  229. }
  230. if ($dots) {
  231. // add functions required for progress bar
  232. $substr .= "\n"
  233. ."function AA_images() {\n"
  234. ." return 'output/hp/6/jquiz/xml/v6/autoadvance/$dots';\n"
  235. ."}\n"
  236. ."function AA_ProgressArrow() {\n"
  237. ." var img = document.createElement('img');\n"
  238. ." var src = 'ProgressDotArrow.gif';\n"
  239. ." img.setAttribute('src', AA_images() + '/' + src);\n"
  240. ." img.setAttribute('alt', src);\n"
  241. ." img.setAttribute('title', src);\n"
  242. ." //img.setAttribute('height', 18);\n"
  243. ." //img.setAttribute('width', 18);\n"
  244. ." img.setAttribute(AA_className(), 'ProgressDotArrow');\n"
  245. ." return img;\n"
  246. ."}\n"
  247. ."function AA_ProgressDot(i) {\n"
  248. ." // i is either an index on QArray \n"
  249. ." // or a string to be used as an id for an HTML element\n"
  250. ." if (typeof(i)=='string') {\n"
  251. ." var id = i;\n"
  252. ." var add_link = false;\n"
  253. ." } else if (QArray[i]) {\n"
  254. ." var id = QArray[i].id;\n"
  255. ." var add_link = true;\n"
  256. ." } else {\n"
  257. ." return false;\n"
  258. ." }\n"
  259. ." // id should now be: 'Q_' + q ...\n"
  260. ." // where q is an index on the State array\n"
  261. ." var src = 'ProgressDotEmpty.gif';\n"
  262. ." var img = document.createElement('img');\n"
  263. ." img.setAttribute('id', id + '_ProgressDotImg');\n"
  264. ." img.setAttribute('src', AA_images() + '/' + src);\n"
  265. ." img.setAttribute('alt', src);\n"
  266. ." img.setAttribute('title', src);\n"
  267. ." //img.setAttribute('height', 18);\n"
  268. ." //img.setAttribute('width', 18);\n"
  269. ." img.setAttribute(AA_className(), 'ProgressDotEmpty');\n"
  270. ." if (add_link) {\n"
  271. ." var link = document.createElement('a');\n"
  272. ." link.setAttribute('id', id + '_ProgressDotLink');\n"
  273. ." link.setAttribute(AA_className(), 'ProgressDotLink');\n"
  274. ." link.setAttribute('title', 'go to question '+(i+1));\n"
  275. ." var fn = 'ChangeQ('+i+'-CurrQNum);return false;';\n"
  276. ." link.setAttribute('onclick', AA_onclickAttribute(fn));\n"
  277. ." link.appendChild(img);\n"
  278. ." }\n"
  279. ." var span = document.createElement('span');\n"
  280. ." span.setAttribute('id', id + '_ProgressDot');\n"
  281. ." span.setAttribute(AA_className(), 'ProgressDot');\n"
  282. ." if (add_link) {\n"
  283. ." span.appendChild(link);\n"
  284. ." } else {\n"
  285. ." span.appendChild(img);\n"
  286. ." }\n"
  287. ." return span;\n"
  288. ."}\n"
  289. ."function AA_JQuiz_GetQ(i) {\n"
  290. ." if (! QArray[i]) {\n"
  291. ." return -1;\n"
  292. ." }\n"
  293. ." if (! QArray[i].id) {\n"
  294. ." return -1;\n"
  295. ." }\n"
  296. ." var matches = QArray[i].id.match(new RegExp('\\\\d+$'));\n"
  297. ." if (! matches) {\n"
  298. ." return -1;\n"
  299. ." }\n"
  300. ." var q = matches[0];\n"
  301. ." if (! State[q]) {\n"
  302. ." return -1;\n"
  303. ." }\n"
  304. ." return parseInt(q);\n"
  305. ."}\n"
  306. ."function AA_SetProgressDot(q, next_q) {\n"
  307. ." var img = document.getElementById('Q_'+q+'_ProgressDotImg');\n"
  308. ." if (! img) {\n"
  309. ." return;\n"
  310. ." }\n"
  311. ." var src = '';\n"
  312. ." // State[q][0] : the score (as a decimal fraction of 1) for this question (initially -1)\n"
  313. ." // State[q][2] : no of checks for this question (initially 0)\n"
  314. ." if (State[q] && State[q][0]>=0) {\n"
  315. ." var score = Math.max(0, I[q][0] * State[q][0]);\n"
  316. ." // Note that if there are only two options on a multiple-choice question, then \n"
  317. ." // even if the wrong answer is chosen, the question will be considered finished\n"
  318. ." if (neutralProgressBar) {\n"
  319. ." src = 'ProgressDotCorrect00Plus'+'.gif';\n"
  320. ." } else if (score >= 99) {\n"
  321. ." src = 'ProgressDotCorrect99Plus'+'.gif';\n"
  322. ." } else if (score >= 80) {\n"
  323. ." src = 'ProgressDotCorrect80Plus'+'.gif';\n"
  324. ." } else if (score >= 60) {\n"
  325. ." src = 'ProgressDotCorrect60Plus'+'.gif';\n"
  326. ." } else if (score >= 40) {\n"
  327. ." src = 'ProgressDotCorrect40Plus'+'.gif';\n"
  328. ." } else if (score >= 20) {\n"
  329. ." src = 'ProgressDotCorrect20Plus'+'.gif';\n"
  330. ." } else if (score >= 0) {\n"
  331. ." src = 'ProgressDotCorrect00Plus'+'.gif';\n"
  332. ." } else {\n"
  333. ." // this question has negative score, which means it has not yet been correctly answered\n"
  334. ." src = 'ProgressDotWrong'+'.gif';\n"
  335. ." }\n"
  336. ." } else {\n"
  337. ." // this question has not been completed\n"
  338. ." if (typeof(next_q)=='number' && q==next_q) {\n"
  339. ." // this question will be attempted next\n"
  340. ." src = 'ProgressDotCurrent'+'.gif';\n"
  341. ." } else {\n"
  342. ." src = 'ProgressDotEmpty'+'.gif';\n"
  343. ." }\n"
  344. ." }\n"
  345. ." var full_src = AA_images() + '/' + src;\n"
  346. ." if (img.src != full_src) {\n"
  347. ." img.setAttribute('src', full_src);\n"
  348. ." }\n"
  349. ."}\n"
  350. ."function AA_SetProgressBar(next_q) {\n"
  351. ." // next_q is an index on State array\n"
  352. ." // CurrQNum is an index on QArray\n"
  353. ." if (typeof(next_q)=='undefined') {\n"
  354. ." next_q = AA_JQuiz_GetQ(window.CurrQNum || 0);\n"
  355. ." }\n"
  356. ." for (var i=0; i<QArray.length; i++) {\n"
  357. ." var q = AA_JQuiz_GetQ(i);\n"
  358. ." if (q>=0) {\n"
  359. ." AA_SetProgressDot(q, next_q);\n"
  360. ." }\n"
  361. ." }\n"
  362. ."}\n"
  363. ;
  364. }
  365. // append the PlaySound() and StopSound() functions
  366. if ($this->expand_UserDefined2()) {
  367. $substr .= "\n"
  368. ."function PlaySound(i, count) {\n"
  369. .' // li (id="Q_99") -> p (class="questionText") -> span (class="mediaplugin_mp3") -> object'."\n"
  370. ." var li = QArray[i];\n"
  371. ." try {\n"
  372. ." var SoundLoaded = li.childNodes[0].childNodes[0].childNodes[0].isSoundLoadedFromJS();\n"
  373. ." } catch (err) {\n"
  374. ." var SoundLoaded = false;\n"
  375. ." }\n"
  376. ." if (SoundLoaded) {\n"
  377. ." try {\n"
  378. ." li.childNodes[0].childNodes[0].childNodes[0].playSoundFromJS();\n"
  379. ." var SoundPlayed = true;\n"
  380. ." } catch (err) {\n"
  381. ." var SoundPlayed = false;\n"
  382. ." }\n"
  383. ." }\n"
  384. ." if (SoundLoaded && SoundPlayed) {\n"
  385. ." // sound was successfully played\n"
  386. ." } else {\n"
  387. ." // sound could not be loaded or played\n"
  388. ." if (count<=100) {\n"
  389. ." // try again in 1/10th of a second\n"
  390. ." setTimeout('PlaySound('+i+','+(count+1)+')', 100);\n"
  391. ." }\n"
  392. ." }\n"
  393. ."}\n"
  394. ."function StopSound(i) {\n"
  395. .' // li (id="Q_99") -> p (class="questionText") -> span (class="mediaplugin_mp3") -> object'."\n"
  396. ." var li = QArray[i];\n"
  397. ." try {\n"
  398. ." var SoundLoaded = li.childNodes[0].childNodes[0].childNodes[0].isSoundLoadedFromJS();\n"
  399. ." } catch (err) {\n"
  400. ." var SoundLoaded = false;\n"
  401. ." }\n"
  402. ." if (SoundLoaded) {\n"
  403. ." try {\n"
  404. ." li.childNodes[0].childNodes[0].childNodes[0].stopSoundFromJS();\n"
  405. ." var SoundStopped = true;\n"
  406. ." } catch (err) {\n"
  407. ." var SoundStopped = false;\n"
  408. ." }\n"
  409. ." }\n"
  410. ."}"
  411. ;
  412. }
  413. // append the SetUpJMixQuestion() and MoveJumbledItem() functions
  414. if ($jmix) {
  415. $substr .= "\n"
  416. ."function SetUpJMixQuestion(q) {\n"
  417. ." var JumbledItems = document.getElementById('Q_'+q+'_JumbledItems');\n"
  418. ." if (JumbledItems==null) {\n"
  419. ." return;\n"
  420. ." }\n"
  421. ." var spans = new Array();\n"
  422. ." var i_max = JumbledItems.getElementsByTagName('span').length;\n"
  423. ." for (var i=i_max; i>0; i--) {\n"
  424. ." spans.push(JumbledItems.removeChild(JumbledItems.getElementsByTagName('span')[i-1]));\n"
  425. ." }\n"
  426. ." var i_max = JumbledItems.childNodes.length;\n"
  427. ." for (var i=i_max; i>0; i--) {\n"
  428. ." JumbledItems.removeChild(JumbledItems.childNodes[i-1]);\n"
  429. ." }\n"
  430. ." spans = Shuffle(spans);\n"
  431. ." var i_max = spans.length\n"
  432. ." for (var i=0; i<i_max; i++) {\n"
  433. ." if (i) {\n"
  434. ." JumbledItems.appendChild(document.createTextNode(' '));\n"
  435. ." }\n"
  436. ." JumbledItems.appendChild(spans[i]);\n"
  437. ." }\n"
  438. ." spans = null;\n"
  439. ." var obj = document.getElementById('Q_'+q+'_Guess');\n"
  440. ." if (obj) {\n"
  441. ." obj.style.display = 'none';\n"
  442. ." var DropArea = document.createElement('span');\n"
  443. ." DropArea.setAttribute('id', 'Q_'+q+'_DropArea');\n"
  444. ." DropArea.setAttribute(AA_className(), 'DropArea');\n"
  445. ." obj.parentNode.insertBefore(DropArea, obj);\n"
  446. ." }\n"
  447. ."}\n"
  448. ."function MoveJumbledItem(obj) {\n"
  449. ." var m = obj.id.match(RegExp('Q_([0-9]+)_[a-zA-Z]+_[0-9]+'));\n"
  450. ." if (m && m[0]) {\n"
  451. ." var JumbledItems = document.getElementById('Q_'+m[1]+'_JumbledItems');\n"
  452. ." var DropArea = document.getElementById('Q_'+m[1]+'_DropArea');\n"
  453. ." var Guess = document.getElementById('Q_'+m[1]+'_Guess');\n"
  454. ." } else {\n"
  455. ." var JumbledItems = null;\n"
  456. ." var DropArea = null;\n"
  457. ." var Guess = null;\n"
  458. ." }\n"
  459. ." if (JumbledItems && DropArea && Guess) {\n"
  460. ." if (obj.parentNode == JumbledItems) {\n"
  461. ." if (obj.previousSibling) {\n"
  462. ." JumbledItems.removeChild(obj.previousSibling);\n"
  463. ." }\n"
  464. ." if (DropArea.childNodes.length) {\n"
  465. ." DropArea.appendChild(document.createTextNode(' '));\n"
  466. ." }\n"
  467. ." DropArea.appendChild(JumbledItems.removeChild(obj));\n"
  468. ." } else {\n"
  469. ." if (obj.previousSibling) {\n"
  470. ." DropArea.removeChild(obj.previousSibling);\n"
  471. ." }\n"
  472. ." if (JumbledItems.childNodes.length) {\n"
  473. ." JumbledItems.appendChild(document.createTextNode(' '));\n"
  474. ." }\n"
  475. ." JumbledItems.appendChild(DropArea.removeChild(obj));\n"
  476. ." }\n"
  477. ." Guess.value = '';\n"
  478. ." var i_max = DropArea.getElementsByTagName('span').length;\n"
  479. ." for (var i=0; i<i_max; i++) {\n"
  480. ." if (i) {\n"
  481. ." Guess.value += ' ';\n"
  482. ." }\n"
  483. ." Guess.value += DropArea.getElementsByTagName('span')[i].innerHTML;\n"
  484. ." }\n"
  485. ." }\n"
  486. ."}"
  487. ;
  488. }
  489. $str = substr_replace($str, $substr, $start, $length);
  490. }
  491. /**
  492. * fix_js_ShowMessage
  493. *
  494. * @param xxx $str (passed by reference)
  495. * @param xxx $start
  496. * @param xxx $length
  497. * @return xxx
  498. */
  499. function fix_js_ShowMessage(&$str, $start, $length) {
  500. $substr = substr($str, $start, $length);
  501. // do standard fix for this function
  502. parent::fix_js_ShowMessage($substr, 0, strlen($substr));
  503. // add extra argument, QNum, to this function
  504. $substr = preg_replace('/(?<=ShowMessage)\('.'(.*)'.'\)/', '(\\1, QNum)', $substr, 1);
  505. if ($this->expand_UserDefined2()) {
  506. $StopSound = "\t\t\t".'StopSound(CurrQNum);'."\n";
  507. } else {
  508. $StopSound = '';
  509. }
  510. if ($pos = strpos($substr, '{')) {
  511. $insert = "\n"
  512. ." if (typeof(QNum)!='undefined' && State[QNum] && State[QNum][0]>=0) {\n"
  513. ." // this question is finished\n"
  514. ." if (ShowingAllQuestions) {\n"
  515. ." CurrQNum = QNum;\n"
  516. ." } else {\n"
  517. ." // move to next question, if there is one\n"
  518. ." var i_max = QArray.length;\n"
  519. ." for (var i=1; i<i_max; i++) {\n"
  520. ." // calculate the next index for QArray\n"
  521. ." var next_i = (i + CurrQNum) % i_max;\n"
  522. ." if (QArray[next_i] && QArray[next_i].id) {\n"
  523. ." var matches = QArray[next_i].id.match(new RegExp('\\\\d+$'));\n"
  524. ." if (matches) {\n"
  525. ." var next_q = parseInt(matches[0]);\n"
  526. ." if (State[next_q] && State[next_q][0]<0) {\n"
  527. ." // change to unanswered question\n"
  528. ." ChangeQ(next_i - CurrQNum);\n"
  529. ." break;\n"
  530. ." }\n"
  531. ." }\n"
  532. ." }\n"
  533. ." }\n"
  534. ." }\n"
  535. ." var q = AA_JQuiz_GetQ(CurrQNum);\n"
  536. ." if (q==QNum) {\n"
  537. ." // this was the last question\n"
  538. ." AA_SetProgressDot(q, q);\n"
  539. ." }\n"
  540. ." }\n"
  541. ." // clear and hide feedback, if necessary\n"
  542. ." if (! Feedback) {\n"
  543. ." var obj = document.getElementById('FeedbackContent')\n"
  544. ." if (obj) {\n"
  545. ." obj.innerHTML = '';\n"
  546. ." }\n"
  547. ." var obj = document.getElementById('FeedbackDiv')\n"
  548. ." if (obj) {\n"
  549. ." obj.style.display = 'none';\n"
  550. ." }\n"
  551. ." obj = null;\n"
  552. ." return false;\n"
  553. ." }"
  554. ;
  555. $substr = substr_replace($substr, $insert, $pos+1, 0);
  556. }
  557. $str = substr_replace($str, $substr, $start, $length);
  558. }
  559. /**
  560. * fix_js_CompleteEmptyFeedback
  561. *
  562. * @param xxx $str (passed by reference)
  563. * @param xxx $start
  564. * @param xxx $length
  565. */
  566. function fix_js_CompleteEmptyFeedback(&$str, $start, $length) {
  567. $substr = substr($str, $start, $length);
  568. // set empty feedback to blank string
  569. $substr = str_replace('DefaultWrong', "''", $substr);
  570. $substr = str_replace('DefaultRight', "''", $substr);
  571. $str = substr_replace($str, $substr, $start, $length);
  572. }
  573. /**
  574. * fix_js_ChangeQ
  575. *
  576. * @param xxx $str (passed by reference)
  577. * @param xxx $start
  578. * @param xxx $length
  579. */
  580. function fix_js_ChangeQ(&$str, $start, $length) {
  581. $substr = substr($str, $start, $length);
  582. $search = '/(\s*)SetQNumReadout\('.'(.*?)'.'\);/s';
  583. $replace = ''
  584. .'\\1'.'var q = AA_JQuiz_GetQ(CurrQNum - ChangeBy);'
  585. .'\\1'.'AA_SetProgressDot(q);'
  586. .'\\1'.'var q = AA_JQuiz_GetQ(CurrQNum);'
  587. .'\\1'.'AA_SetProgressDot(q, q);'
  588. .'\\1'.'StretchCanvasToCoverContent(true);'
  589. ;
  590. $substr = preg_replace($search, $replace, $substr);
  591. $str = substr_replace($str, $substr, $start, $length);
  592. }
  593. // utility function to search and replace and, if required, call the fix_js_xxx() method of the parent class
  594. /**
  595. * fix_js_search_replace
  596. *
  597. * @param xxx $str (passed by reference)
  598. * @param xxx $start
  599. * @param xxx $length
  600. * @param xxx $thisfunction
  601. * @param xxx $parentfunction (optional, default='')
  602. * @param xxx $search_insert (optional, default=null)
  603. */
  604. function fix_js_search_replace(&$str, $start, $length, $thisfunction, $parentfunction='', $search_insert=null){
  605. $substr = substr($str, $start, $length);
  606. if ($search_insert) {
  607. foreach ($search_insert as $search => $insert) {
  608. if ($pos = strpos($substr, $search)) {
  609. $substr = substr_replace($substr, $insert, $pos, 0);
  610. }
  611. }
  612. }
  613. $search = '/(?<='.$thisfunction.')\('.'([^)]*)'.'\)/';
  614. $replace = '(\\1, QNum)';
  615. $substr = preg_replace($search, $replace, $substr);
  616. if ($parentfunction) {
  617. $parentmethod = 'fix_js_'.$parentfunction;
  618. parent::$parentmethod($substr, 0, strlen($substr));
  619. }
  620. $str = substr_replace($str, $substr, $start, $length);
  621. }
  622. /**
  623. * fix_js_ShowAnswers
  624. *
  625. * @param xxx $str (passed by reference)
  626. * @param xxx $start
  627. * @param xxx $length
  628. */
  629. function fix_js_ShowAnswers(&$str, $start, $length) {
  630. $this->fix_js_search_replace($str, $start, $length, 'ShowMessage');
  631. }
  632. /**
  633. * fix_js_ShowHint
  634. *
  635. * @param xxx $str (passed by reference)
  636. * @param xxx $start
  637. * @param xxx $length
  638. */
  639. function fix_js_ShowHint(&$str, $start, $length) {
  640. $this->fix_js_search_replace($str, $start, $length, 'ShowMessage');
  641. }
  642. /**
  643. * fix_js_CheckMCAnswer
  644. *
  645. * @param xxx $str (passed by reference)
  646. * @param xxx $start
  647. * @param xxx $length
  648. */
  649. function fix_js_CheckMCAnswer(&$str, $start, $length) {
  650. $search = 'if (I[QNum][3][ANum][2] < 1){';
  651. $insert = 'if (I[QNum][3][ANum][2] < 1 && ( maximumTriesPerQuestion && State[QNum][2] >= maximumTriesPerQuestion)){'."\n"
  652. ." Btn.innerHTML = IncorrectIndicator;\n"
  653. .$this->js_to_display_feedback('CalculateMCQuestionScore')
  654. ." for (var i=0; i<I[QNum][3].length; i++) {\n"
  655. ." if (i==ANum) {\n"
  656. ." continue;\n"
  657. ." }\n"
  658. ." var obj = document.getElementById('Q_'+QNum+'_'+i+'_Btn');\n"
  659. ." if (obj) {\n"
  660. ." obj.parentNode.removeChild(obj);\n"
  661. ." obj = null;\n"
  662. ." }\n"
  663. ." }\n"
  664. ." } else "
  665. ;
  666. $this->fix_js_search_replace($str, $start, $length, 'ShowMessage', 'CheckMCAnswer', array($search => $insert));
  667. }
  668. /**
  669. * fix_js_CheckMultiSelAnswer
  670. *
  671. * @param xxx $str (passed by reference)
  672. * @param xxx $start
  673. * @param xxx $length
  674. */
  675. function fix_js_CheckMultiSelAnswer(&$str, $start, $length) {
  676. $search = 'if (Matches == I[QNum][3].length){';
  677. $insert = 'if (Matches < I[QNum][3].length && ( maximumTriesPerQuestion && State[QNum][2] >= maximumTriesPerQuestion)){'."\n"
  678. .$this->js_to_display_feedback('CalculateMultiSelQuestionScore')
  679. ." for (var i=0; i<I[QNum][3].length; i++) {\n"
  680. ." var Chk = document.getElementById('Q_'+QNum+'_'+i+'_Chk');\n"
  681. ." if (Chk) {\n"
  682. ." Chk.disabled = true;\n"
  683. ." }\n"
  684. ." Chk = null;\n"
  685. ." }\n"
  686. ." var Btn = document.getElementById('Q_'+QNum).getElementsByTagName('button');\n"
  687. ." if (Btn) {\n"
  688. ." Btn[0].parentNode.removeChild(Btn[0]);\n"
  689. ." }\n"
  690. ." Btn = null;\n"
  691. ." } else \n"
  692. ;
  693. $search_insert = array(
  694. 'Feedback = Matches'=>'if (Feedback) ',
  695. $search => $insert
  696. );
  697. $this->fix_js_search_replace($str, $start, $length, 'ShowMessage', 'CheckMultiSelAnswer', $search_insert);
  698. }
  699. /**
  700. * fix_js_CheckShortAnswer
  701. *
  702. * @param xxx $str (passed by reference)
  703. * @param xxx $start
  704. * @param xxx $length
  705. */
  706. function fix_js_CheckShortAnswer(&$str, $start, $length) {
  707. $search_insert = array();
  708. $search = 'if (CA.CompleteMatch == true){';
  709. $search_insert[$search] = ''
  710. ."if ((CA.CompleteMatch==false || CA.Score < 100) && ( maximumTriesPerQuestion && State[QNum][2] >= maximumTriesPerQuestion)){\n"
  711. .$this->js_to_display_feedback('CalculateShortAnsQuestionScore')
  712. ." ShowMessage(CA.Feedback);\n"
  713. ." ReplaceGuessBox(QNum, G);\n"
  714. ." CheckFinished();\n"
  715. ." return;\n"
  716. ." }\n"
  717. ." "
  718. ;
  719. $search = ' if (CA.Feedback.length < 1){CA.Feedback = DefaultWrong;}';
  720. $search_insert[$search] = ''
  721. ." if (document.getElementById('Q_'+QNum+'_JumbledItems')) {\n"
  722. ." CA.IncorrectFeedback = new Array()\n"
  723. ." var i_max = CA.Answers.length;\n"
  724. ." for (var i=0; i<i_max; i++) {\n"
  725. ." if (CA.Answers[i].PercentCorrect) {\n"
  726. ." continue;\n"
  727. ." }\n"
  728. ." if (CA.Answers[i].Feedback=='') {\n"
  729. ." continue;\n"
  730. ." }\n"
  731. ." if (CA.Answers[i].Answer==G) {\n"
  732. ." continue;\n"
  733. ." }\n"
  734. ." var ii_max = CA.IncorrectFeedback.length;\n"
  735. ." for (var ii=0; ii<ii_max; ii++) {\n"
  736. ." if (CA.IncorrectFeedback[ii]==CA.Answers[i].Feedback) {\n"
  737. ." break;\n"
  738. ." }\n"
  739. ." }\n"
  740. ." if (ii==ii_max) {\n"
  741. ." CA.IncorrectFeedback[ii] = CA.Answers[i].Feedback;\n"
  742. ." }\n"
  743. ." }\n"
  744. ." if (CA.IncorrectFeedback.length) {\n"
  745. ." if (CA.Feedback) {\n"
  746. ." CA.Feedback += '<br /><br />';\n"
  747. ." }\n"
  748. ." CA.Feedback += CA.IncorrectFeedback.join('<br /><br />');\n"
  749. ." }\n"
  750. ." }\n"
  751. ;
  752. $this->fix_js_search_replace($str, $start, $length, 'ShowMessage', 'CheckShortAnswer', $search_insert);
  753. }
  754. /**
  755. * fix_js_ShowHideQuestions
  756. *
  757. * @param xxx $str (passed by reference)
  758. * @param xxx $start
  759. * @param xxx $length
  760. */
  761. function fix_js_ShowHideQuestions(&$str, $start, $length) {
  762. $substr = substr($str, $start, $length);
  763. $search = "/\s*document\.getElementById\('OneByOneReadout'\)\.style.display = '[^']*';/s";
  764. $substr = preg_replace($search, '', $substr);
  765. // do standard fix for this function
  766. parent::fix_js_ShowHideQuestions($substr, 0, strlen($substr));
  767. }
  768. /**
  769. * js_to_display_feedback
  770. *
  771. * @param xxx $functionname
  772. * @return xxx
  773. */
  774. function js_to_display_feedback($functionname) {
  775. return ''
  776. ." State[QNum][0] = 0;\n"
  777. ." $functionname(QNum);\n"
  778. ." var QsDone = CheckQuestionsCompleted();\n"
  779. ." if (ContinuousScoring){\n"
  780. ." CalculateOverallScore();\n"
  781. ." QsDone = YourScoreIs + ' ' + Score + '%.' + '<br />' + QsDone;\n"
  782. ." Feedback += (Feedback=='' ? '' : '<br />') + QsDone;\n"
  783. ." }\n"
  784. ." WriteToInstructions(QsDone);\n"
  785. ;
  786. }
  787. /**
  788. * fix_js_ReplaceGuessBox
  789. *
  790. * @param xxx $str (passed by reference)
  791. * @param xxx $start
  792. * @param xxx $length
  793. */
  794. function fix_js_ReplaceGuessBox(&$str, $start, $length) {
  795. $substr = substr($str, $start, $length);
  796. if ($pos = strpos($substr, '{')) {
  797. $insert = "\n"
  798. ." var obj = document.getElementById('Q_' + QNum + '_JumbledItemsPrefix');\n"
  799. ." if (obj) {\n"
  800. ." Ans = obj.innerHTML + ' ' + Ans;\n"
  801. ." }\n"
  802. ." var obj = document.getElementById('Q_' + QNum + '_JumbledItemsSuffix');\n"
  803. ." if (obj) {\n"
  804. ." Ans += ' ' + obj.innerHTML;\n"
  805. ." }\n"
  806. ;
  807. $substr = substr_replace($substr, $insert, $pos + 1, 0);
  808. }
  809. $str = substr_replace($str, $substr, $start, $length);
  810. }
  811. /**
  812. * fix_js_SetFocusToTextbox
  813. *
  814. * @param xxx $str (passed by reference)
  815. * @param xxx $start
  816. * @param xxx $length
  817. */
  818. function fix_js_SetFocusToTextbox(&$str, $start, $length) {
  819. $substr = ''
  820. ."function SetFocusToTextbox(){\n"
  821. ." var obj = QArray[CurrQNum].getElementsByTagName('input') || QArray[CurrQNum].getElementsByTagName('textarea');\n"
  822. ." if (obj==null || obj.length==0 || obj[0].style.display=='none') {\n"
  823. ." var keypad_display = 'none';\n"
  824. ." } else {\n"
  825. ." if (obj[0].focus && obj[0].disabled==false) {\n"
  826. ." obj[0].focus();\n"
  827. ." }\n"
  828. ." var keypad_display = 'block';\n"
  829. ." }\n"
  830. ." var obj = document.getElementById('CharacterKeypad');\n"
  831. ." if (obj) {\n"
  832. ." obj.style.display = keypad_display;\n"
  833. ." }\n"
  834. ."}\n"
  835. ;
  836. $str = substr_replace($str, $substr, $start, $length);
  837. }
  838. /**
  839. * fix_js_CheckFinished
  840. *
  841. * @param xxx $str (passed by reference)
  842. * @param xxx $start
  843. * @param xxx $length
  844. */
  845. function fix_js_CheckFinished(&$str, $start, $length) {
  846. $substr = substr($str, $start, $length);
  847. $search = ' var AllDone = true;';
  848. if ($pos = strpos($substr, $search)) {
  849. $insert = ''
  850. ." var CountWrong = 0;\n"
  851. ." var TotalWeighting = 0;\n"
  852. ." var BestPossibleScore = 0;\n"
  853. ;
  854. $substr = substr_replace($substr, $insert, $pos, 0);
  855. }
  856. $search = ' if (State[QNum][0] < 0){';
  857. if ($pos = strpos($substr, $search)) {
  858. $insert = ''
  859. ." if (State[QNum][2] && State[QNum][0] <= 0){\n"
  860. ." CountWrong++;\n"
  861. ." }\n"
  862. ." TotalWeighting += I[QNum][0]\n"
  863. ." if (State[QNum][0] >= 0){\n"
  864. ." BestPossibleScore += (I[QNum][0] * State[QNum][0]);\n"
  865. ." } else {\n"
  866. ." BestPossibleScore += I[QNum][0];\n"
  867. ." }\n"
  868. ;
  869. $substr = substr_replace($substr, $insert, $pos, 0);
  870. }
  871. $search = ' if (AllDone == true){';
  872. if ($pos = strpos($substr, $search)) {
  873. $insert = ''
  874. ." if (typeof(ForceQuizStatus)=='undefined') {\n"
  875. ." if (maximumWrong && CountWrong > maximumWrong) {\n"
  876. ." ForceQuizStatus = 3;\n"
  877. ." }\n"
  878. ." if (TotalWeighting > 0) {\n"
  879. ." BestPossibleScore = Math.floor((BestPossibleScore/TotalWeighting)*100);\n"
  880. ." } else {\n"
  881. ." BestPossibleScore = 100;\n"
  882. ." }\n"
  883. ." if (minimumScore && BestPossibleScore < minimumScore) {\n"
  884. ." ForceQuizStatus = 3;\n"
  885. ." }\n"
  886. ." }\n"
  887. ;
  888. $substr = substr_replace($substr, $insert, $pos, 0);
  889. }
  890. parent::fix_js_CheckFinished($substr, 0, strlen($substr));
  891. $str = substr_replace($str, $substr, $start, $length);
  892. }
  893. /**
  894. * expand_HeaderCode
  895. *
  896. * @return xxx
  897. */
  898. function expand_HeaderCode() {
  899. $vars = array(
  900. 'minimumScore' => 0,
  901. 'maximumWrong' => 0,
  902. 'maximumTriesPerQuestion' => 0,
  903. 'neutralProgressBar' => 0
  904. );
  905. $headercode = parent::expand_HeaderCode();
  906. if ($headercode = $this->hotpot->source->single_line($headercode)) {
  907. $search = '/\s*(?:var\s+)?(\w+)\s*=\s*(\d+)(?:\s*;)?/s';
  908. if (preg_match_all($search, $headercode, $matches, PREG_OFFSET_CAPTURE)) {
  909. $i_max = count($matches[0]) - 1;
  910. for ($i=$i_max; $i>=0; $i--) {
  911. list($match, $start) = $matches[0][$i];
  912. if (array_key_exists($matches[1][$i][0], $vars)) {
  913. $vars[$matches[1][$i][0]] = intval($matches[2][$i][0]);
  914. $headercode = substr_replace($headercode, '', $start, strlen($match));
  915. }
  916. }
  917. }
  918. }
  919. $search = array(
  920. '/(?:\s*\/\/?)\s*<!\[CDATA(?:\s*\/\/)?\s*\]\]>/is',
  921. '/(?:\s*\/\/)?\s*<!--(?:\s*\/\/)?\s*-->/s',
  922. '/\s*<script type="text\/javascript">\s*<\/script>/is',
  923. );
  924. $headercode = preg_replace($search, '', $headercode);
  925. if ($headercode = trim($headercode)) {
  926. $headercode .= "\n";
  927. }
  928. $headercode .= '<script type="text/javascript">'."\n".'//<![CDATA['."\n\n";
  929. foreach ($vars as $name => $value) {
  930. $headercode .= " var $name = $value;\n";
  931. }
  932. $headercode .= '//]]>'."\n\n".'</script>'."\n";
  933. $headercode = ''
  934. .'<style type="text/css">'."\n"
  935. .'li.QuizQuestion div.JumbledItems {'."\n"
  936. .' margin-bottom: 1.0em;'."\n"
  937. .' padding-right: 8px;'."\n"
  938. .'}'."\n"
  939. .'li.QuizQuestion div.JumbledItems span.JumbledItem {'."\n"
  940. .' padding: 2px 6px;'."\n"
  941. .' margin: 0px 8px;'."\n"
  942. .' border: 2px solid #ff9999;'."\n"
  943. .' background-color: #fff9f9;'."\n"
  944. .'}'."\n"
  945. .'li.QuizQuestion span.JumbledItemsPrefix {'."\n"
  946. .' font-size: 1.1em;'."\n"
  947. .'}'."\n"
  948. .'li.QuizQuestion span.DropArea {'."\n"
  949. .' padding: 2px 8px;'."\n"
  950. .' border: 2px solid #99ff99;'."\n"
  951. .' background-color: #f9fff9;'."\n"
  952. .'}'."\n"
  953. .'li.QuizQuestion span.DropArea span.JumbledItem {'."\n"
  954. .' padding: 0px 4px;'."\n"
  955. .'}'."\n"
  956. .'li.QuizQuestion span.JumbledItemsSuffix {'."\n"
  957. .' font-size: 1.1em;'."\n"
  958. .'}'."\n"
  959. .'</style>'."\n"
  960. .$headercode
  961. ;
  962. return $headercode;
  963. }
  964. /**
  965. * expand_ItemArray_answertext
  966. *
  967. * @param xxx $tags
  968. * @param xxx $answer
  969. * @param xxx $a
  970. * @return xxx
  971. */
  972. function expand_ItemArray_answertext($tags, $answer, $a) {
  973. $text = $this->hotpot->source->xml_value($tags, $answer."['text'][0]['#']", '', false);
  974. $lines = preg_split('/\s*[\r\n]+\s*/', $text);
  975. if ($a==0 && $lines[0]=='JMIX') {
  976. $text = '';
  977. $jmix = true;
  978. array_shift($lines);
  979. foreach ($lines as $line) {
  980. if (substr($line, 0, 1)=='{' && substr($line, -1)=='}') {
  981. continue;
  982. }
  983. $text .= ($text=='' ? '' : ' ').$line;
  984. }
  985. }
  986. return $this->hotpot->source->js_value_safe($text, true);
  987. }
  988. /**
  989. * expand_jquiz_textbox_details
  990. *
  991. * @param xxx $tags
  992. * @param xxx $answers
  993. * @param xxx $q
  994. * @param xxx $defaultsize (optional, default=9)
  995. * @return xxx
  996. */
  997. function expand_jquiz_textbox_details($tags, $answers, $q, $defaultsize=9) {
  998. $prefix = '';
  999. $suffix = '';
  1000. $size = $defaultsize;
  1001. $a = 0;
  1002. $jmix = false;
  1003. $items = array();
  1004. while (($answer = $answers."['answer'][$a]['#']") && $this->hotpot->source->xml_value($tags, $answer)) {
  1005. $text = $this->hotpot->source->xml_value($tags, $answer."['text'][0]['#']", '', false);
  1006. $lines = preg_split('/\s*[\r\n]+\s*/', $text);
  1007. $lines = array_filter($lines);
  1008. if ($a==0 && $lines[0]=='JMIX') {
  1009. $jmix = true;
  1010. array_shift($lines);
  1011. }
  1012. if ($jmix) {
  1013. $text = '';
  1014. foreach ($lines as $line) {
  1015. if (substr($line, 0, 1)=='{' && substr($line, -1)=='}') {
  1016. if ($text=='') {
  1017. $prefix .= substr($line, 1, -1).' ';
  1018. } else {
  1019. $suffix .= ' '.substr($line, 1, -1);
  1020. }
  1021. } else {
  1022. $text .= ($text=='' ? '' : ' ').$line;
  1023. $items[] = $line;
  1024. }
  1025. }
  1026. }
  1027. $text = preg_replace('/&[#a-zA-Z0-9]+;/', 'x', $text);
  1028. $size = max($size, strlen($text));
  1029. $a++;
  1030. }
  1031. if ($jmix) {
  1032. // add spans to $prefix and $suffix
  1033. if ($prefix) {
  1034. $prefix = '<span id="Q_'.$q.'_JumbledItemsPrefix" class="JumbledItemsPrefix">'.$prefix.'</span>';
  1035. }
  1036. if ($suffix) {
  1037. $suffix = '<span id="Q_'.$q.'_JumbledItemsSuffix" class="JumbledItemsSuffix">'.$suffix.'</span>';
  1038. }
  1039. // create array of random indexes
  1040. $index = range(1, count($items));
  1041. shuffle($index);
  1042. $cues = '';
  1043. foreach ($items as $i => $item) {
  1044. if ($i) {
  1045. $cues .= ' ';
  1046. }
  1047. $cues .= '<span class="JumbledItem" id="Q_'.$q.'_JumbledItem_'.$index[$i].'" onclick="MoveJumbledItem(this);">'.$item.'</span>';
  1048. }
  1049. // add Jumbled items div
  1050. if ($cues) {
  1051. $prefix = '<div class="JumbledItems" id="Q_'.$q.'_JumbledItems">'.$cues.'</div>'.$prefix;
  1052. }
  1053. }
  1054. return array($prefix, $suffix, $size);
  1055. }
  1056. }