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