PageRenderTime 51ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/attempt/renderer.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 1514 lines | 883 code | 184 blank | 447 comment | 155 complexity | 3a26a429ac25e94030033dbbdd5439e2 MD5 | raw file

Large files files are truncated, but you can click here to view the full 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. *
  19. * @package mod-hotpot
  20. * @copyright 2010 Gordon Bateson <gordon.bateson@gmail.com>
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. // get parent class
  25. require_once($CFG->dirroot.'/mod/hotpot/renderer.php');
  26. /**
  27. * mod_hotpot_attempt_renderer
  28. *
  29. * @copyright 2010 Gordon Bateson
  30. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31. * @since Moodle 2.0
  32. */
  33. class mod_hotpot_attempt_renderer extends mod_hotpot_renderer {
  34. /** xml declaration */
  35. protected $xmldeclaration;
  36. /** doctype tag */
  37. protected $doctype;
  38. /** html tag attributes */
  39. protected $htmlattributes;
  40. /** head tag attributes */
  41. protected $headattributes;
  42. /** head content */
  43. public $headcontent;
  44. /** body tag attributes */
  45. protected $bodyattributes;
  46. /** body content */
  47. public $bodycontent;
  48. /** name of html frame */
  49. protected $framename;
  50. /** boolean flag indicating whether or not we should use the Moodle theme */
  51. protected $usemoodletheme;
  52. /** name of theme containiner */
  53. protected $themecontainer;
  54. /** the hotpot object (as defined in "mod/hotpot/locallib.php") representing this HotPot */
  55. public $hotpot;
  56. /** the id for the embedded object used for hotpot::NAVIGATION_EMBED */
  57. protected $embed_object_id = 'hotpot_embed_object';
  58. /** the onload function (in iframe.js) for the embedded object (if used) */
  59. protected $embed_object_onload = 'set_embed_object_height';
  60. /**
  61. * most outputformats use the hotpot cache
  62. * but those that don't can switch this flag off
  63. */
  64. protected $use_hotpot_cache = true;
  65. /** object to store the hotpot_cache record for the quiz */
  66. protected $cache;
  67. /** boolean flag that indicates whether the cache content is uptodate or not */
  68. protected $cache_uptodate;
  69. /**
  70. * these $CFG fields must match those in the "hotpot_cache" table
  71. * "wwwroot" is not stored explicitly because it is included in the md5key
  72. */
  73. protected $cache_CFG_fields = array(
  74. 'slasharguments','hotpot_enableobfuscate','hotpot_enableswf'
  75. );
  76. /**
  77. * these fields in the quiz record must match those in the "hotpot_cache" table
  78. * "outputformat" is not stored explicitly because it is included in the md5key
  79. */
  80. protected $cache_hotpot_fields = array(
  81. 'name','sourcefile','sourcetype','sourcelocation','configfile','configlocation',
  82. 'navigation','stopbutton','stoptext','title','usefilters','useglossary','usemediafilter',
  83. 'studentfeedback','studentfeedbackurl','timelimit','delay3','clickreporting'
  84. );
  85. /** these fields are treated as text fields */
  86. public $cache_text_fields = array(
  87. 'slasharguments','hotpot_enableobfuscate','hotpot_enableswf','name',
  88. 'sourcefile','sourcetype','sourcelastmodified','sourceetag',
  89. 'configfile','configlastmodified','configetag',
  90. 'usemediafilter','studentfeedbackurl','stoptext'
  91. );
  92. /* these fields from the source and config file objects are stored in the cache */
  93. public $cache_remote_fields = array(
  94. 'lastmodified','etag'
  95. );
  96. /**
  97. * these fields will be serialized and then stored in the "content" field of the "hotpot_cache" table
  98. */
  99. protected $cache_content_fields = array(
  100. 'xmldeclaration','doctype','htmlattributes','headattributes','headcontent','bodyattributes','bodycontent'
  101. );
  102. protected $unitid = 0;
  103. protected $unumber = 0;
  104. protected $unitgradeid = 0;
  105. protected $unitattemptid = 0;
  106. protected $quizid = 0;
  107. protected $qnumber = 0;
  108. protected $quizscoreid = 0;
  109. protected $quizattemptid = 0;
  110. /**
  111. * init
  112. *
  113. * @param xxx $hotpot
  114. */
  115. protected function init($hotpot) {
  116. // save a reference to the $hotpot record
  117. $this->hotpot = $hotpot;
  118. $this->hotpot->get_source();
  119. // set the frame name, if any
  120. $this->framename = optional_param('framename', '', PARAM_ALPHA);
  121. switch ($hotpot->navigation) {
  122. case hotpot::NAVIGATION_NONE:
  123. case hotpot::NAVIGATION_FRAME:
  124. case hotpot::NAVIGATION_EMBED:
  125. case hotpot::NAVIGATION_ORIGINAL:
  126. $this->usemoodletheme = true; // ($this->framename=='top');
  127. $this->themecontainer = 'page-mod-hotpot-attempt';
  128. break;
  129. case hotpot::NAVIGATION_MOODLE:
  130. case hotpot::NAVIGATION_TOPBAR:
  131. default:
  132. // using any of the following elements as "themecontainer",
  133. // results in incorrect calculation of ViewportSize in themes
  134. // that use the CSS "overflow: hidden":
  135. // - region-main
  136. // - region-main-wrap
  137. // - region-post-box
  138. // - region-main-box
  139. // - page-content
  140. // using any of the following elements as "themecontainer" is OK:
  141. // - page-content-wrapper
  142. // - wrapper
  143. // - page (doesn't work)
  144. // - my-page-wrapper (doesn't work)
  145. // - page-mod-hotpot-attempt (doesn't work)
  146. $this->usemoodletheme = true;
  147. $this->themecontainer = 'region-main';
  148. break;
  149. }
  150. }
  151. /**
  152. * the source file types with which this output format can be used
  153. *
  154. * @return array of source file types
  155. */
  156. public static function sourcetypes() {
  157. return array();
  158. }
  159. /**
  160. * Returns html code for an attempt at a HotPot instance
  161. * $this->output is a core_renderer (see "lib/outputrenderers.php")
  162. *
  163. * you can set page properties like this
  164. * - $this->page->set_title($title);
  165. * - $this->page->set_heading($this->page->course->fullname);
  166. * - $this->page->requires->js('/mod/hotpot/attempt/iframe.js', true); // add to head
  167. * - $this->page->requires->js_module('hotpot'); // gets mod/hotpot/module.js
  168. * - $this->page->requires->js_init_call('M.mod_hotpot.secure_window.init');
  169. *
  170. * @param hotpot object
  171. * @return string html
  172. */
  173. public function render_attempt($hotpot, $cacheonly=false) {
  174. // initialize some important properties
  175. $this->init($hotpot);
  176. // if necessary, print container page and associated frames
  177. $basetag = '';
  178. if ($this->hotpot->navigation==hotpot::NAVIGATION_FRAME || $this->hotpot->navigation==hotpot::NAVIGATION_EMBED) {
  179. if ($cacheonly) {
  180. $this->framename = 'main';
  181. $this->usemoodletheme = false;
  182. } else if ($this->hotpot->navigation==hotpot::NAVIGATION_FRAME) {
  183. if ($this->framename=='') {
  184. $this->print_frameset();
  185. die;
  186. }
  187. if ($this->framename=='top') {
  188. $this->print_topframe();
  189. die;
  190. }
  191. } else if ($this->hotpot->navigation==hotpot::NAVIGATION_EMBED) {
  192. if ($this->framename=='') {
  193. $this->print_embed_object_page();
  194. die;
  195. }
  196. }
  197. // otherwise we print the "main" frame below
  198. // set basetag to ensure links and forms can escape from frame
  199. $basetag = $this->basetag();
  200. }
  201. // try to get content from cache
  202. $this->get_fields_from_cache();
  203. if ($cacheonly && $this->cache_uptodate) {
  204. return true;
  205. }
  206. // do pre-processing, if required
  207. $this->preprocessing();
  208. // generate the main parts of the page
  209. $this->set_xmldeclaration();
  210. $this->set_doctype();
  211. $this->set_htmlattributes();
  212. $this->set_headattributes();
  213. $this->set_headcontent();
  214. $this->set_bodyattributes();
  215. $this->set_bodycontent();
  216. // save content to cache (if necessary)
  217. $this->set_fields_in_cache();
  218. if ($cacheonly) {
  219. return true;
  220. }
  221. // do post-processing, if required
  222. $this->postprocessing();
  223. if (! $this->bodycontent) {
  224. throw new moodle_exception('sourcefilenotfound', 'hotpot', '', $this->hotpot->sourcefile);
  225. }
  226. echo $this->hotpot_header();
  227. echo $this->hotpot_content();
  228. echo $this->hotpot_footer();
  229. }
  230. /////////////////////////////////////////////////////////////////////
  231. // functions to prepare browser content //
  232. /////////////////////////////////////////////////////////////////////
  233. /**
  234. * preprocessing
  235. */
  236. protected function preprocessing() {
  237. // pre-processing for this output format
  238. // e.g. convert source to ideal format for this output format
  239. }
  240. /**
  241. * set_xmldeclaration
  242. */
  243. protected function set_xmldeclaration() {
  244. if (! isset($this->xmldeclaration)) {
  245. $this->xmldeclaration = '';
  246. }
  247. }
  248. /**
  249. * set_doctype
  250. */
  251. protected function set_doctype() {
  252. if (! isset($this->doctype)) {
  253. $this->doctype = '';
  254. }
  255. }
  256. /**
  257. * set_htmlattributes
  258. */
  259. protected function set_htmlattributes() {
  260. if (! isset($this->htmlattributes)) {
  261. $this->htmlattributes = '';
  262. }
  263. }
  264. /**
  265. * set_headattributes
  266. */
  267. protected function set_headattributes() {
  268. if (! isset($this->headattributes)) {
  269. $this->headattributes = '';
  270. }
  271. }
  272. /**
  273. * set_headcontent
  274. */
  275. protected function set_headcontent() {
  276. if (! isset($this->headcontent)) {
  277. $this->headcontent = '';
  278. }
  279. }
  280. /**
  281. * set_bodyattributes
  282. */
  283. protected function set_bodyattributes() {
  284. if (! isset($this->bodyattributes)) {
  285. $this->bodyattributes = '';
  286. }
  287. }
  288. /**
  289. * set_bodycontent
  290. */
  291. protected function set_bodycontent() {
  292. if (! isset($this->bodycontent)) {
  293. $this->bodycontent = '';
  294. }
  295. }
  296. /**
  297. * postprocessing
  298. */
  299. protected function postprocessing() {
  300. // procesessing for this output format after content has been retrieved from cache
  301. // This is intended for fixes to $this->headcontent and $this->bodycontent,
  302. // that are not to be included in the cached data, e.g. $this->fix_title_icons()
  303. // If you want to fix $this->headcontent and $this->bodycontent before caching,
  304. // add your own "set_bodycontent()" method
  305. }
  306. /**
  307. * fix_title
  308. */
  309. function fix_title() {
  310. if (preg_match($this->tagpattern('h2'), $this->bodycontent, $matches, PREG_OFFSET_CAPTURE)) {
  311. // $matches: <h2 $matches[1]>$matches[2]</h2>
  312. $start = $matches[2][1];
  313. $length = strlen($matches[2][0]);
  314. $this->bodycontent = substr_replace($this->bodycontent, $this->get_title(), $start, $length);
  315. }
  316. }
  317. /**
  318. * fix_title_icons
  319. */
  320. function fix_title_icons() {
  321. // add quiz edit icons if the current user is a teacher/administrator
  322. if ($this->hotpot->can_manage()) {
  323. if (preg_match($this->tagpattern('h2'), $this->bodycontent, $matches, PREG_OFFSET_CAPTURE)) {
  324. // $matches: <h2 $matches[1]>$matches[2]</h2>
  325. $start = $matches[2][1] + strlen($matches[2][0]);
  326. $editicon = $this->modedit_icon($this->hotpot);
  327. $this->bodycontent = substr_replace($this->bodycontent, $editicon, $start, 0);
  328. }
  329. }
  330. }
  331. /////////////////////////////////////////////////////////////////////
  332. // functions to merge external page with Moodle page //
  333. /////////////////////////////////////////////////////////////////////
  334. /**
  335. * generates the header for this hotpot attempt
  336. *
  337. * @return string $output
  338. */
  339. function hotpot_header() {
  340. $header = $this->header();
  341. // $this->xmldeclaration
  342. // $this->doctype
  343. // $this->htmlattributes
  344. // $this->headattributes
  345. if ($pos = strpos($header, '</head>')) {
  346. $header = substr_replace($header, $this->headcontent, $pos, 0);
  347. }
  348. // $this->bodyattributes
  349. return $header;
  350. }
  351. /**
  352. * generates the main content for this hotpot attempt
  353. *
  354. * @return string $output
  355. */
  356. function hotpot_content() {
  357. return $this->bodycontent;
  358. }
  359. /**
  360. * generates the footer for this hotpot attempt
  361. *
  362. * @return string $output
  363. */
  364. function hotpot_footer() {
  365. return $this->footer();
  366. }
  367. /**
  368. * outputs the main content for this page
  369. * (i.e. what comes between the header and footer)
  370. *
  371. * @return string $output
  372. */
  373. function maincontent() {
  374. return $this->bodycontent;
  375. }
  376. /////////////////////////////////////////////////////////////////////
  377. // functions to generate frames //
  378. /////////////////////////////////////////////////////////////////////
  379. /**
  380. * basetag
  381. */
  382. function basetag() {
  383. global $CFG;
  384. if (empty($CFG->framename)) {
  385. $framename = '_top';
  386. } else {
  387. $framename = $CFG->framename;
  388. }
  389. return html_writer::empty_tag('base', array('target'=>$framename, 'href'=>''));
  390. // Note: href is required for strict xhtml
  391. }
  392. /**
  393. * fix_targets
  394. */
  395. function fix_targets() {
  396. global $CFG;
  397. if (empty($CFG->framename)) {
  398. $framename = '_top';
  399. } else {
  400. $framename = $CFG->framename;
  401. }
  402. $this->bodycontent .= ''
  403. .'<script type="text/javascript">'."\n"
  404. .'//<![CDATA['."\n"
  405. ." var obj = document.getElementsByTagName('a');\n"
  406. ." if (obj) {\n"
  407. ." var i_max = obj.length;\n"
  408. ." for (var i=0; i<i_max; i++) {\n"
  409. ." if (obj[i].href && ! obj[i].target) {\n"
  410. ." obj[i].target = '$framename';\n"
  411. ." }\n"
  412. ." }\n"
  413. ." var obj = null;\n"
  414. ." }\n"
  415. ." var obj = document.getElementsByTagName('form');\n"
  416. ." if (obj) {\n"
  417. ." var i_max = obj.length;\n"
  418. ." for (var i=0; i<i_max; i++) {\n"
  419. ." if (obj[i].action && ! obj[i].target) {\n"
  420. ." obj[i].target = '$framename';\n"
  421. ." }\n"
  422. ." }\n"
  423. ." var obj = null;\n"
  424. ." }\n"
  425. .'//]]>'."\n"
  426. .'</script>'."\n"
  427. ;
  428. }
  429. /**
  430. * print frameset containing "top" and "main" frames
  431. */
  432. function print_frameset() {
  433. global $CFG;
  434. $charset = 'utf-8';
  435. $direction = get_string('thisdirection', 'langconfig');
  436. $title = format_text($this->hotpot->name);
  437. $title_top = get_string('navigation_frame', 'hotpot');
  438. $title_main = get_string('modulename', 'hotpot');
  439. $src_top = $this->hotpot->attempt_url('top');
  440. $src_main = $this->hotpot->attempt_url('main');
  441. if (empty($CFG->hotpot_lockframe)) {
  442. $lock_frameset = '';
  443. $lock_top = '';
  444. $lock_main = '';
  445. } else {
  446. $lock_frameset = ' border="0" frameborder="0" framespacing="0"';
  447. $lock_top = ' noresize="noresize" scrolling="no"';
  448. $lock_main = ' noresize="noresize"';
  449. }
  450. if (empty($CFG->hotpot_frameheight)) {
  451. $rows = 85; // default
  452. } else {
  453. $rows = $CFG->hotpot_frameheight;
  454. }
  455. echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'."\n";
  456. echo '<html dir="'.$direction.'">'."\n";
  457. echo '<head>'."\n";
  458. echo '<meta http-equiv="content-type" content="text/html; charset='.$charset.'" />'."\n";
  459. echo $this->basetag()."\n";
  460. echo '<title>'.$title.'</title>'."\n";
  461. echo '</head>'."\n";
  462. echo '<frameset rows="'.$rows.',*".'.$lock_frameset.'>'."\n";
  463. echo '<frame title="'.$title_top.'" src="'.$src_top.'"'.$lock_top.' />'."\n";
  464. echo '<frame title="'.$title_main.'" src="'.$src_main.'"'.$lock_main.' />'."\n";
  465. echo '<noframes>'."\n";
  466. echo '<p>'.get_string('framesetinfo').'</p>'."\n";
  467. echo '<ul>'."\n";
  468. echo '<li><a href="'.$src_top.'">'.$title_top.'</a></li>'."\n";
  469. echo '<li><a href="'.$src_main.'">'.$title_main.'</a></li>'."\n";
  470. echo '</ul>'."\n";
  471. echo '</noframes>'."\n";
  472. echo '</frameset>'."\n";
  473. echo '</html>'."\n";
  474. }
  475. /**
  476. * print "top" frame, containing Moodle navigation bar
  477. */
  478. function print_topframe() {
  479. echo $this->header();
  480. echo $this->footer();
  481. }
  482. /**
  483. * print a page embedded in an object in a standard Moodle page
  484. *
  485. * for XHTML 1.0 Strict compatability, the embedded page should be implemented
  486. * using an <object> not an <iframe>. However, IE <object>'s are problematic
  487. * (links and forms cannot escape), so we use conditional comments to display
  488. * an <iframe> in IE and an <object> in other browsers
  489. */
  490. function print_embed_object_page() {
  491. // external javascript to adjust height of iframe
  492. $this->page->requires->js('/mod/hotpot/attempt/iframe.js', true);
  493. echo $this->header();
  494. // set object attributes
  495. $id = $this->embed_object_id;
  496. $width = '100%';
  497. $height = '100%';
  498. $onload_function = $this->embed_object_onload;
  499. $src = $this->hotpot->attempt_url('main');
  500. // print the html element to hold the embedded html page
  501. // Note: the iframe in IE needs a "name" attribute for the resizing to work
  502. echo '<!--[if IE]>'."\n";
  503. echo '<iframe name="'.$id.'" id="'.$id.'" src="'.$src.'" width="'.$width.'" height="'.$height.'"></iframe>'."\n";
  504. echo '<![endif]-->'."\n";
  505. echo '<!--[if !IE]> <-->'."\n";
  506. echo '<object id="'.$id.'" type="text/html" data="'.$src.'" width="'.$width.'" height="'.$height.'"></object>'."\n";
  507. echo '<!--> <![endif]-->'."\n";
  508. // javascript to add onload event handler - we do this here because
  509. // an object tag should have no onload attribute in XHTML 1.0 Strict
  510. echo '<script type="text/javascript">'."\n";
  511. echo '//<![CDATA['."\n";
  512. echo "var obj = document.getElementById('$id');\n";
  513. echo "if (obj) {\n";
  514. echo " if (obj.addEventListener) {\n";
  515. echo " obj.addEventListener('load', $onload_function, false);\n";
  516. echo " } else if (obj.attachEvent) {\n";
  517. echo " obj.attachEvent('onload', $onload_function);\n";
  518. echo " } else {\n";
  519. echo " obj['onload'] = $onload_function;\n";
  520. echo " }\n";
  521. echo "}\n";
  522. echo "obj = null;\n";
  523. echo '//]]>'."\n";
  524. echo '</script>'."\n";
  525. echo $this->footer();
  526. }
  527. /////////////////////////////////////////////////////////////////////
  528. // functions to set and get cached content //
  529. /////////////////////////////////////////////////////////////////////
  530. /**
  531. * get_cache_md5key
  532. */
  533. function get_cache_md5key() {
  534. global $CFG;
  535. return md5($this->hotpot->outputformat . $this->page->theme->name . $CFG->wwwroot);
  536. }
  537. /**
  538. * get_fields_from_cache
  539. *
  540. * @return xxx
  541. */
  542. function get_fields_from_cache() {
  543. global $CFG, $DB;
  544. if (isset($this->cache_uptodate)) {
  545. return $this->cache_uptodate;
  546. }
  547. // assume cache is not up-to-date
  548. $this->cache_uptodate = false;
  549. if (empty($CFG->hotpot_enablecache)) {
  550. return false; // cache not enabled
  551. }
  552. if (! $this->use_hotpot_cache) {
  553. return false; // this renderer doesn't use a cache
  554. }
  555. $hotpotid = $this->hotpot->id;
  556. $md5key = $this->get_cache_md5key();
  557. $select = "hotpotid=$hotpotid AND md5key='$md5key'";
  558. if (! $this->cache = $DB->get_record_select('hotpot_cache', $select)) {
  559. return false; // no cached content for this quiz (+ outputformat + currenttheme + wwwroot)
  560. }
  561. foreach ($this->cache_CFG_fields as $field) {
  562. if ($this->cache->$field != $CFG->$field) {
  563. return false; // $CFG settings have changed
  564. }
  565. }
  566. foreach ($this->cache_hotpot_fields as $field) {
  567. if ($this->cache->$field != $this->hotpot->$field) {
  568. return false; // quiz settings have changed
  569. }
  570. }
  571. // custom fields
  572. if (isset($this->hotpot->source) && $this->cache->timemodified < $this->hotpot->source->filemtime($this->cache->sourcelastmodified, $this->cache->sourceetag)) {
  573. return false; // sourcefile file has been modified
  574. }
  575. if (isset($this->hotpot->source->config) && $this->cache->timemodified < $this->hotpot->source->config->filemtime($this->cache->configlastmodified, $this->cache->configetag)) {
  576. return false; // config file has been modified
  577. }
  578. if ($this->hotpot->useglossary) {
  579. $select = 'course = ? AND module = ? AND action IN (?, ?, ?, ?) AND time > ?';
  580. $params = array(
  581. $this->hotpot->course->id, 'glossary',
  582. 'add entry','approve entry','update entry','delete entry',
  583. $this->cache->timemodified
  584. );
  585. if ($DB->record_exists_select('log', $select, $params)) {
  586. return false; // glossary entries (for this course) have been modified
  587. }
  588. }
  589. // if we get this far then the cache content is uptodate
  590. // transfer cache content to this quiz object
  591. $content = unserialize(base64_decode($this->cache->content));
  592. foreach ($this->cache_content_fields as $field) {
  593. $this->$field = $content->$field;
  594. }
  595. $this->cache_uptodate = true;
  596. return $this->cache_uptodate;
  597. }
  598. /**
  599. * set_fields_in_cache
  600. *
  601. * @return xxx
  602. */
  603. function set_fields_in_cache() {
  604. global $CFG, $DB;
  605. if (empty($CFG->hotpot_enablecache)) {
  606. return; // cache not enabled
  607. }
  608. if ($this->cache_uptodate) {
  609. return; // cache is already uptodate
  610. }
  611. if (! $this->cache) {
  612. $this->cache = new stdClass();
  613. }
  614. // add special fields to cache record
  615. $this->cache->hotpotid = $this->hotpot->id;
  616. $this->cache->md5key = $this->get_cache_md5key();
  617. $this->cache->timemodified = time();
  618. // transfer $CFG fields to cache record
  619. foreach ($this->cache_CFG_fields as $field) {
  620. $this->cache->$field = $CFG->$field;
  621. }
  622. // transfer quiz fields to cache record
  623. foreach ($this->cache_hotpot_fields as $field) {
  624. $this->cache->$field = $this->hotpot->$field;
  625. }
  626. // transfer remote access fields to cache record
  627. foreach ($this->cache_remote_fields as $field) {
  628. $sourcefield = 'source'.$field;
  629. $configfield = 'config'.$field;
  630. $this->cache->$sourcefield = ''; // $this->hotpot->source->$field;
  631. $this->cache->$configfield = ''; // $this->hotpot->source->config->$field;
  632. }
  633. // create content object
  634. $content = new stdClass();
  635. foreach ($this->cache_content_fields as $field) {
  636. $content->$field = $this->$field;
  637. }
  638. // serialize the $content object
  639. $this->cache->content = base64_encode(serialize($content));
  640. // add / update the cache record
  641. if (isset($this->cache->id)) {
  642. if (! $DB->update_record('hotpot_cache', $this->cache)) {
  643. print_error('error_updaterecord', 'hotpot', '', 'hotpot_cache');
  644. }
  645. } else {
  646. if (! $this->cache->id = $DB->insert_record('hotpot_cache', $this->cache)) {
  647. print_error('error_insertrecord', 'hotpot', '', 'hotpot_cache');
  648. }
  649. }
  650. // cache record was successfully updated/inserted
  651. $this->cache_uptodate = true;
  652. return $this->cache_uptodate;
  653. }
  654. /**
  655. * get_name
  656. *
  657. * @return xxx
  658. */
  659. function get_name() {
  660. return $this->hotpot->source->get_name();
  661. }
  662. /**
  663. * get_title
  664. *
  665. * @return xxx
  666. */
  667. function get_title() {
  668. $format_string = false;
  669. switch ($this->hotpot->title & hotpot::TITLE_SOURCE) {
  670. case hotpot::TEXTSOURCE_FILE:
  671. $title = $this->hotpot->source->get_title();
  672. break;
  673. case hotpot::TEXTSOURCE_FILENAME:
  674. $title = basename($this->hotpot->sourcefile);
  675. break;
  676. case hotpot::TEXTSOURCE_FILEPATH:
  677. $title = ltrim($this->hotpot->sourcefile, '/');
  678. break;
  679. case hotpot::TEXTSOURCE_SPECIFIC:
  680. default:
  681. $title = $this->hotpot->name;
  682. $title = format_string($title); // this will strip tags
  683. }
  684. if ($this->hotpot->title & hotpot::TITLE_UNITNAME) {
  685. $title = $this->hotpot->name.': '.$title;
  686. }
  687. if ($this->hotpot->title & hotpot::TITLE_SORTORDER) {
  688. $title .= ' ('.$this->sortorder.')';
  689. }
  690. $title = hotpot_textlib('utf8_to_entities', $title);
  691. return $title;
  692. }
  693. /////////////////////////////////////////////////////////////////////
  694. // utility functions to indicate whether user can resume, restart //
  695. /////////////////////////////////////////////////////////////////////
  696. // does this output format allow quiz attempts to be resumed?
  697. /**
  698. * provide_resume
  699. *
  700. * @return xxx
  701. */
  702. function provide_resume() {
  703. return false;
  704. }
  705. // does this output format allow a clickreport
  706. // show a click trail of what students clicked
  707. /**
  708. * provide_clickreport
  709. *
  710. * @return xxx
  711. */
  712. function provide_clickreport() {
  713. return false;
  714. }
  715. // can the current unit/quiz attempt be paused and resumed later?
  716. /**
  717. * can_resume
  718. *
  719. * @param xxx $type
  720. * @return xxx
  721. */
  722. function can_resume($type) {
  723. global $QUIZPORT;
  724. if ($type=='unit' || ($type=='quiz' && $this->provide_resume())) {
  725. if (isset($QUIZPORT->$type) && $QUIZPORT->$type->allowresume) {
  726. return true;
  727. }
  728. }
  729. return false;
  730. }
  731. // can the current unit/quiz be restarted after the current attempt finishes?
  732. /**
  733. * can_restart
  734. *
  735. * @param xxx $type
  736. * @return xxx
  737. */
  738. function can_restart($type) {
  739. global $QUIZPORT;
  740. if (isset($QUIZPORT->$type) && $QUIZPORT->$type->attemptlimit) {
  741. if ($attempts = $QUIZPORT->get_attempts($type)) {
  742. if (count($attempts) >= $QUIZPORT->$type->attemptlimit) {
  743. return false;
  744. }
  745. }
  746. }
  747. return true;
  748. }
  749. /**
  750. * can_continue
  751. *
  752. * @return xxx
  753. */
  754. function can_continue() {
  755. if ($this->can_resume('unit')) {
  756. if ($this->can_resume('quiz')) {
  757. return hotpot::CONTINUE_RESUMEQUIZ;
  758. } else if ($this->can_restart('quiz')) {
  759. return hotpot::CONTINUE_RESTARTQUIZ;
  760. }
  761. }
  762. if ($this->can_restart('unit')) {
  763. return hotpot::CONTINUE_RESTARTUNIT;
  764. } else {
  765. return hotpot::CONTINUE_ABANDONUNIT;
  766. }
  767. }
  768. /**
  769. * can_clickreport
  770. *
  771. * @return xxx
  772. */
  773. function can_clickreport() {
  774. if ($this->provide_clickreport() && isset($this->hotpot) && $this->hotpot->clickreporting) {
  775. return true;
  776. } else {
  777. return false;
  778. }
  779. }
  780. /////////////////////////////////////////////////////////////////////
  781. // utility functions for extracting and cleaning html //
  782. /////////////////////////////////////////////////////////////////////
  783. /**
  784. * remove_blank_lines
  785. *
  786. * @param xxx $str
  787. * @return xxx
  788. */
  789. function remove_blank_lines($str) {
  790. // standardize line endings and remove trailing white space and blank lines
  791. $str = preg_replace('/\s+[\r\n]/s', "\n", $str);
  792. return $str;
  793. }
  794. /**
  795. * single_line
  796. *
  797. * @param xxx $str
  798. * @return xxx
  799. */
  800. function single_line($str) {
  801. return trim(preg_replace('/\s+/s', ' ', $str));
  802. }
  803. /**
  804. * tagpattern
  805. *
  806. * @param xxx $tag
  807. * @param xxx $attribute (optional, default='')
  808. * @param xxx $returncontent (optional, default=true)
  809. * @param xxx $before (optional, default='')
  810. * @param xxx $after (optional, default='')
  811. * @return xxx
  812. */
  813. function tagpattern($tag, $attribute='', $returncontent=true, $before='', $after='') {
  814. // $0 : entire match
  815. // if $attribute is empty
  816. // $1 : all tag attrbutes
  817. // $2 : content (if required)
  818. // if $attribute is NOT empty
  819. // $1 : all tag attrbutes
  820. // $2 : first quote of required attibute
  821. // $3 : value of required attibute
  822. // $4 : closing quote of required attibute
  823. // $5 : content (if required)
  824. if ($attribute) {
  825. $attribute .= '=(["\'])(.*?)(\\2)[^>]*';
  826. }
  827. if ($returncontent) {
  828. $content = '(.*?)<\/'.$tag.'>';
  829. } else {
  830. $content = '';
  831. }
  832. return '/'.$before.'<'.$tag.'([^>]*'.$attribute.')>'.$content.$after.'/is';
  833. }
  834. /////////////////////////////////////////////////////////////////////
  835. // utility functions for fixing css, media, onload, and urls //
  836. /////////////////////////////////////////////////////////////////////
  837. /**
  838. * fix_css_definitions
  839. *
  840. * @param xxx $match
  841. * @return xxx
  842. */
  843. function fix_css_definitions($match) {
  844. global $CFG;
  845. $container = '#'.$this->themecontainer;
  846. $css_selector = $match[1];
  847. $css_definition = $match[2];
  848. // additional CSS for list items
  849. $listitem_css = '';
  850. $selectors = array();
  851. foreach (explode(',', $css_selector) as $selector) {
  852. if ($selector = trim($selector)) {
  853. switch (true) {
  854. case preg_match('/^html\b/i', $selector):
  855. // leave "html" as it is
  856. $selectors[] = "$selector";
  857. break;
  858. case preg_match('/^body\b/i', $selector):
  859. // by default, we do nothing here, so that
  860. // HP styles do not affect the Moodle theme
  861. // if this site is set to enable HP body styles
  862. // we replace "body" with the container element
  863. if (empty($CFG->hotpot_bodystyles)) {
  864. $bodystyles = 0;
  865. } else {
  866. $callback = create_function('$x,$y', 'return ($x | $y);');
  867. $bodystyles = explode(',', $CFG->hotpot_bodystyles);
  868. $bodystyles = array_reduce($bodystyles, $callback, 0);
  869. }
  870. // remove font, margin, backgroud and color from the css definition
  871. $search = array();
  872. if (! ($bodystyles & hotpot::BODYSTYLES_BACKGROUND)) {
  873. // background-color, background-image
  874. $search[] = '(?:background[a-z-]*)';
  875. }
  876. if (! ($bodystyles & hotpot::BODYSTYLES_COLOR)) {
  877. // color (the text color)
  878. $search[] = '(?:color[a-z-]*)';
  879. }
  880. if (! ($bodystyles & hotpot::BODYSTYLES_FONT)) {
  881. // font-size, font-family
  882. $search[] = '(?:font[a-z-]*)';
  883. }
  884. if (! ($bodystyles & hotpot::BODYSTYLES_MARGIN)) {
  885. // margin-left, margin-right
  886. $search[] = '(?:margin[a-z-]*)';
  887. }
  888. if ($search = implode('|', $search)) {
  889. $search = "/[ \t]+($search)[^;]*;[ \t]*[\n\r]*/";
  890. $css_definition = preg_replace($search, '', $css_definition);
  891. }
  892. if (trim($css_definition)) {
  893. $selectors[] = "$container";
  894. }
  895. break;
  896. default:
  897. // we need to do some special processing of CSS for list items
  898. // override standard Moodle 2.0 setting of li {list-style-type }
  899. if (preg_match('/(ol|ul)([.#]\w+)?$/', $selector)) {
  900. // CSS for a list ("ol" or "ul")
  901. $search = '/\s*'.'list-style-type'.'\s*:\s*'.'[a-zA-Z0-9_-]+/';
  902. if (preg_match($search, $css_definition, $matches)) {
  903. $listitem_css .= "\n$container $selector li {\n".$matches[0].";\n}";
  904. }
  905. }
  906. // restrict other CSS selectors to affect only the content of the container element
  907. $selectors[] = "$container $selector";
  908. }
  909. }
  910. }
  911. if (empty($selectors)) {
  912. return '';
  913. } else {
  914. return implode(",\n", $selectors)."\n".'{'.$css_definition.'}'.$listitem_css;
  915. }
  916. }
  917. /**
  918. * fix_onload
  919. *
  920. * @param xxx $onload
  921. * @param xxx $script_tags (optional, default=false)
  922. * @return xxx
  923. */
  924. function fix_onload($onload, $script_tags=false) {
  925. static $attacheventid = 0;
  926. $str = '';
  927. if ($script_tags) {
  928. $str .= "\n".'<script type="text/javascript">'."\n"."//<![CDATA[\n";
  929. }
  930. if ($attacheventid && $attacheventid==$this->hotpot->id) {
  931. // do nothing
  932. } else {
  933. // only do this once per quiz
  934. $attacheventid = $this->hotpot->id;
  935. $str .= ''
  936. ."/**\n"
  937. ." * Based on http://phrogz.net/JS/AttachEvent_js.txt - thanks!\n"
  938. ." * That code is copyright 2003 by Gavin Kistner, !@phrogz.net\n"
  939. ." * and is covered under the license viewable at http://phrogz.net/JS/_ReuseLicense.txt\n"
  940. ." */\n"
  941. ."function hotpotAttachEvent(obj, evt, fnc, useCapture) {\n"
  942. ." // obj : an HTML element\n"
  943. ." // evt : the name of the event (without leading 'on')\n"
  944. ." // fnc : the name of the event handler funtion\n"
  945. ." // useCapture : boolean (default = false)\n"
  946. ." if (typeof(fnc)=='string') {\n"
  947. ." fnc = new Function(fnc);\n"
  948. ." }\n"
  949. ." // transfer object's old event handler (if any)\n"
  950. ." var onevent = 'on' + evt;\n"
  951. ." if (obj[onevent]) {\n"
  952. ." var old_event_handler = obj[onevent];\n"
  953. ." obj[onevent] = null;\n"
  954. ." hotpotAttachEvent(obj, evt, old_event_handler, useCapture);\n"
  955. ." }\n"
  956. ." // create key for this event handler\n"
  957. ." var s = fnc.toString();\n"
  958. .' s = s.replace(new RegExp("[; \\\\t\\\\n\\\\r]+", "g"), "");'."\n"
  959. .' s = s.substring(s.indexOf("{") + 1, s.lastIndexOf("}"));'."\n"
  960. ." // skip event handler, if it is a duplicate\n"
  961. ." if (! obj.evt_keys) {\n"
  962. ." obj.evt_keys = new Array();\n"
  963. ." }\n"
  964. ." if (obj.evt_keys[s]) {\n"
  965. ." return true;\n"
  966. ." }\n"
  967. ." obj.evt_keys[s] = true;\n"
  968. ." // standard DOM\n"
  969. ." if (obj.addEventListener) {\n"
  970. ." obj.addEventListener(evt, fnc, (useCapture ? true : false));\n"
  971. ." return true;\n"
  972. ." }\n"
  973. ." // IE\n"
  974. ." if (obj.attachEvent) {\n"
  975. ." return obj.attachEvent(onevent, fnc);\n"
  976. ." }\n"
  977. ." // old browser (e.g. NS4 or IE5Mac)\n"
  978. ." if (! obj.evts) {\n"
  979. ." obj.evts = new Array();\n"
  980. ." }\n"
  981. ." if (! obj.evts[onevent]) {\n"
  982. ." obj.evts[onevent] = new Array();\n"
  983. ." }\n"
  984. ." var i = obj.evts[onevent].length;\n"
  985. ." obj.evts[onevent][i] = fnc;\n"
  986. ." obj[onevent] = new Function('var onevent=\"'+onevent+'\"; for (var i=0; i<this.evts[onevent].length; i++) this.evts[onevent][i]();');\n"
  987. ."}\n"
  988. ;
  989. }
  990. $onload_oneline = preg_replace('/\s+/s', ' ', $onload);
  991. $onload_oneline = preg_replace("/[\\']/", '\\\\$0', $onload_oneline);
  992. $str .= "hotpotAttachEvent(window, 'load', '$onload_oneline');\n";
  993. if ($script_tags) {
  994. $str .= "//]]>\n"."</script>\n";
  995. }
  996. return $str;
  997. }
  998. /**
  999. * fix_mediafilter
  1000. *
  1001. * @return xxx
  1002. */
  1003. function fix_mediafilter() {
  1004. global $CFG;
  1005. if (! $this->hotpot->usemediafilter) {
  1006. return false;
  1007. }
  1008. if (! hotpot::load_mediafilter_filter($this->hotpot->usemediafilter)) {
  1009. return false;
  1010. }
  1011. $mediafilterclass = 'hotpot_mediafilter_'.$this->hotpot->usemediafilter;
  1012. $mediafilter = new $mediafilterclass($this);
  1013. $mediafilter->fix('headcontent', $this);
  1014. $mediafilter->fix('bodycontent', $this);
  1015. if ($mediafilter->js_inline) {
  1016. // remove the internal </script><script ... > joins from the inline javascripts (js_inline)
  1017. $search = '/(?:\s*\/\/\]\]>)?'.'\s*<\/script>\s*<script type="text\/javascript">'.'(?:\s*\/\/<!\[CDATA\[[ \t]*)?/is';
  1018. $mediafilter->js_inline = preg_replace($search, '', $mediafilter->js_inline);
  1019. // extract urls of deferred scripts from $mediafilter->js_external
  1020. if (preg_match_all($this->tagpattern('script'), $mediafilter->js_external, $scripts, PREG_OFFSET_CAPTURE)) {
  1021. $deferred_js = array();
  1022. $strlen_wwwroot = strlen($CFG->wwwroot);
  1023. foreach (array_reverse($scripts[0]) as $script) {
  1024. // $script [0] => matched string, [1] => offset to start of matched string
  1025. $remove = false;
  1026. if (strpos($script[0], 'type="text/javascript"')) {
  1027. if (preg_match('/src="(.*?)"/i', $script[0], $matches)) {
  1028. if (strpos($script[0], 'defer="defer"')===false) {
  1029. $inhead = true;
  1030. } else {
  1031. $inhead = false;
  1032. }
  1033. // we do not add scripts with $this->page->requires->js() because
  1034. // they will not then be added when content is retrieved from cache
  1035. //if (substr($matches[1], 0, $strlen_wwwroot)==$CFG->wwwroot) {
  1036. // $this->page->requires->js(substr($matches[1], $strlen_wwwroot), $inhead);
  1037. // $remove = true;
  1038. //} else
  1039. if ($inhead) {
  1040. // leave this script where it is (i.e. in the head)
  1041. } else {
  1042. array_unshift($deferred_js, '"'.addslashes_js($matches[1]).'"');
  1043. $remove = true;
  1044. }
  1045. }
  1046. }
  1047. if ($remove) {
  1048. $mediafilter->js_external = substr_replace($mediafilter->js_external, '', $script[1], strlen($script[0]));
  1049. }
  1050. }
  1051. $deferred_js = implode(',', array_unique($deferred_js));
  1052. } else {
  1053. $deferred_js = '';
  1054. }
  1055. if ($deferred_js) {
  1056. $deferred_js = ''
  1057. .' // load deferred scripts'."\n"
  1058. .' var head = document.getElementsByTagName("head")[0];'."\n"
  1059. .' var urls = new Array('.$deferred_js.');'."\n"
  1060. .' for (var i=0; i<urls.length; i++) {'."\n"
  1061. .' var script = document.createElement("script");'."\n"
  1062. .' script.type = "text/javascript";'."\n"
  1063. .' script.src = urls[i];'."\n"
  1064. .' head.appendChild(script);'."\n"
  1065. .' }'."\n"
  1066. ;
  1067. }
  1068. $functions = '';
  1069. if (preg_match_all('/(?<=function )\w+/', $mediafilter->js_inline, $names)) {
  1070. foreach ($names[0] as $name) {
  1071. list($start, $finish) = $this->locate_js_function($name, $mediafilter->js_inline, true);
  1072. if ($finish) {
  1073. $functions .= trim(substr($mediafilter->js_inline, $start, ($finish - $start)))."\n";
  1074. $mediafilter->js_inline = substr_replace($mediafilter->js_inline, '', $start, ($finish - $start));
  1075. }
  1076. }
  1077. }
  1078. // put all the inline javascript into one single function called "hotpot_mediafilter_loader()",
  1079. // which also loads up any deferred js, and force this function to be run when the page has loaded
  1080. $onload = 'hotpot_mediafilter_loader()';
  1081. $search = '/(\/\/<!\[CDATA\[)(.*)(\/\/\]\]>)/s';
  1082. $replace = '$1'."\n"
  1083. .$functions
  1084. .'function '.$onload.'{'
  1085. .'$2'
  1086. //."\n"
  1087. .$deferred_js // load deferred scripts, if any
  1088. .$this->fix_mediafilter_onload_extra()
  1089. .'} // end function '.$onload."\n"
  1090. ."\n"
  1091. .$this->fix_onload($onload)
  1092. .'$3'
  1093. ;
  1094. $mediafilter->js_inline = preg_replace($search, $replace, $mediafilter->js_inline, 1);
  1095. // append the inline javascripts to the end of the bodycontent
  1096. $this->bodycontent .= $mediafilter->js_inline;
  1097. }
  1098. if ($mediafilter->js_external) {
  1099. // append the external javascripts to the head content
  1100. $this->headcontent .= $mediafilter->js_external;
  1101. }
  1102. }
  1103. /**
  1104. * fix_mediafilter_onload_extra
  1105. *
  1106. * @return xxx
  1107. */
  1108. function fix_mediafilter_onload_extra() {
  1109. return '';
  1110. }
  1111. /**
  1112. * fix_relativeurls
  1113. *
  1114. * @param xxx $str (optional, default=null)
  1115. * @return xxx
  1116. */
  1117. function fix_relativeurls($str=null) {
  1118. // elements of the regular expression which will search for the URLs
  1119. $tagopen = '(?:(<)|(\\\\u003C)|(&lt;)|(&amp;#x003C;))'; // left angle bracket
  1120. $tagclose = '(?(2)>|(?(3)\\\\u003E|(?(4)&gt;|(?(5)&amp;#x003E;))))'; // right angle bracket (to match left angle bracket)
  1121. $space = '\s+'; // at least one space
  1122. $equals = '\s*=\s*'; // equals sign (+ white space)
  1123. $anychar = '(?:[^>]*?)'; // any character
  1124. $quoteopen = '("|\\\\"|&quot;|&amp;quot;'."|'|\\\\'|&apos;|&amp;apos;".')'; // open quote
  1125. $quoteclose = '\\6'; // close quote (to match open quote)
  1126. $url = '.*?'; // chars between quotes (non-greedy)
  1127. // define which attributes of which HTML tags to search for URLs
  1128. $tags = array(
  1129. // tag => attribute containing url
  1130. 'a' => 'href',
  1131. 'area' => 'href', // <area href="sun.htm" ... shape="..." coords="..." />
  1132. 'embed' => 'src',
  1133. 'iframe' => 'src',
  1134. 'img' => 'src',
  1135. 'input' => 'src', // <input type="image" src="..." >
  1136. 'link' => 'href',
  1137. 'object' => 'data',
  1138. 'param' => 'value',
  1139. 'script' => 'src',
  1140. 'source' => 'src', // HTML5
  1141. '(?:table|th|td)' => 'background',
  1142. '[a-z]+' => 'style'
  1143. );
  1144. // replace relative URLs in attributes of certain HTML tags
  1145. foreach ($tags as $tag=>$attribute) {
  1146. $search = "/($tagopen$tag$space$anychar$attribute$equals$quoteopen)($url)($quoteclose$anychar$tagclose)/is";
  1147. if ($attribute=='style') {
  1148. $callback = array($this, 'convert_urls_css');
  1149. } else if ($tag=='param') {
  1150. $callback = array($this, 'convert_url_param');
  1151. } else {
  1152. $callback = array($this, 'convert_url_relative');
  1153. }
  1154. if (is_string($str)) {
  1155. $str = preg_replace_callback($search, $callback, $str);
  1156. } else {
  1157. $this->headcontent = preg_replace_callback($search, $callback, $this->headcontent);
  1158. $this->bodycontent = preg_replace_callback($search, $callback, $this->bodycontent);
  1159. }
  1160. }
  1161. if (is_string($str)) {
  1162. return $str;
  1163. }
  1164. // replace relative URLs in stylesheets
  1165. $search = '/'.'(<style[^>]*>)'.'(.*?)'.'(<\/style>)'.'/is';
  1166. $callback = array($this, 'convert_urls_css');
  1167. $this->headcontent = preg_replace_callback($search, $callback, $this->headcontent);
  1168. $this->bodycontent = preg_replace_callback($search, $callback, $this->bodycontent);
  1169. // replace relative URLs in <a ... onclick="window.open('...')...">...</a>
  1170. $search = '/'.'('.'onclick="'."window.open\('".')'."([^']*)".'('."'[^\)]*\);return false;".'")'.'/is';
  1171. $callback = array($this, 'convert_url');
  1172. $this->bodycontent = preg_replace_callback($search, $callback, $this->bodycontent);
  1173. }
  1174. /**
  1175. * convert_urls_css
  1176. *
  1177. * @param xxx $match
  1178. * @return xxx
  1179. */
  1180. function convert_urls_css($match) {
  1181. $before = $match[1];
  1182. $css = $match[count($match) - 2];
  1183. $after = $match[count($match) - 1];
  1184. $search = '/(url\(['."'".'"]?)(.+?)(['."'".'"]?\))/is';
  1185. $callback = array($this, 'convert_url');
  1186. return $before.preg_replace_callback($search, $callback, $css).$after;
  1187. }
  1188. /**
  1189. * convert_url_param
  1190. *
  1191. * @param xxx $match
  1192. * @return xxx
  1193. */
  1194. function convert_url_param($match) {
  1195. // make sure the param "name" attribute is one we know about
  1196. $quote = $match[6];
  1197. $search = "/name\s*=\s*$quote(?:data|movie|src|FlashVars)$quote/i";
  1198. if (preg_match($search, $match[0])) {
  1199. return $this->convert_url_relative($match);
  1200. } else {
  1201. return $match[0];
  1202. }
  1203. }
  1204. /**
  1205. * convert_url_relative
  1206. *
  1207. * @param xxx $match
  1208. * @return xxx
  1209. */
  1210. function convert_url_relative($match) {
  1211. if (is_string($match)) {
  1212. $before = '';
  1213. $url = $match;
  1214. $after = '';
  1215. } else {
  1216. $before = $match[1];
  1217. $url = $match[count($match) - 2];
  1218. $after = $match[count($match) - 1];
  1219. }
  1220. switch (true) {
  1221. case preg_match('/^'.'\w+=[^&]+'.'('.'&((amp;#x0026;)?amp;)?'.'\w+=[^&]+)*'.'$/', $url):
  1222. // catch <PARAM name="FlashVars" value="TheSound=soundfile.mp3">
  1223. // ampersands can appear as "&", "&amp;" or "&amp;#x0026;amp;"
  1224. $query = $url;
  1225. $url = '';
  1226. $fragment = '';
  1227. break;
  1228. case preg_match('/^'.'([^?]*)'.'((?:\?[^#]*)?)'.'((?:#.*)?)'.'$/', $url, $matches):
  1229. // parse the $url into $matches
  1230. // [1] path
  1231. // [2] query string, if any
  1232. // [3] anchor fragment, if any
  1233. $url = $matches[1];
  1234. $query = $matches[2];
  1235. $fragment = $matches[3];
  1236. break;
  1237. default:
  1238. // there appears to be no query or fragment in this url
  1239. $query = '';
  1240. $fragment = '';
  1241. } // end switch
  1242. // convert the filepath part of theā€¦

Large files files are truncated, but you can click here to view the full file