PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/attempt/html/renderer.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 346 lines | 218 code | 40 blank | 88 comment | 30 complexity | e57caf54c92b5edb0cfe8e3827be8363 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: html
  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/renderer.php');
  27. /**
  28. * mod_hotpot_attempt_html_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_html_renderer extends mod_hotpot_attempt_renderer {
  35. // strings to mark beginning and end of submission form
  36. protected $BeginSubmissionForm = '<!-- BeginSubmissionForm -->';
  37. protected $EndSubmissionForm = '<!-- EndSubmissionForm -->';
  38. // the id/name of the of the form which returns results to the browser
  39. protected $formid = 'store';
  40. // the name of the score field in the results returned form the browser
  41. protected $scorefield = 'mark';
  42. /**
  43. * preprocessing
  44. */
  45. function preprocessing() {
  46. global $CFG;
  47. if ($this->cache_uptodate) {
  48. $this->fix_title();
  49. $this->fix_links(true);
  50. $this->fix_submissionform();
  51. return true;
  52. }
  53. $this->xmldeclaration = '';
  54. $this->doctype = '';
  55. $this->htmlattributes = '';
  56. $this->headattributes = '';
  57. $this->headcontent = '';
  58. $this->bodyattributes = '';
  59. $this->bodycontent = '';
  60. if (! $this->hotpot->source->get_filecontents()) {
  61. // empty source file - shouldn't happen !!
  62. return false;
  63. }
  64. $this->htmlcontent = &$this->hotpot->source->filecontents;
  65. // extract contents of first <head> tag
  66. if (preg_match($this->tagpattern('head'), $this->htmlcontent, $matches)) {
  67. $this->headcontent = $matches[2];
  68. }
  69. if ($this->usemoodletheme) {
  70. // remove the title from the <head>
  71. $this->headcontent = preg_replace($this->tagpattern('title'), '', $this->headcontent);
  72. } else {
  73. // replace <title> with current name of this quiz
  74. $title = '<title>'.$this->get_title().'</title>'."\n";
  75. $this->headcontent = preg_replace($this->tagpattern('title'), $title, $this->headcontent);
  76. // extract details needed to rebuild page later in $this->view()
  77. if (preg_match($this->tagpattern('\?xml','',false), $this->htmlcontent, $matches)) {
  78. $this->xmldeclaration = $matches[0]."\n";
  79. }
  80. if (preg_match($this->tagpattern('!DOCTYPE','',false,'(?:<!--\s*)?','(?:\s*-->)?'), $this->htmlcontent, $matches)) {
  81. $this->doctype = $this->single_line($matches[0])."\n";
  82. }
  83. if (preg_match($this->tagpattern('html','',false), $this->htmlcontent, $matches)) {
  84. $this->htmlattributes = ' '.$this->single_line($matches[1])."\n";
  85. }
  86. if (preg_match($this->tagpattern('head','',false), $this->htmlcontent, $matches)) {
  87. $this->headattributes = ' '.$this->single_line($matches[1]);
  88. }
  89. }
  90. // transfer <styles> tags from $this->headcontent to $this->styles
  91. $this->styles = '';
  92. if (preg_match_all($this->tagpattern('style'), $this->headcontent, $matches, PREG_OFFSET_CAPTURE)) {
  93. foreach (array_reverse($matches[0]) as $match) {
  94. // $match: [0] = matched string, [1] = offset to start of string
  95. $this->styles = $match[0]."\n".$this->styles;
  96. $this->headcontent = substr_replace($this->headcontent, '', $match[1], strlen($match[0]));
  97. }
  98. if ($this->usemoodletheme) {
  99. // restrict scope of page styles, so they affect only the quiz's containing element (i.e. the middle column)
  100. $search = '/([a-z0-9_\#\.\-\,\: ]+){(.*?)}/is';
  101. $callback = array($this, 'fix_css_definitions');
  102. $this->styles = preg_replace_callback($search, $callback, $this->styles);
  103. // the following is not necessary for standard HP styles, but may required to handle some custom styles
  104. $this->styles = str_replace('TheBody', $this->themecontainer, $this->styles);
  105. }
  106. $this->styles = $this->remove_blank_lines($this->styles);
  107. }
  108. // transfer <script> tags from $this->headcontent to $this->scripts
  109. $this->scripts = '';
  110. if (preg_match_all($this->tagpattern('script'), $this->headcontent, $matches, PREG_OFFSET_CAPTURE)) {
  111. foreach (array_reverse($matches[0]) as $match) {
  112. // $match: [0] = matched string, [1] = offset to start of string
  113. $this->scripts = $match[0]."\n".$this->scripts;
  114. $this->headcontent = substr_replace($this->headcontent, '', $match[1], strlen($match[0]));
  115. }
  116. // remove block and single-line comments - except <![CDATA[ + ]]> and <!-- + --> and http(s)://
  117. if ($CFG->debug <= DEBUG_DEVELOPER) {
  118. $this->scripts = preg_replace('/\s*\/\*.*?\*\//s', '', $this->scripts);
  119. $search = '/\s*([a-z]+:)?\/\/[^\r\n]*/is';
  120. $callback = array($this, 'fix_js_comment');
  121. $this->scripts = preg_replace_callback($search, $callback, $this->scripts);
  122. }
  123. $this->scripts = $this->remove_blank_lines($this->scripts);
  124. // standardize "} else {" formatting
  125. $this->scripts = preg_replace('/}\s*else\s*{/s', '} else {', $this->scripts);
  126. }
  127. // remove blank lines
  128. $this->headcontent = $this->remove_blank_lines($this->headcontent);
  129. // put each <meta> tag on its own line
  130. $this->headcontent = preg_replace('/'.'([^\n])'.'(<\w+)'.'/', '$1'."\n".'$2', $this->headcontent);
  131. // append styles and scripts to the end of the $this->headcontent
  132. $this->headcontent .= $this->styles.$this->scripts;
  133. // extract <body> tag
  134. if (! preg_match($this->tagpattern('body'), $this->htmlcontent, $matches)) {
  135. return false;
  136. }
  137. $this->bodyattributes = $this->single_line(preg_replace('/\s*id="[^"]*"/', '', $matches[1]));
  138. $this->bodycontent = $this->remove_blank_lines($matches[2]);
  139. // fix self-closing <script /> tags, as they cause several browsers to ignore following content
  140. $this->bodycontent = preg_replace('/(<script[^>]*)\/>/is', '$1></script>', $this->bodycontent);
  141. if (preg_match('/\s*onload="([^"]*)"/is', $this->bodyattributes, $matches, PREG_OFFSET_CAPTURE)) {
  142. $this->bodyattributes = substr_replace($this->bodyattributes, '', $matches[0][1], strlen($matches[0][0]));
  143. if ($this->usemoodletheme) {
  144. // workaround to ensure javascript onload routine for quiz is always executed
  145. // $this->bodyattributes will only be inserted into the <body ...> tag
  146. // if it is included in the theme/$CFG->theme/header.html,
  147. // so some old or modified themes may not insert $this->bodyattributes
  148. $this->bodycontent .= $this->fix_onload($matches[1][0], true);
  149. }
  150. }
  151. $this->fix_title();
  152. $this->fix_relativeurls();
  153. $this->fix_mediafilter();
  154. $this->fix_links();
  155. }
  156. /**
  157. * fix_js_comment
  158. *
  159. * @param xxx $match
  160. * @return xxx
  161. */
  162. function fix_js_comment($match) {
  163. $comment = $match[0];
  164. if (isset($match[1]) && strlen($match[1])) {
  165. return $comment; // $match is a URL
  166. }
  167. if (preg_match('/^\s*\/\/((?:<!\[CDATA\[)|(?:<!--)|(?:-->)|(?:\]\]>))/', $comment)) {
  168. return $comment; // $match is start or end of a CDATA comment
  169. }
  170. return '';
  171. }
  172. /**
  173. * fix_links
  174. *
  175. * @param xxx $quickfix (optional, default=false)
  176. * @return xxx
  177. */
  178. function fix_links($quickfix=false) {
  179. global $DB;
  180. if ($quickfix) {
  181. //$search = '/(?<=sesskey=)\w+/';
  182. //$this->bodycontent = preg_replace($search, sesskey(), $this->bodycontent);
  183. $search = '/(?<=["\/]attempt\.php\?id=)[0-9]+/';
  184. $this->bodycontent = preg_replace($search, $this->hotpot->create_attempt(), $this->bodycontent);
  185. return true;
  186. }
  187. $matches = array();
  188. $search = '/<a[^>]*href="([^"]*)"[^>]*>/is';
  189. // [0] : the complete <a ...>...</a> tag
  190. // [1] : the link href
  191. if (preg_match_all($search, $this->bodycontent, $match, PREG_OFFSET_CAPTURE)) {
  192. for ($i=0; $i<count($match); $i++) {
  193. if (empty($matches[$i])) {
  194. $matches[$i] = $match[$i];
  195. } else {
  196. $matches[$i] = array_merge($matches[$i], $match[$i]);
  197. }
  198. }
  199. }
  200. $search = "/location='([^']*)'/i";
  201. // [0] : the complete location="..." match
  202. // [1] : the location href
  203. if (preg_match_all($search, $this->bodycontent, $match, PREG_OFFSET_CAPTURE)) {
  204. for ($i=0; $i<count($match); $i++) {
  205. if (empty($matches[$i])) {
  206. $matches[$i] = $match[$i];
  207. } else {
  208. $matches[$i] = array_merge($matches[$i], $match[$i]);
  209. }
  210. }
  211. }
  212. if (empty($matches[1])) {
  213. return false; // no urls found
  214. }
  215. $urls = array();
  216. $strlen = strlen($this->hotpot->source->baseurl);
  217. foreach ($matches[1] as $i=>$match) {
  218. $url = $this->convert_url_relative($this->hotpot->source->baseurl, $this->hotpot->source->filepath, '', $match[0], '', '');
  219. if (strpos($url, $this->hotpot->source->baseurl.'/')===0) {
  220. $urls[$i] = substr($url, $strlen + 1);
  221. }
  222. }
  223. if (empty($urls)) {
  224. return false; // no links to files in this course
  225. }
  226. list($select, $params) = $DB->get_in_or_equal($urls);
  227. $select = "course=? AND sourcefile $select";
  228. array_unshift($params, $this->hotpot->course->id);
  229. if ($quizzes = $DB->get_records_select('hotpot', $select, $params, 'id', 'id,sourcefile')) {
  230. foreach ($quizzes as $quiz) {
  231. $i = array_search($quiz->sourcefile, $urls);
  232. $matches[1][$i][2] = $quiz->id;
  233. }
  234. foreach (array_reverse($matches[1]) as $match) {
  235. // $match [0] old url, [1] offset [2] quizid
  236. if (array_key_exists(2, $match)) {
  237. // it used to be possible to send the id of the next quiz back as the "redirect" parameter
  238. // but that doesn't work any more bacuse of changes to view.php
  239. // $params = array('quizattemptid'=>$QUIZPORT->quizattemptid, 'redirect'=>$match[2]);
  240. $params = array('id'=>$this->hotpot->attempt->id, 'status'=>STATUS_COMPLETED, 'redirect'=>$match[2]);
  241. $newurl = $this->format_url('view.php', '', $params);
  242. $this->bodycontent = substr_replace($this->bodycontent, $newurl, $match[1], strlen($match[0]));
  243. }
  244. }
  245. }
  246. }
  247. /**
  248. * postprocessing
  249. */
  250. function postprocessing() {
  251. $this->fix_title_icons();
  252. $this->fix_submissionform();
  253. }
  254. /**
  255. * fix_submissionform
  256. */
  257. function fix_submissionform() {
  258. // remove previous submission form, if any
  259. $search = '/\s*('.$this->BeginSubmissionForm.')\s*(.*?)\s*('.$this->EndSubmissionForm.')/s';
  260. $this->bodycontent = preg_replace($search, '', $this->bodycontent);
  261. // prepare form parameters and attributes
  262. $params = array(
  263. 'id' => $this->hotpot->create_attempt(),
  264. $this->scorefield => '0', 'detail' => '0', 'status' => '0',
  265. 'starttime' => '0', 'endtime' => '0', 'redirect' => '0',
  266. );
  267. // add scorefield to params, if necessary (usually it is necessary)
  268. if (! preg_match('/<(input|select)[^>]*name="'.$this->scorefield.'"[^>]*>/is', $this->bodycontent)) {
  269. $params[$this->scorefield] = 100;
  270. }
  271. $attributes = array(
  272. 'id' => $this->formid, 'autocomplete' => 'off'
  273. );
  274. // create submit button, if necessary
  275. if (preg_match('/<input[^>]*type="submit"[^>]*>/is', $this->bodycontent)) {
  276. // one or more (!) submit buttons already exist
  277. $continuebutton = '';
  278. } else {
  279. // prepare continue button
  280. $continuebutton = html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('continue')));
  281. if ($this->usemoodletheme) {
  282. $continuebutton = html_writer::tag('div', $continuebutton, array('class'=>'continuebutton'));
  283. } else {
  284. $continuebutton = html_writer::tag('div', $continuebutton, array('align'=>'center'));
  285. }
  286. }
  287. // wrap submission form around main content
  288. $this->bodycontent = ''
  289. .$this->BeginSubmissionForm."\n"
  290. .$this->form_start('submit.php', $params, $attributes)
  291. .$this->EndSubmissionForm."\n"
  292. .$this->bodycontent."\n"
  293. .$this->BeginSubmissionForm."\n"
  294. .$continuebutton."\n"
  295. .$this->form_end()."\n"
  296. .$this->EndSubmissionForm."\n"
  297. ;
  298. }
  299. }