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