/attempt/hp/6/renderer.php
PHP | 4229 lines | 2453 code | 381 blank | 1395 comment | 398 complexity | 74c22c395ce0ea25cc8f6346482f9e29 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * Render an attempt at a HotPot quiz
- * Output format: hp_6
- *
- * @package mod-hotpot
- * @copyright 2010 Gordon Bateson <gordon.bateson@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- defined('MOODLE_INTERNAL') || die();
- // get parent class
- require_once($CFG->dirroot.'/mod/hotpot/attempt/hp/renderer.php');
- /**
- * mod_hotpot_attempt_hp_6_renderer
- *
- * @copyright 2010 Gordon Bateson
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @since Moodle 2.0
- */
- class mod_hotpot_attempt_hp_6_renderer extends mod_hotpot_attempt_hp_renderer {
- /**
- * init
- *
- * @param xxx $hotpot
- */
- function init($hotpot) {
- parent::init($hotpot);
- array_unshift($this->templatesfolders, 'mod/hotpot/attempt/hp/6/templates');
- }
- /**
- * fix_headcontent_DragAndDrop for JMix and JMatch
- */
- function fix_headcontent_DragAndDrop() {
- // replace one line functions that get and set positions of positionable elements
- $search = array(
- '/(?<=function CardGetL).*/',
- '/(?<=function CardGetT).*/',
- '/(?<=function CardGetW).*/',
- '/(?<=function CardGetH).*/',
- '/(?<=function CardGetB).*/',
- '/(?<=function CardGetR).*/',
- '/(?<=function CardSetL).*/',
- '/(?<=function CardSetT).*/',
- '/(?<=function CardSetW).*/',
- '/(?<=function CardSetH).*/',
- );
- $replace = array(
- "(){return getOffset(this.elm, 'Left')}",
- "(){return getOffset(this.elm, 'Top')}",
- "(){return getOffset(this.elm, 'Width')}",
- "(){return getOffset(this.elm, 'Height')}",
- "(){return getOffset(this.elm, 'Bottom')}",
- "(){return getOffset(this.elm, 'Right')}",
- "(NewL){setOffset(this.elm, 'Left', NewL)}",
- "(NewT){setOffset(this.elm, 'Top', NewT)}",
- "(NewW){setOffset(this.elm, 'Width', NewW)}",
- "(NewH){setOffset(this.elm, 'Height', NewH)}"
- );
- $this->headcontent = preg_replace($search, $replace, $this->headcontent, 1);
- }
- /**
- * fix_headcontent_rottmeier
- *
- * @param xxx $type (optional, default='')
- */
- function fix_headcontent_rottmeier($type='') {
- switch ($type) {
- case 'dropdown':
- // adding missing brackets to call to Is_ExerciseFinished() in CheckAnswers()
- $search = '/(Finished\s*=\s*Is_ExerciseFinished)(;)/';
- $this->headcontent = preg_replace($search, '$1()$2', $this->headcontent);
- break;
- case 'findit':
- // get position of last </style> tag and
- // insert CSS to make <b> and <em> tags bold
- // even within GapSpans added by javascript
- $search = '</style>';
- if ($pos = strrpos($this->headcontent, $search)) {
- $insert = "\n"
- .'<!--[if IE 6]><style type="text/css">'."\n"
- .'span.GapSpan{'."\n"
- .' font-size:24px;'."\n"
- .'}'."\n"
- .'</style><![endif]-->'."\n"
- .'<style type="text/css">'."\n"
- .'b span.GapSpan,'."\n"
- .'em span.GapSpan{'."\n"
- .' font-weight:inherit;'."\n"
- .'}'."\n"
- .'</style>'."\n"
- ;
- $this->headcontent = substr_replace($this->headcontent, $insert, $pos + strlen($search), 0);
- }
- break;
- case 'jintro':
- // add TimeOver variable, so we can use standard detection of quiz completion
- if ($pos = strpos($this->headcontent, 'var Score = 0;')) {
- $insert = "var TimeOver = false;\n";
- $this->headcontent = substr_replace($this->headcontent, $insert, $pos, 0);
- }
- break;
- case 'jmemori':
- // add TimeOver variable, so we can use standard detection of quiz completion
- if ($pos = strpos($this->headcontent, 'var Score = 0;')) {
- $insert = "var TimeOver = false;\n";
- $this->headcontent = substr_replace($this->headcontent, $insert, $pos, 0);
- }
- // override table border collapse from standard Moodle styles
- if ($pos = strrpos($this->headcontent, '</style>')) {
- $insert = ''
- .'#'.$this->themecontainer.' form table'."\n"
- .'{'."\n"
- .' border-collapse: separate;'."\n"
- .' border-spacing: 2px;'."\n"
- .'}'."\n"
- ;
- $this->headcontent = substr_replace($this->headcontent, $insert, $pos, 0);
- }
- break;
- }
- }
- /**
- * fix_bodycontent_rottmeier
- *
- * @param xxx $hideclozeform (optional, default=false)
- */
- function fix_bodycontent_rottmeier($hideclozeform=false) {
- // fix left aligned instructions in Rottmeier-based formats
- // JCloze: DropDown, FindIt(a)+(b), JGloss
- // JMatch: JMemori
- $search = '/<p id="Instructions">(.*?)<\/p>/is';
- $replace = '<div id="Instructions">$1</div>';
- $this->bodycontent = preg_replace($search, $replace, $this->bodycontent);
- if ($hideclozeform) {
- // initially hide the Cloze text (so gaps are not revealed)
- $search = '/<(form id="Cloze" [^>]*)>/is';
- $replace = '<$1 style="display:none;">';
- $this->bodycontent = preg_replace($search, $replace, $this->bodycontent);
- }
- }
- /**
- * get_js_functionnames
- *
- * @return xxx
- */
- function get_js_functionnames() {
- // return a comma-separated list of js functions to be "fixed".
- // Each function name requires an corresponding function called:
- // fix_js_{$name}
- return 'Client,ShowElements,GetViewportHeight,PageDim,TrimString,RemoveBottomNavBarForIE,StartUp,GetUserName,PreloadImages,ShowMessage,HideFeedback,SendResults,Finish,WriteToInstructions,ShowSpecialReadingForQuestion';
- }
- /**
- * fix_js_Client
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_Client(&$str, $start, $length) {
- $substr = substr($str, $start, $length);
- // refine detection of Chrome browser
- $search = 'this.geckoVer < 20020000';
- if ($pos = strpos($substr, $search)) {
- $substr = substr_replace($substr, 'this.geckoVer > 10000000 && ', $pos, 0);
- }
- // add detection of Chrome browser
- $search = '/(\s*)if \(this\.min == false\)\{/s';
- $replace = '$1'
- ."this.chrome = (this.ua.indexOf('Chrome') > 0);".'$1'
- ."if (this.chrome) {".'$1'
- ." this.geckoVer = 0;".'$1'
- ." this.safari = false;".'$1'
- ." this.min = true;".'$1'
- ."}$0"
- ;
- $substr = preg_replace($search, $replace, $substr, 1);
- $str = substr_replace($str, $substr, $start, $length);
- }
- /**
- * fix_js_ShowElements
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_ShowElements(&$str, $start, $length) {
- $substr = substr($str, $start, $length);
- // hide <embed> tags (required for Chrome browser)
- if ($pos = strpos($substr, 'TagName == "object"')) {
- $substr = substr_replace($substr, 'TagName == "embed" || ', $pos, 0);
- }
- $str = substr_replace($str, $substr, $start, $length);
- }
- /**
- * fix_js_PageDim
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- * @return xxx
- */
- function fix_js_PageDim(&$str, $start, $length) {
- if ($this->usemoodletheme) {
- $obj = "document.getElementById('$this->themecontainer')"; // moodle
- } else {
- $obj = "document.getElementsByTagName('body')[0]"; // original
- }
- $replace = ''
- ."function getStyleValue(obj, property_name, propertyName){\n"
- ." var value = 0;\n"
- ." // Watch out for HTMLDocument which has no style property\n"
- ." // as this causes errors later in getComputedStyle() in FF\n"
- ." if (obj && obj.style){\n"
- ." // based on http://www.quirksmode.org/dom/getstyles.html\n"
- ." if (document.defaultView && document.defaultView.getComputedStyle){\n"
- ." // Firefox, Opera, Safari\n"
- ." value = document.defaultView.getComputedStyle(obj, null).getPropertyValue(property_name);\n"
- ." } else if (obj.currentStyle) {"
- ." // IE (and Opera)\n"
- ." value = obj.currentStyle[propertyName];\n"
- ." }\n"
- ." if (typeof(value)=='string'){\n"
- ." var r = new RegExp('([0-9.]*)([a-z]+)');\n"
- ." var m = value.match(r);\n"
- ." if (m){\n"
- ." switch (m[2]){\n"
- ." case 'em':\n"
- ." // as far as I can see, only IE needs this\n"
- ." // other browsers have getComputedStyle() in px\n"
- ." if (typeof(obj.EmInPx)=='undefined'){\n"
- ." var div = obj.parentNode.appendChild(document.createElement('div'));\n"
- ." div.style.margin = '0px';\n"
- ." div.style.padding = '0px';\n"
- ." div.style.border = 'none';\n"
- ." div.style.height = '1em';\n"
- ." obj.EmInPx = getOffset(div, 'Height');\n"
- ." obj.parentNode.removeChild(div);\n"
- ." }\n"
- ." value = parseFloat(m[1] * obj.EmInPx);\n"
- ." break;\n"
- ." case 'px':\n"
- ." value = parseFloat(m[1]);\n"
- ." break;\n"
- ." default:\n"
- ." value = 0;\n"
- ." }\n"
- ." } else {\n"
- ." value = 0 ;\n"
- ." }\n"
- ." } else {\n"
- ." value = 0;\n"
- ." }\n"
- ." }\n"
- ." return value;\n"
- ."}\n"
- ."function isStrict(){\n"
- ." return false;\n"
- ." if (typeof(window.cache_isStrict)=='undefined'){\n"
- ." if (document.compatMode) { // ie6+\n"
- ." window.cache_isStrict = (document.compatMode=='CSS1Compat');\n"
- ." } else if (document.doctype){\n"
- ." var s = document.doctype.systemId || document.doctype.name; // n6 OR ie5mac\n"
- ." window.cache_isStrict = (s && s.indexOf('strict.dtd') >= 0);\n"
- ." } else {\n"
- ." window.cache_isStrict = false;\n"
- ." }\n"
- ." }\n"
- ." return window.cache_isStrict;\n"
- ."}\n"
- ."function setOffset(obj, type, value){\n"
- ." if (! obj){\n"
- ." return false;\n"
- ." }\n"
- ."\n"
- ." switch (type){\n"
- ." case 'Right':\n"
- ." return setOffset(obj, 'Width', value - getOffset(obj, 'Left'));\n"
- ." case 'Bottom':\n"
- ." return setOffset(obj, 'Height', value - getOffset(obj, 'Top'));\n"
- ." }\n"
- ."\n"
- ." if (isStrict()){\n"
- ." // set arrays of p(roperties) and s(ub-properties)\n"
- ." var properties = new Array('margin', 'border', 'padding');\n"
- ." switch (type){\n"
- ." case 'Top':\n"
- ." var sides = new Array('Top');\n"
- ." break;\n"
- ." case 'Left':\n"
- ." var sides = new Array('Left');\n"
- ." break;\n"
- ." case 'Width':\n"
- ." var sides = new Array('Left', 'Right');\n"
- ." break;\n"
- ." case 'Height':\n"
- ." var sides = new Array('Top', 'Bottom');\n"
- ." break;\n"
- ." default:\n"
- ." return 0;\n"
- ." }\n"
- ." for (var p=0; p<properties.length; p++){\n"
- ." for (var s=0; s<sides.length; s++){\n"
- ." var propertyName = properties[p] + sides[s];\n"
- ." var property_name = properties[p] + '-' + sides[s].toLowerCase();\n"
- ." value -= getStyleValue(obj, property_name, propertyName);\n"
- ." }\n"
- ." }\n"
- ." value = Math.floor(value);\n"
- ." }\n"
- ." if (type=='Top' || type=='Left') {\n"
- ." value -= getOffset(obj.offsetParent, type);\n"
- ." }\n"
- ." if (obj.style) {\n"
- ." obj.style[type.toLowerCase()] = value + 'px';\n"
- ." }\n"
- ."}\n"
- ."function getOffset(obj, type){\n"
- ." if (! obj){\n"
- ." return 0;\n"
- ." }\n"
- ." switch (type){\n"
- ." case 'Width':\n"
- ." case 'Height':\n"
- ." return eval('(obj.offset'+type+'||0)');\n"
- ."\n"
- ." case 'Top':\n"
- ." case 'Left':\n"
- ." return eval('(obj.offset'+type+'||0) + getOffset(obj.offsetParent, type)');\n"
- ."\n"
- ." case 'Right':\n"
- ." return getOffset(obj, 'Left') + getOffset(obj, 'Width');\n"
- ."\n"
- ." case 'Bottom':\n"
- ." return getOffset(obj, 'Top') + getOffset(obj, 'Height');\n"
- ."\n"
- ." default:\n"
- ." return 0;\n"
- ." } // end switch\n"
- ."}\n"
- ."function PageDim(){\n"
- ." var obj = $obj;\n"
- ." this.W = getOffset(obj, 'Width');\n"
- ." this.H = getOffset(obj, 'Height');\n"
- ." this.Top = getOffset(obj, 'Top');\n"
- ." this.Left = getOffset(obj, 'Left');\n"
- ."}\n"
- ."function getClassAttribute(className, attributeName){\n"
- ." //based on http://www.shawnolson.net/a/503/\n"
- ." if (! document.styleSheets){\n"
- ." return null; // old browser\n"
- ." }\n"
- ." var css = document.styleSheets;\n"
- ." var rules = (document.all ? 'rules' : 'cssRules');\n"
- ." var regexp = new RegExp('\\\\.'+className+'\\\\b');\n"
- ." try {\n"
- ." var i_max = css.length;\n"
- ." } catch(err) {\n"
- ." var i_max = 0; // shouldn't happen !!\n"
- ." }\n"
- ." for (var i=0; i<i_max; i++){\n"
- ." try {\n"
- ." var ii_max = css[i][rules].length;\n"
- ." } catch(err) {\n"
- ." var ii_max = 0; // shouldn't happen !!\n"
- ." }\n"
- ." for (var ii=0; ii<ii_max; ii++){\n"
- ." if (! css[i][rules][ii].selectorText){\n"
- ." continue;\n"
- ." }\n"
- ." if (css[i][rules][ii].selectorText.match(regexp)){\n"
- ." if (css[i][rules][ii].style[attributeName]){\n"
- ." // class/attribute found\n"
- ." return css[i][rules][ii].style[attributeName];\n"
- ." }\n"
- ." }\n"
- ." }\n"
- ." }\n"
- ." // class/attribute not found\n"
- ." return null;\n"
- ."}\n"
- ;
- $str = substr_replace($str, $replace, $start, $length);
- }
- /**
- * fix_js_GetViewportHeight
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- * @return xxx
- */
- function fix_js_GetViewportHeight(&$str, $start, $length) {
- $replace = ''
- ."function GetViewportSize(type){\n"
- ." if (eval('window.inner' + type)){\n"
- ." return eval('window.inner' + type);\n"
- ." }\n"
- ." if (document.documentElement){\n"
- ." if (eval('document.documentElement.client' + type)){\n"
- ." return eval('document.documentElement.client' + type);\n"
- ." }\n"
- ." }\n"
- ." if (document.body){\n"
- ." if (eval('document.body.client' + type)){\n"
- ." return eval('document.body.client' + type);\n"
- ." }\n"
- ." }\n"
- ." return 0;\n"
- ."}\n"
- ."function GetViewportHeight(){\n"
- ." return GetViewportSize('Height');\n"
- ."}\n"
- ."function GetViewportWidth(){\n"
- ." return GetViewportSize('Width');\n"
- ."}"
- ;
- $str = substr_replace($str, $replace, $start, $length);
- }
- /**
- * remove_js_function
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- * @param xxx $function
- */
- function remove_js_function(&$str, $start, $length, $function) {
- // remove this function
- $str = substr_replace($str, '', $start, $length);
- // remove all direct calls to this function
- $search = '/\s*'.$function.'\([^)]*\);/s';
- $str = preg_replace($search, '', $str);
- }
- /**
- * fix_js_TrimString
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- * @return xxx
- */
- function fix_js_TrimString(&$str, $start, $length) {
- $replace = ''
- ."function TrimString(InString){\n"
- ." if (typeof(InString)=='string'){\n"
- ." InString = InString.replace(new RegExp('^\\\\s+', 'g'), ''); // left\n"
- ." InString = InString.replace(new RegExp('\\\\s+$', 'g'), ''); // right\n"
- ." InString = InString.replace(new RegExp('\\\\s+', 'g'), ' '); // inner\n"
- ." }\n"
- ." return InString;\n"
- ."}"
- ;
- $str = substr_replace($str, $replace, $start, $length);
- }
- /**
- * fix_js_TypeChars
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_TypeChars(&$str, $start, $length) {
- if ($obj = $this->fix_js_TypeChars_obj()) {
- $substr = substr($str, $start, $length);
- if (strpos($substr, 'document.selection')===false) {
- $replace = ''
- ."function TypeChars(Chars){\n"
- .$this->fix_js_TypeChars_init()
- ." if ($obj==null || $obj.style.display=='none') {\n"
- ." return;\n"
- ." }\n"
- ." $obj.focus();\n"
- ." if (typeof($obj.selectionStart)=='number') {\n"
- ." // FF, Safari, Chrome, Opera\n"
- ." var startPos = $obj.selectionStart;\n"
- ." var endPos = $obj.selectionEnd;\n"
- ." $obj.value = $obj.value.substring(0, startPos) + Chars + $obj.value.substring(endPos);\n"
- ." var newPos = startPos + Chars.length;\n"
- ." $obj.setSelectionRange(newPos, newPos);\n"
- ." } else if (document.selection) {\n"
- ." // IE (tested on IE6, IE7, IE8)\n"
- ." var rng = document.selection.createRange();\n"
- ." rng.text = Chars;\n"
- ." rng = null; // prevent memory leak\n"
- ." } else {\n"
- ." // this browser can't insert text, so append instead\n"
- ." $obj.value += Chars;\n"
- ." }\n"
- ."}"
- ;
- $str = substr_replace($str, $replace, $start, $length);
- }
- }
- }
- /**
- * fix_js_TypeChars_init
- *
- * @return xxx
- */
- function fix_js_TypeChars_init() {
- return '';
- }
- /**
- * fix_js_TypeChars_obj
- *
- * @return xxx
- */
- function fix_js_TypeChars_obj() {
- return '';
- }
- /**
- * fix_js_SendResults
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_SendResults(&$str, $start, $length) {
- $this->remove_js_function($str, $start, $length, 'SendResults');
- }
- /**
- * fix_js_GetUserName
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_GetUserName(&$str, $start, $length) {
- $this->remove_js_function($str, $start, $length, 'GetUserName');
- }
- /**
- * fix_js_Finish
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_Finish(&$str, $start, $length) {
- $this->remove_js_function($str, $start, $length, 'Finish');
- }
- /**
- * fix_js_PreloadImages
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_PreloadImages(&$str, $start, $length) {
- $substr = substr($str, $start, $length);
- // fix issue in IE8 which sometimes doesn't have Image object in popups
- // http://moodle.org/mod/forum/discuss.php?d=134510
- $search = "Imgs[i] = new Image();";
- if ($pos = strpos($substr, $search)) {
- $replace = "Imgs[i] = (window.Image ? new Image() : document.createElement('img'));";
- $substr = substr_replace($substr, $replace, $pos, strlen($search));
- }
- $str = substr_replace($str, $substr, $start, $length);
- }
- /**
- * fix_js_WriteToInstructions
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- * @return xxx
- */
- function fix_js_WriteToInstructions(&$str, $start, $length) {
- $substr = substr($str, $start, $length);
- if ($pos = strpos($substr, '{')) {
- $insert = "\n"
- ." // check required HTML element exists\n"
- ." if (! document.getElementById('InstructionsDiv')) return false;\n"
- ;
- $substr = substr_replace($substr, $insert, $pos+1, 0);
- }
- if ($pos = strrpos($substr, '}')) {
- $append = "\n"
- ." StretchCanvasToCoverContent(true);\n"
- ;
- $substr = substr_replace($substr, $append, $pos, 0);
- }
- $str = substr_replace($str, $substr, $start, $length);
- }
- /**
- * fix_js_ShowMessage
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- * @return xxx
- */
- function fix_js_ShowMessage(&$str, $start, $length) {
- // the ShowMessage function is used by all HP6 quizzes
- $substr = substr($str, $start, $length);
- // only show feedback if the required HTML elements exist
- // this prevents JavaScript errors which block the returning of the quiz results to Moodle
- if ($pos = strpos($substr, '{')) {
- $insert = "\n"
- ." // check required HTML elements exist\n"
- ." if (! document.getElementById('FeedbackDiv')) return false;\n"
- ." if (! document.getElementById('FeedbackContent')) return false;\n"
- ." if (! document.getElementById('FeedbackOKButton')) return false;\n"
- ;
- $substr = substr_replace($substr, $insert, $pos+1, 0);
- }
- // hide <embed> elements on Chrome browser
- $search = "/(\s*)ShowElements\(true, 'object', 'FeedbackContent'\);/s";
- $replace = ''
- ."$0".'$1'
- ."if (C.chrome) {".'$1'
- ." ShowElements(false, 'embed');".'$1'
- ." ShowElements(true, 'embed', 'FeedbackContent');".'$1'
- ."}"
- ;
- $substr = preg_replace($search, $replace, $substr, 1);
- // fix "top" setting to position FeedbackDiv
- if ($this->usemoodletheme) {
- $canvas = "document.getElementById('$this->themecontainer')"; // moodle
- } else {
- $canvas = "document.getElementsByTagName('body')[0]"; // original
- }
- $search = "/FDiv.style.top = [^;]*;(\s*)(FDiv.style.display = [^;]*;)/s";
- $replace = ''
- .'$1$2'
- .'$1'."var t = getOffset($canvas, 'Top');"
- .'$1'."setOffset(FDiv, 'Top', Math.max(t, TopSettingWithScrollOffset(30)));"
- ;
- $substr = preg_replace($search, $replace, $substr, 1);
- // append link to student feedback form, if necessary
- if ($this->hotpot->studentfeedback) {
- $search = '/(\s*)var Output = [^;]*;/';
- $replace = ''
- ."$0".'$1'
- ."if (window.FEEDBACK) {".'$1'
- ." Output += '".'<a href="javascript:hpFeedback();">'."' + FEEDBACK[6] + '</a>';".'$1'
- ."}"
- ;
- $substr = preg_replace($search, $replace, $substr, 1);
- }
- $substr = preg_replace($search, $replace, $substr, 1);
- }
- /**
- * fix_js_RemoveBottomNavBarForIE
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_RemoveBottomNavBarForIE(&$str, $start, $length) {
- $replace = ''
- ."function RemoveBottomNavBarForIE(){\n"
- ." if (C.ie) {\n"
- ." if (document.getElementById('Reading')){\n"
- ." var obj = document.getElementById('BottomNavBar');\n"
- ." if (obj){\n"
- ." obj.parentNode.removeChild(obj);\n"
- ." }\n"
- ." }\n"
- ." }\n"
- ."}"
- ;
- $str = substr_replace($str, $replace, $start, $length);
- }
- /**
- * fix_js_StartUp_DragAndDrop
- *
- * @param xxx $substr (passed by reference)
- */
- function fix_js_StartUp_DragAndDrop(&$substr) {
- // fixes for Drag and Drop (JMatch and JMix)
- }
- /**
- * fix_js_StartUp_DragAndDrop_DragArea
- *
- * @param xxx $substr (passed by reference)
- */
- function fix_js_StartUp_DragAndDrop_DragArea(&$substr) {
- // fix LeftCol (=left side of drag area)
- $search = '/(LeftColPos = [^;]+);/';
- $replace = '$1 + pg.Left;';
- $substr = preg_replace($search, $replace, $substr, 1);
- // fix DragTop (=top side of Drag area)
- $search = '/DragTop = [^;]+;/';
- $replace = "DragTop = getOffset(document.getElementById('CheckButtonDiv'),'Bottom') + 10;";
- $substr = preg_replace($search, $replace, $substr, 1);
- }
- /**
- * fix_js_StartUp
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_StartUp(&$str, $start, $length) {
- $substr = substr($str, $start, $length);
- // if necessary, fix drag area for JMatch or JMix drag-and-drop
- $this->fix_js_StartUp_DragAndDrop($substr);
- if ($pos = strrpos($substr, '}')) {
- if ($this->hotpot->delay3==hotpot::TIME_DISABLE) {
- $forceajax = 1;
- } else {
- $forceajax = 0;
- }
- if ($this->can_continue()==hotpot::CONTINUE_RESUMEQUIZ) {
- $onunload_status = hotpot::STATUS_INPROGRESS;
- } else {
- $onunload_status = hotpot::STATUS_ABANDONED;
- }
- $append = "\n"
- ."// adjust size and position of Feedback DIV\n"
- ." if (! window.pg){\n"
- ." window.pg = new PageDim();\n"
- ." }\n"
- ." var FDiv = document.getElementById('FeedbackDiv');\n"
- ." if (FDiv){\n"
- ." var w = getOffset(FDiv, 'Width') || FDiv.style.width || getClassAttribute(FDiv.className, 'width');\n"
- ." if (w){\n"
- ." if (typeof(w)=='string' && w.indexOf('%')>=0){\n"
- ." var percent = parseInt(w);\n"
- ." } else {\n"
- ." var percent = Math.floor(100 * parseInt(w) / pg.W);\n"
- ." }\n"
- ." } else if (window.FeedbackWidth && window.DivWidth){\n"
- ." var percent = Math.floor(100 * FeedbackWidth / DivWidth);\n"
- ." } else {\n"
- ." var percent = 34; // default width as percentage\n"
- ." }\n"
- ." FDiv.style.display = 'block';\n"
- ." setOffset(FDiv, 'Left', pg.Left + Math.floor(pg.W * (50 - percent/2) / 100));\n"
- ." setOffset(FDiv, 'Width', Math.floor(pg.W * percent / 100));\n"
- ." FDiv.style.display = 'none';\n"
- ." }\n"
- ."\n"
- ."// create HP object (to collect and send responses)\n"
- ." window.HP = new ".$this->js_object_type."('".$this->can_clickreport()."','".$forceajax."');\n"
- ."\n"
- ."// call HP.onunload to send results when this page unloads\n"
- ." var s = '';\n"
- ." if (typeof(window.onunload)=='function'){\n"
- ." window.onunload_StartUp = onunload;\n"
- ." s += 'window.onunload_StartUp();'\n"
- ." }\n"
- ." window.onunload = new Function(s + 'if(window.HP){HP.status=$onunload_status;HP.onunload();object_destroy(HP);}return true;');\n"
- ."\n"
- ;
- $substr = substr_replace($substr, $append, $pos, 0);
- }
- // stretch the canvas vertically down, if there is a reading
- if ($pos = strrpos($substr, '}')) {
- // Reading is contained in <div class="LeftContainer">
- // MainDiv is contained in <div class="RightContainer">
- // when there is a reading. Otherwise, MainDiv is not contained.
- // ReadingDiv is used to show different reading for each question
- if ($this->usemoodletheme) {
- $canvas = "document.getElementById('$this->themecontainer')"; // moodle
- } else {
- $canvas = "document.getElementsByTagName('body')[0]"; // original
- }
- // None: $canvas = "document.getElementById('page-mod-hotpot-attempt')"
- $id = $this->embed_object_id;
- $onload = $this->embed_object_onload;
- $insert = "\n"
- ."// fix canvas height, if necessary\n"
- ." if (! window.hotpot_mediafilter_loader){\n"
- ." StretchCanvasToCoverContent();\n"
- ." }\n"
- ."}\n"
- ."function StretchCanvasToCoverContent(skipTimeout){\n"
- ." if (! skipTimeout){\n"
- ." if (navigator.userAgent.indexOf('Firefox/3')>=0){\n"
- ." var millisecs = 1000;\n"
- ." } else {\n"
- ." var millisecs = 500;\n"
- ." }\n"
- ." setTimeout('StretchCanvasToCoverContent(true)', millisecs);\n"
- ." return;\n"
- ." }\n"
- ." var canvas = $canvas;\n"
- ." if (canvas){\n"
- ." var ids = new Array('Reading','ReadingDiv','MainDiv');\n"
- ." var i_max = ids.length;\n"
- ." for (var i=i_max-1; i>=0; i--){\n"
- ." var obj = document.getElementById(ids[i]);\n"
- ." if (obj){\n"
- ." obj.style.height = ''; // reset height\n"
- ." } else {\n"
- ." ids.splice(i, 1); // remove this id\n"
- ." i_max--;\n"
- ." }\n"
- ." }\n"
- ." var b = 0;\n"
- ." for (var i=0; i<i_max; i++){\n"
- ." var obj = document.getElementById(ids[i]);\n"
- ." b = Math.max(b, getOffset(obj,'Bottom'));\n"
- ." }\n"
- ." if (window.Segments) {\n" // JMix special
- ." var obj = document.getElementById('D'+(Segments.length-1));\n"
- ." if (obj) {\n"
- ." b = Math.max(b, getOffset(obj,'Bottom'));\n"
- ." }\n"
- ." }\n"
- ." if (b){\n"
- ." setOffset(canvas, 'Bottom', b + 21);\n"
- ." for (var i=0; i<i_max; i++){\n"
- ." var obj = document.getElementById(ids[i]);\n"
- ." setOffset(obj, 'Bottom', b);\n"
- ." }\n"
- ." }\n"
- ." }\n"
- ;
- if ($this->hotpot->navigation==hotpot::NAVIGATION_EMBED) {
- // stretch container object/iframe
- $insert .= ''
- ." if (parent.$onload) {\n"
- ." parent.$onload(null, parent.document.getElementById('".$this->embed_object_id."'));\n"
- ." }\n"
- ;
- }
- $substr = substr_replace($substr, $insert, $pos, 0);
- }
- $str = substr_replace($str, $substr, $start, $length);
- }
- /**
- * fix_js_HideFeedback
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_HideFeedback(&$str, $start, $length) {
- global $CFG;
- $substr = substr($str, $start, $length);
- // unhide <embed> elements on Chrome browser
- $search = "/(\s*)ShowElements\(true, 'object'\);/s";
- $replace = ''
- .'$0$1'
- ."if (C.chrome) {".'$1'
- ." ShowElements(true, 'embed');".'$1'
- ."}"
- ;
- $substr = preg_replace($search, $replace, $substr, 1);
- $search = '/('.'\s*if \(Finished == true\){\s*)(?:.*?)(\s*})/s';
- if ($this->hotpot->delay3==hotpot::TIME_AFTEROK) {
- // -1 : send form only (do not set form values, as that has already been done)
- $replace = '$1'.'HP.onunload(HP.status,-1);'.'$2';
- } else {
- $replace = ''; // i.e. remove this if-block
- }
- $substr = preg_replace($search, $replace, $substr, 1);
- $str = substr_replace($str, $substr, $start, $length);
- }
- /**
- * fix_js_ShowSpecialReadingForQuestion
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_ShowSpecialReadingForQuestion(&$str, $start, $length) {
- $replace = ''
- ."function ShowSpecialReadingForQuestion(){\n"
- ." var ReadingDiv = document.getElementById('ReadingDiv');\n"
- ." if (ReadingDiv){\n"
- ." var ReadingText = null;\n"
- ." var divs = ReadingDiv.getElementsByTagName('div');\n"
- ." for (var i=0; i<divs.length; i++){\n"
- ." if (divs[i].className=='ReadingText' || divs[i].className=='TempReadingText'){\n"
- ." ReadingText = divs[i];\n"
- ." break;\n"
- ." }\n"
- ." }\n"
- ." if (ReadingText && HiddenReadingShown){\n"
- ." SwapReadingTexts(ReadingText, HiddenReadingShown);\n"
- ." ReadingText = HiddenReadingShown;\n"
- ." HiddenReadingShown = false;\n"
- ." }\n"
- ." var HiddenReading = null;\n"
- ." if (QArray[CurrQNum]){\n"
- ." var divs = QArray[CurrQNum].getElementsByTagName('div');\n"
- ." for (var i=0; i<divs.length; i++){\n"
- ." if (divs[i].className=='HiddenReading'){\n"
- ." HiddenReading = divs[i];\n"
- ." break;\n"
- ." }\n"
- ." }\n"
- ." }\n"
- ." if (HiddenReading){\n"
- ." if (! ReadingText){\n"
- ." ReadingText = document.createElement('div');\n"
- ." ReadingText.className = 'ReadingText';\n"
- ." ReadingDiv.appendChild(ReadingText);\n"
- ." }\n"
- ." SwapReadingTexts(ReadingText, HiddenReading);\n"
- ." HiddenReadingShown = ReadingText;\n"
- ." }\n"
- ." var btn = document.getElementById('ShowMethodButton');\n"
- ." if (btn){\n"
- ." if (HiddenReadingShown){\n"
- ." if (btn.style.display!='none'){\n"
- ." btn.style.display = 'none';\n"
- ." }\n"
- ." } else {\n"
- ." if (btn.style.display=='none'){\n"
- ." btn.style.display = '';\n"
- ." }\n"
- ." }\n"
- ." }\n"
- ." btn = null;\n"
- ." ReadingDiv = null;\n"
- ." ReadingText = null;\n"
- ." HiddenReading = null;\n"
- ." }\n"
- ."}\n"
- ."function SwapReadingTexts(ReadingText, HiddenReading) {\n"
- ." HiddenReadingParentNode = HiddenReading.parentNode;\n"
- ." HiddenReadingParentNode.removeChild(HiddenReading);\n"
- ."\n"
- ." // replaceChild(new_node, old_node)\n"
- ." ReadingText.parentNode.replaceChild(HiddenReading, ReadingText);\n"
- ."\n"
- ." if (HiddenReading.IsOriginalReadingText){\n"
- ." HiddenReading.className = 'ReadingText';\n"
- ." } else {\n"
- ." HiddenReading.className = 'TempReadingText';\n"
- ." }\n"
- ." HiddenReading.style.display = '';\n"
- ."\n"
- ." if (ReadingText.className=='ReadingText'){\n"
- ." ReadingText.IsOriginalReadingText = true;\n"
- ." } else {\n"
- ." ReadingText.IsOriginalReadingText = false;\n"
- ." }\n"
- ." ReadingText.style.display = 'none';\n"
- ." ReadingText.className = 'HiddenReading';\n"
- ."\n"
- ." HiddenReadingParentNode.appendChild(ReadingText);\n"
- ." HiddenReadingParentNode = null;\n"
- ."}\n"
- ;
- $str = substr_replace($str, $replace, $start, $length);
- }
- /**
- * fix_js_CheckAnswers
- *
- * @param xxx $str (passed by reference)
- * @param xxx $start
- * @param xxx $length
- */
- function fix_js_CheckAnswers(&$str, $start, $length) {
- // JCloze, JCross, JMatch : CheckAnswers
- // JMix : CheckAnswer
- // JQuiz : CheckFinished
- $substr = substr($str, $start, $length);
- // intercept Checks, if necessary
- if ($insert = $this->get_stop_function_intercept()) {
- if ($pos = strpos($substr, '{')) {
- $substr = substr_replace($substr, $insert, $pos+1, 0);
- }
- }
- // add extra argument to function - so it can be called from the "Give Up" button
- $name = $this->get_stop_function_name();
- $search = '/(function '.$name.'\()(.*?)(\))/s';
- $callback = array($this, 'fix_js_CheckAnswers_arguments');
- $substr = preg_replace_callback($search, $callback, $substr, 1);
- // add call to Finish function (including QuizStatus)
- $search = $this->get_stop_function_search();
- $replace = $this->get_stop_function_replace();
- $substr = preg_replace($search, $replace, $substr, 1);
- $str = substr_replace($str, $substr, $start, $length);
- }
- /**
- * fix_js_CheckAnswers_arguments
- *
- * @param xxx $match
- * @return xxx
- */
- function fix_js_CheckAnswers_arguments($match) {
- if (empty($match[2])) {
- return $match[1].'ForceQuizStatus'.$match[3];
- } else {
- return $match[1].$match[2].',ForceQuizStatus'.$match[3];
- }
- }
- /**
- * get_stop_onclick
- *
- * @return xxx
- */
- function get_stop_onclick() {
- if ($name = $this->get_stop_function_name()) {
- return 'if('.$this->get_stop_function_confirm().')'.$name.'('.$this->get_stop_function_args().')';
- } else {
- return 'if(window.HP)HP.onunload('.QUIZPORT_STATUS_ABANDONED.')';
- }
- }
- /**
- * get_stop_function_confirm
- *
- * @return xxx
- */
- function get_stop_function_confirm() {
- // Note: "&&" in onclick must be encoded as html-entities for strict XHTML
- return ''
- ."confirm("
- ."'".$this->hotpot->source->js_value_safe(get_string('confirmstop', 'hotpot'), true)."'"
- ."+'\\n\\n'+(window.onbeforeunload && onbeforeunload()?(onbeforeunload()+'\\n\\n'):'')+"
- ."'".$this->hotpot->source->js_value_safe(get_string('pressoktocontinue', 'hotpot'), true)."'"
- .")"
- ;
- }
- /**
- * get_stop_function_name
- *
- * @return xxx
- */
- function get_stop_function_name() {
- // the name of the javascript function into which the "give up" code should be inserted
- return '';
- }
- /**
- * get_stop_function_args
- *
- * @return xxx
- */
- function get_stop_function_args() {
- // the arguments required by the javascript function which the stop_function() code calls
- return hotpot::STATUS_ABANDONED;
- }
- /**
- * get_stop_function_intercept
- *
- * @return xxx
- */
- function get_stop_function_intercept() {
- // JMix and JQuiz each have their own version of this function
- return "\n"
- ." // intercept this Check\n"
- ." HP.onclickCheck();\n"
- ;
- }
- /**
- * get_stop_function_search
- *
- * @return xxx
- */
- function get_stop_function_search() {
- // JCloze : AllCorrect || Finished
- // JCross : AllCorrect || TimeOver
- // JMatch : AllDone || TimeOver
- // JMix : AllDone || TimeOver (in the CheckAnswer function)
- // JQuiz : AllDone (in the CheckFinished function)
- return '/\s*if \(\((\w+) == true\)\|\|\(\w+ == true\)\)({).*?}\s*/s';
- }
- /**
- * get_stop_function_replace
- *
- * @return xxx
- */
- function get_stop_function_replace() {
- // $1 : name of the "all correct/done" variable
- // $2 : opening curly brace of if-block plus any following text to be kept
- if ($this->hotpot->delay3==hotpot::TIME_AFTEROK) {
- $flag = 1; // set form values only
- } else {
- $flag = 0; // set form values and send form
- }
- return "\n"
- ." if ($1){\n"
- ." var QuizStatus = 4; // completed\n"
- ." } else if (ForceQuizStatus){\n"
- ." var QuizStatus = ForceQuizStatus; // 3=abandoned\n"
- ." } else if (TimeOver){\n"
- ." var QuizStatus = 2; // timed out\n"
- ." } else {\n"
- ." var QuizStatus = 1; // in progress\n"
- ." }\n"
- ." if (QuizStatus > 1) $2\n"
- ." if (window.Interval) {\n"
- ." clearInterval(window.Interval);\n"
- ." }\n"
- ." TimeOver = true;\n"
- ." Locked = true;\n"
- ." Finished = true;\n"
- ." }\n"
- ." if (Finished || HP.sendallclicks){\n"
- ." if (ForceQuizStatus || QuizStatus==1){\n"
- ." // send results immediately\n"
- ." HP.onunload(QuizStatus);\n"
- ." } else {\n"
- ." // send results after delay\n"
- ." setTimeout('HP.onunload('+QuizStatus+',$flag)', SubmissionTimeout);\n"
- ." }\n"
- ." }\n"
- ;
- }
- /**
- * postprocessing
- *
- * after headcontent and bodycontent have been setup and
- * before content is sent to browser, we add title edit icon,
- * insert submission form, adjust navigation butons (if any)
- * and add external javascripts (to the top of the page)
- */
- function postprocessing() {
- $this->fix_title_icons();
- $this->fix_submissionform();
- $this->fix_navigation_buttons();
- foreach ($this->javascripts as $script) {
- $this->page->requires->js('/'.$script, true);
- }
- }
- /**
- * fix_navigation_buttons
- *
- * @return xxx
- */
- function fix_navigation_buttons() {
- if ($this->hotpot->navigation==hotpot::NAVIGATION_ORIGINAL) {
- // replace relative URLs in <button class="NavButton" ... onclick="location='...'">
- $search = '/'.'(?<='.'onclick="'."location='".')'."([^']*)".'(?='."'; return false;".'")'.'/is';
- $callback = array($this, 'convert_url_navbutton');
- $this->bodycontent = preg_replace_callback($search, $callback, $this->bodycontent);
- // replace history.back() in <button class="NavButton" ... onclick="history.back(); ...">
- // with a link to the course page
- $params = array('id'=>$this->hotpot->course->id);
- $search = '/'.'(?<='.'onclick=")'.'history\.back\(\)'.'(?=; return false;")'.'/';
- $replace = "location='".new moodle_url('/course/view.php', $params)."'";
- $this->bodycontent = preg_replace($search, $replace, $this->bodycontent);
- }
- }
- /**
- * fix_TimeLimit
- */
- function fix_TimeLimit() {
- if ($this->hotpot->timelimit > 0) {
- $search = '/(?<=var Seconds = )\d+(?=;)/';
- $this->headcontent = preg_replace($search, $this->hotpot->timelimit, $this->headcontent, 1);
- }
- }
- /**
- * fix_SubmissionTimeout
- */
- function fix_SubmissionTimeout() {
- if ($this->hotpot->delay3==hotpot::TIME_TEMPLATE) {
- // use default from source/template file (=30000 ms =30 seconds)
- if ($this->hasSubmissionTimeout) {
- $timeout = null;
- } else {
- $timeout = 30000; // = 30 secs is HP default
- }
- } else {
- if ($this->hotpot->delay3 >= 0) {
- $timeout = $this->hotpot->delay3 * 1000; // milliseconds
- } else {
- $timeout = 0; // i.e. immediately
- }
- }
- if (is_null($timeout)) {
- return; // nothing to do
- }
- if ($this->hasSubmissionTimeout) {
- // remove HPNStartTime
- $search = '/var HPNStartTime\b[^;]*?;\s*/';
- $this->headcontent = preg_replace($search, '', $this->headcontent, 1);
- // reset the value of SubmissionTimeout
- $search = '/(?<=var SubmissionTimeout = )\d+(?=;)/';
- $this->headcontent = preg_replace($search, $timeout, $this->headcontent, 1);
- } else {
- // Rhubarb, Sequitur and Quandary
- $search = '/var FinalScore = 0;/';
- $replace = '$0'."\n".'var SubmissionTimeout = '.$timeout.';';
- $this->headcontent = preg_replace($search, $replace, $this->headcontent, 1);
- }
- }
- /**
- * fix_navigation
- */
- function fix_navigation() {
- if ($this->hotpot->navigation==hotpot::NAVIGATION_ORIGINAL) {
- // do nothing - leave navigation as it is
- return;
- }
- // insert the stop button, if required
- if ($this->hotpot->stopbutton) {
- // replace top nav buttons with a single stop button
- if ($this->hotpot->stopbutton==hotpot::STOPBUTTON_LANGPACK) {
- if ($pos = strpos($this->hotpot->stoptext, '_')) {
- $mod = substr($this->hotpot->stoptext, 0, $pos);
- $str = substr($this->hotpot->stoptext, $pos + 1);
- $stoptext = get_string($str, $mod);
- } else if ($this->hotpot->stoptext) {
- $stoptext = get_string($this->hotpot->stoptext);
- } else {
- $stoptext = '';
- }
- } else {
- $stoptext = $this->hotpot->stoptext;
- }
- if (trim($stoptext)=='') {
- $stoptext = get_string('giveup', 'hotpot');
- }
- $confirm = get_string('confirmstop', 'hotpot');
- //$search = '/<!-- BeginTopNavButtons -->'.'.*?'.'<!-- EndTopNavButtons -->/s';
- $search = '/<(div class="Titles")>/s';
- $replace = '<$1 style="position: relative">'."\n\t"
- .'<div class="hotpotstopbutton">'
- .'<button class="FuncButton" '
- .'onclick="'.$this->get_stop_onclick().'" '
- .'onfocus="FuncBtnOver(this)" onblur="FuncBtnOut(this)" '
- .'onmouseover="FuncBtnOver(this)" onmouseout="FuncBtnOut(this)" '
- .'onmousedown="FuncBtnDown(this)" onmouseup="FuncBtnOut(this)">'
- .hotpot_textlib('utf8_to_entities', $stoptext)
- .'</button>'
- .'</div>'
- ;
- $this->bodycontent = preg_replace($search, $replace, $this->bodycontent, 1);
- }
- // remove (remaining) navigation buttons
- $search = '/<!-- Begin(Top|Bottom)NavButtons -->'.'.*?'.'<!-- End'.'\\1'.'NavButtons -->/…
Large files files are truncated, but you can click here to view the full file