MDL-38540 Lesson: import images in question answers
[moodle.git] / mod / lesson / format.php
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.
14 //
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.
21  *
22  * Included by import.ph
23  *
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
33  *
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;
43     }
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));
50 }
52 /**
53  * Given some question info and some data about the the answers
54  * this function parses, organises and saves the question
55  *
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
65  *
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);
78     }
79     $manager = lesson_page_type_manager::get($lesson);
81     $timenow = time();
82     $result = new stdClass();
83     switch ($question->qtype) {
84         case LESSON_PAGE_SHORTANSWER:
86             $answers = array();
87             $maxfraction = -1;
89             // Insert all the new answers
90             foreach ($question->answer as $key => $dataanswer) {
91                 if ($dataanswer != "") {
92                     $answer = new stdClass;
93                     $answer->lessonid   = $question->lessonid;
94                     $answer->pageid   = $question->id;
95                     if ($question->fraction[$key] >=0.5) {
96                         $answer->jumpto = LESSON_NEXTPAGE;
97                     }
98                     $answer->timecreated   = $timenow;
99                     $answer->grade = $question->fraction[$key] * 100;
100                     $answer->answer   = $dataanswer;
101                     $answer->response = $question->feedback[$key]['text'];
102                     $answer->responseformat = $question->feedback[$key]['format'];
103                     $answer->id = $DB->insert_record("lesson_answers", $answer);
104                     lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid);
105                     $answers[] = $answer->id;
106                     if ($question->fraction[$key] > $maxfraction) {
107                         $maxfraction = $question->fraction[$key];
108                     }
109                 }
110             }
113             /// Perform sanity checks on fractional grades
114             if ($maxfraction != 1) {
115                 $maxfraction = $maxfraction * 100;
116                 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction);
117                 return $result;
118             }
119             break;
121         case LESSON_PAGE_NUMERICAL:   // Note similarities to shortanswer.
123             $answers = array();
124             $maxfraction = -1;
127             // for each answer store the pair of min and max values even if they are the same
128             foreach ($question->answer as $key => $dataanswer) {
129                 if ($dataanswer != "") {
130                     $answer = new stdClass;
131                     $answer->lessonid   = $question->lessonid;
132                     $answer->pageid   = $question->id;
133                     $answer->jumpto = LESSON_NEXTPAGE;
134                     $answer->timecreated   = $timenow;
135                     $answer->grade = $question->fraction[$key] * 100;
136                     $min = $question->answer[$key] - $question->tolerance[$key];
137                     $max = $question->answer[$key] + $question->tolerance[$key];
138                     $answer->answer   = $min.":".$max;
139                     // $answer->answer   = $question->min[$key].":".$question->max[$key]; original line for min/max
140                     $answer->response = $question->feedback[$key]['text'];
141                     $answer->responseformat = $question->feedback[$key]['format'];
142                     $answer->id = $DB->insert_record("lesson_answers", $answer);
143                     lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid);
145                     $answers[] = $answer->id;
146                     if ($question->fraction[$key] > $maxfraction) {
147                         $maxfraction = $question->fraction[$key];
148                     }
149                 }
150             }
152             /// Perform sanity checks on fractional grades
153             if ($maxfraction != 1) {
154                 $maxfraction = $maxfraction * 100;
155                 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction);
156                 return $result;
157             }
158         break;
161         case LESSON_PAGE_TRUEFALSE:
163             // the truth
164             $answer = new stdClass();
165             $answer->lessonid   = $question->lessonid;
166             $answer->pageid = $question->id;
167             $answer->timecreated   = $timenow;
168             $answer->answer = get_string("true", "lesson");
169             $answer->grade = $question->correctanswer * 100;
170             if ($answer->grade > 50 ) {
171                 $answer->jumpto = LESSON_NEXTPAGE;
172             }
173             if (isset($question->feedbacktrue)) {
174                 $answer->response = $question->feedbacktrue['text'];
175                 $answer->responseformat = $question->feedbacktrue['format'];
176             }
177             $answer->id = $DB->insert_record("lesson_answers", $answer);
178             lesson_import_question_files('response', $question->feedbacktrue, $answer, $contextid);
180             // the lie
181             $answer = new stdClass;
182             $answer->lessonid   = $question->lessonid;
183             $answer->pageid = $question->id;
184             $answer->timecreated   = $timenow;
185             $answer->answer = get_string("false", "lesson");
186             $answer->grade = (1 - (int)$question->correctanswer) * 100;
187             if ($answer->grade > 50 ) {
188                 $answer->jumpto = LESSON_NEXTPAGE;
189             }
190             if (isset($question->feedbackfalse)) {
191                 $answer->response = $question->feedbackfalse['text'];
192                 $answer->responseformat = $question->feedbackfalse['format'];
193             }
194             $answer->id = $DB->insert_record("lesson_answers", $answer);
195             lesson_import_question_files('response', $question->feedbackfalse, $answer, $contextid);
197           break;
199         case LESSON_PAGE_MULTICHOICE:
201             $totalfraction = 0;
202             $maxfraction = -1;
204             $answers = array();
206             // Insert all the new answers
207             foreach ($question->answer as $key => $dataanswer) {
208                 if ($dataanswer != "") {
209                     $answer = new stdClass;
210                     $answer->lessonid   = $question->lessonid;
211                     $answer->pageid   = $question->id;
212                     $answer->timecreated   = $timenow;
213                     $answer->grade = $question->fraction[$key] * 100;
214                     // changed some defaults
215                     /* Original Code
216                     if ($answer->grade > 50 ) {
217                         $answer->jumpto = LESSON_NEXTPAGE;
218                     }
219                     Replaced with:                    */
220                     if ($answer->grade > 50 ) {
221                         $answer->jumpto = LESSON_NEXTPAGE;
222                         $answer->score = 1;
223                     }
224                     // end Replace
225                     $answer->answer   = $dataanswer['text'];
226                     $answer->answerformat   = $dataanswer['format'];
227                     $answer->response = $question->feedback[$key]['text'];
228                     $answer->responseformat = $question->feedback[$key]['format'];
229                     $answer->id = $DB->insert_record("lesson_answers", $answer);
230                     lesson_import_question_files('answer', $dataanswer, $answer, $contextid);
231                     lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid);
233                     // for Sanity checks
234                     if ($question->fraction[$key] > 0) {
235                         $totalfraction += $question->fraction[$key];
236                     }
237                     if ($question->fraction[$key] > $maxfraction) {
238                         $maxfraction = $question->fraction[$key];
239                     }
240                 }
241             }
243             /// Perform sanity checks on fractional grades
244             if ($question->single) {
245                 if ($maxfraction != 1) {
246                     $maxfraction = $maxfraction * 100;
247                     $result->notice = get_string("fractionsnomax", "lesson", $maxfraction);
248                     return $result;
249                 }
250             } else {
251                 $totalfraction = round($totalfraction,2);
252                 if ($totalfraction != 1) {
253                     $totalfraction = $totalfraction * 100;
254                     $result->notice = get_string("fractionsaddwrong", "lesson", $totalfraction);
255                     return $result;
256                 }
257             }
258         break;
260         case LESSON_PAGE_MATCHING:
262             $subquestions = array();
264             $defaultanswer = new stdClass;
265             $defaultanswer->lessonid   = $question->lessonid;
266             $defaultanswer->pageid   = $question->id;
267             $defaultanswer->timecreated   = $timenow;
268             $defaultanswer->grade = 0;
270             // The first answer should always be the correct answer
271             $correctanswer = clone($defaultanswer);
272             $correctanswer->answer = get_string('thatsthecorrectanswer', 'lesson');
273             $correctanswer->jumpto = LESSON_NEXTPAGE;
274             $DB->insert_record("lesson_answers", $correctanswer);
276             // The second answer should always be the wrong answer
277             $wronganswer = clone($defaultanswer);
278             $wronganswer->answer = get_string('thatsthewronganswer', 'lesson');
279             $DB->insert_record("lesson_answers", $wronganswer);
281             $i = 0;
282             // Insert all the new question+answer pairs
283             foreach ($question->subquestions as $key => $questiontext) {
284                 $answertext = $question->subanswers[$key];
285                 if (!empty($questiontext) and !empty($answertext)) {
286                     $answer = clone($defaultanswer);
287                     $answer->answer = $questiontext['text'];
288                     $answer->answerformat   = $questiontext['format'];
289                     $answer->response   = $answertext;
290                     if ($i == 0) {
291                         // first answer contains the correct answer jump
292                         $answer->jumpto = LESSON_NEXTPAGE;
293                     }
294                     $answer->id = $DB->insert_record("lesson_answers", $answer);
295                     lesson_import_question_files('answer', $questiontext, $answer, $contextid);
296                     $subquestions[] = $answer->id;
297                     $i++;
298                 }
299             }
301             if (count($subquestions) < 3) {
302                 $result->notice = get_string("notenoughsubquestions", "lesson");
303                 return $result;
304             }
305             break;
306         default:
307             $result->error = "Unsupported question type ($question->qtype)!";
308             return $result;
309     }
310     return true;
314 class qformat_default {
316     var $displayerrors = true;
317     var $category = null;
318     var $questionids = array();
319     protected $importcontext = null;
320     var $qtypeconvert = array('numerical'   => LESSON_PAGE_NUMERICAL,
321                                'multichoice' => LESSON_PAGE_MULTICHOICE,
322                                'truefalse'   => LESSON_PAGE_TRUEFALSE,
323                                'shortanswer' => LESSON_PAGE_SHORTANSWER,
324                                'match'       => LESSON_PAGE_MATCHING
325                               );
327     // Importing functions
328     function provide_import() {
329         return false;
330     }
332     function set_importcontext($context) {
333         $this->importcontext = $context;
334     }
336     /**
337      * Handle parsing error
338      *
339      * @param string $message information about error
340      * @param string $text imported text that triggered the error
341      * @param string $questionname imported question name
342      */
343     protected function error($message, $text='', $questionname='') {
344         $importerrorquestion = get_string('importerrorquestion', 'question');
346         echo "<div class=\"importerror\">\n";
347         echo "<strong>$importerrorquestion $questionname</strong>";
348         if (!empty($text)) {
349             $text = s($text);
350             echo "<blockquote>$text</blockquote>\n";
351         }
352         echo "<strong>$message</strong>\n";
353         echo "</div>";
354     }
356     function importpreprocess() {
357         // Does any pre-processing that may be desired
358         return true;
359     }
361     function importprocess($filename, $lesson, $pageid) {
362         global $DB, $OUTPUT;
364     /// Processes a given file.  There's probably little need to change this
365         $timenow = time();
367         if (! $lines = $this->readdata($filename)) {
368             echo $OUTPUT->notification("File could not be read, or was empty");
369             return false;
370         }
372         if (! $questions = $this->readquestions($lines)) {   // Extract all the questions
373             echo $OUTPUT->notification("There are no questions in this file!");
374             return false;
375         }
377         //Avoid category as question type
378         echo $OUTPUT->notification(get_string('importcount', 'lesson',
379                 $this->count_questions($questions)), 'notifysuccess');
381         $count = 0;
382         $addquestionontop = false;
383         if ($pageid == 0) {
384             $addquestionontop = true;
385             $updatelessonpage = $DB->get_record('lesson_pages', array('lessonid' => $lesson->id, 'prevpageid' => 0));
386         } else {
387             $updatelessonpage = $DB->get_record('lesson_pages', array('lessonid' => $lesson->id, 'id' => $pageid));
388         }
390         $unsupportedquestions = 0;
392         foreach ($questions as $question) {   // Process and store each question
393             switch ($question->qtype) {
394                 //TODO: Bad way to bypass category in data... Quickfix for MDL-27964
395                 case 'category':
396                     break;
397                 // the good ones
398                 case 'shortanswer' :
399                 case 'numerical' :
400                 case 'truefalse' :
401                 case 'multichoice' :
402                 case 'match' :
403                     $count++;
405                     //Show nice formated question in one line.
406                     echo "<hr><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
408                     $newpage = new stdClass;
409                     $newpage->lessonid = $lesson->id;
410                     $newpage->qtype = $this->qtypeconvert[$question->qtype];
411                     switch ($question->qtype) {
412                         case 'shortanswer' :
413                             if (isset($question->usecase)) {
414                                 $newpage->qoption = $question->usecase;
415                             }
416                             break;
417                         case 'multichoice' :
418                             if (isset($question->single)) {
419                                 $newpage->qoption = !$question->single;
420                             }
421                             break;
422                     }
423                     $newpage->timecreated = $timenow;
424                     if ($question->name != $question->questiontext) {
425                         $newpage->title = $question->name;
426                     } else {
427                         $newpage->title = "Page $count";
428                     }
429                     $newpage->contents = $question->questiontext;
430                     $newpage->contentsformat = isset($question->questionformat) ? $question->questionformat : FORMAT_HTML;
432                     // set up page links
433                     if ($pageid) {
434                         // the new page follows on from this page
435                         if (!$page = $DB->get_record("lesson_pages", array("id" => $pageid))) {
436                             print_error('invalidpageid', 'lesson');
437                         }
438                         $newpage->prevpageid = $pageid;
439                         $newpage->nextpageid = $page->nextpageid;
440                         // insert the page and reset $pageid
441                         $newpageid = $DB->insert_record("lesson_pages", $newpage);
442                         // update the linked list
443                         $DB->set_field("lesson_pages", "nextpageid", $newpageid, array("id" => $pageid));
444                     } else {
445                         // new page is the first page
446                         // get the existing (first) page (if any)
447                         $params = array ("lessonid" => $lesson->id, "prevpageid" => 0);
448                         if (!$page = $DB->get_record_select("lesson_pages", "lessonid = :lessonid AND prevpageid = :prevpageid", $params)) {
449                             // there are no existing pages
450                             $newpage->prevpageid = 0; // this is a first page
451                             $newpage->nextpageid = 0; // this is the only page
452                             $newpageid = $DB->insert_record("lesson_pages", $newpage);
453                         } else {
454                             // there are existing pages put this at the start
455                             $newpage->prevpageid = 0; // this is a first page
456                             $newpage->nextpageid = $page->id;
457                             $newpageid = $DB->insert_record("lesson_pages", $newpage);
458                             // update the linked list
459                             $DB->set_field("lesson_pages", "prevpageid", $newpageid, array("id" => $page->id));
460                         }
461                     }
463                     // reset $pageid and put the page ID in $question, used in save_question_option()
464                     $pageid = $newpageid;
465                     $question->id = $newpageid;
467                     $this->questionids[] = $question->id;
469                     // Import images in question text.
470                     if (isset($question->questiontextitemid)) {
471                         $questiontext = file_save_draft_area_files($question->questiontextitemid,
472                                 $this->importcontext->id, 'mod_lesson', 'page_contents', $newpageid,
473                                 null , $question->questiontext);
474                         // Update content with recoded urls.
475                         $DB->set_field("lesson_pages", "contents", $questiontext, array("id" => $newpageid));
476                     }
478                     // Now to save all the answers and type-specific options
480                     $question->lessonid = $lesson->id; // needed for foreign key
481                     $question->qtype = $this->qtypeconvert[$question->qtype];
482                     $result = lesson_save_question_options($question, $lesson, $this->importcontext->id);
484                     if (!empty($result->error)) {
485                         echo $OUTPUT->notification($result->error);
486                         return false;
487                     }
489                     if (!empty($result->notice)) {
490                         echo $OUTPUT->notification($result->notice);
491                         return true;
492                     }
493                     break;
494             // the Bad ones
495                 default :
496                     $unsupportedquestions++;
497                     break;
498             }
499         }
500         // Update the prev links if there were existing pages.
501         if (!empty($updatelessonpage)) {
502             if ($addquestionontop) {
503                 $DB->set_field("lesson_pages", "prevpageid", $pageid, array("id" => $updatelessonpage->id));
504             } else {
505                 $DB->set_field("lesson_pages", "prevpageid", $pageid, array("id" => $updatelessonpage->nextpageid));
506             }
507         }
508         if ($unsupportedquestions) {
509             echo $OUTPUT->notification(get_string('unknownqtypesnotimported', 'lesson', $unsupportedquestions));
510         }
511         return true;
512     }
514     /**
515      * Count all non-category questions in the questions array.
516      *
517      * @param array questions An array of question objects.
518      * @return int The count.
519      *
520      */
521     protected function count_questions($questions) {
522         $count = 0;
523         if (!is_array($questions)) {
524             return $count;
525         }
526         foreach ($questions as $question) {
527             if (!is_object($question) || !isset($question->qtype) ||
528                     ($question->qtype == 'category')) {
529                 continue;
530             }
531             $count++;
532         }
533         return $count;
534     }
536     function readdata($filename) {
537     /// Returns complete file with an array, one item per line
539         if (is_readable($filename)) {
540             $filearray = file($filename);
542             /// Check for Macintosh OS line returns (ie file on one line), and fix
543             if (preg_match("/\r/", $filearray[0]) AND !preg_match("/\n/", $filearray[0])) {
544                 return explode("\r", $filearray[0]);
545             } else {
546                 return $filearray;
547             }
548         }
549         return false;
550     }
552     protected function readquestions($lines) {
553     /// Parses an array of lines into an array of questions,
554     /// where each item is a question object as defined by
555     /// readquestion().   Questions are defined as anything
556     /// between blank lines.
558         $questions = array();
559         $currentquestion = array();
561         foreach ($lines as $line) {
562             $line = trim($line);
563             if (empty($line)) {
564                 if (!empty($currentquestion)) {
565                     if ($question = $this->readquestion($currentquestion)) {
566                         $questions[] = $question;
567                     }
568                     $currentquestion = array();
569                 }
570             } else {
571                 $currentquestion[] = $line;
572             }
573         }
575         if (!empty($currentquestion)) {  // There may be a final question
576             if ($question = $this->readquestion($currentquestion)) {
577                 $questions[] = $question;
578             }
579         }
581         return $questions;
582     }
585     protected function readquestion($lines) {
586     /// Given an array of lines known to define a question in
587     /// this format, this function converts it into a question
588     /// object suitable for processing and insertion into Moodle.
590         echo "<p>This flash question format has not yet been completed!</p>";
592         return null;
593     }
595     /**
596      * Construct a reasonable default question name, based on the start of the question text.
597      * @param string $questiontext the question text.
598      * @param string $default default question name to use if the constructed one comes out blank.
599      * @return string a reasonable question name.
600      */
601     public function create_default_question_name($questiontext, $default) {
602         $name = $this->clean_question_name(shorten_text($questiontext, 80));
603         if ($name) {
604             return $name;
605         } else {
606             return $default;
607         }
608     }
610     /**
611      * Ensure that a question name does not contain anything nasty, and will fit in the DB field.
612      * @param string $name the raw question name.
613      * @return string a safe question name.
614      */
615     public function clean_question_name($name) {
616         $name = clean_param($name, PARAM_TEXT); // Matches what the question editing form does.
617         $name = trim($name);
618         $trimlength = 251;
619         while (core_text::strlen($name) > 255 && $trimlength > 0) {
620             $name = shorten_text($name, $trimlength);
621             $trimlength -= 10;
622         }
623         return $name;
624     }
626     function defaultquestion() {
627     // returns an "empty" question
628     // Somewhere to specify question parameters that are not handled
629     // by import but are required db fields.
630     // This should not be overridden.
631         global $CFG;
633         $question = new stdClass();
634         $question->shuffleanswers = get_config('quiz', 'shuffleanswers');
635         $question->defaultmark = 1;
636         $question->image = "";
637         $question->usecase = 0;
638         $question->multiplier = array();
639         $question->generalfeedback = '';
640         $question->correctfeedback = '';
641         $question->partiallycorrectfeedback = '';
642         $question->incorrectfeedback = '';
643         $question->answernumbering = 'abc';
644         $question->penalty = 0.1;
645         $question->length = 1;
646         $question->qoption = 0;
647         $question->layout = 1;
649         // this option in case the questiontypes class wants
650         // to know where the data came from
651         $question->export_process = true;
652         $question->import_process = true;
654         return $question;
655     }
657     function importpostprocess() {
658         /// Does any post-processing that may be desired
659         /// Argument is a simple array of question ids that
660         /// have just been added.
661         return true;
662     }
664     /**
665      * Convert the question text to plain text, so it can safely be displayed
666      * during import to let the user see roughly what is going on.
667      */
668     protected function format_question_text($question) {
669         $formatoptions = new stdClass();
670         $formatoptions->noclean = true;
671         // The html_to_text call strips out all URLs, but format_text complains
672         // if it finds @@PLUGINFILE@@ tokens. So, we need to replace
673         // @@PLUGINFILE@@ with a real URL, but it doesn't matter what.
674         // We use http://example.com/.
675         $text = str_replace('@@PLUGINFILE@@/', 'http://example.com/', $question->questiontext);
676         return html_to_text(format_text($text,
677                 $question->questiontextformat, $formatoptions), 0, false);
678     }
680     /**
681      * Since the lesson module tries to re-use the question bank import classes in
682      * a crazy way, this is necessary to stop things breaking.
683      */
684     protected function add_blank_combined_feedback($question) {
685         return $question;
686     }
690 /**
691  * Since the lesson module tries to re-use the question bank import classes in
692  * a crazy way, this is necessary to stop things breaking. This should be exactly
693  * the same as the class defined in question/format.php.
694  */
695 class qformat_based_on_xml extends qformat_default {
696     /**
697      * A lot of imported files contain unwanted entities.
698      * This method tries to clean up all known problems.
699      * @param string str string to correct
700      * @return string the corrected string
701      */
702     public function cleaninput($str) {
704         $html_code_list = array(
705             "&#039;" => "'",
706             "&#8217;" => "'",
707             "&#8220;" => "\"",
708             "&#8221;" => "\"",
709             "&#8211;" => "-",
710             "&#8212;" => "-",
711         );
712         $str = strtr($str, $html_code_list);
713         // Use core_text entities_to_utf8 function to convert only numerical entities.
714         $str = core_text::entities_to_utf8($str, false);
715         return $str;
716     }
718     /**
719      * Return the array moodle is expecting
720      * for an HTML text. No processing is done on $text.
721      * qformat classes that want to process $text
722      * for instance to import external images files
723      * and recode urls in $text must overwrite this method.
724      * @param array $text some HTML text string
725      * @return array with keys text, format and files.
726      */
727     public function text_field($text) {
728         return array(
729             'text' => trim($text),
730             'format' => FORMAT_HTML,
731             'files' => array(),
732         );
733     }
735     /**
736      * Return the value of a node, given a path to the node
737      * if it doesn't exist return the default value.
738      * @param array xml data to read
739      * @param array path path to node expressed as array
740      * @param mixed default
741      * @param bool istext process as text
742      * @param string error if set value must exist, return false and issue message if not
743      * @return mixed value
744      */
745     public function getpath($xml, $path, $default, $istext=false, $error='') {
746         foreach ($path as $index) {
747             if (!isset($xml[$index])) {
748                 if (!empty($error)) {
749                     $this->error($error);
750                     return false;
751                 } else {
752                     return $default;
753                 }
754             }
756             $xml = $xml[$index];
757         }
759         if ($istext) {
760             if (!is_string($xml)) {
761                 $this->error(get_string('invalidxml', 'qformat_xml'));
762             }
763             $xml = trim($xml);
764         }
766         return $xml;
767     }