MDL-22028 multichoice is now default new question type - anything is better that...
[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 *
5491947a 24 * @package lesson
0a4abb73
SH
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 **/
28
1e7f8ea2
PS
29defined('MOODLE_INTERNAL') || die();
30
0a4abb73
SH
31/**
32 * Given some question info and some data about the the answers
33 * this function parses, organises and saves the question
34 *
35 * This is only used when IMPORTING questions and is only called
36 * from format.php
37 * Lifted from mod/quiz/lib.php -
38 * 1. all reference to oldanswers removed
39 * 2. all reference to quiz_multichoice table removed
40 * 3. In SHORTANSWER questions usecase is store in the qoption field
41 * 4. In NUMERIC questions store the range as two answers
42 * 5. TRUEFALSE options are ignored
43 * 6. For MULTICHOICE questions with more than one answer the qoption field is true
44 *
45 * @param opject $question Contains question data like question, type and answers.
46 * @return object Returns $result->error or $result->notice.
5491947a 47 **/
0a4abb73
SH
48function lesson_save_question_options($question, $lesson) {
49 global $DB;
50
51 // These lines are required to ensure that all page types have
52 // been loaded for the following switch
53 if (!($lesson instanceof lesson)) {
54 $lesson = new lesson($lesson);
55 }
56 $manager = lesson_page_type_manager::get($lesson);
2f67a9b3 57
0a4abb73
SH
58 $timenow = time();
59 switch ($question->qtype) {
60 case LESSON_PAGE_SHORTANSWER:
61
62 $answers = array();
63 $maxfraction = -1;
64
65 // Insert all the new answers
66 foreach ($question->answer as $key => $dataanswer) {
67 if ($dataanswer != "") {
68 $answer = new stdClass;
69 $answer->lessonid = $question->lessonid;
70 $answer->pageid = $question->id;
71 if ($question->fraction[$key] >=0.5) {
72 $answer->jumpto = LESSON_NEXTPAGE;
73 }
74 $answer->timecreated = $timenow;
75 $answer->grade = $question->fraction[$key] * 100;
76 $answer->answer = $dataanswer;
77 $answer->response = $question->feedback[$key];
78 $answer->id = $DB->insert_record("lesson_answers", $answer);
79 $answers[] = $answer->id;
80 if ($question->fraction[$key] > $maxfraction) {
81 $maxfraction = $question->fraction[$key];
82 }
83 }
84 }
85
86
87 /// Perform sanity checks on fractional grades
88 if ($maxfraction != 1) {
89 $maxfraction = $maxfraction * 100;
90 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
91 return $result;
92 }
93 break;
94
95 case LESSON_PAGE_NUMERICAL: // Note similarities to SHORTANSWER
96
97 $answers = array();
98 $maxfraction = -1;
99
100
101 // for each answer store the pair of min and max values even if they are the same
102 foreach ($question->answer as $key => $dataanswer) {
103 if ($dataanswer != "") {
104 $answer = new stdClass;
105 $answer->lessonid = $question->lessonid;
106 $answer->pageid = $question->id;
107 $answer->jumpto = LESSON_NEXTPAGE;
108 $answer->timecreated = $timenow;
109 $answer->grade = $question->fraction[$key] * 100;
110 $min = $question->answer[$key] - $question->tolerance[$key];
111 $max = $question->answer[$key] + $question->tolerance[$key];
112 $answer->answer = $min.":".$max;
113 // $answer->answer = $question->min[$key].":".$question->max[$key]; original line for min/max
114 $answer->response = $question->feedback[$key];
115 $answer->id = $DB->insert_record("lesson_answers", $answer);
116
117 $answers[] = $answer->id;
118 if ($question->fraction[$key] > $maxfraction) {
119 $maxfraction = $question->fraction[$key];
120 }
121 }
122 }
123
124 /// Perform sanity checks on fractional grades
125 if ($maxfraction != 1) {
126 $maxfraction = $maxfraction * 100;
127 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
128 return $result;
129 }
130 break;
131
132
133 case LESSON_PAGE_TRUEFALSE:
134
135 // the truth
136 $answer->lessonid = $question->lessonid;
137 $answer->pageid = $question->id;
138 $answer->timecreated = $timenow;
139 $answer->answer = get_string("true", "quiz");
140 $answer->grade = $question->answer * 100;
141 if ($answer->grade > 50 ) {
142 $answer->jumpto = LESSON_NEXTPAGE;
143 }
144 if (isset($question->feedbacktrue)) {
145 $answer->response = $question->feedbacktrue;
146 }
147 $true->id = $DB->insert_record("lesson_answers", $answer);
148
149 // the lie
150 $answer = new stdClass;
151 $answer->lessonid = $question->lessonid;
152 $answer->pageid = $question->id;
153 $answer->timecreated = $timenow;
154 $answer->answer = get_string("false", "quiz");
155 $answer->grade = (1 - (int)$question->answer) * 100;
156 if ($answer->grade > 50 ) {
157 $answer->jumpto = LESSON_NEXTPAGE;
158 }
159 if (isset($question->feedbackfalse)) {
160 $answer->response = $question->feedbackfalse;
161 }
162 $false->id = $DB->insert_record("lesson_answers", $answer);
163
164 break;
165
166 case LESSON_PAGE_MULTICHOICE:
167
168 $totalfraction = 0;
169 $maxfraction = -1;
170
171 $answers = array();
172
173 // Insert all the new answers
174 foreach ($question->answer as $key => $dataanswer) {
175 if ($dataanswer != "") {
176 $answer = new stdClass;
177 $answer->lessonid = $question->lessonid;
178 $answer->pageid = $question->id;
179 $answer->timecreated = $timenow;
180 $answer->grade = $question->fraction[$key] * 100;
181 // changed some defaults
182 /* Original Code
183 if ($answer->grade > 50 ) {
184 $answer->jumpto = LESSON_NEXTPAGE;
185 }
186 Replaced with: */
187 if ($answer->grade > 50 ) {
188 $answer->jumpto = LESSON_NEXTPAGE;
189 $answer->score = 1;
190 }
191 // end Replace
192 $answer->answer = $dataanswer;
193 $answer->response = $question->feedback[$key];
194 $answer->id = $DB->insert_record("lesson_answers", $answer);
195 // for Sanity checks
196 if ($question->fraction[$key] > 0) {
197 $totalfraction += $question->fraction[$key];
198 }
199 if ($question->fraction[$key] > $maxfraction) {
200 $maxfraction = $question->fraction[$key];
201 }
202 }
203 }
204
205 /// Perform sanity checks on fractional grades
206 if ($question->single) {
207 if ($maxfraction != 1) {
208 $maxfraction = $maxfraction * 100;
209 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
210 return $result;
211 }
212 } else {
213 $totalfraction = round($totalfraction,2);
214 if ($totalfraction != 1) {
215 $totalfraction = $totalfraction * 100;
216 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
217 return $result;
218 }
219 }
220 break;
221
222 case LESSON_PAGE_MATCHING:
223
224 $subquestions = array();
225
226 $defaultanswer = new stdClass;
227 $defaultanswer->lessonid = $question->lessonid;
228 $defaultanswer->pageid = $question->id;
229 $defaultanswer->timecreated = $timenow;
230 $defaultanswer->grade = 0;
231
232 // The first answer should always be the correct answer
233 $correctanswer = clone($defaultanswer);
234 $correctanswer->answer = get_string('thatsthecorrectanswer', 'lesson');
235 $DB->insert_record("lesson_answers", $correctanswer);
236
237 // The second answer should always be the wrong answer
238 $wronganswer = clone($defaultanswer);
239 $wronganswer->answer = get_string('thatsthewronganswer', 'lesson');
240 $DB->insert_record("lesson_answers", $wronganswer);
241
242 $i = 0;
243 // Insert all the new question+answer pairs
244 foreach ($question->subquestions as $key => $questiontext) {
245 $answertext = $question->subanswers[$key];
246 if (!empty($questiontext) and !empty($answertext)) {
247 $answer = clone($defaultanswer);
248 $answer->answer = $questiontext;
249 $answer->response = $answertext;
250 if ($i == 0) {
251 // first answer contains the correct answer jump
252 $answer->jumpto = LESSON_NEXTPAGE;
253 }
254 $subquestion->id = $DB->insert_record("lesson_answers", $answer);
255 $subquestions[] = $subquestion->id;
256 $i++;
257 }
258 }
259
260 if (count($subquestions) < 3) {
261 $result->notice = get_string("notenoughsubquestions", "quiz");
262 return $result;
263 }
264 break;
265 default:
266 $result->error = "Unsupported question type ($question->qtype)!";
267 return $result;
268 }
269 return true;
270}
394c97c8 271
394c97c8 272
90455bb3 273class qformat_default {
394c97c8 274
275 var $displayerrors = true;
276 var $category = NULL;
277 var $questionids = array();
0a4abb73
SH
278 var $qtypeconvert = array(NUMERICAL => LESSON_PAGE_NUMERICAL,
279 MULTICHOICE => LESSON_PAGE_MULTICHOICE,
280 TRUEFALSE => LESSON_PAGE_TRUEFALSE,
281 SHORTANSWER => LESSON_PAGE_SHORTANSWER,
282 MATCH => LESSON_PAGE_MATCHING
90455bb3 283 );
394c97c8 284
0a4abb73
SH
285 // Importing functions
286 function provide_import() {
287 return false;
288 }
394c97c8 289
290 function importpreprocess() {
0a4abb73 291 // Does any pre-processing that may be desired
394c97c8 292 return true;
293 }
294
295 function importprocess($filename, $lesson, $pageid) {
d68ccdba 296 global $DB, $OUTPUT;
86342d63 297
394c97c8 298 /// Processes a given file. There's probably little need to change this
299 $timenow = time();
300
301 if (! $lines = $this->readdata($filename)) {
d68ccdba 302 echo $OUTPUT->notification("File could not be read, or was empty");
394c97c8 303 return false;
304 }
305
306 if (! $questions = $this->readquestions($lines)) { // Extract all the questions
d68ccdba 307 echo $OUTPUT->notification("There are no questions in this file!");
394c97c8 308 return false;
309 }
86342d63 310
d68ccdba 311 echo $OUTPUT->notification(get_string('importcount', 'lesson', sizeof($questions)));
394c97c8 312
313 $count = 0;
314
0a4abb73
SH
315 $unsupportedquestions = 0;
316
394c97c8 317 foreach ($questions as $question) { // Process and store each question
318 switch ($question->qtype) {
319 // the good ones
320 case SHORTANSWER :
321 case NUMERICAL :
322 case TRUEFALSE :
323 case MULTICHOICE :
324 case MATCH :
325 $count++;
326
294ce987 327 echo "<hr><p><b>$count</b>. ".$question->questiontext."</p>";
f7ffb898 328 $newpage = new stdClass;
394c97c8 329 $newpage->lessonid = $lesson->id;
90455bb3 330 $newpage->qtype = $this->qtypeconvert[$question->qtype];
394c97c8 331 switch ($question->qtype) {
332 case SHORTANSWER :
90455bb3 333 if (isset($question->usecase)) {
334 $newpage->qoption = $question->usecase;
335 }
394c97c8 336 break;
337 case MULTICHOICE :
338 if (isset($question->single)) {
339 $newpage->qoption = !$question->single;
340 }
341 break;
342 }
343 $newpage->timecreated = $timenow;
344 if ($question->name != $question->questiontext) {
345 $newpage->title = $question->name;
346 } else {
347 $newpage->title = "Page $count";
348 }
349 $newpage->contents = $question->questiontext;
350
351 // set up page links
352 if ($pageid) {
353 // the new page follows on from this page
646fc290 354 if (!$page = $DB->get_record("lesson_pages", array("id" => $pageid))) {
771dc7b2 355 print_error('invalidpageid', 'lesson');
394c97c8 356 }
357 $newpage->prevpageid = $pageid;
358 $newpage->nextpageid = $page->nextpageid;
359 // insert the page and reset $pageid
6d40f12e 360 $newpageid = $DB->insert_record("lesson_pages", $newpage);
394c97c8 361 // update the linked list
6d40f12e 362 $DB->set_field("lesson_pages", "nextpageid", $newpageid, array("id" => $pageid));
394c97c8 363
364 } else {
365 // new page is the first page
366 // get the existing (first) page (if any)
646fc290 367 $params = array ("lessonid" => $lesson->id, "prevpageid" => 0);
368 if (!$page = $DB->get_record_select("lesson_pages", "lessonid = :lessonid AND prevpageid = :prevpageid", $params)) {
394c97c8 369 // there are no existing pages
370 $newpage->prevpageid = 0; // this is a first page
371 $newpage->nextpageid = 0; // this is the only page
646fc290 372 $newpageid = $DB->insert_record("lesson_pages", $newpage);
394c97c8 373 } else {
374 // there are existing pages put this at the start
375 $newpage->prevpageid = 0; // this is a first page
376 $newpage->nextpageid = $page->id;
646fc290 377 $newpageid = $DB->insert_record("lesson_pages", $newpage);
394c97c8 378 // update the linked list
6d40f12e 379 $DB->set_field("lesson_pages", "prevpageid", $newpageid, array("id" => $page->id));
394c97c8 380 }
381 }
382 // reset $pageid and put the page ID in $question, used in save_question_option()
383 $pageid = $newpageid;
384 $question->id = $newpageid;
86342d63 385
394c97c8 386 $this->questionids[] = $question->id;
387
388 // Now to save all the answers and type-specific options
389
390 $question->lessonid = $lesson->id; // needed for foreign key
90455bb3 391 $question->qtype = $this->qtypeconvert[$question->qtype];
0a4abb73 392 $result = lesson_save_question_options($question, $lesson);
394c97c8 393
394 if (!empty($result->error)) {
d68ccdba 395 echo $OUTPUT->notification($result->error);
394c97c8 396 return false;
397 }
398
399 if (!empty($result->notice)) {
d68ccdba 400 echo $OUTPUT->notification($result->notice);
394c97c8 401 return true;
402 }
403 break;
404 // the Bad ones
405 default :
0a4abb73
SH
406 $unsupportedquestions++;
407 break;
394c97c8 408 }
86342d63 409
394c97c8 410 }
0a4abb73
SH
411 if ($unsupportedquestions) {
412 echo $OUTPUT->notification(get_string('unknownqtypesnotimported', 'lesson', $unsupportedquestions));
413 }
394c97c8 414 return true;
415 }
416
417
418 function readdata($filename) {
419 /// Returns complete file with an array, one item per line
420
421 if (is_readable($filename)) {
422 $filearray = file($filename);
423
424 /// Check for Macintosh OS line returns (ie file on one line), and fix
6dbcacee 425 if (preg_match("/\r/", $filearray[0]) AND !preg_match("/\n/", $filearray[0])) {
394c97c8 426 return explode("\r", $filearray[0]);
427 } else {
428 return $filearray;
429 }
430 }
431 return false;
432 }
433
434 function readquestions($lines) {
86342d63
PS
435 /// Parses an array of lines into an array of questions,
436 /// where each item is a question object as defined by
437 /// readquestion(). Questions are defined as anything
394c97c8 438 /// between blank lines.
86342d63 439
394c97c8 440 $questions = array();
441 $currentquestion = array();
442
443 foreach ($lines as $line) {
444 $line = trim($line);
445 if (empty($line)) {
446 if (!empty($currentquestion)) {
447 if ($question = $this->readquestion($currentquestion)) {
448 $questions[] = $question;
449 }
450 $currentquestion = array();
451 }
452 } else {
453 $currentquestion[] = $line;
454 }
455 }
456
457 if (!empty($currentquestion)) { // There may be a final question
458 if ($question = $this->readquestion($currentquestion)) {
459 $questions[] = $question;
460 }
461 }
462
463 return $questions;
464 }
465
466
467 function readquestion($lines) {
86342d63
PS
468 /// Given an array of lines known to define a question in
469 /// this format, this function converts it into a question
394c97c8 470 /// object suitable for processing and insertion into Moodle.
471
472 echo "<p>This flash question format has not yet been completed!</p>";
473
474 return NULL;
475 }
476
90455bb3 477 function defaultquestion() {
478 // returns an "empty" question
479 // Somewhere to specify question parameters that are not handled
480 // by import but are required db fields.
86342d63 481 // This should not be overridden.
508fe4d8 482 global $CFG;
483
90455bb3 484 $question = new stdClass();
0a4abb73 485 $question->shuffleanswers = get_config('quiz', 'shuffleanswers');
508fe4d8 486 $question->defaultgrade = 1;
487 $question->image = "";
488 $question->usecase = 0;
489 $question->multiplier = array();
490 $question->generalfeedback = '';
491 $question->correctfeedback = '';
492 $question->partiallycorrectfeedback = '';
493 $question->incorrectfeedback = '';
494 $question->answernumbering = 'abc';
495 $question->penalty = 0.1;
496 $question->length = 1;
90455bb3 497 $question->qoption = 0;
498 $question->layout = 1;
86342d63 499
90455bb3 500 return $question;
501 }
394c97c8 502
503 function importpostprocess() {
0a4abb73
SH
504 /// Does any post-processing that may be desired
505 /// Argument is a simple array of question ids that
506 /// have just been added.
394c97c8 507 return true;
508 }
509
510}
511
86342d63 512