PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/question/format/hotpot/format.php

https://bitbucket.org/ciceidev/cicei_moodle_conditional_activities
PHP | 719 lines | 561 code | 74 blank | 84 comment | 128 complexity | e1d2f895925809d23e0ca0341ab516c4 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause
  1. <?PHP // $Id$
  2. ////////////////////////////////////////////////////////////////////////////
  3. /// Hotpotatoes 5.0 and 6.0 Format
  4. ///
  5. /// This Moodle class provides all functions necessary to import
  6. /// (export is not implemented ... yet)
  7. ///
  8. ////////////////////////////////////////////////////////////////////////////
  9. // Based on default.php, included by ../import.php
  10. /**
  11. * @package questionbank
  12. * @subpackage importexport
  13. */
  14. require_once($CFG->dirroot . '/mod/hotpot/lib.php');
  15. class qformat_hotpot extends qformat_default {
  16. function provide_import() {
  17. return true;
  18. }
  19. function readquestions ($lines) {
  20. /// Parses an array of lines into an array of questions,
  21. /// where each item is a question object as defined by
  22. /// readquestion().
  23. // set courseid and baseurl
  24. global $CFG, $COURSE, $course;
  25. switch (true) {
  26. case isset($this->course->id):
  27. // import to quiz module
  28. $courseid = $this->course->id;
  29. break;
  30. case isset($course->id):
  31. // import to lesson module
  32. $courseid = $course->id;
  33. break;
  34. case isset($COURSE->id):
  35. // last resort
  36. $courseid = $COURSE->id;
  37. break;
  38. default:
  39. // shouldn't happen !!
  40. $courseid = 0;
  41. }
  42. require_once($CFG->libdir.'/filelib.php');
  43. $baseurl = get_file_url($courseid).'/';
  44. // get import file name
  45. global $params;
  46. if (! empty($this->realfilename)) {
  47. $filename = $this->realfilename;
  48. } else if (isset($params) && !empty($params->choosefile)) {
  49. // course file (Moodle >=1.6+)
  50. $filename = $params->choosefile;
  51. } else {
  52. // uploaded file (all Moodles)
  53. $filename = basename($_FILES['newfile']['tmp_name']);
  54. }
  55. // get hotpot file source
  56. $source = implode($lines, " ");
  57. $source = hotpot_convert_relative_urls($source, $baseurl, $filename);
  58. // create xml tree for this hotpot
  59. $xml = new hotpot_xml_tree($source);
  60. // determine the quiz type
  61. $xml->quiztype = '';
  62. $keys = array_keys($xml->xml);
  63. foreach ($keys as $key) {
  64. if (preg_match('/^(hotpot|textoys)-(\w+)-file$/i', $key, $matches)) {
  65. $xml->quiztype = strtolower($matches[2]);
  66. $xml->xml_root = "['$key']['#']";
  67. break;
  68. }
  69. }
  70. // convert xml to questions array
  71. $questions = array();
  72. switch ($xml->quiztype) {
  73. case 'jcloze':
  74. if (strpos($source, '<gap-fill><question-record>')) {
  75. $startwithgap = true;
  76. } else {
  77. $startwithgap = false;
  78. }
  79. $this->process_jcloze($xml, $questions, $startwithgap);
  80. break;
  81. case 'jcross':
  82. $this->process_jcross($xml, $questions);
  83. break;
  84. case 'jmatch':
  85. $this->process_jmatch($xml, $questions);
  86. break;
  87. case 'jmix':
  88. $this->process_jmix($xml, $questions);
  89. break;
  90. case 'jbc':
  91. case 'jquiz':
  92. $this->process_jquiz($xml, $questions);
  93. break;
  94. default:
  95. if (empty($xml->quiztype)) {
  96. notice("Input file not recognized as a Hot Potatoes XML file");
  97. } else {
  98. notice("Unknown quiz type '$xml->quiztype'");
  99. }
  100. } // end switch
  101. if (count($questions)) {
  102. return $questions;
  103. } else {
  104. if (method_exists($this, 'error')) { // Moodle >= 1.8
  105. $this->error(get_string('giftnovalidquestion', 'quiz'));
  106. }
  107. return false;
  108. }
  109. }
  110. function process_jcloze(&$xml, &$questions, $startwithgap) {
  111. // define default grade (per cloze gap)
  112. $defaultgrade = 1;
  113. $gap_count = 0;
  114. // detect old Moodles (1.4 and earlier)
  115. global $CFG, $db;
  116. $moodle_14 = false;
  117. if ($columns = $db->MetaColumns("{$CFG->prefix}question_multianswer")) {
  118. foreach ($columns as $column) {
  119. if ($column->name=='answers' || $column->name=='positionkey' || $column->name=='answertype' || $column->name=='norm') {
  120. $moodle_14 = true;
  121. }
  122. }
  123. }
  124. // xml tags for the start of the gap-fill exercise
  125. $tags = 'data,gap-fill';
  126. $x = 0;
  127. while (($exercise = "[$x]['#']") && $xml->xml_value($tags, $exercise)) {
  128. // there is usually only one exercise in a file
  129. if (method_exists($this, 'defaultquestion')) {
  130. $question = $this->defaultquestion();
  131. } else {
  132. $question = new stdClass();
  133. $question->usecase = 0; // Ignore case
  134. $question->image = ""; // No images with this format
  135. }
  136. $question->qtype = MULTIANSWER;
  137. $question->name = $this->hotpot_get_title($xml, $x);
  138. $question->questiontext = '';
  139. // add get dropdown list, if any
  140. if (intval($xml->xml_value('hotpot-config-file,'.$xml->quiztype.',use-drop-down-list'))) {
  141. $dropdownlist = $this->hotpot_jcloze_wordlist($xml);
  142. $answertype = MULTICHOICE;
  143. } else {
  144. $dropdownlist = false;
  145. $answertype = SHORTANSWER;
  146. // add wordlist, if required (not required if we are using dropdowns)
  147. if (intval($xml->xml_value('hotpot-config-file,'.$xml->quiztype.',include-word-list'))) {
  148. $question->questiontext .= '<p>'.implode(' ', $this->hotpot_jcloze_wordlist($xml)).'</p>';
  149. }
  150. }
  151. // add reading, if any
  152. $question->questiontext .= $this->hotpot_get_reading($xml);
  153. // setup answer arrays
  154. if ($moodle_14) {
  155. $question->answers = array();
  156. } else {
  157. global $COURSE; // initialized in questions/import.php
  158. $question->course = $COURSE->id;
  159. $question->options = new stdClass();
  160. $question->options->questions = array(); // one for each gap
  161. }
  162. $q = 0;
  163. $looping = true;
  164. while ($looping) {
  165. // get next bit of text
  166. $questiontext = $xml->xml_value($tags, $exercise."[$q]");
  167. $questiontext = $this->hotpot_prepare_str($questiontext);
  168. // get next gap
  169. $gap = '';
  170. $question_record = $exercise."['question-record'][$q]['#']";
  171. if ($xml->xml_value($tags, $question_record)) {
  172. // add gap
  173. $gap_count ++;
  174. $positionkey = $q+1;
  175. $gap = '{#'.$positionkey.'}';
  176. // initialize answer settings
  177. if ($moodle_14) {
  178. $question->answers[$q]->positionkey = $positionkey;
  179. $question->answers[$q]->answertype = $answertype;
  180. $question->answers[$q]->norm = $defaultgrade;
  181. $question->answers[$q]->alternatives = array();
  182. } else {
  183. $wrapped = new stdClass();
  184. $wrapped->qtype = $answertype;
  185. $wrapped->usecase = 0;
  186. $wrapped->defaultgrade = $defaultgrade;
  187. $wrapped->questiontextformat = 0;
  188. $wrapped->answer = array();
  189. $wrapped->fraction = array();
  190. $wrapped->feedback = array();
  191. // required for multichoice
  192. $wrapped->single = 1;
  193. $wrapped->answernumbering = 0;
  194. $wrapped->shuffleanswers = 0;
  195. $wrapped->correctfeedback = '';
  196. $wrapped->partiallycorrectfeedback = '';
  197. $wrapped->incorrectfeedback = '';
  198. // array of answers
  199. $answers = array();
  200. }
  201. // add answers
  202. if ($dropdownlist) {
  203. $a = 0;
  204. $correcttext = '';
  205. $correctfeedback = '';
  206. while (($answer=$question_record."['answer'][$a]['#']") && $xml->xml_value($tags, $answer)) {
  207. if (intval($xml->xml_value($tags, $answer."['correct'][0]['#']"))) {
  208. $correcttext = $this->hotpot_prepare_str($xml->xml_value($tags, $answer."['text'][0]['#']"));
  209. $correctfeedback = $this->hotpot_prepare_str($xml->xml_value($tags, $answer."['feedback'][0]['#']"));
  210. break;
  211. }
  212. $a++;
  213. }
  214. foreach ($dropdownlist as $a => $answer) {
  215. if ($answer==$correcttext) {
  216. $fraction = 1;
  217. $feedback = $correctfeedback;
  218. } else {
  219. $fraction = 0;
  220. $feedback = '';
  221. }
  222. if ($moodle_14) {
  223. $question->answers[$q]->alternatives[$a] = new stdClass();
  224. $question->answers[$q]->alternatives[$a]->answer = $answer;
  225. $question->answers[$q]->alternatives[$a]->fraction = $fraction;
  226. $question->answers[$q]->alternatives[$a]->feedback = $feedback;
  227. } else {
  228. $wrapped->answer[] = $answer;
  229. $wrapped->fraction[] = $fraction;
  230. $wrapped->feedback[] = $feedback;
  231. $answers[] = ($fraction==0 ? '' : '=').$answer.($feedback=='' ? '' : ('#'.$feedback));
  232. }
  233. }
  234. } else {
  235. $a = 0;
  236. while (($answer=$question_record."['answer'][$a]['#']") && $xml->xml_value($tags, $answer)) {
  237. $text = $this->hotpot_prepare_str($xml->xml_value($tags, $answer."['text'][0]['#']"));
  238. $correct = $xml->xml_value($tags, $answer."['correct'][0]['#']");
  239. $feedback = $this->hotpot_prepare_str($xml->xml_value($tags, $answer."['feedback'][0]['#']"));
  240. if (strlen($text)) {
  241. // set score (0=0%, 1=100%)
  242. $fraction = empty($correct) ? 0 : 1;
  243. // store answer
  244. if ($moodle_14) {
  245. $question->answers[$q]->alternatives[$a] = new stdClass();
  246. $question->answers[$q]->alternatives[$a]->answer = $text;
  247. $question->answers[$q]->alternatives[$a]->fraction = $fraction;
  248. $question->answers[$q]->alternatives[$a]->feedback = $feedback;
  249. } else {
  250. $wrapped->answer[] = $text;
  251. $wrapped->fraction[] = $fraction;
  252. $wrapped->feedback[] = $feedback;
  253. $answers[] = (empty($fraction) ? '' : '=').$text.(empty($feedback) ? '' : ('#'.$feedback));
  254. }
  255. }
  256. $a++;
  257. }
  258. }
  259. // compile answers into question text, if necessary
  260. if ($moodle_14) {
  261. // do nothing
  262. } else {
  263. $wrapped->questiontext = '{'.$defaultgrade.':'.$answertype.':'.implode('~', $answers).'}';
  264. $question->options->questions[] = $wrapped;
  265. }
  266. } // end if gap
  267. if (strlen($questiontext) || strlen($gap)) {
  268. if ($startwithgap) {
  269. $question->questiontext .= $gap.$questiontext;
  270. } else {
  271. $question->questiontext .= $questiontext.$gap;
  272. }
  273. } else {
  274. $looping = false;
  275. }
  276. $q++;
  277. } // end while $looping
  278. if ($q) {
  279. // define total grade for this exercise
  280. $question->defaultgrade = $gap_count * $defaultgrade;
  281. // add this cloze as a single question object
  282. $questions[] = $question;
  283. } else {
  284. // no gaps found in this text so don't add this question
  285. // import will fail and error message will be displayed:
  286. }
  287. $x++;
  288. } // end while $exercise
  289. }
  290. function hotpot_jcloze_wordlist(&$xml) {
  291. $wordlist = array();
  292. $q = 0;
  293. $tags = 'data,gap-fill,question-record';
  294. while (($question="[$q]['#']") && $xml->xml_value($tags, $question)) {
  295. $a = 0;
  296. $aa = 0;
  297. while (($answer=$question."['answer'][$a]['#']") && $xml->xml_value($tags, $answer)) {
  298. $text = $xml->xml_value($tags, $answer."['text'][0]['#']");
  299. $correct = $xml->xml_value($tags, $answer."['correct'][0]['#']");
  300. if (strlen($text) && intval($correct)) {
  301. $wordlist[] = $text;
  302. $aa++;
  303. }
  304. $a++;
  305. }
  306. $q++;
  307. }
  308. $wordlist = array_unique($wordlist);
  309. sort($wordlist);
  310. return $wordlist;
  311. }
  312. function process_jcross(&$xml, &$questions) {
  313. // xml tags to the start of the crossword exercise clue items
  314. $tags = 'data,crossword,clues,item';
  315. $x = 0;
  316. while (($item = "[$x]['#']") && $xml->xml_value($tags, $item)) {
  317. $text = $xml->xml_value($tags, $item."['def'][0]['#']");
  318. $answer = $xml->xml_value($tags, $item."['word'][0]['#']");
  319. if ($text && $answer) {
  320. if (method_exists($this, 'defaultquestion')) {
  321. $question = $this->defaultquestion();
  322. } else {
  323. $question = new stdClass();
  324. $question->usecase = 0; // Ignore case
  325. $question->image = ""; // No images with this format
  326. }
  327. $question->qtype = SHORTANSWER;
  328. $question->name = $this->hotpot_get_title($xml, $x, true);
  329. $question->questiontext = $this->hotpot_prepare_str($text);
  330. $question->answer = array($this->hotpot_prepare_str($answer));
  331. $question->fraction = array(1);
  332. $question->feedback = array('');
  333. $questions[] = $question;
  334. }
  335. $x++;
  336. }
  337. }
  338. function process_jmatch(&$xml, &$questions) {
  339. // define default grade (per matched pair)
  340. $defaultgrade = 1;
  341. $match_count = 0;
  342. // xml tags to the start of the matching exercise
  343. $tags = 'data,matching-exercise';
  344. $x = 0;
  345. while (($exercise = "[$x]['#']") && $xml->xml_value($tags, $exercise)) {
  346. // there is usually only one exercise in a file
  347. if (method_exists($this, 'defaultquestion')) {
  348. $question = $this->defaultquestion();
  349. } else {
  350. $question = new stdClass();
  351. $question->usecase = 0; // Ignore case
  352. $question->image = ""; // No images with this format
  353. }
  354. $question->qtype = MATCH;
  355. $question->name = $this->hotpot_get_title($xml, $x);
  356. $question->questiontext = $this->hotpot_get_reading($xml);
  357. $question->questiontext .= $this->hotpot_get_instructions($xml);
  358. $question->subquestions = array();
  359. $question->subanswers = array();
  360. $p = 0;
  361. while (($pair = $exercise."['pair'][$p]['#']") && $xml->xml_value($tags, $pair)) {
  362. $left = $xml->xml_value($tags, $pair."['left-item'][0]['#']['text'][0]['#']");
  363. $right = $xml->xml_value($tags, $pair."['right-item'][0]['#']['text'][0]['#']");
  364. if ($left && $right) {
  365. $match_count++;
  366. $question->subquestions[$p] = $this->hotpot_prepare_str($left);
  367. $question->subanswers[$p] = $this->hotpot_prepare_str($right);
  368. }
  369. $p++;
  370. }
  371. $question->defaultgrade = $match_count * $defaultgrade;
  372. $questions[] = $question;
  373. $x++;
  374. }
  375. }
  376. function process_jmix(&$xml, &$questions) {
  377. // define default grade (per segment)
  378. $defaultgrade = 1;
  379. $segment_count = 0;
  380. // xml tags to the start of the jumbled order exercise
  381. $tags = 'data,jumbled-order-exercise';
  382. $x = 0;
  383. while (($exercise = "[$x]['#']") && $xml->xml_value($tags, $exercise)) {
  384. // there is usually only one exercise in a file
  385. if (method_exists($this, 'defaultquestion')) {
  386. $question = $this->defaultquestion();
  387. } else {
  388. $question = new stdClass();
  389. $question->usecase = 0; // Ignore case
  390. $question->image = ""; // No images with this format
  391. }
  392. $question->qtype = SHORTANSWER;
  393. $question->name = $this->hotpot_get_title($xml, $x);
  394. $question->answer = array();
  395. $question->fraction = array();
  396. $question->feedback = array();
  397. $i = 0;
  398. $segments = array();
  399. while ($segment = $xml->xml_value($tags, $exercise."['main-order'][0]['#']['segment'][$i]['#']")) {
  400. $segments[] = $this->hotpot_prepare_str($segment);
  401. $segment_count++;
  402. $i++;
  403. }
  404. $answer = implode(' ', $segments);
  405. $this->hotpot_seed_RNG();
  406. shuffle($segments);
  407. $question->questiontext = $this->hotpot_get_reading($xml);
  408. $question->questiontext .= $this->hotpot_get_instructions($xml);
  409. $question->questiontext .= ' &nbsp; <NOBR><B>[ &nbsp; '.implode(' &nbsp; ', $segments).' &nbsp; ]</B></NOBR>';
  410. $a = 0;
  411. while (!empty($answer)) {
  412. $question->answer[$a] = $answer;
  413. $question->fraction[$a] = 1;
  414. $question->feedback[$a] = '';
  415. $answer = $this->hotpot_prepare_str($xml->xml_value($tags, $exercise."['alternate'][$a]['#']"));
  416. $a++;
  417. }
  418. $question->defaultgrade = $segment_count * $defaultgrade;
  419. $questions[] = $question;
  420. $x++;
  421. }
  422. }
  423. function process_jquiz(&$xml, &$questions) {
  424. // define default grade (per question)
  425. $defaultgrade = 1;
  426. // xml tags to the start of the questions
  427. $tags = 'data,questions';
  428. $x = 0;
  429. while (($exercise = "[$x]['#']") && $xml->xml_value($tags, $exercise)) {
  430. // there is usually only one 'questions' object in a single exercise
  431. $q = 0;
  432. while (($question_record = $exercise."['question-record'][$q]['#']") && $xml->xml_value($tags, $question_record)) {
  433. if (method_exists($this, 'defaultquestion')) {
  434. $question = $this->defaultquestion();
  435. } else {
  436. $question = new stdClass();
  437. $question->usecase = 0; // Ignore case
  438. $question->image = ""; // No images with this format
  439. }
  440. $question->defaultgrade = $defaultgrade;
  441. $question->name = $this->hotpot_get_title($xml, $q, true);
  442. $text = $xml->xml_value($tags, $question_record."['question'][0]['#']");
  443. $question->questiontext = $this->hotpot_prepare_str($text);
  444. if ($xml->xml_value($tags, $question_record."['answers']")) {
  445. // HP6 JQuiz
  446. $answers = $question_record."['answers'][0]['#']";
  447. } else {
  448. // HP5 JBC or JQuiz
  449. $answers = $question_record;
  450. }
  451. if($xml->xml_value($tags, $question_record."['question-type']")) {
  452. // HP6 JQuiz
  453. $type = $xml->xml_value($tags, $question_record."['question-type'][0]['#']");
  454. // 1 : multiple choice
  455. // 2 : short-answer
  456. // 3 : hybrid
  457. // 4 : multiple select
  458. } else {
  459. // HP5
  460. switch ($xml->quiztype) {
  461. case 'jbc':
  462. $must_select_all = $xml->xml_value($tags, $question_record."['must-select-all'][0]['#']");
  463. if (empty($must_select_all)) {
  464. $type = 1; // multichoice
  465. } else {
  466. $type = 4; // multiselect
  467. }
  468. break;
  469. case 'jquiz':
  470. $type = 2; // shortanswer
  471. break;
  472. default:
  473. $type = 0; // unknown
  474. }
  475. }
  476. $question->qtype = ($type==2 ? SHORTANSWER : MULTICHOICE);
  477. $question->single = ($type==4 ? 0 : 1);
  478. // workaround required to calculate scores for multiple select answers
  479. $no_of_correct_answers = 0;
  480. if ($type==4) {
  481. $a = 0;
  482. while (($answer = $answers."['answer'][$a]['#']") && $xml->xml_value($tags, $answer)) {
  483. $correct = $xml->xml_value($tags, $answer."['correct'][0]['#']");
  484. if (empty($correct)) {
  485. // do nothing
  486. } else {
  487. $no_of_correct_answers++;
  488. }
  489. $a++;
  490. }
  491. }
  492. $a = 0;
  493. $question->answer = array();
  494. $question->fraction = array();
  495. $question->feedback = array();
  496. $aa = 0;
  497. $correct_answers = array();
  498. $correct_answers_all_zero = true;
  499. while (($answer = $answers."['answer'][$a]['#']") && $xml->xml_value($tags, $answer)) {
  500. $correct = $xml->xml_value($tags, $answer."['correct'][0]['#']");
  501. if (empty($correct)) {
  502. $fraction = 0;
  503. } else if ($type==4) { // multiple select
  504. // strange behavior if the $fraction isn't exact to 5 decimal places
  505. $fraction = round(1/$no_of_correct_answers, 5);
  506. } else {
  507. if ($xml->xml_value($tags, $answer."['percent-correct']")) {
  508. // HP6 JQuiz
  509. $percent = $xml->xml_value($tags, $answer."['percent-correct'][0]['#']");
  510. $fraction = $percent/100;
  511. } else {
  512. // HP5 JBC or JQuiz
  513. $fraction = 1;
  514. }
  515. }
  516. $answertext = $this->hotpot_prepare_str($xml->xml_value($tags, $answer."['text'][0]['#']"));
  517. if ($answertext!='') {
  518. $question->answer[$aa] = $answertext;
  519. $question->fraction[$aa] = $fraction;
  520. $question->feedback[$aa] = $this->hotpot_prepare_str($xml->xml_value($tags, $answer."['feedback'][0]['#']"));
  521. if ($correct) {
  522. if ($fraction) {
  523. $correct_answers_all_zero = false;
  524. }
  525. $correct_answers[] = $aa;
  526. }
  527. $aa++;
  528. }
  529. $a++;
  530. }
  531. if ($correct_answers_all_zero) {
  532. // correct answers all have score of 0%,
  533. // so reset score for correct answers 100%
  534. foreach ($correct_answers as $aa) {
  535. $question->fraction[$aa] = 1;
  536. }
  537. }
  538. // add a sanity check for empty questions, see MDL-17779
  539. if (!empty($question->questiontext)) {
  540. $questions[] = $question;
  541. }
  542. $q++;
  543. }
  544. $x++;
  545. }
  546. }
  547. function hotpot_seed_RNG() {
  548. // seed the random number generator
  549. static $HOTPOT_SEEDED_RNG = FALSE;
  550. if (!$HOTPOT_SEEDED_RNG) {
  551. srand((double) microtime() * 1000000);
  552. $HOTPOT_SEEDED_RNG = TRUE;
  553. }
  554. }
  555. function hotpot_get_title(&$xml, $x, $flag=false) {
  556. $title = $xml->xml_value('data,title');
  557. if ($x || $flag) {
  558. $title .= ' ('.($x+1).')';
  559. }
  560. return $this->hotpot_prepare_str($title);
  561. }
  562. function hotpot_get_instructions(&$xml) {
  563. $text = $xml->xml_value('hotpot-config-file,'.$xml->quiztype.',instructions');
  564. if (empty($text)) {
  565. $text = "Hot Potatoes $xml->quiztype";
  566. }
  567. return $this->hotpot_prepare_str($text);
  568. }
  569. function hotpot_get_reading(&$xml) {
  570. $str = '';
  571. $tags = 'data,reading';
  572. if ($xml->xml_value("$tags,include-reading")) {
  573. if ($title = $xml->xml_value("$tags,reading-title")) {
  574. $str .= "<h3>$title</h3>";
  575. }
  576. if ($text = $xml->xml_value("$tags,reading-text")) {
  577. $str .= "<p>$text</p>";
  578. }
  579. }
  580. return $this->hotpot_prepare_str($str);
  581. }
  582. function hotpot_prepare_str($str) {
  583. // convert html entities to unicode and add slashes
  584. $str = preg_replace_callback('/&#([0-9]+);/', array(&$this, 'hotpot_prepare_str_dec'), $str);
  585. $str = preg_replace_callback('/&#x([0-9a-f]+);/i', array(&$this, 'hotpot_prepare_str_hexdec'), $str);
  586. return addslashes($str);
  587. }
  588. function hotpot_prepare_str_dec(&$matches) {
  589. return hotpot_charcode_to_utf8($matches[1]);
  590. }
  591. function hotpot_prepare_str_hexdec(&$matches) {
  592. return hotpot_charcode_to_utf8(hexdec($matches[1]));
  593. }
  594. } // end class
  595. function hotpot_charcode_to_utf8($charcode) {
  596. // thanks to Miguel Perez: http://jp2.php.net/chr (19-Sep-2007)
  597. if ($charcode <= 0x7F) {
  598. // ascii char (roman alphabet + punctuation)
  599. return chr($charcode);
  600. }
  601. if ($charcode <= 0x7FF) {
  602. // 2-byte char
  603. return chr(($charcode >> 0x06) + 0xC0).chr(($charcode & 0x3F) + 0x80);
  604. }
  605. if ($charcode <= 0xFFFF) {
  606. // 3-byte char
  607. return chr(($charcode >> 0x0C) + 0xE0).chr((($charcode >> 0x06) & 0x3F) + 0x80).chr(($charcode & 0x3F) + 0x80);
  608. }
  609. if ($charcode <= 0x1FFFFF) {
  610. // 4-byte char
  611. return chr(($charcode >> 0x12) + 0xF0).chr((($charcode >> 0x0C) & 0x3F) + 0x80).chr((($charcode >> 0x06) & 0x3F) + 0x80).chr(($charcode & 0x3F) + 0x80);
  612. }
  613. // unidentified char code !!
  614. return ' ';
  615. }
  616. function hotpot_convert_relative_urls($str, $baseurl, $filename) {
  617. $tagopen = '(?:(<)|(&lt;)|(&amp;#x003C;))'; // left angle bracket
  618. $tagclose = '(?(2)>|(?(3)&gt;|(?(4)&amp;#x003E;)))'; // right angle bracket (to match left angle bracket)
  619. $space = '\s+'; // at least one space
  620. $anychar = '(?:[^>]*?)'; // any character
  621. $quoteopen = '("|&quot;|&amp;quot;)'; // open quote
  622. $quoteclose = '\\5'; // close quote (to match open quote)
  623. $replace = "hotpot_convert_relative_url('".$baseurl."', '".$filename."', '\\1', '\\6', '\\7')";
  624. $tags = array('script'=>'src', 'link'=>'href', 'a'=>'href','img'=>'src','param'=>'value', 'object'=>'data', 'embed'=>'src');
  625. foreach ($tags as $tag=>$attribute) {
  626. if ($tag=='param') {
  627. $url = '\S+?\.\S+?'; // must include a filename and have no spaces
  628. } else {
  629. $url = '.*?';
  630. }
  631. $search = "/($tagopen$tag$space$anychar$attribute=$quoteopen)($url)($quoteclose$anychar$tagclose)/is";
  632. if (preg_match_all($search, $str, $matches, PREG_OFFSET_CAPTURE)) {
  633. $i_max = count($matches[0]) - 1;
  634. for ($i=$i_max; $i>=0; $i--) {
  635. $match = $matches[0][$i][0];
  636. $start = $matches[0][$i][1];
  637. $replace = hotpot_convert_relative_url(
  638. $baseurl, $filename, $matches[1][$i][0], $matches[6][$i][0], $matches[7][$i][0], false
  639. );
  640. $str = substr_replace($str, $replace, $start, strlen($match));
  641. }
  642. }
  643. }
  644. return $str;
  645. }