Merge branch 'MDL-74184' of https://github.com/timhunt/moodle
[moodle.git] / mod / lesson / format.php
blobd7c68d695c14e25a3e2b8dc0a66a5ce008cd363c
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * format.php - Default format class for file imports/exports. Doesn't do
20 * everything on it's own -- it needs to be extended.
22 * Included by import.ph
24 * @package mod_lesson
25 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 **/
29 defined('MOODLE_INTERNAL') || die();
31 /**
32 * Import files embedded into answer or response
34 * @param string $field nfield name (answer or response)
35 * @param array $data imported data
36 * @param object $answer answer object
37 * @param int $contextid
38 **/
39 function lesson_import_question_files($field, $data, $answer, $contextid) {
40 global $DB;
41 if (!isset($data['itemid'])) {
42 return;
44 $text = file_save_draft_area_files($data['itemid'],
45 $contextid, 'mod_lesson', 'page_' . $field . 's', $answer->id,
46 array('subdirs' => false, 'maxfiles' => -1, 'maxbytes' => 0),
47 $answer->$field);
49 $DB->set_field("lesson_answers", $field, $text, array("id" => $answer->id));
52 /**
53 * Given some question info and some data about the the answers
54 * this function parses, organises and saves the question
56 * This is only used when IMPORTING questions and is only called
57 * from format.php
58 * Lifted from mod/quiz/lib.php -
59 * 1. all reference to oldanswers removed
60 * 2. all reference to quiz_multichoice table removed
61 * 3. In shortanswer questions usecase is store in the qoption field
62 * 4. In numeric questions store the range as two answers
63 * 5. truefalse options are ignored
64 * 6. For multichoice questions with more than one answer the qoption field is true
66 * @param object $question Contains question data like question, type and answers.
67 * @param object $lesson
68 * @param int $contextid
69 * @return object Returns $result->error or $result->notice.
70 **/
71 function lesson_save_question_options($question, $lesson, $contextid) {
72 global $DB;
74 // These lines are required to ensure that all page types have
75 // been loaded for the following switch
76 if (!($lesson instanceof lesson)) {
77 $lesson = new lesson($lesson);
79 $manager = lesson_page_type_manager::get($lesson);
81 $timenow = time();
82 $result = new stdClass();
84 // Default answer to avoid code duplication.
85 $defaultanswer = new stdClass();
86 $defaultanswer->lessonid = $question->lessonid;
87 $defaultanswer->pageid = $question->id;
88 $defaultanswer->timecreated = $timenow;
89 $defaultanswer->answerformat = FORMAT_HTML;
90 $defaultanswer->jumpto = LESSON_THISPAGE;
91 $defaultanswer->grade = 0;
92 $defaultanswer->score = 0;
94 switch ($question->qtype) {
95 case LESSON_PAGE_SHORTANSWER:
97 $answers = array();
98 $maxfraction = -1;
100 // Insert all the new answers
101 foreach ($question->answer as $key => $dataanswer) {
102 if ($dataanswer != "") {
103 $answer = clone($defaultanswer);
104 if ($question->fraction[$key] >=0.5) {
105 $answer->jumpto = LESSON_NEXTPAGE;
106 $answer->score = 1;
108 $answer->grade = round($question->fraction[$key] * 100);
109 $answer->answer = $dataanswer;
110 $answer->response = $question->feedback[$key]['text'];
111 $answer->responseformat = $question->feedback[$key]['format'];
112 $answer->id = $DB->insert_record("lesson_answers", $answer);
113 lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid);
114 $answers[] = $answer->id;
115 if ($question->fraction[$key] > $maxfraction) {
116 $maxfraction = $question->fraction[$key];
122 /// Perform sanity checks on fractional grades
123 if ($maxfraction != 1) {
124 $maxfraction = $maxfraction * 100;
125 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction);
126 return $result;
128 break;
130 case LESSON_PAGE_NUMERICAL: // Note similarities to shortanswer.
132 $answers = array();
133 $maxfraction = -1;
136 // for each answer store the pair of min and max values even if they are the same
137 foreach ($question->answer as $key => $dataanswer) {
138 if ($dataanswer != "") {
139 $answer = clone($defaultanswer);
140 if ($question->fraction[$key] >= 0.5) {
141 $answer->jumpto = LESSON_NEXTPAGE;
142 $answer->score = 1;
144 $answer->grade = round($question->fraction[$key] * 100);
145 $min = $question->answer[$key] - $question->tolerance[$key];
146 $max = $question->answer[$key] + $question->tolerance[$key];
147 $answer->answer = $min.":".$max;
148 $answer->response = $question->feedback[$key]['text'];
149 $answer->responseformat = $question->feedback[$key]['format'];
150 $answer->id = $DB->insert_record("lesson_answers", $answer);
151 lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid);
153 $answers[] = $answer->id;
154 if ($question->fraction[$key] > $maxfraction) {
155 $maxfraction = $question->fraction[$key];
160 /// Perform sanity checks on fractional grades
161 if ($maxfraction != 1) {
162 $maxfraction = $maxfraction * 100;
163 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction);
164 return $result;
166 break;
169 case LESSON_PAGE_TRUEFALSE:
171 // In lesson the correct answer always come first, as it was the case
172 // in question bank exports years ago.
173 $answer = clone($defaultanswer);
174 $answer->grade = 100;
175 $answer->jumpto = LESSON_NEXTPAGE;
176 $answer->score = 1;
177 if ($question->correctanswer) {
178 $answer->answer = get_string("true", "lesson");
179 if (isset($question->feedbacktrue)) {
180 $answer->response = $question->feedbacktrue['text'];
181 $answer->responseformat = $question->feedbacktrue['format'];
182 $answer->id = $DB->insert_record("lesson_answers", $answer);
183 lesson_import_question_files('response', $question->feedbacktrue, $answer, $contextid);
185 } else {
186 $answer->answer = get_string("false", "lesson");
187 if (isset($question->feedbackfalse)) {
188 $answer->response = $question->feedbackfalse['text'];
189 $answer->responseformat = $question->feedbackfalse['format'];
190 $answer->id = $DB->insert_record("lesson_answers", $answer);
191 lesson_import_question_files('response', $question->feedbackfalse, $answer, $contextid);
195 // Now the wrong answer.
196 $answer = clone($defaultanswer);
197 if ($question->correctanswer) {
198 $answer->answer = get_string("false", "lesson");
199 if (isset($question->feedbackfalse)) {
200 $answer->response = $question->feedbackfalse['text'];
201 $answer->responseformat = $question->feedbackfalse['format'];
202 $answer->id = $DB->insert_record("lesson_answers", $answer);
203 lesson_import_question_files('response', $question->feedbackfalse, $answer, $contextid);
205 } else {
206 $answer->answer = get_string("true", "lesson");
207 if (isset($question->feedbacktrue)) {
208 $answer->response = $question->feedbacktrue['text'];
209 $answer->responseformat = $question->feedbacktrue['format'];
210 $answer->id = $DB->insert_record("lesson_answers", $answer);
211 lesson_import_question_files('response', $question->feedbacktrue, $answer, $contextid);
215 break;
217 case LESSON_PAGE_MULTICHOICE:
219 $totalfraction = 0;
220 $maxfraction = -1;
222 $answers = array();
224 // Insert all the new answers
225 foreach ($question->answer as $key => $dataanswer) {
226 if ($dataanswer != "") {
227 $answer = clone($defaultanswer);
228 $answer->grade = round($question->fraction[$key] * 100);
230 if ($question->single) {
231 if ($answer->grade > 50) {
232 $answer->jumpto = LESSON_NEXTPAGE;
233 $answer->score = 1;
235 } else {
236 // If multi answer allowed, any answer with fraction > 0 is considered correct.
237 if ($question->fraction[$key] > 0) {
238 $answer->jumpto = LESSON_NEXTPAGE;
239 $answer->score = 1;
242 $answer->answer = $dataanswer['text'];
243 $answer->answerformat = $dataanswer['format'];
244 $answer->response = $question->feedback[$key]['text'];
245 $answer->responseformat = $question->feedback[$key]['format'];
246 $answer->id = $DB->insert_record("lesson_answers", $answer);
247 lesson_import_question_files('answer', $dataanswer, $answer, $contextid);
248 lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid);
250 // for Sanity checks
251 if ($question->fraction[$key] > 0) {
252 $totalfraction += $question->fraction[$key];
254 if ($question->fraction[$key] > $maxfraction) {
255 $maxfraction = $question->fraction[$key];
260 /// Perform sanity checks on fractional grades
261 if ($question->single) {
262 if ($maxfraction != 1) {
263 $maxfraction = $maxfraction * 100;
264 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction);
265 return $result;
267 } else {
268 $totalfraction = round($totalfraction,2);
269 if ($totalfraction != 1) {
270 $totalfraction = $totalfraction * 100;
271 $result->notice = get_string("fractionsaddwrong", "lesson", $totalfraction);
272 return $result;
275 break;
277 case LESSON_PAGE_MATCHING:
279 $subquestions = array();
281 // The first answer should always be the correct answer
282 $correctanswer = clone($defaultanswer);
283 $correctanswer->answer = get_string('thatsthecorrectanswer', 'lesson');
284 $correctanswer->jumpto = LESSON_NEXTPAGE;
285 $correctanswer->score = 1;
286 $DB->insert_record("lesson_answers", $correctanswer);
288 // The second answer should always be the wrong answer
289 $wronganswer = clone($defaultanswer);
290 $wronganswer->answer = get_string('thatsthewronganswer', 'lesson');
291 $DB->insert_record("lesson_answers", $wronganswer);
293 $i = 0;
294 // Insert all the new question+answer pairs
295 foreach ($question->subquestions as $key => $questiontext) {
296 $answertext = $question->subanswers[$key];
297 if (!empty($questiontext) and !empty($answertext)) {
298 $answer = clone($defaultanswer);
299 $answer->answer = $questiontext['text'];
300 $answer->answerformat = $questiontext['format'];
301 $answer->response = $answertext;
302 if ($i == 0) {
303 // first answer contains the correct answer jump
304 $answer->jumpto = LESSON_NEXTPAGE;
306 $answer->id = $DB->insert_record("lesson_answers", $answer);
307 lesson_import_question_files('answer', $questiontext, $answer, $contextid);
308 $subquestions[] = $answer->id;
309 $i++;
313 if (count($subquestions) < 3) {
314 $result->notice = get_string("notenoughsubquestions", "lesson");
315 return $result;
317 break;
319 case LESSON_PAGE_ESSAY:
320 $answer = new stdClass();
321 $answer->lessonid = $question->lessonid;
322 $answer->pageid = $question->id;
323 $answer->timecreated = $timenow;
324 $answer->answer = null;
325 $answer->answerformat = FORMAT_MOODLE;
326 $answer->grade = 0;
327 $answer->score = 1;
328 $answer->jumpto = LESSON_NEXTPAGE;
329 $answer->response = null;
330 $answer->responseformat = FORMAT_MOODLE;
331 $answer->id = $DB->insert_record("lesson_answers", $answer);
332 break;
333 default:
334 $result->error = "Unsupported question type ($question->qtype)!";
335 return $result;
337 return true;
341 class qformat_default {
343 var $displayerrors = true;
344 var $category = null;
345 var $questionids = array();
346 protected $importcontext = null;
347 var $qtypeconvert = array('numerical' => LESSON_PAGE_NUMERICAL,
348 'multichoice' => LESSON_PAGE_MULTICHOICE,
349 'truefalse' => LESSON_PAGE_TRUEFALSE,
350 'shortanswer' => LESSON_PAGE_SHORTANSWER,
351 'match' => LESSON_PAGE_MATCHING,
352 'essay' => LESSON_PAGE_ESSAY
355 // Importing functions
356 function provide_import() {
357 return false;
360 function set_importcontext($context) {
361 $this->importcontext = $context;
365 * Handle parsing error
367 * @param string $message information about error
368 * @param string $text imported text that triggered the error
369 * @param string $questionname imported question name
371 protected function error($message, $text='', $questionname='') {
372 $importerrorquestion = get_string('importerrorquestion', 'question');
374 echo "<div class=\"importerror\">\n";
375 echo "<strong>$importerrorquestion $questionname</strong>";
376 if (!empty($text)) {
377 $text = s($text);
378 echo "<blockquote>$text</blockquote>\n";
380 echo "<strong>$message</strong>\n";
381 echo "</div>";
385 * Import for questiontype plugins
386 * @param mixed $data The segment of data containing the question
387 * @param object $question processed (so far) by standard import code if appropriate
388 * @param object $extra mixed any additional format specific data that may be passed by the format
389 * @param string $qtypehint hint about a question type from format
390 * @return object question object suitable for save_options() or false if cannot handle
392 public function try_importing_using_qtypes($data, $question = null, $extra = null,
393 $qtypehint = '') {
395 return false;
398 function importpreprocess() {
399 // Does any pre-processing that may be desired
400 return true;
403 function importprocess($filename, $lesson, $pageid) {
404 global $DB, $OUTPUT;
406 /// Processes a given file. There's probably little need to change this
407 $timenow = time();
409 if (! $lines = $this->readdata($filename)) {
410 echo $OUTPUT->notification("File could not be read, or was empty");
411 return false;
414 if (! $questions = $this->readquestions($lines)) { // Extract all the questions
415 echo $OUTPUT->notification("There are no questions in this file!");
416 return false;
419 //Avoid category as question type
420 echo $OUTPUT->notification(get_string('importcount', 'lesson',
421 $this->count_questions($questions)), 'notifysuccess');
423 $count = 0;
424 $addquestionontop = false;
425 if ($pageid == 0) {
426 $addquestionontop = true;
427 $updatelessonpage = $DB->get_record('lesson_pages', array('lessonid' => $lesson->id, 'prevpageid' => 0));
428 } else {
429 $updatelessonpage = $DB->get_record('lesson_pages', array('lessonid' => $lesson->id, 'id' => $pageid));
432 $unsupportedquestions = 0;
434 foreach ($questions as $question) { // Process and store each question
435 switch ($question->qtype) {
436 //TODO: Bad way to bypass category in data... Quickfix for MDL-27964
437 case 'category':
438 break;
439 // the good ones
440 case 'shortanswer' :
441 case 'numerical' :
442 case 'truefalse' :
443 case 'multichoice' :
444 case 'match' :
445 case 'essay' :
446 $count++;
448 //Show nice formated question in one line.
449 echo "<hr><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
451 $newpage = new stdClass;
452 $newpage->lessonid = $lesson->id;
453 $newpage->qtype = $this->qtypeconvert[$question->qtype];
454 switch ($question->qtype) {
455 case 'shortanswer' :
456 if (isset($question->usecase)) {
457 $newpage->qoption = $question->usecase;
459 break;
460 case 'multichoice' :
461 if (isset($question->single)) {
462 $newpage->qoption = !$question->single;
464 break;
466 $newpage->timecreated = $timenow;
467 if ($question->name != $question->questiontext) {
468 $newpage->title = $question->name;
469 } else {
470 $newpage->title = "Page $count";
472 $newpage->contents = $question->questiontext;
473 $newpage->contentsformat = isset($question->questionformat) ? $question->questionformat : FORMAT_HTML;
475 // set up page links
476 if ($pageid) {
477 // the new page follows on from this page
478 if (!$page = $DB->get_record("lesson_pages", array("id" => $pageid))) {
479 print_error('invalidpageid', 'lesson');
481 $newpage->prevpageid = $pageid;
482 $newpage->nextpageid = $page->nextpageid;
483 // insert the page and reset $pageid
484 $newpageid = $DB->insert_record("lesson_pages", $newpage);
485 // update the linked list
486 $DB->set_field("lesson_pages", "nextpageid", $newpageid, array("id" => $pageid));
487 } else {
488 // new page is the first page
489 // get the existing (first) page (if any)
490 $params = array ("lessonid" => $lesson->id, "prevpageid" => 0);
491 if (!$page = $DB->get_record_select("lesson_pages", "lessonid = :lessonid AND prevpageid = :prevpageid", $params)) {
492 // there are no existing pages
493 $newpage->prevpageid = 0; // this is a first page
494 $newpage->nextpageid = 0; // this is the only page
495 $newpageid = $DB->insert_record("lesson_pages", $newpage);
496 } else {
497 // there are existing pages put this at the start
498 $newpage->prevpageid = 0; // this is a first page
499 $newpage->nextpageid = $page->id;
500 $newpageid = $DB->insert_record("lesson_pages", $newpage);
501 // update the linked list
502 $DB->set_field("lesson_pages", "prevpageid", $newpageid, array("id" => $page->id));
506 // reset $pageid and put the page ID in $question, used in save_question_option()
507 $pageid = $newpageid;
508 $question->id = $newpageid;
510 $this->questionids[] = $question->id;
512 // Import images in question text.
513 if (isset($question->questiontextitemid)) {
514 $questiontext = file_save_draft_area_files($question->questiontextitemid,
515 $this->importcontext->id, 'mod_lesson', 'page_contents', $newpageid,
516 null , $question->questiontext);
517 // Update content with recoded urls.
518 $DB->set_field("lesson_pages", "contents", $questiontext, array("id" => $newpageid));
521 // Now to save all the answers and type-specific options
523 $question->lessonid = $lesson->id; // needed for foreign key
524 $question->qtype = $this->qtypeconvert[$question->qtype];
525 $result = lesson_save_question_options($question, $lesson, $this->importcontext->id);
527 if (!empty($result->error)) {
528 echo $OUTPUT->notification($result->error);
529 return false;
532 if (!empty($result->notice)) {
533 echo $OUTPUT->notification($result->notice);
534 return true;
536 break;
537 // the Bad ones
538 default :
539 $unsupportedquestions++;
540 break;
543 // Update the prev links if there were existing pages.
544 if (!empty($updatelessonpage)) {
545 if ($addquestionontop) {
546 $DB->set_field("lesson_pages", "prevpageid", $pageid, array("id" => $updatelessonpage->id));
547 } else {
548 $DB->set_field("lesson_pages", "prevpageid", $pageid, array("id" => $updatelessonpage->nextpageid));
551 if ($unsupportedquestions) {
552 echo $OUTPUT->notification(get_string('unknownqtypesnotimported', 'lesson', $unsupportedquestions));
554 return true;
558 * Count all non-category questions in the questions array.
560 * @param array questions An array of question objects.
561 * @return int The count.
564 protected function count_questions($questions) {
565 $count = 0;
566 if (!is_array($questions)) {
567 return $count;
569 foreach ($questions as $question) {
570 if (!is_object($question) || !isset($question->qtype) ||
571 ($question->qtype == 'category')) {
572 continue;
574 $count++;
576 return $count;
579 function readdata($filename) {
580 /// Returns complete file with an array, one item per line
582 if (is_readable($filename)) {
583 $filearray = file($filename);
585 /// Check for Macintosh OS line returns (ie file on one line), and fix
586 if (preg_match("/\r/", $filearray[0]) AND !preg_match("/\n/", $filearray[0])) {
587 return explode("\r", $filearray[0]);
588 } else {
589 return $filearray;
592 return false;
595 protected function readquestions($lines) {
596 /// Parses an array of lines into an array of questions,
597 /// where each item is a question object as defined by
598 /// readquestion(). Questions are defined as anything
599 /// between blank lines.
601 $questions = array();
602 $currentquestion = array();
604 foreach ($lines as $line) {
605 $line = trim($line);
606 if (empty($line)) {
607 if (!empty($currentquestion)) {
608 if ($question = $this->readquestion($currentquestion)) {
609 $questions[] = $question;
611 $currentquestion = array();
613 } else {
614 $currentquestion[] = $line;
618 if (!empty($currentquestion)) { // There may be a final question
619 if ($question = $this->readquestion($currentquestion)) {
620 $questions[] = $question;
624 return $questions;
628 protected function readquestion($lines) {
629 /// Given an array of lines known to define a question in
630 /// this format, this function converts it into a question
631 /// object suitable for processing and insertion into Moodle.
633 // We should never get there unless the qformat plugin is broken.
634 throw new coding_exception('Question format plugin is missing important code: readquestion.');
636 return null;
640 * Construct a reasonable default question name, based on the start of the question text.
641 * @param string $questiontext the question text.
642 * @param string $default default question name to use if the constructed one comes out blank.
643 * @return string a reasonable question name.
645 public function create_default_question_name($questiontext, $default) {
646 $name = $this->clean_question_name(shorten_text($questiontext, 80));
647 if ($name) {
648 return $name;
649 } else {
650 return $default;
655 * Ensure that a question name does not contain anything nasty, and will fit in the DB field.
656 * @param string $name the raw question name.
657 * @return string a safe question name.
659 public function clean_question_name($name) {
660 $name = clean_param($name, PARAM_TEXT); // Matches what the question editing form does.
661 $name = trim($name);
662 $trimlength = 251;
663 while (core_text::strlen($name) > 255 && $trimlength > 0) {
664 $name = shorten_text($name, $trimlength);
665 $trimlength -= 10;
667 return $name;
671 * return an "empty" question
672 * Somewhere to specify question parameters that are not handled
673 * by import but are required db fields.
674 * This should not be overridden.
675 * @return object default question
677 protected function defaultquestion() {
678 global $CFG;
679 static $defaultshuffleanswers = null;
680 if (is_null($defaultshuffleanswers)) {
681 $defaultshuffleanswers = get_config('quiz', 'shuffleanswers');
684 $question = new stdClass();
685 $question->shuffleanswers = $defaultshuffleanswers;
686 $question->defaultmark = 1;
687 $question->image = "";
688 $question->usecase = 0;
689 $question->multiplier = array();
690 $question->questiontextformat = FORMAT_MOODLE;
691 $question->generalfeedback = '';
692 $question->generalfeedbackformat = FORMAT_MOODLE;
693 $question->correctfeedback = '';
694 $question->partiallycorrectfeedback = '';
695 $question->incorrectfeedback = '';
696 $question->answernumbering = 'abc';
697 $question->penalty = 0.3333333;
698 $question->length = 1;
699 $question->qoption = 0;
700 $question->layout = 1;
702 // this option in case the questiontypes class wants
703 // to know where the data came from
704 $question->export_process = true;
705 $question->import_process = true;
707 return $question;
710 function importpostprocess() {
711 /// Does any post-processing that may be desired
712 /// Argument is a simple array of question ids that
713 /// have just been added.
714 return true;
718 * Convert the question text to plain text, so it can safely be displayed
719 * during import to let the user see roughly what is going on.
721 protected function format_question_text($question) {
722 $formatoptions = new stdClass();
723 $formatoptions->noclean = true;
724 // The html_to_text call strips out all URLs, but format_text complains
725 // if it finds @@PLUGINFILE@@ tokens. So, we need to replace
726 // @@PLUGINFILE@@ with a real URL, but it doesn't matter what.
727 // We use http://example.com/.
728 $text = str_replace('@@PLUGINFILE@@/', 'http://example.com/', $question->questiontext);
729 return s(html_to_text(format_text($text,
730 $question->questiontextformat, $formatoptions), 0, false));
734 * Since the lesson module tries to re-use the question bank import classes in
735 * a crazy way, this is necessary to stop things breaking.
737 protected function add_blank_combined_feedback($question) {
738 return $question;
744 * Since the lesson module tries to re-use the question bank import classes in
745 * a crazy way, this is necessary to stop things breaking. This should be exactly
746 * the same as the class defined in question/format.php.
748 class qformat_based_on_xml extends qformat_default {
750 * A lot of imported files contain unwanted entities.
751 * This method tries to clean up all known problems.
752 * @param string str string to correct
753 * @return string the corrected string
755 public function cleaninput($str) {
757 $html_code_list = array(
758 "&#039;" => "'",
759 "&#8217;" => "'",
760 "&#8220;" => "\"",
761 "&#8221;" => "\"",
762 "&#8211;" => "-",
763 "&#8212;" => "-",
765 $str = strtr($str, $html_code_list);
766 // Use core_text entities_to_utf8 function to convert only numerical entities.
767 $str = core_text::entities_to_utf8($str, false);
768 return $str;
772 * Return the array moodle is expecting
773 * for an HTML text. No processing is done on $text.
774 * qformat classes that want to process $text
775 * for instance to import external images files
776 * and recode urls in $text must overwrite this method.
777 * @param array $text some HTML text string
778 * @return array with keys text, format and files.
780 public function text_field($text) {
781 return array(
782 'text' => trim($text),
783 'format' => FORMAT_HTML,
784 'files' => array(),
789 * Return the value of a node, given a path to the node
790 * if it doesn't exist return the default value.
791 * @param array xml data to read
792 * @param array path path to node expressed as array
793 * @param mixed default
794 * @param bool istext process as text
795 * @param string error if set value must exist, return false and issue message if not
796 * @return mixed value
798 public function getpath($xml, $path, $default, $istext=false, $error='') {
799 foreach ($path as $index) {
800 if (!isset($xml[$index])) {
801 if (!empty($error)) {
802 $this->error($error);
803 return false;
804 } else {
805 return $default;
809 $xml = $xml[$index];
812 if ($istext) {
813 if (!is_string($xml)) {
814 $this->error(get_string('invalidxml', 'qformat_xml'));
816 $xml = trim($xml);
819 return $xml;