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