PageRenderTime 84ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/attempt/hp/6/renderer.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 4229 lines | 2453 code | 381 blank | 1395 comment | 398 complexity | 74c22c395ce0ea25cc8f6346482f9e29 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
  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/renderer.php');
  27. /**
  28. * mod_hotpot_attempt_hp_6_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_renderer extends mod_hotpot_attempt_hp_renderer {
  35. /**
  36. * init
  37. *
  38. * @param xxx $hotpot
  39. */
  40. function init($hotpot) {
  41. parent::init($hotpot);
  42. array_unshift($this->templatesfolders, 'mod/hotpot/attempt/hp/6/templates');
  43. }
  44. /**
  45. * fix_headcontent_DragAndDrop for JMix and JMatch
  46. */
  47. function fix_headcontent_DragAndDrop() {
  48. // replace one line functions that get and set positions of positionable elements
  49. $search = array(
  50. '/(?<=function CardGetL).*/',
  51. '/(?<=function CardGetT).*/',
  52. '/(?<=function CardGetW).*/',
  53. '/(?<=function CardGetH).*/',
  54. '/(?<=function CardGetB).*/',
  55. '/(?<=function CardGetR).*/',
  56. '/(?<=function CardSetL).*/',
  57. '/(?<=function CardSetT).*/',
  58. '/(?<=function CardSetW).*/',
  59. '/(?<=function CardSetH).*/',
  60. );
  61. $replace = array(
  62. "(){return getOffset(this.elm, 'Left')}",
  63. "(){return getOffset(this.elm, 'Top')}",
  64. "(){return getOffset(this.elm, 'Width')}",
  65. "(){return getOffset(this.elm, 'Height')}",
  66. "(){return getOffset(this.elm, 'Bottom')}",
  67. "(){return getOffset(this.elm, 'Right')}",
  68. "(NewL){setOffset(this.elm, 'Left', NewL)}",
  69. "(NewT){setOffset(this.elm, 'Top', NewT)}",
  70. "(NewW){setOffset(this.elm, 'Width', NewW)}",
  71. "(NewH){setOffset(this.elm, 'Height', NewH)}"
  72. );
  73. $this->headcontent = preg_replace($search, $replace, $this->headcontent, 1);
  74. }
  75. /**
  76. * fix_headcontent_rottmeier
  77. *
  78. * @param xxx $type (optional, default='')
  79. */
  80. function fix_headcontent_rottmeier($type='') {
  81. switch ($type) {
  82. case 'dropdown':
  83. // adding missing brackets to call to Is_ExerciseFinished() in CheckAnswers()
  84. $search = '/(Finished\s*=\s*Is_ExerciseFinished)(;)/';
  85. $this->headcontent = preg_replace($search, '$1()$2', $this->headcontent);
  86. break;
  87. case 'findit':
  88. // get position of last </style> tag and
  89. // insert CSS to make <b> and <em> tags bold
  90. // even within GapSpans added by javascript
  91. $search = '</style>';
  92. if ($pos = strrpos($this->headcontent, $search)) {
  93. $insert = "\n"
  94. .'<!--[if IE 6]><style type="text/css">'."\n"
  95. .'span.GapSpan{'."\n"
  96. .' font-size:24px;'."\n"
  97. .'}'."\n"
  98. .'</style><![endif]-->'."\n"
  99. .'<style type="text/css">'."\n"
  100. .'b span.GapSpan,'."\n"
  101. .'em span.GapSpan{'."\n"
  102. .' font-weight:inherit;'."\n"
  103. .'}'."\n"
  104. .'</style>'."\n"
  105. ;
  106. $this->headcontent = substr_replace($this->headcontent, $insert, $pos + strlen($search), 0);
  107. }
  108. break;
  109. case 'jintro':
  110. // add TimeOver variable, so we can use standard detection of quiz completion
  111. if ($pos = strpos($this->headcontent, 'var Score = 0;')) {
  112. $insert = "var TimeOver = false;\n";
  113. $this->headcontent = substr_replace($this->headcontent, $insert, $pos, 0);
  114. }
  115. break;
  116. case 'jmemori':
  117. // add TimeOver variable, so we can use standard detection of quiz completion
  118. if ($pos = strpos($this->headcontent, 'var Score = 0;')) {
  119. $insert = "var TimeOver = false;\n";
  120. $this->headcontent = substr_replace($this->headcontent, $insert, $pos, 0);
  121. }
  122. // override table border collapse from standard Moodle styles
  123. if ($pos = strrpos($this->headcontent, '</style>')) {
  124. $insert = ''
  125. .'#'.$this->themecontainer.' form table'."\n"
  126. .'{'."\n"
  127. .' border-collapse: separate;'."\n"
  128. .' border-spacing: 2px;'."\n"
  129. .'}'."\n"
  130. ;
  131. $this->headcontent = substr_replace($this->headcontent, $insert, $pos, 0);
  132. }
  133. break;
  134. }
  135. }
  136. /**
  137. * fix_bodycontent_rottmeier
  138. *
  139. * @param xxx $hideclozeform (optional, default=false)
  140. */
  141. function fix_bodycontent_rottmeier($hideclozeform=false) {
  142. // fix left aligned instructions in Rottmeier-based formats
  143. // JCloze: DropDown, FindIt(a)+(b), JGloss
  144. // JMatch: JMemori
  145. $search = '/<p id="Instructions">(.*?)<\/p>/is';
  146. $replace = '<div id="Instructions">$1</div>';
  147. $this->bodycontent = preg_replace($search, $replace, $this->bodycontent);
  148. if ($hideclozeform) {
  149. // initially hide the Cloze text (so gaps are not revealed)
  150. $search = '/<(form id="Cloze" [^>]*)>/is';
  151. $replace = '<$1 style="display:none;">';
  152. $this->bodycontent = preg_replace($search, $replace, $this->bodycontent);
  153. }
  154. }
  155. /**
  156. * get_js_functionnames
  157. *
  158. * @return xxx
  159. */
  160. function get_js_functionnames() {
  161. // return a comma-separated list of js functions to be "fixed".
  162. // Each function name requires an corresponding function called:
  163. // fix_js_{$name}
  164. return 'Client,ShowElements,GetViewportHeight,PageDim,TrimString,RemoveBottomNavBarForIE,StartUp,GetUserName,PreloadImages,ShowMessage,HideFeedback,SendResults,Finish,WriteToInstructions,ShowSpecialReadingForQuestion';
  165. }
  166. /**
  167. * fix_js_Client
  168. *
  169. * @param xxx $str (passed by reference)
  170. * @param xxx $start
  171. * @param xxx $length
  172. */
  173. function fix_js_Client(&$str, $start, $length) {
  174. $substr = substr($str, $start, $length);
  175. // refine detection of Chrome browser
  176. $search = 'this.geckoVer < 20020000';
  177. if ($pos = strpos($substr, $search)) {
  178. $substr = substr_replace($substr, 'this.geckoVer > 10000000 && ', $pos, 0);
  179. }
  180. // add detection of Chrome browser
  181. $search = '/(\s*)if \(this\.min == false\)\{/s';
  182. $replace = '$1'
  183. ."this.chrome = (this.ua.indexOf('Chrome') > 0);".'$1'
  184. ."if (this.chrome) {".'$1'
  185. ." this.geckoVer = 0;".'$1'
  186. ." this.safari = false;".'$1'
  187. ." this.min = true;".'$1'
  188. ."}$0"
  189. ;
  190. $substr = preg_replace($search, $replace, $substr, 1);
  191. $str = substr_replace($str, $substr, $start, $length);
  192. }
  193. /**
  194. * fix_js_ShowElements
  195. *
  196. * @param xxx $str (passed by reference)
  197. * @param xxx $start
  198. * @param xxx $length
  199. */
  200. function fix_js_ShowElements(&$str, $start, $length) {
  201. $substr = substr($str, $start, $length);
  202. // hide <embed> tags (required for Chrome browser)
  203. if ($pos = strpos($substr, 'TagName == "object"')) {
  204. $substr = substr_replace($substr, 'TagName == "embed" || ', $pos, 0);
  205. }
  206. $str = substr_replace($str, $substr, $start, $length);
  207. }
  208. /**
  209. * fix_js_PageDim
  210. *
  211. * @param xxx $str (passed by reference)
  212. * @param xxx $start
  213. * @param xxx $length
  214. * @return xxx
  215. */
  216. function fix_js_PageDim(&$str, $start, $length) {
  217. if ($this->usemoodletheme) {
  218. $obj = "document.getElementById('$this->themecontainer')"; // moodle
  219. } else {
  220. $obj = "document.getElementsByTagName('body')[0]"; // original
  221. }
  222. $replace = ''
  223. ."function getStyleValue(obj, property_name, propertyName){\n"
  224. ." var value = 0;\n"
  225. ." // Watch out for HTMLDocument which has no style property\n"
  226. ." // as this causes errors later in getComputedStyle() in FF\n"
  227. ." if (obj && obj.style){\n"
  228. ." // based on http://www.quirksmode.org/dom/getstyles.html\n"
  229. ." if (document.defaultView && document.defaultView.getComputedStyle){\n"
  230. ." // Firefox, Opera, Safari\n"
  231. ." value = document.defaultView.getComputedStyle(obj, null).getPropertyValue(property_name);\n"
  232. ." } else if (obj.currentStyle) {"
  233. ." // IE (and Opera)\n"
  234. ." value = obj.currentStyle[propertyName];\n"
  235. ." }\n"
  236. ." if (typeof(value)=='string'){\n"
  237. ." var r = new RegExp('([0-9.]*)([a-z]+)');\n"
  238. ." var m = value.match(r);\n"
  239. ." if (m){\n"
  240. ." switch (m[2]){\n"
  241. ." case 'em':\n"
  242. ." // as far as I can see, only IE needs this\n"
  243. ." // other browsers have getComputedStyle() in px\n"
  244. ." if (typeof(obj.EmInPx)=='undefined'){\n"
  245. ." var div = obj.parentNode.appendChild(document.createElement('div'));\n"
  246. ." div.style.margin = '0px';\n"
  247. ." div.style.padding = '0px';\n"
  248. ." div.style.border = 'none';\n"
  249. ." div.style.height = '1em';\n"
  250. ." obj.EmInPx = getOffset(div, 'Height');\n"
  251. ." obj.parentNode.removeChild(div);\n"
  252. ." }\n"
  253. ." value = parseFloat(m[1] * obj.EmInPx);\n"
  254. ." break;\n"
  255. ." case 'px':\n"
  256. ." value = parseFloat(m[1]);\n"
  257. ." break;\n"
  258. ." default:\n"
  259. ." value = 0;\n"
  260. ." }\n"
  261. ." } else {\n"
  262. ." value = 0 ;\n"
  263. ." }\n"
  264. ." } else {\n"
  265. ." value = 0;\n"
  266. ." }\n"
  267. ." }\n"
  268. ." return value;\n"
  269. ."}\n"
  270. ."function isStrict(){\n"
  271. ." return false;\n"
  272. ." if (typeof(window.cache_isStrict)=='undefined'){\n"
  273. ." if (document.compatMode) { // ie6+\n"
  274. ." window.cache_isStrict = (document.compatMode=='CSS1Compat');\n"
  275. ." } else if (document.doctype){\n"
  276. ." var s = document.doctype.systemId || document.doctype.name; // n6 OR ie5mac\n"
  277. ." window.cache_isStrict = (s && s.indexOf('strict.dtd') >= 0);\n"
  278. ." } else {\n"
  279. ." window.cache_isStrict = false;\n"
  280. ." }\n"
  281. ." }\n"
  282. ." return window.cache_isStrict;\n"
  283. ."}\n"
  284. ."function setOffset(obj, type, value){\n"
  285. ." if (! obj){\n"
  286. ." return false;\n"
  287. ." }\n"
  288. ."\n"
  289. ." switch (type){\n"
  290. ." case 'Right':\n"
  291. ." return setOffset(obj, 'Width', value - getOffset(obj, 'Left'));\n"
  292. ." case 'Bottom':\n"
  293. ." return setOffset(obj, 'Height', value - getOffset(obj, 'Top'));\n"
  294. ." }\n"
  295. ."\n"
  296. ." if (isStrict()){\n"
  297. ." // set arrays of p(roperties) and s(ub-properties)\n"
  298. ." var properties = new Array('margin', 'border', 'padding');\n"
  299. ." switch (type){\n"
  300. ." case 'Top':\n"
  301. ." var sides = new Array('Top');\n"
  302. ." break;\n"
  303. ." case 'Left':\n"
  304. ." var sides = new Array('Left');\n"
  305. ." break;\n"
  306. ." case 'Width':\n"
  307. ." var sides = new Array('Left', 'Right');\n"
  308. ." break;\n"
  309. ." case 'Height':\n"
  310. ." var sides = new Array('Top', 'Bottom');\n"
  311. ." break;\n"
  312. ." default:\n"
  313. ." return 0;\n"
  314. ." }\n"
  315. ." for (var p=0; p<properties.length; p++){\n"
  316. ." for (var s=0; s<sides.length; s++){\n"
  317. ." var propertyName = properties[p] + sides[s];\n"
  318. ." var property_name = properties[p] + '-' + sides[s].toLowerCase();\n"
  319. ." value -= getStyleValue(obj, property_name, propertyName);\n"
  320. ." }\n"
  321. ." }\n"
  322. ." value = Math.floor(value);\n"
  323. ." }\n"
  324. ." if (type=='Top' || type=='Left') {\n"
  325. ." value -= getOffset(obj.offsetParent, type);\n"
  326. ." }\n"
  327. ." if (obj.style) {\n"
  328. ." obj.style[type.toLowerCase()] = value + 'px';\n"
  329. ." }\n"
  330. ."}\n"
  331. ."function getOffset(obj, type){\n"
  332. ." if (! obj){\n"
  333. ." return 0;\n"
  334. ." }\n"
  335. ." switch (type){\n"
  336. ." case 'Width':\n"
  337. ." case 'Height':\n"
  338. ." return eval('(obj.offset'+type+'||0)');\n"
  339. ."\n"
  340. ." case 'Top':\n"
  341. ." case 'Left':\n"
  342. ." return eval('(obj.offset'+type+'||0) + getOffset(obj.offsetParent, type)');\n"
  343. ."\n"
  344. ." case 'Right':\n"
  345. ." return getOffset(obj, 'Left') + getOffset(obj, 'Width');\n"
  346. ."\n"
  347. ." case 'Bottom':\n"
  348. ." return getOffset(obj, 'Top') + getOffset(obj, 'Height');\n"
  349. ."\n"
  350. ." default:\n"
  351. ." return 0;\n"
  352. ." } // end switch\n"
  353. ."}\n"
  354. ."function PageDim(){\n"
  355. ." var obj = $obj;\n"
  356. ." this.W = getOffset(obj, 'Width');\n"
  357. ." this.H = getOffset(obj, 'Height');\n"
  358. ." this.Top = getOffset(obj, 'Top');\n"
  359. ." this.Left = getOffset(obj, 'Left');\n"
  360. ."}\n"
  361. ."function getClassAttribute(className, attributeName){\n"
  362. ." //based on http://www.shawnolson.net/a/503/\n"
  363. ." if (! document.styleSheets){\n"
  364. ." return null; // old browser\n"
  365. ." }\n"
  366. ." var css = document.styleSheets;\n"
  367. ." var rules = (document.all ? 'rules' : 'cssRules');\n"
  368. ." var regexp = new RegExp('\\\\.'+className+'\\\\b');\n"
  369. ." try {\n"
  370. ." var i_max = css.length;\n"
  371. ." } catch(err) {\n"
  372. ." var i_max = 0; // shouldn't happen !!\n"
  373. ." }\n"
  374. ." for (var i=0; i<i_max; i++){\n"
  375. ." try {\n"
  376. ." var ii_max = css[i][rules].length;\n"
  377. ." } catch(err) {\n"
  378. ." var ii_max = 0; // shouldn't happen !!\n"
  379. ." }\n"
  380. ." for (var ii=0; ii<ii_max; ii++){\n"
  381. ." if (! css[i][rules][ii].selectorText){\n"
  382. ." continue;\n"
  383. ." }\n"
  384. ." if (css[i][rules][ii].selectorText.match(regexp)){\n"
  385. ." if (css[i][rules][ii].style[attributeName]){\n"
  386. ." // class/attribute found\n"
  387. ." return css[i][rules][ii].style[attributeName];\n"
  388. ." }\n"
  389. ." }\n"
  390. ." }\n"
  391. ." }\n"
  392. ." // class/attribute not found\n"
  393. ." return null;\n"
  394. ."}\n"
  395. ;
  396. $str = substr_replace($str, $replace, $start, $length);
  397. }
  398. /**
  399. * fix_js_GetViewportHeight
  400. *
  401. * @param xxx $str (passed by reference)
  402. * @param xxx $start
  403. * @param xxx $length
  404. * @return xxx
  405. */
  406. function fix_js_GetViewportHeight(&$str, $start, $length) {
  407. $replace = ''
  408. ."function GetViewportSize(type){\n"
  409. ." if (eval('window.inner' + type)){\n"
  410. ." return eval('window.inner' + type);\n"
  411. ." }\n"
  412. ." if (document.documentElement){\n"
  413. ." if (eval('document.documentElement.client' + type)){\n"
  414. ." return eval('document.documentElement.client' + type);\n"
  415. ." }\n"
  416. ." }\n"
  417. ." if (document.body){\n"
  418. ." if (eval('document.body.client' + type)){\n"
  419. ." return eval('document.body.client' + type);\n"
  420. ." }\n"
  421. ." }\n"
  422. ." return 0;\n"
  423. ."}\n"
  424. ."function GetViewportHeight(){\n"
  425. ." return GetViewportSize('Height');\n"
  426. ."}\n"
  427. ."function GetViewportWidth(){\n"
  428. ." return GetViewportSize('Width');\n"
  429. ."}"
  430. ;
  431. $str = substr_replace($str, $replace, $start, $length);
  432. }
  433. /**
  434. * remove_js_function
  435. *
  436. * @param xxx $str (passed by reference)
  437. * @param xxx $start
  438. * @param xxx $length
  439. * @param xxx $function
  440. */
  441. function remove_js_function(&$str, $start, $length, $function) {
  442. // remove this function
  443. $str = substr_replace($str, '', $start, $length);
  444. // remove all direct calls to this function
  445. $search = '/\s*'.$function.'\([^)]*\);/s';
  446. $str = preg_replace($search, '', $str);
  447. }
  448. /**
  449. * fix_js_TrimString
  450. *
  451. * @param xxx $str (passed by reference)
  452. * @param xxx $start
  453. * @param xxx $length
  454. * @return xxx
  455. */
  456. function fix_js_TrimString(&$str, $start, $length) {
  457. $replace = ''
  458. ."function TrimString(InString){\n"
  459. ." if (typeof(InString)=='string'){\n"
  460. ." InString = InString.replace(new RegExp('^\\\\s+', 'g'), ''); // left\n"
  461. ." InString = InString.replace(new RegExp('\\\\s+$', 'g'), ''); // right\n"
  462. ." InString = InString.replace(new RegExp('\\\\s+', 'g'), ' '); // inner\n"
  463. ." }\n"
  464. ." return InString;\n"
  465. ."}"
  466. ;
  467. $str = substr_replace($str, $replace, $start, $length);
  468. }
  469. /**
  470. * fix_js_TypeChars
  471. *
  472. * @param xxx $str (passed by reference)
  473. * @param xxx $start
  474. * @param xxx $length
  475. */
  476. function fix_js_TypeChars(&$str, $start, $length) {
  477. if ($obj = $this->fix_js_TypeChars_obj()) {
  478. $substr = substr($str, $start, $length);
  479. if (strpos($substr, 'document.selection')===false) {
  480. $replace = ''
  481. ."function TypeChars(Chars){\n"
  482. .$this->fix_js_TypeChars_init()
  483. ." if ($obj==null || $obj.style.display=='none') {\n"
  484. ." return;\n"
  485. ." }\n"
  486. ." $obj.focus();\n"
  487. ." if (typeof($obj.selectionStart)=='number') {\n"
  488. ." // FF, Safari, Chrome, Opera\n"
  489. ." var startPos = $obj.selectionStart;\n"
  490. ." var endPos = $obj.selectionEnd;\n"
  491. ." $obj.value = $obj.value.substring(0, startPos) + Chars + $obj.value.substring(endPos);\n"
  492. ." var newPos = startPos + Chars.length;\n"
  493. ." $obj.setSelectionRange(newPos, newPos);\n"
  494. ." } else if (document.selection) {\n"
  495. ." // IE (tested on IE6, IE7, IE8)\n"
  496. ." var rng = document.selection.createRange();\n"
  497. ." rng.text = Chars;\n"
  498. ." rng = null; // prevent memory leak\n"
  499. ." } else {\n"
  500. ." // this browser can't insert text, so append instead\n"
  501. ." $obj.value += Chars;\n"
  502. ." }\n"
  503. ."}"
  504. ;
  505. $str = substr_replace($str, $replace, $start, $length);
  506. }
  507. }
  508. }
  509. /**
  510. * fix_js_TypeChars_init
  511. *
  512. * @return xxx
  513. */
  514. function fix_js_TypeChars_init() {
  515. return '';
  516. }
  517. /**
  518. * fix_js_TypeChars_obj
  519. *
  520. * @return xxx
  521. */
  522. function fix_js_TypeChars_obj() {
  523. return '';
  524. }
  525. /**
  526. * fix_js_SendResults
  527. *
  528. * @param xxx $str (passed by reference)
  529. * @param xxx $start
  530. * @param xxx $length
  531. */
  532. function fix_js_SendResults(&$str, $start, $length) {
  533. $this->remove_js_function($str, $start, $length, 'SendResults');
  534. }
  535. /**
  536. * fix_js_GetUserName
  537. *
  538. * @param xxx $str (passed by reference)
  539. * @param xxx $start
  540. * @param xxx $length
  541. */
  542. function fix_js_GetUserName(&$str, $start, $length) {
  543. $this->remove_js_function($str, $start, $length, 'GetUserName');
  544. }
  545. /**
  546. * fix_js_Finish
  547. *
  548. * @param xxx $str (passed by reference)
  549. * @param xxx $start
  550. * @param xxx $length
  551. */
  552. function fix_js_Finish(&$str, $start, $length) {
  553. $this->remove_js_function($str, $start, $length, 'Finish');
  554. }
  555. /**
  556. * fix_js_PreloadImages
  557. *
  558. * @param xxx $str (passed by reference)
  559. * @param xxx $start
  560. * @param xxx $length
  561. */
  562. function fix_js_PreloadImages(&$str, $start, $length) {
  563. $substr = substr($str, $start, $length);
  564. // fix issue in IE8 which sometimes doesn't have Image object in popups
  565. // http://moodle.org/mod/forum/discuss.php?d=134510
  566. $search = "Imgs[i] = new Image();";
  567. if ($pos = strpos($substr, $search)) {
  568. $replace = "Imgs[i] = (window.Image ? new Image() : document.createElement('img'));";
  569. $substr = substr_replace($substr, $replace, $pos, strlen($search));
  570. }
  571. $str = substr_replace($str, $substr, $start, $length);
  572. }
  573. /**
  574. * fix_js_WriteToInstructions
  575. *
  576. * @param xxx $str (passed by reference)
  577. * @param xxx $start
  578. * @param xxx $length
  579. * @return xxx
  580. */
  581. function fix_js_WriteToInstructions(&$str, $start, $length) {
  582. $substr = substr($str, $start, $length);
  583. if ($pos = strpos($substr, '{')) {
  584. $insert = "\n"
  585. ." // check required HTML element exists\n"
  586. ." if (! document.getElementById('InstructionsDiv')) return false;\n"
  587. ;
  588. $substr = substr_replace($substr, $insert, $pos+1, 0);
  589. }
  590. if ($pos = strrpos($substr, '}')) {
  591. $append = "\n"
  592. ." StretchCanvasToCoverContent(true);\n"
  593. ;
  594. $substr = substr_replace($substr, $append, $pos, 0);
  595. }
  596. $str = substr_replace($str, $substr, $start, $length);
  597. }
  598. /**
  599. * fix_js_ShowMessage
  600. *
  601. * @param xxx $str (passed by reference)
  602. * @param xxx $start
  603. * @param xxx $length
  604. * @return xxx
  605. */
  606. function fix_js_ShowMessage(&$str, $start, $length) {
  607. // the ShowMessage function is used by all HP6 quizzes
  608. $substr = substr($str, $start, $length);
  609. // only show feedback if the required HTML elements exist
  610. // this prevents JavaScript errors which block the returning of the quiz results to Moodle
  611. if ($pos = strpos($substr, '{')) {
  612. $insert = "\n"
  613. ." // check required HTML elements exist\n"
  614. ." if (! document.getElementById('FeedbackDiv')) return false;\n"
  615. ." if (! document.getElementById('FeedbackContent')) return false;\n"
  616. ." if (! document.getElementById('FeedbackOKButton')) return false;\n"
  617. ;
  618. $substr = substr_replace($substr, $insert, $pos+1, 0);
  619. }
  620. // hide <embed> elements on Chrome browser
  621. $search = "/(\s*)ShowElements\(true, 'object', 'FeedbackContent'\);/s";
  622. $replace = ''
  623. ."$0".'$1'
  624. ."if (C.chrome) {".'$1'
  625. ." ShowElements(false, 'embed');".'$1'
  626. ." ShowElements(true, 'embed', 'FeedbackContent');".'$1'
  627. ."}"
  628. ;
  629. $substr = preg_replace($search, $replace, $substr, 1);
  630. // fix "top" setting to position FeedbackDiv
  631. if ($this->usemoodletheme) {
  632. $canvas = "document.getElementById('$this->themecontainer')"; // moodle
  633. } else {
  634. $canvas = "document.getElementsByTagName('body')[0]"; // original
  635. }
  636. $search = "/FDiv.style.top = [^;]*;(\s*)(FDiv.style.display = [^;]*;)/s";
  637. $replace = ''
  638. .'$1$2'
  639. .'$1'."var t = getOffset($canvas, 'Top');"
  640. .'$1'."setOffset(FDiv, 'Top', Math.max(t, TopSettingWithScrollOffset(30)));"
  641. ;
  642. $substr = preg_replace($search, $replace, $substr, 1);
  643. // append link to student feedback form, if necessary
  644. if ($this->hotpot->studentfeedback) {
  645. $search = '/(\s*)var Output = [^;]*;/';
  646. $replace = ''
  647. ."$0".'$1'
  648. ."if (window.FEEDBACK) {".'$1'
  649. ." Output += '".'<a href="javascript:hpFeedback();">'."' + FEEDBACK[6] + '</a>';".'$1'
  650. ."}"
  651. ;
  652. $substr = preg_replace($search, $replace, $substr, 1);
  653. }
  654. $substr = preg_replace($search, $replace, $substr, 1);
  655. }
  656. /**
  657. * fix_js_RemoveBottomNavBarForIE
  658. *
  659. * @param xxx $str (passed by reference)
  660. * @param xxx $start
  661. * @param xxx $length
  662. */
  663. function fix_js_RemoveBottomNavBarForIE(&$str, $start, $length) {
  664. $replace = ''
  665. ."function RemoveBottomNavBarForIE(){\n"
  666. ." if (C.ie) {\n"
  667. ." if (document.getElementById('Reading')){\n"
  668. ." var obj = document.getElementById('BottomNavBar');\n"
  669. ." if (obj){\n"
  670. ." obj.parentNode.removeChild(obj);\n"
  671. ." }\n"
  672. ." }\n"
  673. ." }\n"
  674. ."}"
  675. ;
  676. $str = substr_replace($str, $replace, $start, $length);
  677. }
  678. /**
  679. * fix_js_StartUp_DragAndDrop
  680. *
  681. * @param xxx $substr (passed by reference)
  682. */
  683. function fix_js_StartUp_DragAndDrop(&$substr) {
  684. // fixes for Drag and Drop (JMatch and JMix)
  685. }
  686. /**
  687. * fix_js_StartUp_DragAndDrop_DragArea
  688. *
  689. * @param xxx $substr (passed by reference)
  690. */
  691. function fix_js_StartUp_DragAndDrop_DragArea(&$substr) {
  692. // fix LeftCol (=left side of drag area)
  693. $search = '/(LeftColPos = [^;]+);/';
  694. $replace = '$1 + pg.Left;';
  695. $substr = preg_replace($search, $replace, $substr, 1);
  696. // fix DragTop (=top side of Drag area)
  697. $search = '/DragTop = [^;]+;/';
  698. $replace = "DragTop = getOffset(document.getElementById('CheckButtonDiv'),'Bottom') + 10;";
  699. $substr = preg_replace($search, $replace, $substr, 1);
  700. }
  701. /**
  702. * fix_js_StartUp
  703. *
  704. * @param xxx $str (passed by reference)
  705. * @param xxx $start
  706. * @param xxx $length
  707. */
  708. function fix_js_StartUp(&$str, $start, $length) {
  709. $substr = substr($str, $start, $length);
  710. // if necessary, fix drag area for JMatch or JMix drag-and-drop
  711. $this->fix_js_StartUp_DragAndDrop($substr);
  712. if ($pos = strrpos($substr, '}')) {
  713. if ($this->hotpot->delay3==hotpot::TIME_DISABLE) {
  714. $forceajax = 1;
  715. } else {
  716. $forceajax = 0;
  717. }
  718. if ($this->can_continue()==hotpot::CONTINUE_RESUMEQUIZ) {
  719. $onunload_status = hotpot::STATUS_INPROGRESS;
  720. } else {
  721. $onunload_status = hotpot::STATUS_ABANDONED;
  722. }
  723. $append = "\n"
  724. ."// adjust size and position of Feedback DIV\n"
  725. ." if (! window.pg){\n"
  726. ." window.pg = new PageDim();\n"
  727. ." }\n"
  728. ." var FDiv = document.getElementById('FeedbackDiv');\n"
  729. ." if (FDiv){\n"
  730. ." var w = getOffset(FDiv, 'Width') || FDiv.style.width || getClassAttribute(FDiv.className, 'width');\n"
  731. ." if (w){\n"
  732. ." if (typeof(w)=='string' && w.indexOf('%')>=0){\n"
  733. ." var percent = parseInt(w);\n"
  734. ." } else {\n"
  735. ." var percent = Math.floor(100 * parseInt(w) / pg.W);\n"
  736. ." }\n"
  737. ." } else if (window.FeedbackWidth && window.DivWidth){\n"
  738. ." var percent = Math.floor(100 * FeedbackWidth / DivWidth);\n"
  739. ." } else {\n"
  740. ." var percent = 34; // default width as percentage\n"
  741. ." }\n"
  742. ." FDiv.style.display = 'block';\n"
  743. ." setOffset(FDiv, 'Left', pg.Left + Math.floor(pg.W * (50 - percent/2) / 100));\n"
  744. ." setOffset(FDiv, 'Width', Math.floor(pg.W * percent / 100));\n"
  745. ." FDiv.style.display = 'none';\n"
  746. ." }\n"
  747. ."\n"
  748. ."// create HP object (to collect and send responses)\n"
  749. ." window.HP = new ".$this->js_object_type."('".$this->can_clickreport()."','".$forceajax."');\n"
  750. ."\n"
  751. ."// call HP.onunload to send results when this page unloads\n"
  752. ." var s = '';\n"
  753. ." if (typeof(window.onunload)=='function'){\n"
  754. ." window.onunload_StartUp = onunload;\n"
  755. ." s += 'window.onunload_StartUp();'\n"
  756. ." }\n"
  757. ." window.onunload = new Function(s + 'if(window.HP){HP.status=$onunload_status;HP.onunload();object_destroy(HP);}return true;');\n"
  758. ."\n"
  759. ;
  760. $substr = substr_replace($substr, $append, $pos, 0);
  761. }
  762. // stretch the canvas vertically down, if there is a reading
  763. if ($pos = strrpos($substr, '}')) {
  764. // Reading is contained in <div class="LeftContainer">
  765. // MainDiv is contained in <div class="RightContainer">
  766. // when there is a reading. Otherwise, MainDiv is not contained.
  767. // ReadingDiv is used to show different reading for each question
  768. if ($this->usemoodletheme) {
  769. $canvas = "document.getElementById('$this->themecontainer')"; // moodle
  770. } else {
  771. $canvas = "document.getElementsByTagName('body')[0]"; // original
  772. }
  773. // None: $canvas = "document.getElementById('page-mod-hotpot-attempt')"
  774. $id = $this->embed_object_id;
  775. $onload = $this->embed_object_onload;
  776. $insert = "\n"
  777. ."// fix canvas height, if necessary\n"
  778. ." if (! window.hotpot_mediafilter_loader){\n"
  779. ." StretchCanvasToCoverContent();\n"
  780. ." }\n"
  781. ."}\n"
  782. ."function StretchCanvasToCoverContent(skipTimeout){\n"
  783. ." if (! skipTimeout){\n"
  784. ." if (navigator.userAgent.indexOf('Firefox/3')>=0){\n"
  785. ." var millisecs = 1000;\n"
  786. ." } else {\n"
  787. ." var millisecs = 500;\n"
  788. ." }\n"
  789. ." setTimeout('StretchCanvasToCoverContent(true)', millisecs);\n"
  790. ." return;\n"
  791. ." }\n"
  792. ." var canvas = $canvas;\n"
  793. ." if (canvas){\n"
  794. ." var ids = new Array('Reading','ReadingDiv','MainDiv');\n"
  795. ." var i_max = ids.length;\n"
  796. ." for (var i=i_max-1; i>=0; i--){\n"
  797. ." var obj = document.getElementById(ids[i]);\n"
  798. ." if (obj){\n"
  799. ." obj.style.height = ''; // reset height\n"
  800. ." } else {\n"
  801. ." ids.splice(i, 1); // remove this id\n"
  802. ." i_max--;\n"
  803. ." }\n"
  804. ." }\n"
  805. ." var b = 0;\n"
  806. ." for (var i=0; i<i_max; i++){\n"
  807. ." var obj = document.getElementById(ids[i]);\n"
  808. ." b = Math.max(b, getOffset(obj,'Bottom'));\n"
  809. ." }\n"
  810. ." if (window.Segments) {\n" // JMix special
  811. ." var obj = document.getElementById('D'+(Segments.length-1));\n"
  812. ." if (obj) {\n"
  813. ." b = Math.max(b, getOffset(obj,'Bottom'));\n"
  814. ." }\n"
  815. ." }\n"
  816. ." if (b){\n"
  817. ." setOffset(canvas, 'Bottom', b + 21);\n"
  818. ." for (var i=0; i<i_max; i++){\n"
  819. ." var obj = document.getElementById(ids[i]);\n"
  820. ." setOffset(obj, 'Bottom', b);\n"
  821. ." }\n"
  822. ." }\n"
  823. ." }\n"
  824. ;
  825. if ($this->hotpot->navigation==hotpot::NAVIGATION_EMBED) {
  826. // stretch container object/iframe
  827. $insert .= ''
  828. ." if (parent.$onload) {\n"
  829. ." parent.$onload(null, parent.document.getElementById('".$this->embed_object_id."'));\n"
  830. ." }\n"
  831. ;
  832. }
  833. $substr = substr_replace($substr, $insert, $pos, 0);
  834. }
  835. $str = substr_replace($str, $substr, $start, $length);
  836. }
  837. /**
  838. * fix_js_HideFeedback
  839. *
  840. * @param xxx $str (passed by reference)
  841. * @param xxx $start
  842. * @param xxx $length
  843. */
  844. function fix_js_HideFeedback(&$str, $start, $length) {
  845. global $CFG;
  846. $substr = substr($str, $start, $length);
  847. // unhide <embed> elements on Chrome browser
  848. $search = "/(\s*)ShowElements\(true, 'object'\);/s";
  849. $replace = ''
  850. .'$0$1'
  851. ."if (C.chrome) {".'$1'
  852. ." ShowElements(true, 'embed');".'$1'
  853. ."}"
  854. ;
  855. $substr = preg_replace($search, $replace, $substr, 1);
  856. $search = '/('.'\s*if \(Finished == true\){\s*)(?:.*?)(\s*})/s';
  857. if ($this->hotpot->delay3==hotpot::TIME_AFTEROK) {
  858. // -1 : send form only (do not set form values, as that has already been done)
  859. $replace = '$1'.'HP.onunload(HP.status,-1);'.'$2';
  860. } else {
  861. $replace = ''; // i.e. remove this if-block
  862. }
  863. $substr = preg_replace($search, $replace, $substr, 1);
  864. $str = substr_replace($str, $substr, $start, $length);
  865. }
  866. /**
  867. * fix_js_ShowSpecialReadingForQuestion
  868. *
  869. * @param xxx $str (passed by reference)
  870. * @param xxx $start
  871. * @param xxx $length
  872. */
  873. function fix_js_ShowSpecialReadingForQuestion(&$str, $start, $length) {
  874. $replace = ''
  875. ."function ShowSpecialReadingForQuestion(){\n"
  876. ." var ReadingDiv = document.getElementById('ReadingDiv');\n"
  877. ." if (ReadingDiv){\n"
  878. ." var ReadingText = null;\n"
  879. ." var divs = ReadingDiv.getElementsByTagName('div');\n"
  880. ." for (var i=0; i<divs.length; i++){\n"
  881. ." if (divs[i].className=='ReadingText' || divs[i].className=='TempReadingText'){\n"
  882. ." ReadingText = divs[i];\n"
  883. ." break;\n"
  884. ." }\n"
  885. ." }\n"
  886. ." if (ReadingText && HiddenReadingShown){\n"
  887. ." SwapReadingTexts(ReadingText, HiddenReadingShown);\n"
  888. ." ReadingText = HiddenReadingShown;\n"
  889. ." HiddenReadingShown = false;\n"
  890. ." }\n"
  891. ." var HiddenReading = null;\n"
  892. ." if (QArray[CurrQNum]){\n"
  893. ." var divs = QArray[CurrQNum].getElementsByTagName('div');\n"
  894. ." for (var i=0; i<divs.length; i++){\n"
  895. ." if (divs[i].className=='HiddenReading'){\n"
  896. ." HiddenReading = divs[i];\n"
  897. ." break;\n"
  898. ." }\n"
  899. ." }\n"
  900. ." }\n"
  901. ." if (HiddenReading){\n"
  902. ." if (! ReadingText){\n"
  903. ." ReadingText = document.createElement('div');\n"
  904. ." ReadingText.className = 'ReadingText';\n"
  905. ." ReadingDiv.appendChild(ReadingText);\n"
  906. ." }\n"
  907. ." SwapReadingTexts(ReadingText, HiddenReading);\n"
  908. ." HiddenReadingShown = ReadingText;\n"
  909. ." }\n"
  910. ." var btn = document.getElementById('ShowMethodButton');\n"
  911. ." if (btn){\n"
  912. ." if (HiddenReadingShown){\n"
  913. ." if (btn.style.display!='none'){\n"
  914. ." btn.style.display = 'none';\n"
  915. ." }\n"
  916. ." } else {\n"
  917. ." if (btn.style.display=='none'){\n"
  918. ." btn.style.display = '';\n"
  919. ." }\n"
  920. ." }\n"
  921. ." }\n"
  922. ." btn = null;\n"
  923. ." ReadingDiv = null;\n"
  924. ." ReadingText = null;\n"
  925. ." HiddenReading = null;\n"
  926. ." }\n"
  927. ."}\n"
  928. ."function SwapReadingTexts(ReadingText, HiddenReading) {\n"
  929. ." HiddenReadingParentNode = HiddenReading.parentNode;\n"
  930. ." HiddenReadingParentNode.removeChild(HiddenReading);\n"
  931. ."\n"
  932. ." // replaceChild(new_node, old_node)\n"
  933. ." ReadingText.parentNode.replaceChild(HiddenReading, ReadingText);\n"
  934. ."\n"
  935. ." if (HiddenReading.IsOriginalReadingText){\n"
  936. ." HiddenReading.className = 'ReadingText';\n"
  937. ." } else {\n"
  938. ." HiddenReading.className = 'TempReadingText';\n"
  939. ." }\n"
  940. ." HiddenReading.style.display = '';\n"
  941. ."\n"
  942. ." if (ReadingText.className=='ReadingText'){\n"
  943. ." ReadingText.IsOriginalReadingText = true;\n"
  944. ." } else {\n"
  945. ." ReadingText.IsOriginalReadingText = false;\n"
  946. ." }\n"
  947. ." ReadingText.style.display = 'none';\n"
  948. ." ReadingText.className = 'HiddenReading';\n"
  949. ."\n"
  950. ." HiddenReadingParentNode.appendChild(ReadingText);\n"
  951. ." HiddenReadingParentNode = null;\n"
  952. ."}\n"
  953. ;
  954. $str = substr_replace($str, $replace, $start, $length);
  955. }
  956. /**
  957. * fix_js_CheckAnswers
  958. *
  959. * @param xxx $str (passed by reference)
  960. * @param xxx $start
  961. * @param xxx $length
  962. */
  963. function fix_js_CheckAnswers(&$str, $start, $length) {
  964. // JCloze, JCross, JMatch : CheckAnswers
  965. // JMix : CheckAnswer
  966. // JQuiz : CheckFinished
  967. $substr = substr($str, $start, $length);
  968. // intercept Checks, if necessary
  969. if ($insert = $this->get_stop_function_intercept()) {
  970. if ($pos = strpos($substr, '{')) {
  971. $substr = substr_replace($substr, $insert, $pos+1, 0);
  972. }
  973. }
  974. // add extra argument to function - so it can be called from the "Give Up" button
  975. $name = $this->get_stop_function_name();
  976. $search = '/(function '.$name.'\()(.*?)(\))/s';
  977. $callback = array($this, 'fix_js_CheckAnswers_arguments');
  978. $substr = preg_replace_callback($search, $callback, $substr, 1);
  979. // add call to Finish function (including QuizStatus)
  980. $search = $this->get_stop_function_search();
  981. $replace = $this->get_stop_function_replace();
  982. $substr = preg_replace($search, $replace, $substr, 1);
  983. $str = substr_replace($str, $substr, $start, $length);
  984. }
  985. /**
  986. * fix_js_CheckAnswers_arguments
  987. *
  988. * @param xxx $match
  989. * @return xxx
  990. */
  991. function fix_js_CheckAnswers_arguments($match) {
  992. if (empty($match[2])) {
  993. return $match[1].'ForceQuizStatus'.$match[3];
  994. } else {
  995. return $match[1].$match[2].',ForceQuizStatus'.$match[3];
  996. }
  997. }
  998. /**
  999. * get_stop_onclick
  1000. *
  1001. * @return xxx
  1002. */
  1003. function get_stop_onclick() {
  1004. if ($name = $this->get_stop_function_name()) {
  1005. return 'if('.$this->get_stop_function_confirm().')'.$name.'('.$this->get_stop_function_args().')';
  1006. } else {
  1007. return 'if(window.HP)HP.onunload('.QUIZPORT_STATUS_ABANDONED.')';
  1008. }
  1009. }
  1010. /**
  1011. * get_stop_function_confirm
  1012. *
  1013. * @return xxx
  1014. */
  1015. function get_stop_function_confirm() {
  1016. // Note: "&&" in onclick must be encoded as html-entities for strict XHTML
  1017. return ''
  1018. ."confirm("
  1019. ."'".$this->hotpot->source->js_value_safe(get_string('confirmstop', 'hotpot'), true)."'"
  1020. ."+'\\n\\n'+(window.onbeforeunload &amp;&amp; onbeforeunload()?(onbeforeunload()+'\\n\\n'):'')+"
  1021. ."'".$this->hotpot->source->js_value_safe(get_string('pressoktocontinue', 'hotpot'), true)."'"
  1022. .")"
  1023. ;
  1024. }
  1025. /**
  1026. * get_stop_function_name
  1027. *
  1028. * @return xxx
  1029. */
  1030. function get_stop_function_name() {
  1031. // the name of the javascript function into which the "give up" code should be inserted
  1032. return '';
  1033. }
  1034. /**
  1035. * get_stop_function_args
  1036. *
  1037. * @return xxx
  1038. */
  1039. function get_stop_function_args() {
  1040. // the arguments required by the javascript function which the stop_function() code calls
  1041. return hotpot::STATUS_ABANDONED;
  1042. }
  1043. /**
  1044. * get_stop_function_intercept
  1045. *
  1046. * @return xxx
  1047. */
  1048. function get_stop_function_intercept() {
  1049. // JMix and JQuiz each have their own version of this function
  1050. return "\n"
  1051. ." // intercept this Check\n"
  1052. ." HP.onclickCheck();\n"
  1053. ;
  1054. }
  1055. /**
  1056. * get_stop_function_search
  1057. *
  1058. * @return xxx
  1059. */
  1060. function get_stop_function_search() {
  1061. // JCloze : AllCorrect || Finished
  1062. // JCross : AllCorrect || TimeOver
  1063. // JMatch : AllDone || TimeOver
  1064. // JMix : AllDone || TimeOver (in the CheckAnswer function)
  1065. // JQuiz : AllDone (in the CheckFinished function)
  1066. return '/\s*if \(\((\w+) == true\)\|\|\(\w+ == true\)\)({).*?}\s*/s';
  1067. }
  1068. /**
  1069. * get_stop_function_replace
  1070. *
  1071. * @return xxx
  1072. */
  1073. function get_stop_function_replace() {
  1074. // $1 : name of the "all correct/done" variable
  1075. // $2 : opening curly brace of if-block plus any following text to be kept
  1076. if ($this->hotpot->delay3==hotpot::TIME_AFTEROK) {
  1077. $flag = 1; // set form values only
  1078. } else {
  1079. $flag = 0; // set form values and send form
  1080. }
  1081. return "\n"
  1082. ." if ($1){\n"
  1083. ." var QuizStatus = 4; // completed\n"
  1084. ." } else if (ForceQuizStatus){\n"
  1085. ." var QuizStatus = ForceQuizStatus; // 3=abandoned\n"
  1086. ." } else if (TimeOver){\n"
  1087. ." var QuizStatus = 2; // timed out\n"
  1088. ." } else {\n"
  1089. ." var QuizStatus = 1; // in progress\n"
  1090. ." }\n"
  1091. ." if (QuizStatus > 1) $2\n"
  1092. ." if (window.Interval) {\n"
  1093. ." clearInterval(window.Interval);\n"
  1094. ." }\n"
  1095. ." TimeOver = true;\n"
  1096. ." Locked = true;\n"
  1097. ." Finished = true;\n"
  1098. ." }\n"
  1099. ." if (Finished || HP.sendallclicks){\n"
  1100. ." if (ForceQuizStatus || QuizStatus==1){\n"
  1101. ." // send results immediately\n"
  1102. ." HP.onunload(QuizStatus);\n"
  1103. ." } else {\n"
  1104. ." // send results after delay\n"
  1105. ." setTimeout('HP.onunload('+QuizStatus+',$flag)', SubmissionTimeout);\n"
  1106. ." }\n"
  1107. ." }\n"
  1108. ;
  1109. }
  1110. /**
  1111. * postprocessing
  1112. *
  1113. * after headcontent and bodycontent have been setup and
  1114. * before content is sent to browser, we add title edit icon,
  1115. * insert submission form, adjust navigation butons (if any)
  1116. * and add external javascripts (to the top of the page)
  1117. */
  1118. function postprocessing() {
  1119. $this->fix_title_icons();
  1120. $this->fix_submissionform();
  1121. $this->fix_navigation_buttons();
  1122. foreach ($this->javascripts as $script) {
  1123. $this->page->requires->js('/'.$script, true);
  1124. }
  1125. }
  1126. /**
  1127. * fix_navigation_buttons
  1128. *
  1129. * @return xxx
  1130. */
  1131. function fix_navigation_buttons() {
  1132. if ($this->hotpot->navigation==hotpot::NAVIGATION_ORIGINAL) {
  1133. // replace relative URLs in <button class="NavButton" ... onclick="location='...'">
  1134. $search = '/'.'(?<='.'onclick="'."location='".')'."([^']*)".'(?='."'; return false;".'")'.'/is';
  1135. $callback = array($this, 'convert_url_navbutton');
  1136. $this->bodycontent = preg_replace_callback($search, $callback, $this->bodycontent);
  1137. // replace history.back() in <button class="NavButton" ... onclick="history.back(); ...">
  1138. // with a link to the course page
  1139. $params = array('id'=>$this->hotpot->course->id);
  1140. $search = '/'.'(?<='.'onclick=")'.'history\.back\(\)'.'(?=; return false;")'.'/';
  1141. $replace = "location='".new moodle_url('/course/view.php', $params)."'";
  1142. $this->bodycontent = preg_replace($search, $replace, $this->bodycontent);
  1143. }
  1144. }
  1145. /**
  1146. * fix_TimeLimit
  1147. */
  1148. function fix_TimeLimit() {
  1149. if ($this->hotpot->timelimit > 0) {
  1150. $search = '/(?<=var Seconds = )\d+(?=;)/';
  1151. $this->headcontent = preg_replace($search, $this->hotpot->timelimit, $this->headcontent, 1);
  1152. }
  1153. }
  1154. /**
  1155. * fix_SubmissionTimeout
  1156. */
  1157. function fix_SubmissionTimeout() {
  1158. if ($this->hotpot->delay3==hotpot::TIME_TEMPLATE) {
  1159. // use default from source/template file (=30000 ms =30 seconds)
  1160. if ($this->hasSubmissionTimeout) {
  1161. $timeout = null;
  1162. } else {
  1163. $timeout = 30000; // = 30 secs is HP default
  1164. }
  1165. } else {
  1166. if ($this->hotpot->delay3 >= 0) {
  1167. $timeout = $this->hotpot->delay3 * 1000; // milliseconds
  1168. } else {
  1169. $timeout = 0; // i.e. immediately
  1170. }
  1171. }
  1172. if (is_null($timeout)) {
  1173. return; // nothing to do
  1174. }
  1175. if ($this->hasSubmissionTimeout) {
  1176. // remove HPNStartTime
  1177. $search = '/var HPNStartTime\b[^;]*?;\s*/';
  1178. $this->headcontent = preg_replace($search, '', $this->headcontent, 1);
  1179. // reset the value of SubmissionTimeout
  1180. $search = '/(?<=var SubmissionTimeout = )\d+(?=;)/';
  1181. $this->headcontent = preg_replace($search, $timeout, $this->headcontent, 1);
  1182. } else {
  1183. // Rhubarb, Sequitur and Quandary
  1184. $search = '/var FinalScore = 0;/';
  1185. $replace = '$0'."\n".'var SubmissionTimeout = '.$timeout.';';
  1186. $this->headcontent = preg_replace($search, $replace, $this->headcontent, 1);
  1187. }
  1188. }
  1189. /**
  1190. * fix_navigation
  1191. */
  1192. function fix_navigation() {
  1193. if ($this->hotpot->navigation==hotpot::NAVIGATION_ORIGINAL) {
  1194. // do nothing - leave navigation as it is
  1195. return;
  1196. }
  1197. // insert the stop button, if required
  1198. if ($this->hotpot->stopbutton) {
  1199. // replace top nav buttons with a single stop button
  1200. if ($this->hotpot->stopbutton==hotpot::STOPBUTTON_LANGPACK) {
  1201. if ($pos = strpos($this->hotpot->stoptext, '_')) {
  1202. $mod = substr($this->hotpot->stoptext, 0, $pos);
  1203. $str = substr($this->hotpot->stoptext, $pos + 1);
  1204. $stoptext = get_string($str, $mod);
  1205. } else if ($this->hotpot->stoptext) {
  1206. $stoptext = get_string($this->hotpot->stoptext);
  1207. } else {
  1208. $stoptext = '';
  1209. }
  1210. } else {
  1211. $stoptext = $this->hotpot->stoptext;
  1212. }
  1213. if (trim($stoptext)=='') {
  1214. $stoptext = get_string('giveup', 'hotpot');
  1215. }
  1216. $confirm = get_string('confirmstop', 'hotpot');
  1217. //$search = '/<!-- BeginTopNavButtons -->'.'.*?'.'<!-- EndTopNavButtons -->/s';
  1218. $search = '/<(div class="Titles")>/s';
  1219. $replace = '<$1 style="position: relative">'."\n\t"
  1220. .'<div class="hotpotstopbutton">'
  1221. .'<button class="FuncButton" '
  1222. .'onclick="'.$this->get_stop_onclick().'" '
  1223. .'onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" '
  1224. .'onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" '
  1225. .'onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">'
  1226. .hotpot_textlib('utf8_to_entities', $stoptext)
  1227. .'</button>'
  1228. .'</div>'
  1229. ;
  1230. $this->bodycontent = preg_replace($search, $replace, $this->bodycontent, 1);
  1231. }
  1232. // remove (remaining) navigation buttons
  1233. $search = '/<!-- Begin(Top|Bottom)NavButtons -->'.'.*?'.'<!-- End'.'\\1'.'NavButtons -->/s';
  1234. $this->bodycontent = preg_replace($search, '', $this->bodycontent);
  1235. }
  1236. /**
  1237. * fix_filters
  1238. *
  1239. * @return xxx
  1240. */
  1241. function fix_filters() {
  1242. global $CFG;
  1243. if (isset($CFG->textfilters)) {
  1244. $textfilters = $CFG->textfilters;
  1245. } else {
  1246. $textfilters = '';
  1247. }
  1248. if ($this->hotpot->usefilters) {
  1249. $filters = filter_get_active_in_context($this->hotpot->context);
  1250. $filters = array_keys($filters);
  1251. } else {
  1252. $filters = array();
  1253. }
  1254. if ($this->hotpot->useglossary && ! in_array('mod/glossary', $filters)) {
  1255. $filters[] = 'mod/glossary';
  1256. }
  1257. if ($this->hotpot->usemediafilter) {
  1258. // exclude certain unnecessary or miscreant $filters
  1259. // - "mediaplugins" because it duplicates work done by "usemediafilter" setting
  1260. // - "asciimath" because it does not behave like a filter is supposed to behave
  1261. $filters = preg_grep('/^filter\/(mediaplugin|asciimath)$/', $filters, PREG_GREP_INVERT);
  1262. }
  1263. $CFG->textfilters = implode(',', $filters);
  1264. $this->filter_text_headcontent();
  1265. $this->filter_text_bodycontent();
  1266. $CFG->textfilters = $textfilters;
  1267. // fix unwanted conversions by the Moodle's Tex filter
  1268. // http://moodle.org/mod/forum/discuss.php?d=68435
  1269. // http://tracker.moodle.org/browse/MDL-7849
  1270. if (preg_match('/jcross|jmix/', get_class($this))) {
  1271. $search = '/(?<=replace\(\/)'.'<a[^>]*><img[^>]*class="texrender"[^>]*title="(.*?)"[^>]*><\/a>'.'(?=\/g)/is';
  1272. $replace = '\['.'$1'.'\]';
  1273. $this->headcontent = preg_replace($search, $replace, $this->headcontent);
  1274. }
  1275. // make sure openpopup() function is available if needed (for glossary entries)
  1276. // Note: this could also be done using filter_add_javascript($this->htmlcontent)
  1277. // but that function expects entire htmlcontent, where we would prefer just the headcontent
  1278. if ($this->hotpot->navigation==hotpot::NAVIGATION_ORIGINAL && in_array('mod/glossary', $filters)) {
  1279. // add openwindow() function (from lib/javascript.php)
  1280. $this->headcontent .= "\n"
  1281. .'<script type="text/javascript">'."\n"
  1282. .'//<![CDATA['."\n"
  1283. .'function openpopup(url, name, options, fullscreen) {'."\n"
  1284. .' var fullurl = "'.$CFG->httpswwwroot.'" + url;'."\n"
  1285. .' var windowobj = window.open(fullurl, name, options);'."\n"
  1286. .' if (!windowobj) {'."\n"
  1287. .' return true;'."\n"
  1288. .' }'."\n"
  1289. .' if (fullscreen) {'."\n"
  1290. .' windowobj.moveTo(0, 0);'."\n"
  1291. .' windowobj.resizeTo(screen.availWidth, screen.availHeight);'."\n"
  1292. .' }'."\n"
  1293. .' windowobj.focus();'."\n"
  1294. .' return false;'."\n"
  1295. .'}'."\n"
  1296. .'//]]>'."\n"
  1297. .'</script>'
  1298. ;
  1299. }
  1300. }
  1301. /**
  1302. * filter_text_headcontent
  1303. */
  1304. function filter_text_headcontent() {
  1305. if ($names = $this->headcontent_strings) {
  1306. $search = '/^'."((?:var )?(?:$names)(?:\[\d+\])*\s*=\s*')(.*)(';)".'$/m';
  1307. $callback = array($this, 'filter_text_headcontent_string');
  1308. $this->headcontent = preg_replace_callback($search, $callback, $this->headcontent);
  1309. }
  1310. if ($names = $this->headcontent_arrays) {
  1311. $search = '/^'."((?:var )?(?:$names)(?:\[\d+\])* = new Array\()(.*)(\);)".'$/m';
  1312. $callback = array($this, 'filter_text_headcontent_array');
  1313. $this->headcontent = preg_replace_callback($search, $callback, $this->headcontent);
  1314. }
  1315. }
  1316. /**
  1317. * filter_text_headcontent_array
  1318. *
  1319. * @param xxx $match
  1320. * @return xxx
  1321. */
  1322. function filter_text_headcontent_array($match) {
  1323. // I[q][0][a] = new Array('JQuiz answer text', 'feedback', 0, 0, 0)
  1324. $before = $match[1];
  1325. $str = $match[count($match) - 2];
  1326. $after = $match[count($match) - 1];
  1327. $search = "/(')((?:\\\\\\\\|\\\\'|[^'])*)(')/";
  1328. $callback = array($this, 'filter_text_headcontent_string');
  1329. return $before.preg_replace_callback($search, $callback, $str).$after;
  1330. }
  1331. /**
  1332. * filter_text_headcontent_string
  1333. *
  1334. * @param xxx $match
  1335. * @return xxx
  1336. */
  1337. function filter_text_headcontent_string($match) {
  1338. // var YourScoreIs = 'Your score is';
  1339. // I[q][1][a][2] = 'JCloze clue';
  1340. global $CFG;
  1341. static $replace_pairs = array(
  1342. // backslashes and quotes
  1343. '\\\\'=>'\\', "\\'"=>"'", '\\"'=>'"',
  1344. // newlines
  1345. '\\n'=>"\n",
  1346. // other (closing tag is for XHTML compliance)
  1347. '\\0'=>"\0", '<\\/'=>'</'
  1348. );
  1349. $before = $match[1];
  1350. $str = $match[count($match) - 2];
  1351. $after = $match[count($match) - 1];
  1352. // unescape backslashes, quote and newlines
  1353. $str = strtr($str, $replace_pairs);
  1354. // convert javascript unicode
  1355. $search = '/\\\\u([0-9a-f]{4})/i';
  1356. $str = $this->filter_text_to_utf8($str, $search);
  1357. // convert html entities
  1358. $search = '/&#x([0-9a-f]+);/i';
  1359. $str = $this->filter_text_to_utf8($str, $search);
  1360. // fix relative urls
  1361. $str = $this->fix_relativeurls($str);
  1362. // filter string,
  1363. // $str = filter_text($str);
  1364. // return safe javascript unicode
  1365. return $before.$this->hotpot->source->js_value_safe($str, true).$after;
  1366. }
  1367. /**
  1368. * filter_text_bodycontent
  1369. *
  1370. * @param xxx $str
  1371. * @param xxx $search
  1372. * @return string $str
  1373. * @return boolean $modified
  1374. */
  1375. function filter_text_to_utf8($str, $search) {
  1376. if (preg_match_all($search, $str, $matches, PREG_OFFSET_CAPTURE)) {
  1377. $i_max = count($matches[0]) - 1;
  1378. for ($i=$i_max; $i>=0; $i--) {
  1379. list($match, $start) = $matches[0][$i];
  1380. $char = $matches[1][$i][0];
  1381. $char = hotpot_textlib('code2utf8', hexdec($char));
  1382. $str = substr_replace($str, $char, $start, strlen($match));
  1383. }
  1384. }
  1385. return $str;
  1386. }
  1387. /**
  1388. * filter_text_bodycontent
  1389. */
  1390. function filter_text_bodycontent() {
  1391. // convert entities to utf8, filter text and convert back
  1392. //$this->bodycontent = hotpot_textlib('entities_to_utf8', $this->bodycontent);
  1393. //$this->bodycontent = filter_text($this->bodycontent);
  1394. //$this->bodycontent = hotpot_textlib('utf8_to_entities', $this->bodycontent);
  1395. }
  1396. /**
  1397. * fix_feedbackform
  1398. */
  1399. function fix_feedbackform() {
  1400. // we are aiming to generate the following javascript to send to the client
  1401. //FEEDBACK = new Array();
  1402. //FEEDBACK[0] = ''; // url of feedback page/script
  1403. //FEEDBACK[1] = ''; // array of array('teachername', 'value');
  1404. //FEEDBACK[2] = ''; // 'student name' [formmail only]
  1405. //FEEDBACK[3] = ''; // 'student email' [formmail only]
  1406. //FEEDBACK[4] = ''; // window width
  1407. //FEEDBACK[5] = ''; // window height
  1408. //FEEDBACK[6] = ''; // 'Send a message to teacher' [prompt/button text]
  1409. //FEEDBACK[7] = ''; // 'Title'
  1410. //FEEDBACK[8] = ''; // 'Teacher'
  1411. //FEEDBACK[9] = ''; // 'Message'
  1412. //FEEDBACK[10] = ''; // 'Close this window'
  1413. global $CFG, $USER;
  1414. $feedback = array();
  1415. switch ($this->hotpot->studentfeedback) {
  1416. case hotpot::FEEDBACK_NONE:
  1417. // do nothing - feedback form is not required
  1418. break;
  1419. case hotpot::FEEDBACK_WEBPAGE:
  1420. if ($this->hotpot->studentfeedbackurl) {
  1421. $feedback[0] = "'".addslashes_js($this->hotpot->studentfeedbackurl)."'";
  1422. } else {
  1423. $this->hotpot->studentfeedback = hotpot::FEEDBACK_NONE;
  1424. }
  1425. break;
  1426. case hotpot::FEEDBACK_FORMMAIL:
  1427. if ($this->hotpot->studentfeedbackurl) {
  1428. $teachers = $this->get_feedback_teachers();
  1429. } else {
  1430. $teachers = '';
  1431. }
  1432. if ($teachers) {
  1433. $feedback[0] = "'".addslashes_js($this->hotpot->studentfeedbackurl)."'";
  1434. $feedback[1] = $teachers;
  1435. $feedback[2] = "'".addslashes_js(fullname($USER))."'";
  1436. $feedback[3] = "'".addslashes_js($USER->email)."'";
  1437. $feedback[4] = 500; // width
  1438. $feedback[5] = 300; // height
  1439. } else {
  1440. // no teachers (or no feedback url)
  1441. $this->hotpot->studentfeedback = hotpot::FEEDBACK_NONE;
  1442. }
  1443. break;
  1444. case hotpot::FEEDBACK_MOODLEFORUM:
  1445. $cmids = array();
  1446. if ($modinfo = get_fast_modinfo($this->hotpot->course)) {
  1447. foreach ($modinfo->cms as $cmid=>$mod) {
  1448. if ($mod->modname=='forum' && $mod->visible) {
  1449. $cmids[] = $cmid;
  1450. }
  1451. }
  1452. }
  1453. switch (count($cmids)) {
  1454. case 0: $this->hotpot->studentfeedback = hotpot::FEEDBACK_NONE; break; // no forums !!
  1455. case 1: $feedback[0] = "'".$CFG->wwwroot.'/mod/forum/view.php?id='.$cmids[0]."'"; break;
  1456. default: $feedback[0] = "'".$CFG->wwwroot.'/mod/forum/index.php?id='.$this->hotpot->course->id."'";
  1457. }
  1458. break;
  1459. case hotpot::FEEDBACK_MOODLEMESSAGING:
  1460. if ($CFG->messaging) {
  1461. $teachers = $this->get_feedback_teachers();
  1462. } else {
  1463. $teachers = '';
  1464. }
  1465. if ($teachers) {
  1466. $feedback[0] = "'$CFG->wwwroot/message/discussion.php?id='";
  1467. $feedback[1] = $teachers;
  1468. $feedback[4] = 400; // width
  1469. $feedback[5] = 500; // height
  1470. } else {
  1471. // no teachers (or no Moodle messaging)
  1472. $this->hotpot->studentfeedback = hotpot::FEEDBACK_NONE;
  1473. }
  1474. break;
  1475. default:
  1476. // unrecognized feedback setting, so reset it to something valid
  1477. $this->hotpot->studentfeedback = hotpot::FEEDBACK_NONE;
  1478. }
  1479. if ($this->hotpot->studentfeedback==hotpot::FEEDBACK_NONE) {
  1480. // do nothing - feedback form is not required
  1481. } else {
  1482. // complete remaining feedback fields
  1483. if ($this->hotpot->studentfeedback==hotpot::FEEDBACK_MOODLEFORUM) {
  1484. $feedback[6] = "'".addslashes_js(get_string('feedbackdiscuss', 'hotpot'))."'";
  1485. } else {
  1486. // FEEDBACK_WEBPAGE, FEEDBACK_FORMMAIL, FEEDBACK_MOODLEMESSAGING
  1487. $feedback[6] = "'".addslashes_js(get_string('feedbacksendmessage', 'hotpot'))."'";
  1488. }
  1489. $feedback[7] = "'".addslashes_js(get_string('feedback'))."'";
  1490. $feedback[8] = "'".addslashes_js(get_string('defaultcourseteacher'))."'";
  1491. $feedback[9] = "'".addslashes_js(get_string('messagebody'))."'";
  1492. $feedback[10] = "'".addslashes_js(get_string('closewindow'))."'";
  1493. $js = '';
  1494. foreach ($feedback as $i=>$str) {
  1495. $js .= 'FEEDBACK['.$i."] = $str;\n";
  1496. }
  1497. $js = '<script type="text/javascript">'."\n//<![CDATA[\n"."FEEDBACK = new Array();\n".$js."//]]>\n</script>\n";
  1498. if ($this->usemoodletheme) {
  1499. $this->headcontent .= $js;
  1500. } else {
  1501. $this->bodycontent = preg_replace('/<\/head>/i', "$js</head>", $this->bodycontent, 1);
  1502. }
  1503. }
  1504. }
  1505. /**
  1506. * get_feedback_teachers
  1507. *
  1508. * @return xxx
  1509. */
  1510. function get_feedback_teachers() {
  1511. $context = hotpot_get_context(CONTEXT_COURSE, $this->hotpot->source->courseid);
  1512. $teachers = get_users_by_capability($context, 'mod/hotpot:grade');
  1513. if (! $teachers) {
  1514. return '';
  1515. }
  1516. if ($this->hotpot->studentfeedback==hotpot::FEEDBACK_MOODLEMESSAGING) {
  1517. $detail = 'id';
  1518. } else {
  1519. $detail = 'email';
  1520. }
  1521. $details = array();
  1522. foreach ($teachers as $teacher) {
  1523. $details[] = "new Array('".addslashes_js(fullname($teacher))."', '".addslashes_js($teacher->$detail)."')";
  1524. }
  1525. return 'new Array('.implode(', ', $details).')';
  1526. }
  1527. /**
  1528. * fix_reviewoptions
  1529. */
  1530. function fix_reviewoptions() {
  1531. // enable / disable review options
  1532. }
  1533. /**
  1534. * fix_submissionform
  1535. */
  1536. function fix_submissionform() {
  1537. $params = array(
  1538. 'id' => $this->hotpot->create_attempt(),
  1539. $this->scorefield => '0', 'detail' => '0', 'status' => '0',
  1540. 'starttime' => '0', 'endtime' => '0', 'redirect' => '0',
  1541. );
  1542. $attributes = array(
  1543. 'id' => $this->formid, 'autocomplete' => 'off'
  1544. );
  1545. $form_start = $this->form_start('submit.php', $params, $attributes);
  1546. $search = '<!-- BeginSubmissionForm -->';
  1547. if (! $pos = strpos($this->bodycontent, $search)) {
  1548. throw new moodle_exception('couldnotinsertsubmissionform', 'hotpot');
  1549. }
  1550. $this->bodycontent = substr_replace($this->bodycontent, $form_start, $pos, strlen($search));
  1551. $search = '<!-- EndSubmissionForm -->';
  1552. if (! $pos = strpos($this->bodycontent, $search)) {
  1553. throw new moodle_exception('couldnotinsertsubmissionform', 'hotpot');
  1554. }
  1555. $this->bodycontent = substr_replace($this->bodycontent, $this->form_end(), $pos, strlen($search));
  1556. }
  1557. /**
  1558. * fix_mediafilter_onload_extra
  1559. *
  1560. * @return xxx
  1561. */
  1562. function fix_mediafilter_onload_extra() {
  1563. return ''
  1564. .' if(window.StretchCanvasToCoverContent) {'."\n"
  1565. .' StretchCanvasToCoverContent();'."\n"
  1566. .' }'."\n"
  1567. ;
  1568. }
  1569. // captions and messages
  1570. /**
  1571. * expand_AlsoCorrect
  1572. *
  1573. * @return xxx
  1574. */
  1575. function expand_AlsoCorrect() {
  1576. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',also-correct');
  1577. }
  1578. /**
  1579. * expand_BottomNavBar
  1580. *
  1581. * @return xxx
  1582. */
  1583. function expand_BottomNavBar() {
  1584. return $this->expand_NavBar('BottomNavBar');
  1585. }
  1586. /**
  1587. * expand_CapitalizeFirst
  1588. *
  1589. * @return xxx
  1590. */
  1591. function expand_CapitalizeFirst() {
  1592. return $this->hotpot->source->xml_value_bool($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',capitalize-first-letter');
  1593. }
  1594. /**
  1595. * expand_CheckCaption
  1596. *
  1597. * @return xxx
  1598. */
  1599. function expand_CheckCaption() {
  1600. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,check-caption');
  1601. }
  1602. /**
  1603. * expand_ContentsURL
  1604. *
  1605. * @return xxx
  1606. */
  1607. function expand_ContentsURL() {
  1608. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,contents-url');
  1609. }
  1610. /**
  1611. * expand_CorrectIndicator
  1612. *
  1613. * @return xxx
  1614. */
  1615. function expand_CorrectIndicator() {
  1616. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,correct-indicator');
  1617. }
  1618. /**
  1619. * expand_Back
  1620. *
  1621. * @return xxx
  1622. */
  1623. function expand_Back() {
  1624. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,global,include-back');
  1625. }
  1626. /**
  1627. * expand_BackCaption
  1628. *
  1629. * @return xxx
  1630. */
  1631. function expand_BackCaption() {
  1632. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,back-caption');
  1633. }
  1634. /**
  1635. * expand_CaseSensitive
  1636. *
  1637. * @return xxx
  1638. */
  1639. function expand_CaseSensitive() {
  1640. return $this->hotpot->source->xml_value_bool($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',case-sensitive');
  1641. }
  1642. /**
  1643. * expand_ClickToAdd
  1644. *
  1645. * @return xxx
  1646. */
  1647. function expand_ClickToAdd() {
  1648. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',click-to-add');
  1649. }
  1650. /**
  1651. * expand_ClueCaption
  1652. *
  1653. * @return xxx
  1654. */
  1655. function expand_ClueCaption() {
  1656. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,clue-caption');
  1657. }
  1658. /**
  1659. * expand_Clues
  1660. *
  1661. * @return xxx
  1662. */
  1663. function expand_Clues() {
  1664. // Note: WinHotPot6 uses "include-clues", but JavaHotPotatoes6 uses "include-clue" (missing "s")
  1665. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-clues');
  1666. }
  1667. /**
  1668. * expand_Contents
  1669. *
  1670. * @return xxx
  1671. */
  1672. function expand_Contents() {
  1673. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,global,include-contents');
  1674. }
  1675. /**
  1676. * expand_ContentsCaption
  1677. *
  1678. * @return xxx
  1679. */
  1680. function expand_ContentsCaption() {
  1681. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,contents-caption');
  1682. }
  1683. /**
  1684. * expand_Correct
  1685. *
  1686. * @return xxx
  1687. */
  1688. function expand_Correct() {
  1689. if ($this->hotpot->source->hbs_quiztype=='jcloze') {
  1690. $tag = 'guesses-correct';
  1691. } else {
  1692. $tag = 'guess-correct';
  1693. }
  1694. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.','.$tag);
  1695. }
  1696. /**
  1697. * expand_DeleteCaption
  1698. *
  1699. * @return xxx
  1700. */
  1701. function expand_DeleteCaption() {
  1702. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',delete-caption');
  1703. }
  1704. /**
  1705. * expand_DublinCoreMetadata
  1706. *
  1707. * @return xxx
  1708. */
  1709. function expand_DublinCoreMetadata() {
  1710. $dc = '';
  1711. if ($value = $this->hotpot->source->xml_value('', "['rdf:RDF'][0]['@']['xmlns:dc']")) {
  1712. $dc .= '<link rel="schema.DC" href="'.str_replace('"', '&quot;', $value).'" />'."\n";
  1713. }
  1714. if (is_array($this->hotpot->source->xml_value('rdf:RDF,rdf:Description'))) {
  1715. $names = array('DC:Creator'=>'dc:creator', 'DC:Title'=>'dc:title');
  1716. foreach ($names as $name => $tag) {
  1717. $i = 0;
  1718. $values = array();
  1719. while($value = $this->hotpot->source->xml_value("rdf:RDF,rdf:Description,$tag", "[$i]['#']")) {
  1720. if ($value = trim(strip_tags($value))) {
  1721. $values[strtoupper($value)] = htmlspecialchars($value);
  1722. }
  1723. $i++;
  1724. }
  1725. if ($value = implode(', ', $values)) {
  1726. $dc .= '<meta name="'.$name.'" content="'.$value.'" />'."\n";
  1727. }
  1728. }
  1729. }
  1730. return $dc;
  1731. }
  1732. /**
  1733. * expand_EMail
  1734. *
  1735. * @return xxx
  1736. */
  1737. function expand_EMail() {
  1738. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,email');
  1739. }
  1740. /**
  1741. * expand_EscapedExerciseTitle
  1742. * this string only used in resultsp6sendresults.js_ which is not required in Moodle
  1743. *
  1744. * @return xxx
  1745. */
  1746. function expand_EscapedExerciseTitle() {
  1747. return $this->hotpot->source->xml_value_js('data,title');
  1748. }
  1749. /**
  1750. * expand_ExBGColor
  1751. *
  1752. * @return xxx
  1753. */
  1754. function expand_ExBGColor() {
  1755. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,ex-bg-color');
  1756. }
  1757. /**
  1758. * expand_ExerciseSubtitle
  1759. *
  1760. * @return xxx
  1761. */
  1762. function expand_ExerciseSubtitle() {
  1763. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',exercise-subtitle');
  1764. }
  1765. /**
  1766. * expand_ExerciseTitle
  1767. *
  1768. * @return xxx
  1769. */
  1770. function expand_ExerciseTitle() {
  1771. return $this->hotpot->source->xml_value('data,title');
  1772. }
  1773. /**
  1774. * expand_FontFace
  1775. *
  1776. * @return xxx
  1777. */
  1778. function expand_FontFace() {
  1779. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,font-face');
  1780. }
  1781. /**
  1782. * expand_FontSize
  1783. *
  1784. * @return xxx
  1785. */
  1786. function expand_FontSize() {
  1787. $value = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,font-size');
  1788. return (empty($value) ? 'small' : $value);
  1789. }
  1790. /**
  1791. * expand_FormMailURL
  1792. *
  1793. * @return xxx
  1794. */
  1795. function expand_FormMailURL() {
  1796. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,formmail-url');
  1797. }
  1798. /**
  1799. * expand_FullVersionInfo
  1800. *
  1801. * @return xxx
  1802. */
  1803. function expand_FullVersionInfo() {
  1804. global $CFG;
  1805. return $this->hotpot->source->xml_value('version').'.x (Moodle '.$CFG->release.', HotPot '.hotpot::get_version_info('release').')';
  1806. }
  1807. /**
  1808. * expand_FuncLightColor
  1809. *
  1810. * @return xxx
  1811. */
  1812. function expand_FuncLightColor() { // top-left of buttons
  1813. $color = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,ex-bg-color');
  1814. return $this->expand_halfway_color($color, '#ffffff');
  1815. }
  1816. /**
  1817. * expand_FuncShadeColor
  1818. *
  1819. * @return xxx
  1820. */
  1821. function expand_FuncShadeColor() { // bottom right of buttons
  1822. $color = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,ex-bg-color');
  1823. return $this->expand_halfway_color($color, '#000000');
  1824. }
  1825. /**
  1826. * expand_GiveHint
  1827. *
  1828. * @return xxx
  1829. */
  1830. function expand_GiveHint() {
  1831. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',next-correct-letter');
  1832. }
  1833. /**
  1834. * expand_GraphicURL
  1835. *
  1836. * @return xxx
  1837. */
  1838. function expand_GraphicURL() {
  1839. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,graphic-url');
  1840. }
  1841. /**
  1842. * expand_GuessCorrect
  1843. *
  1844. * @return xxx
  1845. */
  1846. function expand_GuessCorrect() {
  1847. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',guess-correct');
  1848. }
  1849. /**
  1850. * expand_GuessIncorrect
  1851. *
  1852. * @return xxx
  1853. */
  1854. function expand_GuessIncorrect() {
  1855. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',guess-incorrect');
  1856. }
  1857. /**
  1858. * expand_HeaderCode
  1859. *
  1860. * @return xxx
  1861. */
  1862. function expand_HeaderCode() {
  1863. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,header-code');
  1864. }
  1865. /**
  1866. * expand_Hint
  1867. *
  1868. * @return xxx
  1869. */
  1870. function expand_Hint() {
  1871. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-hint');
  1872. }
  1873. /**
  1874. * expand_HintCaption
  1875. *
  1876. * @return xxx
  1877. */
  1878. function expand_HintCaption() {
  1879. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,hint-caption');
  1880. }
  1881. /**
  1882. * expand_Incorrect
  1883. *
  1884. * @return xxx
  1885. */
  1886. function expand_Incorrect() {
  1887. if ($this->hotpot->source->hbs_quiztype=='jcloze') {
  1888. $tag = 'guesses-incorrect';
  1889. } else {
  1890. $tag = 'guess-incorrect';
  1891. }
  1892. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.','.$tag);
  1893. }
  1894. /**
  1895. * expand_IncorrectIndicator
  1896. *
  1897. * @return xxx
  1898. */
  1899. function expand_IncorrectIndicator() {
  1900. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,incorrect-indicator');
  1901. }
  1902. /**
  1903. * expand_Instructions
  1904. *
  1905. * @return xxx
  1906. */
  1907. function expand_Instructions() {
  1908. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',instructions');
  1909. }
  1910. /**
  1911. * expand_JSBrowserCheck
  1912. *
  1913. * @return xxx
  1914. */
  1915. function expand_JSBrowserCheck() {
  1916. return $this->expand_template('hp6browsercheck.js_');
  1917. }
  1918. /**
  1919. * expand_JSButtons
  1920. *
  1921. * @return xxx
  1922. */
  1923. function expand_JSButtons() {
  1924. return $this->expand_template('hp6buttons.js_');
  1925. }
  1926. /**
  1927. * expand_JSCard
  1928. *
  1929. * @return xxx
  1930. */
  1931. function expand_JSCard() {
  1932. return $this->expand_template('hp6card.js_');
  1933. }
  1934. /**
  1935. * expand_JSCheckShortAnswer
  1936. *
  1937. * @return xxx
  1938. */
  1939. function expand_JSCheckShortAnswer() {
  1940. return $this->expand_template('hp6checkshortanswer.js_');
  1941. }
  1942. /**
  1943. * expand_JSHotPotNet
  1944. *
  1945. * @return xxx
  1946. */
  1947. function expand_JSHotPotNet() {
  1948. return $this->expand_template('hp6hotpotnet.js_');
  1949. }
  1950. /**
  1951. * expand_JSSendResults
  1952. *
  1953. * @return xxx
  1954. */
  1955. function expand_JSSendResults() {
  1956. return $this->expand_template('hp6sendresults.js_');
  1957. }
  1958. /**
  1959. * expand_JSShowMessage
  1960. *
  1961. * @return xxx
  1962. */
  1963. function expand_JSShowMessage() {
  1964. return $this->expand_template('hp6showmessage.js_');
  1965. }
  1966. /**
  1967. * expand_JSTimer
  1968. *
  1969. * @return xxx
  1970. */
  1971. function expand_JSTimer() {
  1972. return $this->expand_template('hp6timer.js_');
  1973. }
  1974. /**
  1975. * expand_JSUtilities
  1976. *
  1977. * @return xxx
  1978. */
  1979. function expand_JSUtilities() {
  1980. return $this->expand_template('hp6utilities.js_');
  1981. }
  1982. /**
  1983. * expand_LastQCaption
  1984. *
  1985. * @return xxx
  1986. */
  1987. function expand_LastQCaption() {
  1988. $caption = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,last-q-caption');
  1989. return ($caption=='<=' ? '&lt;=' : $caption);
  1990. }
  1991. /**
  1992. * expand_LinkColor
  1993. *
  1994. * @return xxx
  1995. */
  1996. function expand_LinkColor() {
  1997. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,link-color');
  1998. }
  1999. /**
  2000. * expand_NamePlease
  2001. *
  2002. * @return xxx
  2003. */
  2004. function expand_NamePlease() {
  2005. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,name-please');
  2006. }
  2007. /**
  2008. * expand_NavBar
  2009. *
  2010. * @param xxx $navbarid (optional, default='')
  2011. * @return xxx
  2012. */
  2013. function expand_NavBar($navbarid='') {
  2014. $this->navbarid = $navbarid;
  2015. $navbar = $this->expand_template('hp6navbar.ht_');
  2016. unset($this->navbarid);
  2017. return $navbar;
  2018. }
  2019. /**
  2020. * expand_NavBarID
  2021. *
  2022. * @return xxx
  2023. */
  2024. function expand_NavBarID() {
  2025. // $this->navbarid is set in "$this->expand_NavBar"
  2026. return empty($this->navbarid) ? '' : $this->navbarid;
  2027. }
  2028. /**
  2029. * expand_NavBarJS
  2030. *
  2031. * @return xxx
  2032. */
  2033. function expand_NavBarJS() {
  2034. return $this->expand_NavButtons();
  2035. }
  2036. /**
  2037. * expand_NavButtons
  2038. *
  2039. * @return xxx
  2040. */
  2041. function expand_NavButtons() {
  2042. return ($this->expand_Back() || $this->expand_NextEx() || $this->expand_Contents());
  2043. }
  2044. /**
  2045. * expand_NavTextColor
  2046. *
  2047. * @return xxx
  2048. */
  2049. function expand_NavTextColor() {
  2050. // might be 'title-color' ?
  2051. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,text-color');
  2052. }
  2053. /**
  2054. * expand_NavBarColor
  2055. *
  2056. * @return xxx
  2057. */
  2058. function expand_NavBarColor() {
  2059. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,nav-bar-color');
  2060. }
  2061. /**
  2062. * expand_NavLightColor
  2063. *
  2064. * @return xxx
  2065. */
  2066. function expand_NavLightColor() {
  2067. $color = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,nav-bar-color');
  2068. return $this->expand_halfway_color($color, '#ffffff');
  2069. }
  2070. /**
  2071. * expand_NavShadeColor
  2072. *
  2073. * @return xxx
  2074. */
  2075. function expand_NavShadeColor() {
  2076. $color = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,nav-bar-color');
  2077. return $this->expand_halfway_color($color, '#000000');
  2078. }
  2079. /**
  2080. * expand_NextCaption
  2081. *
  2082. * @return xxx
  2083. */
  2084. function expand_NextCaption() {
  2085. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',next-caption');
  2086. }
  2087. /**
  2088. * expand_NextCorrect
  2089. *
  2090. * @return xxx
  2091. */
  2092. function expand_NextCorrect() {
  2093. if ($this->hotpot->source->hbs_quiztype=='jquiz') {
  2094. $tag = 'next-correct-letter'; // jquiz
  2095. } else {
  2096. $tag = 'next-correct-part'; // jmix
  2097. }
  2098. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.','.$tag);
  2099. }
  2100. /**
  2101. * expand_NextEx
  2102. *
  2103. * @return xxx
  2104. */
  2105. function expand_NextEx() {
  2106. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,global,include-next-ex');
  2107. }
  2108. /**
  2109. * expand_NextExCaption
  2110. *
  2111. * @return xxx
  2112. */
  2113. function expand_NextExCaption() {
  2114. $caption = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,next-ex-caption');
  2115. return ($caption=='=>' ? '=&gt;' : $caption);
  2116. }
  2117. /**
  2118. * expand_NextQCaption
  2119. *
  2120. * @return xxx
  2121. */
  2122. function expand_NextQCaption() {
  2123. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,next-q-caption');
  2124. }
  2125. /**
  2126. * expand_NextExURL
  2127. *
  2128. * @return xxx
  2129. */
  2130. function expand_NextExURL() {
  2131. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',next-ex-url');
  2132. }
  2133. /**
  2134. * expand_OKCaption
  2135. *
  2136. * @return xxx
  2137. */
  2138. function expand_OKCaption() {
  2139. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,ok-caption');
  2140. }
  2141. /**
  2142. * expand_PageBGColor
  2143. *
  2144. * @return xxx
  2145. */
  2146. function expand_PageBGColor() {
  2147. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,page-bg-color');
  2148. }
  2149. /**
  2150. * expand_PlainTitle
  2151. *
  2152. * @return xxx
  2153. */
  2154. function expand_PlainTitle() {
  2155. return $this->hotpot->source->xml_value('data,title');
  2156. }
  2157. /**
  2158. * expand_PreloadImages
  2159. *
  2160. * @return xxx
  2161. */
  2162. function expand_PreloadImages() {
  2163. $value = $this->expand_PreloadImageList();
  2164. return empty($value) ? false : true;
  2165. }
  2166. /**
  2167. * expand_PreloadImageList
  2168. *
  2169. * @return xxx
  2170. */
  2171. function expand_PreloadImageList() {
  2172. if (! isset($this->PreloadImageList)) {
  2173. $this->PreloadImageList = '';
  2174. $images = array();
  2175. // extract all src values from <img> tags in the xml file
  2176. $search = '/&amp;#x003C;img.*?src=&quot;(.*?)&quot;.*?&amp;#x003E;/is';
  2177. if (preg_match_all($search, $this->hotpot->source->filecontents, $matches)) {
  2178. $images = array_merge($images, $matches[1]);
  2179. }
  2180. // extract all urls from QuizPort's [square bracket] notation
  2181. // e.g. [%sitefiles%/images/screenshot.jpg image 350 265 center]
  2182. $search = '/\['."([^\?\]]*\.(?:jpg|gif|png)(?:\?[^ \t\r\n\]]*)?)".'[^\]]*'.'\]/s';
  2183. if (preg_match_all($search, $this->hotpot->source->filecontents, $matches)) {
  2184. $images = array_merge($images, $matches[1]);
  2185. }
  2186. if (count($images)) {
  2187. $images = array_unique($images);
  2188. $this->PreloadImageList = "\n\t\t'".implode("',\n\t\t'", $images)."'\n\t";
  2189. }
  2190. }
  2191. return $this->PreloadImageList;
  2192. }
  2193. /**
  2194. * expand_Reading
  2195. *
  2196. * @return xxx
  2197. */
  2198. function expand_Reading() {
  2199. return $this->hotpot->source->xml_value_int('data,reading,include-reading');
  2200. }
  2201. /**
  2202. * expand_ReadingText
  2203. *
  2204. * @return xxx
  2205. */
  2206. function expand_ReadingText() {
  2207. $title = $this->expand_ReadingTitle();
  2208. if ($value = $this->hotpot->source->xml_value('data,reading,reading-text')) {
  2209. $value = '<div class="ReadingText">'.$value.'</div>';
  2210. } else {
  2211. $value = '';
  2212. }
  2213. return $title.$value;
  2214. }
  2215. /**
  2216. * expand_ReadingTitle
  2217. *
  2218. * @return xxx
  2219. */
  2220. function expand_ReadingTitle() {
  2221. $value = $this->hotpot->source->xml_value('data,reading,reading-title');
  2222. return empty($value) ? '' : ('<h3 class="ExerciseSubtitle">'.$value.'</h3>');
  2223. }
  2224. /**
  2225. * expand_Restart
  2226. *
  2227. * @return xxx
  2228. */
  2229. function expand_Restart() {
  2230. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-restart');
  2231. }
  2232. /**
  2233. * expand_RestartCaption
  2234. *
  2235. * @return xxx
  2236. */
  2237. function expand_RestartCaption() {
  2238. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,restart-caption');
  2239. }
  2240. /**
  2241. * expand_Scorm12
  2242. *
  2243. * @return xxx
  2244. */
  2245. function expand_Scorm12() {
  2246. return false; // HP scorm functionality is always disabled in Moodle
  2247. }
  2248. /**
  2249. * expand_Seconds
  2250. *
  2251. * @return xxx
  2252. */
  2253. function expand_Seconds() {
  2254. return $this->hotpot->source->xml_value('data,timer,seconds');
  2255. }
  2256. /**
  2257. * expand_SendResults
  2258. *
  2259. * @return xxx
  2260. */
  2261. function expand_SendResults() {
  2262. return false; // send results (via formmail) is always disabled in Moodle
  2263. // $tags = $this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',send-email';
  2264. // return $this->hotpot->source->xml_value($tags);
  2265. }
  2266. /**
  2267. * expand_ShowAllQuestionsCaption
  2268. *
  2269. * @return xxx
  2270. */
  2271. function expand_ShowAllQuestionsCaption($convert_to_unicode=false) {
  2272. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,show-all-questions-caption');
  2273. }
  2274. /**
  2275. * expand_ShowAnswer
  2276. *
  2277. * @return xxx
  2278. */
  2279. function expand_ShowAnswer() {
  2280. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-show-answer');
  2281. }
  2282. /**
  2283. * expand_SolutionCaption
  2284. *
  2285. * @return xxx
  2286. */
  2287. function expand_SolutionCaption() {
  2288. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,solution-caption');
  2289. }
  2290. /**
  2291. * expand_ShowOneByOneCaption
  2292. *
  2293. * @return xxx
  2294. */
  2295. function expand_ShowOneByOneCaption() {
  2296. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,show-one-by-one-caption');
  2297. }
  2298. /**
  2299. * expand_StyleSheet
  2300. *
  2301. * @return xxx
  2302. */
  2303. function expand_StyleSheet() {
  2304. return $this->expand_template('hp6.cs_');
  2305. }
  2306. /**
  2307. * expand_TextColor
  2308. *
  2309. * @return xxx
  2310. */
  2311. function expand_TextColor() {
  2312. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,text-color');
  2313. }
  2314. /**
  2315. * expand_TheseAnswersToo
  2316. *
  2317. * @return xxx
  2318. */
  2319. function expand_TheseAnswersToo() {
  2320. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',also-correct');
  2321. }
  2322. /**
  2323. * expand_ThisMuch
  2324. *
  2325. * @return xxx
  2326. */
  2327. function expand_ThisMuch() {
  2328. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',this-much-correct');
  2329. }
  2330. /**
  2331. * expand_Timer
  2332. *
  2333. * @return xxx
  2334. */
  2335. function expand_Timer() {
  2336. if ($this->hotpot->timelimit < 0) {
  2337. // use setting in source file
  2338. return $this->hotpot->source->xml_value_int('data,timer,include-timer');
  2339. } else {
  2340. // override setting in source file
  2341. return $this->hotpot->timelimit;
  2342. }
  2343. }
  2344. /**
  2345. * expand_TimesUp
  2346. *
  2347. * @return xxx
  2348. */
  2349. function expand_TimesUp() {
  2350. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,times-up');
  2351. }
  2352. /**
  2353. * expand_TitleColor
  2354. *
  2355. * @return xxx
  2356. */
  2357. function expand_TitleColor() {
  2358. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,title-color');
  2359. }
  2360. /**
  2361. * expand_TopNavBar
  2362. *
  2363. * @return xxx
  2364. */
  2365. function expand_TopNavBar() {
  2366. return $this->expand_NavBar('TopNavBar');
  2367. }
  2368. /**
  2369. * expand_Undo
  2370. *
  2371. * @return xxx
  2372. */
  2373. function expand_Undo() {
  2374. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-undo');
  2375. }
  2376. /**
  2377. * expand_UndoCaption
  2378. *
  2379. * @return xxx
  2380. */
  2381. function expand_UndoCaption() {
  2382. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,undo-caption');
  2383. }
  2384. /**
  2385. * expand_UserDefined1
  2386. *
  2387. * @return xxx
  2388. */
  2389. function expand_UserDefined1() {
  2390. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,user-string-1');
  2391. }
  2392. /**
  2393. * expand_UserDefined2
  2394. *
  2395. * @return xxx
  2396. */
  2397. function expand_UserDefined2() {
  2398. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,user-string-2');
  2399. }
  2400. /**
  2401. * expand_UserDefined3
  2402. *
  2403. * @return xxx
  2404. */
  2405. function expand_UserDefined3() {
  2406. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,user-string-3');
  2407. }
  2408. /**
  2409. * expand_VLinkColor
  2410. *
  2411. * @return xxx
  2412. */
  2413. function expand_VLinkColor() {
  2414. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,vlink-color');
  2415. }
  2416. /**
  2417. * expand_YourScoreIs
  2418. *
  2419. * @return xxx
  2420. */
  2421. function expand_YourScoreIs() {
  2422. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,your-score-is');
  2423. }
  2424. /**
  2425. * expand_Keypad
  2426. *
  2427. * @return xxx
  2428. */
  2429. function expand_Keypad() {
  2430. $str = '';
  2431. if ($this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-keypad')) {
  2432. // these characters must always be in the keypad
  2433. $chars = array();
  2434. $this->add_keypad_chars($chars, $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,global,keypad-characters'));
  2435. // append other characters used in the answers
  2436. switch ($this->hotpot->source->hbs_quiztype) {
  2437. case 'jcloze':
  2438. $tags = 'data,gap-fill,question-record';
  2439. break;
  2440. case 'jquiz':
  2441. $tags = 'data,questions,question-record';
  2442. break;
  2443. case 'rhubarb':
  2444. $tags = 'data,rhubarb-text';
  2445. break;
  2446. default:
  2447. $tags = '';
  2448. }
  2449. if ($tags) {
  2450. $q = 0;
  2451. while (($question="[$q]['#']") && $this->hotpot->source->xml_value($tags, $question)) {
  2452. if ($this->hotpot->source->hbs_quiztype=='jquiz') {
  2453. $answers = $question."['answers'][0]['#']";
  2454. } else {
  2455. $answers = $question;
  2456. }
  2457. $a = 0;
  2458. while (($answer=$answers."['answer'][$a]['#']") && $this->hotpot->source->xml_value($tags, $answer)) {
  2459. $this->add_keypad_chars($chars, $this->hotpot->source->xml_value($tags, $answer."['text'][0]['#']"));
  2460. $a++;
  2461. }
  2462. $q++;
  2463. }
  2464. }
  2465. // remove duplicate characters and sort
  2466. $chars = array_unique($chars);
  2467. usort($chars, array($this, 'hotpot_keypad_chars_sort'));
  2468. // create keypad buttons for each character
  2469. foreach ($chars as $char) {
  2470. $str .= '<button onclick="'."TypeChars('".$this->hotpot->source->js_value_safe($char, true)."');".'return false;">'.$char.'</button>';
  2471. }
  2472. }
  2473. return $str;
  2474. }
  2475. /**
  2476. * add_keypad_chars
  2477. *
  2478. * @param xxx $chars (passed by reference)
  2479. * @param xxx $text
  2480. */
  2481. function add_keypad_chars(&$chars, $text) {
  2482. if (preg_match_all('/&[^;]+;/', $text, $more_chars)) {
  2483. $chars = array_merge($chars, $more_chars[0]);
  2484. }
  2485. }
  2486. /**
  2487. * hotpot_keypad_chars_sort
  2488. *
  2489. * @param xxx $a_char
  2490. * @param xxx $b_char
  2491. * @return xxx
  2492. */
  2493. function hotpot_keypad_chars_sort($a_char, $b_char) {
  2494. $a_value = $this->hotpot_keypad_char_value($a_char);
  2495. $b_value = $this->hotpot_keypad_char_value($b_char);
  2496. if ($a_value < $b_value) {
  2497. return -1;
  2498. }
  2499. if ($a_value > $b_value) {
  2500. return 1;
  2501. }
  2502. // values are equal
  2503. return 0;
  2504. }
  2505. /**
  2506. * hotpot_keypad_char_value
  2507. *
  2508. * @param xxx $char
  2509. * @return xxx
  2510. */
  2511. function hotpot_keypad_char_value($char) {
  2512. $char = hotpot_textlib('entities_to_utf8', $char);
  2513. $ord = ord($char);
  2514. // lowercase letters (plain or accented)
  2515. if (($ord>=97 && $ord<=122) || ($ord>=224 && $ord<=255)) {
  2516. return ($ord-31) + ($ord/1000);
  2517. }
  2518. // subscripts and superscripts
  2519. switch ($ord) {
  2520. case 0x2070: return 48.1; // super 0 = ord('0') + 0.1
  2521. case 0x00B9: return 49.1; // super 1
  2522. case 0x00B2: return 50.1; // super 2
  2523. case 0x00B3: return 51.1; // super 3
  2524. case 0x2074: return 52.1; // super 4
  2525. case 0x2075: return 53.1; // super 5
  2526. case 0x2076: return 54.1; // super 6
  2527. case 0x2077: return 55.1; // super 7
  2528. case 0x2078: return 56.1; // super 8
  2529. case 0x2079: return 57.1; // super 9
  2530. case 0x207A: return 43.1; // super +
  2531. case 0x207B: return 45.1; // super -
  2532. case 0x207C: return 61.1; // super =
  2533. case 0x207D: return 40.1; // super (
  2534. case 0x207E: return 41.1; // super )
  2535. case 0x207F: return 110.1; // super n
  2536. case 0x2080: return 47.9; // sub 0 = ord('0') - 0.1
  2537. case 0x2081: return 48.9; // sub 1
  2538. case 0x2082: return 49.9; // sub 2
  2539. case 0x2083: return 50.9; // sub 3
  2540. case 0x2084: return 51.9; // sub 4
  2541. case 0x2085: return 52.9; // sub 5
  2542. case 0x2086: return 53.9; // sub 6
  2543. case 0x2087: return 54.9; // sub 7
  2544. case 0x2088: return 55.9; // sub 8
  2545. case 0x2089: return 56.9; // sub 9
  2546. case 0x208A: return 42.9; // sub +
  2547. case 0x208B: return 44.9; // sub -
  2548. case 0x208C: return 60.9; // sub =
  2549. case 0x208D: return 39.9; // sub (
  2550. case 0x208E: return 40.9; // sub )
  2551. case 0x208F: return 109.9; // sub n
  2552. }
  2553. return $ord;
  2554. }
  2555. // JCloze
  2556. /**
  2557. * expand_JSJCloze6
  2558. *
  2559. * @return xxx
  2560. */
  2561. function expand_JSJCloze6() {
  2562. return $this->expand_template('jcloze6.js_');
  2563. }
  2564. /**
  2565. * expand_ClozeBody
  2566. *
  2567. * @return xxx
  2568. */
  2569. function expand_ClozeBody() {
  2570. $str = '';
  2571. // get drop down list of words, if required
  2572. $dropdownlist = '';
  2573. if ($this->use_DropDownList()) {
  2574. $this->set_WordList();
  2575. foreach ($this->wordlist as $word) {
  2576. $dropdownlist .= '<option value="'.$word.'">'.$word.'</option>';
  2577. }
  2578. }
  2579. // cache clues flag and caption
  2580. $includeclues = $this->expand_Clues();
  2581. $cluecaption = $this->expand_ClueCaption();
  2582. // detect if cloze starts with gap
  2583. if (strpos($this->hotpot->source->filecontents, '<gap-fill><question-record>')) {
  2584. $startwithgap = true;
  2585. } else {
  2586. $startwithgap = false;
  2587. }
  2588. // initialize loop values
  2589. $q = 0;
  2590. $tags = 'data,gap-fill';
  2591. $question_record = "$tags,question-record";
  2592. // initialize loop values
  2593. $q = 0;
  2594. $tags = 'data,gap-fill';
  2595. $question_record = "$tags,question-record";
  2596. // loop through text and gaps
  2597. $looping = true;
  2598. while ($looping) {
  2599. $text = $this->hotpot->source->xml_value($tags, "[0]['#'][$q]");
  2600. $gap = '';
  2601. if (($question="[$q]['#']") && $this->hotpot->source->xml_value($question_record, $question)) {
  2602. $gap .= '<span class="GapSpan" id="GapSpan'.$q.'">';
  2603. if ($this->use_DropDownList()) {
  2604. $gap .= '<select id="Gap'.$q.'"><option value=""></option>'.$dropdownlist.'</select>';
  2605. } else {
  2606. // minimum gap size
  2607. if (! $gapsize = $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',minimum-gap-size')) {
  2608. $gapsize = 6;
  2609. }
  2610. // increase gap size to length of longest answer for this gap
  2611. $a = 0;
  2612. while (($answer=$question."['answer'][$a]['#']") && $this->hotpot->source->xml_value($question_record, $answer)) {
  2613. $answertext = $this->hotpot->source->xml_value($question_record, $answer."['text'][0]['#']");
  2614. $answertext = preg_replace('/&[#a-zA-Z0-9]+;/', 'x', $answertext);
  2615. $gapsize = max($gapsize, strlen($answertext));
  2616. $a++;
  2617. }
  2618. $gap .= '<input type="text" id="Gap'.$q.'" onfocus="TrackFocus('.$q.')" onblur="LeaveGap()" class="GapBox" size="'.$gapsize.'"></input>';
  2619. }
  2620. if ($includeclues) {
  2621. $clue = $this->hotpot->source->xml_value($question_record, $question."['clue'][0]['#']");
  2622. if (strlen($clue)) {
  2623. $gap .= '<button style="line-height: 1.0" class="FuncButton" onfocus="FuncBtnOver(this)" onmouseover="FuncBtnOver(this)" onblur="FuncBtnOut(this)" onmouseout="FuncBtnOut(this)" onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)" onclick="ShowClue('.$q.')">'.$cluecaption.'</button>';
  2624. }
  2625. }
  2626. $gap .= '</span>';
  2627. }
  2628. if (strlen($text) || strlen($gap)) {
  2629. if ($startwithgap) {
  2630. $str .= $gap.$text;
  2631. } else {
  2632. $str .= $text.$gap;
  2633. }
  2634. $q++;
  2635. } else {
  2636. // no text or gap, so force end of loop
  2637. $looping = false;
  2638. }
  2639. }
  2640. if ($q==0) {
  2641. // oops, no gaps found!
  2642. return $this->hotpot->source->xml_value($tags);
  2643. } else {
  2644. return $str;
  2645. }
  2646. }
  2647. /**
  2648. * expand_ItemArray
  2649. *
  2650. * @return xxx
  2651. */
  2652. function expand_ItemArray() {
  2653. // this method is overridden by JCloze and JQuiz output formats
  2654. }
  2655. /**
  2656. * expand_WordList
  2657. *
  2658. * @return xxx
  2659. */
  2660. function expand_WordList() {
  2661. $str = '';
  2662. if ($this->include_WordList()) {
  2663. $this->set_WordList();
  2664. $str = implode(' &#160;&#160; ', $this->wordlist);
  2665. }
  2666. return $str;
  2667. }
  2668. /**
  2669. * include_WordList
  2670. *
  2671. * @return xxx
  2672. */
  2673. function include_WordList() {
  2674. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-word-list');
  2675. }
  2676. /**
  2677. * use_DropDownList
  2678. *
  2679. * @return xxx
  2680. */
  2681. function use_DropDownList() {
  2682. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',use-drop-down-list');
  2683. }
  2684. /**
  2685. * set_WordList
  2686. */
  2687. function set_WordList() {
  2688. if (isset($this->wordlist)) {
  2689. // do nothing
  2690. } else {
  2691. $this->wordlist = array();
  2692. // is the wordlist required
  2693. if ($this->include_WordList() || $this->use_DropDownList()) {
  2694. $q = 0;
  2695. $tags = 'data,gap-fill,question-record';
  2696. while (($question="[$q]['#']") && $this->hotpot->source->xml_value($tags, $question)) {
  2697. $a = 0;
  2698. $aa = 0;
  2699. while (($answer=$question."['answer'][$a]['#']") && $this->hotpot->source->xml_value($tags, $answer)) {
  2700. $text = $this->hotpot->source->xml_value($tags, $answer."['text'][0]['#']");
  2701. $correct = $this->hotpot->source->xml_value_int($tags, $answer."['correct'][0]['#']");
  2702. if (strlen($text) && $correct) { // $correct is always true
  2703. $this->wordlist[] = $text;
  2704. $aa++;
  2705. }
  2706. $a++;
  2707. }
  2708. $q++;
  2709. }
  2710. $this->wordlist = array_unique($this->wordlist);
  2711. sort($this->wordlist);
  2712. }
  2713. }
  2714. }
  2715. // jcross
  2716. /**
  2717. * expand_JSJCross6
  2718. *
  2719. * @return xxx
  2720. */
  2721. function expand_JSJCross6() {
  2722. return $this->expand_template('jcross6.js_');
  2723. }
  2724. /**
  2725. * expand_CluesAcrossLabel
  2726. *
  2727. * @return xxx
  2728. */
  2729. function expand_CluesAcrossLabel() {
  2730. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',clues-across');
  2731. }
  2732. /**
  2733. * expand_CluesDownLabel
  2734. *
  2735. * @return xxx
  2736. */
  2737. function expand_CluesDownLabel() {
  2738. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',clues-down');
  2739. }
  2740. /**
  2741. * expand_EnterCaption
  2742. *
  2743. * @return xxx
  2744. */
  2745. function expand_EnterCaption() {
  2746. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',enter-caption');
  2747. }
  2748. /**
  2749. * expand_ShowHideClueList
  2750. *
  2751. * @return xxx
  2752. */
  2753. function expand_ShowHideClueList() {
  2754. $value = $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',include-clue-list');
  2755. return empty($value) ? ' style="display: none;"' : '';
  2756. }
  2757. /**
  2758. * expand_CluesDown
  2759. *
  2760. * @return xxx
  2761. */
  2762. function expand_CluesDown() {
  2763. return $this->expand_jcross_clues('D');
  2764. }
  2765. /**
  2766. * expand_CluesAcross
  2767. *
  2768. * @return xxx
  2769. */
  2770. function expand_CluesAcross() {
  2771. return $this->expand_jcross_clues('A');
  2772. }
  2773. /**
  2774. * expand_jcross_clues
  2775. *
  2776. * @param xxx $direction
  2777. * @return xxx
  2778. */
  2779. function expand_jcross_clues($direction) {
  2780. // $direction: A(cross) or D(own)
  2781. $row = null;
  2782. $r_max = 0;
  2783. $c_max = 0;
  2784. $this->get_jcross_grid($row, $r_max, $c_max);
  2785. $clue_i = 0; // clue index;
  2786. $str = '';
  2787. for ($r=0; $r<=$r_max; $r++) {
  2788. for ($c=0; $c<=$c_max; $c++) {
  2789. $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max);
  2790. $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max);
  2791. if ($aword || $dword) {
  2792. $clue_i++; // increment clue index
  2793. // get the definition for this word
  2794. $def = '';
  2795. $word = ($direction=='A') ? $aword : $dword;
  2796. $word = hotpot_textlib('utf8_to_entities', $word);
  2797. $i = 0;
  2798. $clues = 'data,crossword,clues,item';
  2799. while (($clue = "[$i]['#']") && $this->hotpot->source->xml_value($clues, $clue)) {
  2800. if ($word==$this->hotpot->source->xml_value($clues, $clue."['word'][0]['#']")) {
  2801. $def = $this->hotpot->source->xml_value($clues, $clue."['def'][0]['#']");
  2802. break;
  2803. }
  2804. $i++;
  2805. }
  2806. if ($def) {
  2807. $str .= '<tr><td class="ClueNum">'.$clue_i.'. </td><td id="Clue_'.$direction.'_'.$clue_i.'" class="Clue">'.$def.'</td></tr>';
  2808. }
  2809. }
  2810. }
  2811. }
  2812. return $str;
  2813. }
  2814. /**
  2815. * expand_LetterArray
  2816. *
  2817. * @return xxx
  2818. */
  2819. function expand_LetterArray() {
  2820. $row = null;
  2821. $r_max = 0;
  2822. $c_max = 0;
  2823. $this->get_jcross_grid($row, $r_max, $c_max);
  2824. $str = '';
  2825. for ($r=0; $r<=$r_max; $r++) {
  2826. $str .= "L[$r] = new Array(";
  2827. for ($c=0; $c<=$c_max; $c++) {
  2828. $str .= ($c>0 ? ',' : '')."'".$this->hotpot->source->js_value_safe($row[$r]['cell'][$c]['#'], true)."'";
  2829. }
  2830. $str .= ");\n";
  2831. }
  2832. return $str;
  2833. }
  2834. /**
  2835. * expand_GuessArray
  2836. *
  2837. * @return xxx
  2838. */
  2839. function expand_GuessArray() {
  2840. $row = null;
  2841. $r_max = 0;
  2842. $c_max = 0;
  2843. $this->get_jcross_grid($row, $r_max, $c_max);
  2844. $str = '';
  2845. for ($r=0; $r<=$r_max; $r++) {
  2846. $str .= "G[$r] = new Array('".str_repeat("','", $c_max)."');\n";
  2847. }
  2848. return $str;
  2849. }
  2850. /**
  2851. * expand_ClueNumArray
  2852. *
  2853. * @return xxx
  2854. */
  2855. function expand_ClueNumArray() {
  2856. $row = null;
  2857. $r_max = 0;
  2858. $c_max = 0;
  2859. $this->get_jcross_grid($row, $r_max, $c_max);
  2860. $i = 0; // clue index
  2861. $str = '';
  2862. for ($r=0; $r<=$r_max; $r++) {
  2863. $str .= "CL[$r] = new Array(";
  2864. for ($c=0; $c<=$c_max; $c++) {
  2865. if ($c>0) {
  2866. $str .= ',';
  2867. }
  2868. $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max);
  2869. $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max);
  2870. if (empty($aword) && empty($dword)) {
  2871. $str .= 0;
  2872. } else {
  2873. $i++; // increment the clue index
  2874. $str .= $i;
  2875. }
  2876. }
  2877. $str .= ");\n";
  2878. }
  2879. return $str;
  2880. }
  2881. /**
  2882. * expand_GridBody
  2883. *
  2884. * @return xxx
  2885. */
  2886. function expand_GridBody() {
  2887. $row = null;
  2888. $r_max = 0;
  2889. $c_max = 0;
  2890. $this->get_jcross_grid($row, $r_max, $c_max);
  2891. $i = 0; // clue index;
  2892. $str = '';
  2893. for ($r=0; $r<=$r_max; $r++) {
  2894. $str .= '<tr id="Row_'.$r.'">';
  2895. for ($c=0; $c<=$c_max; $c++) {
  2896. if (empty($row[$r]['cell'][$c]['#'])) {
  2897. $str .= '<td class="BlankCell">&nbsp;</td>';
  2898. } else {
  2899. $aword = $this->get_jcross_aword($row, $r, $r_max, $c, $c_max);
  2900. $dword = $this->get_jcross_dword($row, $r, $r_max, $c, $c_max);
  2901. if (empty($aword) && empty($dword)) {
  2902. $str .= '<td class="LetterOnlyCell"><span id="L_'.$r.'_'.$c.'">&nbsp;</span></td>';
  2903. } else {
  2904. $i++; // increment clue index
  2905. $str .= '<td class="NumLetterCell"><a href="javascript:void(0);" class="GridNum" onclick="ShowClue('.$i.','.$r.','.$c.')">'.$i.'</a><span class="NumLetterCellText" id="L_'.$r.'_'.$c.'" onclick="ShowClue('.$i.','.$r.','.$c.')">&nbsp;&nbsp;&nbsp;</span></td>';
  2906. }
  2907. }
  2908. }
  2909. $str .= '</tr>';
  2910. }
  2911. return $str;
  2912. }
  2913. /**
  2914. * get_jcross_grid
  2915. *
  2916. * @param xxx $rows (passed by reference)
  2917. * @param xxx $r_max (passed by reference)
  2918. * @param xxx $c_max (passed by reference)
  2919. */
  2920. function get_jcross_grid(&$rows, &$r_max, &$c_max) {
  2921. $r_max = 0;
  2922. $c_max = 0;
  2923. $r = 0;
  2924. $tags = 'data,crossword,grid,row';
  2925. while (($moretags="[$r]['#']") && $row = $this->hotpot->source->xml_value($tags, $moretags)) {
  2926. $rows[$r] = $row;
  2927. for ($c=0; $c<count($row['cell']); $c++) {
  2928. if (! empty($row['cell'][$c]['#'])) {
  2929. $r_max = max($r, $r_max);
  2930. $c_max = max($c, $c_max);
  2931. }
  2932. }
  2933. $r++;
  2934. }
  2935. }
  2936. /**
  2937. * get_jcross_dword
  2938. *
  2939. * @param xxx $row (passed by reference)
  2940. * @param xxx $r
  2941. * @param xxx $r_max
  2942. * @param xxx $c
  2943. * @param xxx $c_max
  2944. * @return xxx
  2945. */
  2946. function get_jcross_dword(&$row, $r, $r_max, $c, $c_max) {
  2947. $str = '';
  2948. if (($r==0 || empty($row[$r-1]['cell'][$c]['#'])) && $r<$r_max && !empty($row[$r+1]['cell'][$c]['#'])) {
  2949. $str = $this->get_jcross_word($row, $r, $r_max, $c, $c_max, true);
  2950. }
  2951. return $str;
  2952. }
  2953. /**
  2954. * get_jcross_aword
  2955. *
  2956. * @param xxx $row (passed by reference)
  2957. * @param xxx $r
  2958. * @param xxx $r_max
  2959. * @param xxx $c
  2960. * @param xxx $c_max
  2961. * @return xxx
  2962. */
  2963. function get_jcross_aword(&$row, $r, $r_max, $c, $c_max) {
  2964. $str = '';
  2965. if (($c==0 || empty($row[$r]['cell'][$c-1]['#'])) && $c<$c_max && !empty($row[$r]['cell'][$c+1]['#'])) {
  2966. $str = $this->get_jcross_word($row, $r, $r_max, $c, $c_max, false);
  2967. }
  2968. return $str;
  2969. }
  2970. /**
  2971. * get_jcross_word
  2972. *
  2973. * @param xxx $row (passed by reference)
  2974. * @param xxx $r
  2975. * @param xxx $r_max
  2976. * @param xxx $c
  2977. * @param xxx $c_max
  2978. * @param xxx $go_down (optional, default=false)
  2979. * @return xxx
  2980. */
  2981. function get_jcross_word(&$row, $r, $r_max, $c, $c_max, $go_down=false) {
  2982. $str = '';
  2983. while ($r<=$r_max && $c<=$c_max && !empty($row[$r]['cell'][$c]['#'])) {
  2984. $str .= $row[$r]['cell'][$c]['#'];
  2985. if ($go_down) {
  2986. $r++;
  2987. } else {
  2988. $c++;
  2989. }
  2990. }
  2991. return $str;
  2992. }
  2993. // jmatch
  2994. /**
  2995. * expand_JSJMatch6
  2996. *
  2997. * @return xxx
  2998. */
  2999. function expand_JSJMatch6() {
  3000. return $this->expand_template('jmatch6.js_');
  3001. }
  3002. /**
  3003. * expand_JSDJMatch6
  3004. *
  3005. * @return xxx
  3006. */
  3007. function expand_JSDJMatch6() {
  3008. return $this->expand_template('djmatch6.js_');
  3009. }
  3010. /**
  3011. * expand_JSFJMatch6
  3012. *
  3013. * @return xxx
  3014. */
  3015. function expand_JSFJMatch6() {
  3016. return $this->expand_template('fjmatch6.js_');
  3017. }
  3018. /**
  3019. * expand_ShuffleQs
  3020. *
  3021. * @return xxx
  3022. */
  3023. function expand_ShuffleQs() {
  3024. return $this->hotpot->source->xml_value_bool($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',shuffle-questions');
  3025. }
  3026. /**
  3027. * expand_QsToShow
  3028. *
  3029. * @return xxx
  3030. */
  3031. function expand_QsToShow() {
  3032. $i = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',show-limited-questions');
  3033. if ($i) {
  3034. $i = $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',questions-to-show');
  3035. }
  3036. if (empty($i)) {
  3037. $i = 0;
  3038. if ($this->hotpot->source->hbs_quiztype=='jmatch') {
  3039. $tags = 'data,matching-exercise,pair';
  3040. } else if ($this->hotpot->source->hbs_quiztype=='jquiz') {
  3041. $tags = 'data,questions,question-record';
  3042. } else {
  3043. $tags = '';
  3044. }
  3045. if ($tags) {
  3046. while (($moretags="[$i]['#']") && $value = $this->hotpot->source->xml_value($tags, $moretags)) {
  3047. $i++;
  3048. }
  3049. }
  3050. }
  3051. return $i;
  3052. }
  3053. /**
  3054. * expand_MatchDivItems
  3055. *
  3056. * @return xxx
  3057. */
  3058. function expand_MatchDivItems() {
  3059. $this->set_jmatch_items();
  3060. $l_keys = $this->shuffle_jmatch_items($this->l_items);
  3061. $r_keys = $this->shuffle_jmatch_items($this->r_items);
  3062. $options = '<option value="x">'.$this->hotpot->source->xml_value('data,matching-exercise,default-right-item').'</option>'."\n";
  3063. foreach ($r_keys as $key) {
  3064. // only add the first occurrence of the text (i.e. skip duplicates)
  3065. if ($key==$this->r_items[$key]['key']) {
  3066. $options .= '<option value="'.$key.'">'.$this->r_items[$key]['text'].'</option>'."\n";
  3067. // Note: if the 'text' contains an image, it could be added as the background image of the option
  3068. // http://www.small-software-utilities.com/design/91/html-select-with-background-image-or-icon-next-to-text/
  3069. // ... or of an optgroup ...
  3070. // http://ask.metafilter.com/16153/Images-in-HTML-select-form-elements
  3071. }
  3072. }
  3073. $str = '';
  3074. foreach ($l_keys as $key) {
  3075. $str .= '<tr><td class="l_item">'.$this->l_items[$key]['text'].'</td>';
  3076. $str .= '<td class="r_item">';
  3077. if ($this->r_items[$key]['fixed']) {
  3078. $str .= $this->r_items[$key]['text'];
  3079. } else {
  3080. $str .= '<select id="s'.$this->r_items[$key]['key'].'_'.$key.'">'.$options.'</select>';
  3081. }
  3082. $str .= '</td><td></td></tr>';
  3083. }
  3084. return $str;
  3085. }
  3086. /**
  3087. * expand_FixedArray
  3088. *
  3089. * @return xxx
  3090. */
  3091. function expand_FixedArray() {
  3092. $this->set_jmatch_items();
  3093. $str = '';
  3094. foreach ($this->l_items as $i=>$item) {
  3095. $str .= "F[$i] = new Array();\n";
  3096. $str .= "F[$i][0] = '".$this->hotpot->source->js_value_safe($item['text'], true)."';\n";
  3097. $str .= "F[$i][1] = ".($item['key']+1).";\n";
  3098. }
  3099. return $str;
  3100. }
  3101. /**
  3102. * expand_DragArray
  3103. *
  3104. * @return xxx
  3105. */
  3106. function expand_DragArray() {
  3107. $this->set_jmatch_items();
  3108. $str = '';
  3109. foreach ($this->r_items as $i=>$item) {
  3110. $str .= "D[$i] = new Array();\n";
  3111. $str .= "D[$i][0] = '".$this->hotpot->source->js_value_safe($item['text'], true)."';\n";
  3112. $str .= "D[$i][1] = ".($item['key']+1).";\n";
  3113. $str .= "D[$i][2] = ".$item['fixed'].";\n";
  3114. }
  3115. return $str;
  3116. }
  3117. /**
  3118. * expand_Slide
  3119. *
  3120. * @return xxx
  3121. */
  3122. function expand_Slide() {
  3123. // return true if any JMatch drag-and-drop RH items are fixed and therefore need to slide to the LHS
  3124. $this->set_jmatch_items();
  3125. foreach ($this->r_items as $i=>$item) {
  3126. if ($item['fixed']) {
  3127. return true;
  3128. }
  3129. }
  3130. return false;
  3131. }
  3132. /**
  3133. * set_jmatch_items
  3134. */
  3135. function set_jmatch_items() {
  3136. if (count($this->l_items)) {
  3137. return;
  3138. }
  3139. $tags = 'data,matching-exercise,pair';
  3140. $i = 0;
  3141. while (($item = "[$i]['#']") && $this->hotpot->source->xml_value($tags, $item)) {
  3142. $l_item = $item."['left-item'][0]['#']";
  3143. $l_text = $this->hotpot->source->xml_value($tags, $l_item."['text'][0]['#']");
  3144. $l_fixed = $this->hotpot->source->xml_value_int($tags, $l_item."['fixed'][0]['#']");
  3145. $r_item = $item."['right-item'][0]['#']";
  3146. $r_text = $this->hotpot->source->xml_value($tags, $r_item."['text'][0]['#']");
  3147. $r_fixed = $this->hotpot->source->xml_value_int($tags, $r_item."['fixed'][0]['#']");
  3148. // typically all right-hand items are unique, but there may be duplicates
  3149. // in which case we want the key of the first item containing this text
  3150. for ($key=0; $key<$i; $key++) {
  3151. if (isset($this->r_items[$key]) && $this->r_items[$key]['text']==$r_text) {
  3152. break;
  3153. }
  3154. }
  3155. if (strlen($r_text)) {
  3156. $addright = true;
  3157. } else {
  3158. $addright = false;
  3159. }
  3160. if (strlen($l_text)) {
  3161. $this->l_items[] = array('key' => $key, 'text' => $l_text, 'fixed' => $l_fixed);
  3162. $addright = true; // force right item to be added
  3163. }
  3164. if ($addright) {
  3165. $this->r_items[] = array('key' => $key, 'text' => $r_text, 'fixed' => $r_fixed);
  3166. }
  3167. $i++;
  3168. }
  3169. }
  3170. /**
  3171. * shuffle_jmatch_items
  3172. *
  3173. * @param xxx $items (passed by reference)
  3174. * @return xxx
  3175. */
  3176. function shuffle_jmatch_items(&$items) {
  3177. // get moveable items
  3178. $moveable_keys = array();
  3179. for ($i=0; $i<count($items); $i++) {
  3180. if(! $items[$i]['fixed']) {
  3181. $moveable_keys[] = $i;
  3182. }
  3183. }
  3184. // shuffle moveable items
  3185. $this->seed_random_number_generator();
  3186. shuffle($moveable_keys);
  3187. $keys = array();
  3188. for ($i=0, $ii=0; $i<count($items); $i++) {
  3189. if($items[$i]['fixed']) {
  3190. // fixed items stay where they are
  3191. $keys[] = $i;
  3192. } else {
  3193. // moveable items are inserted in a shuffled order
  3194. $keys[] = $moveable_keys[$ii++];
  3195. }
  3196. }
  3197. return $keys;
  3198. }
  3199. /**
  3200. * seed_random_number_generator
  3201. */
  3202. function seed_random_number_generator() {
  3203. static $seeded = false;
  3204. if (! $seeded) {
  3205. srand((double) microtime() * 1000000);
  3206. $seeded = true;
  3207. }
  3208. }
  3209. // JMatch flash card
  3210. /**
  3211. * expand_TRows
  3212. *
  3213. * @return xxx
  3214. */
  3215. function expand_TRows() {
  3216. $str = '';
  3217. $this->set_jmatch_items();
  3218. $i_max = count($this->l_items);
  3219. for ($i=0; $i<$i_max; $i++) {
  3220. $str .= '<tr class="FlashcardRow" id="I_'.$i.'"><td id="L_'.$i.'">'.$this->l_items[$i]['text'].'</td><td id="R_'.$i.'">'.$this->r_items[$i]['text'].'</td></tr>'."\n";
  3221. }
  3222. return $str;
  3223. }
  3224. // jmix
  3225. /**
  3226. * expand_JSJMix6
  3227. *
  3228. * @return xxx
  3229. */
  3230. function expand_JSJMix6() {
  3231. return $this->expand_template('jmix6.js_');
  3232. }
  3233. /**
  3234. * expand_JSFJMix6
  3235. *
  3236. * @return xxx
  3237. */
  3238. function expand_JSFJMix6() {
  3239. return $this->expand_template('fjmix6.js_');
  3240. }
  3241. /**
  3242. * expand_JSDJMix6
  3243. *
  3244. * @return xxx
  3245. */
  3246. function expand_JSDJMix6() {
  3247. return $this->expand_template('djmix6.js_');
  3248. }
  3249. /**
  3250. * expand_Punctuation
  3251. *
  3252. * @return xxx
  3253. */
  3254. function expand_Punctuation() {
  3255. $chars = array();
  3256. // RegExp pattern to match HTML entity
  3257. $pattern = '/&#x([0-9A-F]+);/i';
  3258. // entities for all punctutation except '&#;' (because they are used in html entities)
  3259. $entities = $this->jmix_encode_punctuation('!"$%'."'".'()*+,-./:<=>?@[\]^_`{|}~');
  3260. // xml tags for JMix segments and alternate answers
  3261. $punctuation_tags = array(
  3262. 'data,jumbled-order-exercise,main-order,segment',
  3263. 'data,jumbled-order-exercise,alternate'
  3264. );
  3265. foreach ($punctuation_tags as $tags) {
  3266. // get next segment (or alternate answer)
  3267. $i = 0;
  3268. while ($value = $this->hotpot->source->xml_value($tags, "[$i]['#']")) {
  3269. // convert low-ascii punctuation to entities
  3270. $value = strtr($value, $entities);
  3271. // extract all hex HTML entities
  3272. if (preg_match_all($pattern, $value, $matches)) {
  3273. // loop through hex entities
  3274. $m_max = count($matches[0]);
  3275. for ($m=0; $m<$m_max; $m++) {
  3276. // convert to hex string to number
  3277. //eval('$hex=0x'.$matches[1][$m].';');
  3278. $hex = hexdec($matches[1][$m]);
  3279. // is this a punctuation character?
  3280. if (
  3281. ($hex>=0x0020 && $hex<=0x00BF) || // ascii punctuation
  3282. ($hex>=0x2000 && $hex<=0x206F) || // general punctuation
  3283. ($hex>=0x3000 && $hex<=0x303F) || // CJK punctuation
  3284. ($hex>=0xFE30 && $hex<=0xFE4F) || // CJK compatability
  3285. ($hex>=0xFE50 && $hex<=0xFE6F) || // small form variants
  3286. ($hex>=0xFF00 && $hex<=0xFF40) || // halfwidth and fullwidth forms (1)
  3287. ($hex>=0xFF5B && $hex<=0xFF65) || // halfwidth and fullwidth forms (2)
  3288. ($hex>=0xFFE0 && $hex<=0xFFEE) // halfwidth and fullwidth forms (3)
  3289. ) {
  3290. // add this character
  3291. $chars[] = $matches[0][$m];
  3292. }
  3293. }
  3294. } // end if HTML entity
  3295. $i++;
  3296. } // end while next segment (or alternate answer)
  3297. } // end foreach $tags
  3298. $chars = implode('', array_unique($chars));
  3299. return $this->hotpot->source->js_value_safe($chars, true);
  3300. }
  3301. /**
  3302. * expand_OpenPunctuation
  3303. *
  3304. * @return xxx
  3305. */
  3306. function expand_OpenPunctuation() {
  3307. $chars = array();
  3308. // unicode punctuation designations (pi="initial quote", ps="open")
  3309. // http://www.sql-und-xml.de/unicode-database/pi.html
  3310. // http://www.sql-und-xml.de/unicode-database/ps.html
  3311. $pi = '0022|0027|00AB|2018|201B|201C|201F|2039';
  3312. $ps = '0028|005B|007B|0F3A|0F3C|169B|201A|201E|2045|207D|208D|2329|23B4|2768|276A|276C|276E|2770|2772|2774|27E6|27E8|27EA|2983|2985|2987|2989|298B|298D|298F|2991|2993|2995|2997|29D8|29DA|29FC|3008|300A|300C|300E|3010|3014|3016|3018|301A|301D|FD3E|FE35|FE37|FE39|FE3B|FE3D|FE3F|FE41|FE43|FE47|FE59|FE5B|FE5D|FF08|FF3B|FF5B|FF5F|FF62';
  3313. $pattern = '/(&#x('.$pi.'|'.$ps.');)/i';
  3314. // HMTL entities of opening punctuation
  3315. $entities = $this->jmix_encode_punctuation('"'."'".'(<[{');
  3316. // xml tags for JMix segments and alternate answers
  3317. $punctuation_tags = array(
  3318. 'data,jumbled-order-exercise,main-order,segment',
  3319. 'data,jumbled-order-exercise,alternate'
  3320. );
  3321. foreach ($punctuation_tags as $tags) {
  3322. $i = 0;
  3323. while ($value = $this->hotpot->source->xml_value($tags, "[$i]['#']")) {
  3324. $value = strtr($value, $entities);
  3325. if (preg_match_all($pattern, $value, $matches)) {
  3326. $chars = array_merge($chars, $matches[0]);
  3327. }
  3328. $i++;
  3329. }
  3330. }
  3331. $chars = implode('', array_unique($chars));
  3332. return $this->hotpot->source->js_value_safe($chars, true);
  3333. }
  3334. /**
  3335. * jmix_encode_punctuation
  3336. *
  3337. * @param xxx $str
  3338. * @return xxx
  3339. */
  3340. function jmix_encode_punctuation($str) {
  3341. $entities = array();
  3342. $i_max = strlen($str);
  3343. for ($i=0; $i<$i_max; $i++) {
  3344. $entities[$str{$i}] = '&#x'.sprintf('%04X', ord($str{$i})).';';
  3345. }
  3346. return $entities;
  3347. }
  3348. /*
  3349. * expand_ForceLowercase
  3350. *
  3351. * Should we force the first letter of the first word to be lowercase?
  3352. * (all other letters are assumed to have the correct case)
  3353. *
  3354. * When generating html files with standard JMix program, the user is prompted:
  3355. * Should the word Xxxxx begin with a capital letter
  3356. * even when it isn't at the beginning of a sentence?
  3357. *
  3358. * The "force-lowercase" xml tag implements a similar functionality
  3359. * This tag does not exist in standard Hot Potatoes XML files,
  3360. * but it can be added manually, for example to a HP config file
  3361. *
  3362. * @return xxx
  3363. */
  3364. function expand_ForceLowercase() {
  3365. $tag = $this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',force-lowercase';
  3366. return $this->hotpot->source->xml_value_int($tag);
  3367. }
  3368. /**
  3369. * expand_SegmentArray
  3370. *
  3371. * @return xxx
  3372. */
  3373. function expand_SegmentArray($more_values=array()) {
  3374. $segments = array();
  3375. $values = array();
  3376. // XML tags to the start of a segment
  3377. $tags = 'data,jumbled-order-exercise,main-order,segment';
  3378. $i = 0;
  3379. while ($value = $this->hotpot->source->xml_value($tags, "[$i]['#']")) {
  3380. if ($i==0 && $this->expand_ForceLowercase()) {
  3381. $value = strtolower(substr($value, 0, 1)).substr($value, 1);
  3382. }
  3383. $key = array_search($value, $values);
  3384. if (is_numeric($key)) {
  3385. $segments[] = $key;
  3386. } else {
  3387. $segments[] = $i;
  3388. $values[$i] = $value;
  3389. }
  3390. $i++;
  3391. }
  3392. foreach ($more_values as $value) {
  3393. $key = array_search($value, $values);
  3394. if (is_numeric($key)) {
  3395. $segments[] = $key;
  3396. } else {
  3397. $segments[] = $i;
  3398. $values[$i] = $value;
  3399. }
  3400. $i++;
  3401. }
  3402. $this->seed_random_number_generator();
  3403. $keys = array_keys($segments);
  3404. shuffle($keys);
  3405. $str = '';
  3406. for ($i=0; $i<count($keys); $i++) {
  3407. $key = $segments[$keys[$i]];
  3408. $str .= "Segments[$i] = new Array();\n";
  3409. $str .= "Segments[$i][0] = '".$this->hotpot->source->js_value_safe($values[$key], true)."';\n";
  3410. $str .= "Segments[$i][1] = ".($key+1).";\n";
  3411. $str .= "Segments[$i][2] = 0;\n";
  3412. }
  3413. return $str;
  3414. }
  3415. /**
  3416. * expand_AnswerArray
  3417. *
  3418. * @return xxx
  3419. */
  3420. function expand_AnswerArray() {
  3421. $segments = array();
  3422. $values = array();
  3423. $escapedvalues = array();
  3424. // XML tags to the start of a segment
  3425. $tags = 'data,jumbled-order-exercise,main-order,segment';
  3426. $i = 0;
  3427. while ($value = $this->hotpot->source->xml_value($tags, "[$i]['#']")) {
  3428. if ($i==0 && $this->expand_ForceLowercase()) {
  3429. $value = strtolower(substr($value, 0, 1)).substr($value, 1);
  3430. }
  3431. $key = array_search($value, $values);
  3432. if (is_numeric($key)) {
  3433. $segments[] = $key+1;
  3434. } else {
  3435. $segments[] = $i+1;
  3436. $values[$i] = $value;
  3437. $escapedvalues[] = preg_quote($value, '/');
  3438. }
  3439. $i++;
  3440. }
  3441. // start the answers array
  3442. $a = 0;
  3443. $str = 'Answers['.($a++).'] = new Array('.implode(',', $segments).");\n";
  3444. // pattern to match the next part of an alternate answer
  3445. $pattern = '/^('.implode('|', $escapedvalues).')\s*/i';
  3446. // XML tags to the start of an alternate answer
  3447. $tags = 'data,jumbled-order-exercise,alternate';
  3448. $i = 0;
  3449. while ($value = $this->hotpot->source->xml_value($tags, "[$i]['#']")) {
  3450. $segments = array();
  3451. while (strlen($value) && preg_match($pattern, $value, $matches)) {
  3452. $key = array_search($matches[1], $values);
  3453. if (is_numeric($key)) {
  3454. $segments[] = $key+1;
  3455. $value = substr($value, strlen($matches[0]));
  3456. } else {
  3457. // invalid alternate sequence - shouldn't happen !!
  3458. $segments = array();
  3459. break;
  3460. }
  3461. }
  3462. if (count($segments)) {
  3463. $str .= 'Answers['.($a++).'] = new Array('.implode(',', $segments).");\n";
  3464. }
  3465. $i++;
  3466. }
  3467. return $str;
  3468. }
  3469. /**
  3470. * expand_RemainingWords
  3471. *
  3472. * @return xxx
  3473. */
  3474. function expand_RemainingWords() {
  3475. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',remaining-words');
  3476. }
  3477. /**
  3478. * expand_DropTotal
  3479. *
  3480. * @return xxx
  3481. */
  3482. function expand_DropTotal() {
  3483. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,global,drop-total');
  3484. }
  3485. // JQuiz
  3486. /**
  3487. * expand_JSJQuiz6
  3488. *
  3489. * @return xxx
  3490. */
  3491. function expand_JSJQuiz6() {
  3492. return $this->expand_template('jquiz6.js_');
  3493. }
  3494. /**
  3495. * expand_QuestionOutput
  3496. *
  3497. * @return xxx
  3498. */
  3499. function expand_QuestionOutput() {
  3500. // start question list
  3501. $str = '<ol class="QuizQuestions" id="Questions">'."\n";
  3502. $q = 0;
  3503. $tags = 'data,questions,question-record';
  3504. while (($question="[$q]['#']") && $this->hotpot->source->xml_value($tags, $question) && ($answers = $question."['answers'][0]['#']") && $this->hotpot->source->xml_value($tags, $answers)) {
  3505. // get question
  3506. $question_text = $this->hotpot->source->xml_value($tags, $question."['question'][0]['#']");
  3507. $question_type = $this->hotpot->source->xml_value_int($tags, $question."['question-type'][0]['#']");
  3508. switch ($question_type) {
  3509. case 1: // MULTICHOICE:
  3510. $textbox = false;
  3511. $liststart = '<ol class="MCAnswers">'."\n";
  3512. break;
  3513. case 2: // SHORTANSWER:
  3514. $textbox = true;
  3515. $liststart = '';
  3516. break;
  3517. case 3: // HYBRID:
  3518. $textbox = true;
  3519. $liststart = '<ol class="MCAnswers" id="Q_'.$q.'_Hybrid_MC" style="display: none;">'."\n";
  3520. break;
  3521. case 4: // MULTISELECT:
  3522. $textbox = false;
  3523. $liststart = '<ol class="MSelAnswers">'."\n";
  3524. break;
  3525. default:
  3526. continue; // unknown question type
  3527. }
  3528. $first_answer_tags = $question."['answers'][0]['#']['answer'][0]['#']['text'][0]['#']";
  3529. $first_answer_text = $this->hotpot->source->xml_value($tags, $first_answer_tags, '', false);
  3530. // check we have a question (or at least one answer)
  3531. if (strlen($question_text) || strlen($first_answer_text)) {
  3532. // start question
  3533. $str .= '<li class="QuizQuestion" id="Q_'.$q.'" style="display: none;">';
  3534. $str .= '<p class="QuestionText">'.$question_text.'</p>';
  3535. if ($textbox) {
  3536. // get prefix, suffix and maximum size of ShortAnswer box (default = 9)
  3537. list($prefix, $suffix, $size) = $this->expand_jquiz_textbox_details($tags, $answers, $q);
  3538. $str .= '<div class="ShortAnswer" id="Q_'.$q.'_SA"><form method="post" action="" onsubmit="return false;"><div>';
  3539. $str .= $prefix;
  3540. if ($size<=25) {
  3541. // text box
  3542. $str .= '<input type="text" id="Q_'.$q.'_Guess" onfocus="TrackFocus('."'".'Q_'.$q.'_Guess'."'".')" onblur="LeaveGap()" class="ShortAnswerBox" size="'.$size.'"></input>';
  3543. } else {
  3544. // textarea (29 cols wide)
  3545. $str .= '<textarea id="Q_'.$q.'_Guess" onfocus="TrackFocus('."'".'Q_'.$q.'_Guess'."'".')" onblur="LeaveGap()" class="ShortAnswerBox" cols="29" rows="'.ceil($size/25).'"></textarea>';
  3546. }
  3547. $str .= $suffix;
  3548. $str .= '<br /><br />';
  3549. $caption = $this->expand_CheckCaption();
  3550. $str .= $this->expand_jquiz_button($caption, "CheckShortAnswer($q)");
  3551. if ($this->expand_Hint()) {
  3552. $caption = $this->expand_HintCaption();
  3553. $str .= $this->expand_jquiz_button($caption, "ShowHint($q)");
  3554. }
  3555. if ($this->expand_ShowAnswer()) {
  3556. $caption = $this->expand_ShowAnswerCaption();
  3557. $str .= $this->expand_jquiz_button($caption, "ShowAnswers($q)");
  3558. }
  3559. $str .= '</div></form></div>';
  3560. }
  3561. if ($liststart) {
  3562. $str .= $liststart;
  3563. $a = 0;
  3564. $aa = 0;
  3565. while (($answer = $answers."['answer'][$a]['#']") && $this->hotpot->source->xml_value($tags, $answer)) {
  3566. $text = $this->hotpot->source->xml_value($tags, $answer."['text'][0]['#']");
  3567. if (strlen($text)) {
  3568. if ($question_type==1 || $question_type==3) {
  3569. // MULTICHOICE or HYBRID: button
  3570. if ($this->hotpot->source->xml_value_int($tags, $answer."['include-in-mc-options'][0]['#']")) {
  3571. $str .= '<li id="Q_'.$q.'_'.$aa.'"><button class="FuncButton" onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)" id="Q_'.$q.'_'.$aa.'_Btn" onclick="CheckMCAnswer('.$q.','.$aa.',this)">&nbsp;&nbsp;?&nbsp;&nbsp;</button>&nbsp;&nbsp;'.$text.'</li>'."\n";
  3572. }
  3573. } else if ($question_type==4) {
  3574. // MULTISELECT: checkbox
  3575. $str .= '<li id="Q_'.$q.'_'.$aa.'"><form method="post" action="" onsubmit="return false;"><div><input type="checkbox" id="Q_'.$q.'_'.$aa.'_Chk" class="MSelCheckbox" />'.$text.'</div></form></li>'."\n";
  3576. }
  3577. $aa++;
  3578. }
  3579. $a++;
  3580. }
  3581. // finish answer list
  3582. $str .= '</ol>';
  3583. if ($question_type==4) {
  3584. // MULTISELECT: check button
  3585. $caption = $this->expand_CheckCaption();
  3586. $str .= $this->expand_jquiz_button($caption, "CheckMultiSelAnswer($q)");
  3587. }
  3588. }
  3589. // finish question
  3590. $str .= "</li>\n";
  3591. }
  3592. $q++;
  3593. } // end while $question
  3594. // finish question list and finish
  3595. return $str."</ol>\n";
  3596. }
  3597. /**
  3598. * expand_jquiz_textbox_details
  3599. *
  3600. * @param xxx $tags
  3601. * @param xxx $answers
  3602. * @param xxx $q
  3603. * @param xxx $defaultsize (optional, default=9)
  3604. * @return xxx
  3605. */
  3606. function expand_jquiz_textbox_details($tags, $answers, $q, $defaultsize=9) {
  3607. $prefix = '';
  3608. $suffix = '';
  3609. $size = $defaultsize;
  3610. $a = 0;
  3611. while (($answer = $answers."['answer'][$a]['#']") && $this->hotpot->source->xml_value($tags, $answer)) {
  3612. $text = $this->hotpot->source->xml_value($tags, $answer."['text'][0]['#']", '', false);
  3613. $text = preg_replace('/&[#a-zA-Z0-9]+;/', 'x', $text);
  3614. $size = max($size, strlen($text));
  3615. $a++;
  3616. }
  3617. return array($prefix, $suffix, $size);
  3618. }
  3619. /**
  3620. * expand_jquiz_button
  3621. *
  3622. * @param xxx $caption
  3623. * @param xxx $onclick
  3624. * @return xxx
  3625. */
  3626. function expand_jquiz_button($caption, $onclick) {
  3627. return '<button class="FuncButton" onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)" onclick="'.$onclick.'">'.$caption.'</button>';
  3628. }
  3629. /**
  3630. * expand_MultiChoice
  3631. *
  3632. * @return xxx
  3633. */
  3634. function expand_MultiChoice() {
  3635. return $this->jquiz_has_question_type(1);
  3636. }
  3637. /**
  3638. * expand_ShortAnswer
  3639. *
  3640. * @return xxx
  3641. */
  3642. function expand_ShortAnswer() {
  3643. return $this->jquiz_has_question_type(2);
  3644. }
  3645. /**
  3646. * expand_MultiSelect
  3647. *
  3648. * @return xxx
  3649. */
  3650. function expand_MultiSelect() {
  3651. return $this->jquiz_has_question_type(4);
  3652. }
  3653. /**
  3654. * jquiz_has_question_type
  3655. *
  3656. * @param xxx $type
  3657. * @return xxx
  3658. */
  3659. function jquiz_has_question_type($type) {
  3660. // does this JQuiz have any questions of the given $type?
  3661. $q = 0;
  3662. $tags = 'data,questions,question-record';
  3663. while (($question = "[$q]['#']") && $this->hotpot->source->xml_value($tags, $question)) {
  3664. $question_type = $this->hotpot->source->xml_value($tags, $question."['question-type'][0]['#']");
  3665. if ($question_type==$type || ($question_type==3 && ($type==1 || $type==2))) {
  3666. // 1=MULTICHOICE 2=SHORTANSWER 3=HYBRID
  3667. return true;
  3668. }
  3669. $q++;
  3670. }
  3671. return false;
  3672. }
  3673. /**
  3674. * expand_CompletedSoFar
  3675. *
  3676. * @return xxx
  3677. */
  3678. function expand_CompletedSoFar() {
  3679. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,completed-so-far');
  3680. }
  3681. /**
  3682. * expand_ContinuousScoring
  3683. *
  3684. * @return xxx
  3685. */
  3686. function expand_ContinuousScoring() {
  3687. return $this->hotpot->source->xml_value_bool($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',continuous-scoring');
  3688. }
  3689. /**
  3690. * expand_CorrectFirstTime
  3691. *
  3692. * @return xxx
  3693. */
  3694. function expand_CorrectFirstTime() {
  3695. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,correct-first-time');
  3696. }
  3697. /**
  3698. * expand_ExerciseCompleted
  3699. *
  3700. * @return xxx
  3701. */
  3702. function expand_ExerciseCompleted() {
  3703. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,exercise-completed');
  3704. }
  3705. /**
  3706. * expand_ShowCorrectFirstTime
  3707. *
  3708. * @return xxx
  3709. */
  3710. function expand_ShowCorrectFirstTime() {
  3711. return $this->hotpot->source->xml_value_bool($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',show-correct-first-time');
  3712. }
  3713. /**
  3714. * expand_ShuffleAs
  3715. *
  3716. * @return xxx
  3717. */
  3718. function expand_ShuffleAs() {
  3719. return $this->hotpot->source->xml_value_bool($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',shuffle-answers');
  3720. }
  3721. /**
  3722. * expand_DefaultRight
  3723. *
  3724. * @return xxx
  3725. */
  3726. function expand_DefaultRight() {
  3727. return $this->expand_GuessCorrect();
  3728. }
  3729. /**
  3730. * expand_DefaultWrong
  3731. *
  3732. * @return xxx
  3733. */
  3734. function expand_DefaultWrong() {
  3735. return $this->expand_GuessIncorrect();
  3736. }
  3737. /**
  3738. * expand_ShowAllQuestionsCaptionJS
  3739. *
  3740. * @return xxx
  3741. */
  3742. function expand_ShowAllQuestionsCaptionJS() {
  3743. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,show-all-questions-caption');
  3744. }
  3745. /**
  3746. * expand_ShowOneByOneCaptionJS
  3747. *
  3748. * @return xxx
  3749. */
  3750. function expand_ShowOneByOneCaptionJS() {
  3751. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,global,show-one-by-one-caption');
  3752. }
  3753. /**
  3754. * expand_CorrectList
  3755. *
  3756. * @return xxx
  3757. */
  3758. function expand_CorrectList() {
  3759. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',correct-answers');
  3760. }
  3761. /**
  3762. * expand_HybridTries
  3763. *
  3764. * @return xxx
  3765. */
  3766. function expand_HybridTries() {
  3767. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',short-answer-tries-on-hybrid-q');
  3768. }
  3769. /**
  3770. * expand_PleaseEnter
  3771. *
  3772. * @return xxx
  3773. */
  3774. function expand_PleaseEnter() {
  3775. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',enter-a-guess');
  3776. }
  3777. /**
  3778. * expand_PartlyIncorrect
  3779. *
  3780. * @return xxx
  3781. */
  3782. function expand_PartlyIncorrect() {
  3783. return $this->hotpot->source->xml_value_js($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',partly-incorrect');
  3784. }
  3785. /**
  3786. * expand_ShowAnswerCaption
  3787. *
  3788. * @return xxx
  3789. */
  3790. function expand_ShowAnswerCaption() {
  3791. return $this->hotpot->source->xml_value($this->hotpot->source->hbs_software.'-config-file,'.$this->hotpot->source->hbs_quiztype.',show-answer-caption');
  3792. }
  3793. /**
  3794. * expand_ShowAlsoCorrect
  3795. *
  3796. * @return xxx
  3797. */
  3798. function expand_ShowAlsoCorrect() {
  3799. return $this->hotpot->source->xml_value_bool($this->hotpot->source->hbs_software.'-config-file,global,show-also-correct');
  3800. }
  3801. // Textoys stylesheets (tt3.cs_)
  3802. /**
  3803. * expand_isRTL
  3804. *
  3805. * @return xxx
  3806. */
  3807. function expand_isRTL() {
  3808. // this may require some clever detection of RTL languages (e.g. Hebrew)
  3809. // but for now we just check the RTL setting in Options -> Configure Output
  3810. return $this->hotpot->source->xml_value_int($this->hotpot->source->hbs_software.'-config-file,global,process-for-rtl');
  3811. }
  3812. /**
  3813. * expand_isLTR
  3814. *
  3815. * @return xxx
  3816. */
  3817. function expand_isLTR() {
  3818. if ($this->expand_isRTL()) {
  3819. return false;
  3820. } else {
  3821. return true;
  3822. }
  3823. }
  3824. /**
  3825. * expand_RTLText
  3826. *
  3827. * @return xxx
  3828. */
  3829. function expand_RTLText() {
  3830. return $this->expand_isRTL();
  3831. }
  3832. }