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