MDL-21695 adding help string
[moodle.git] / mod / lesson / format.php
blobefa058c2e306b6cc7a4b2a290b15823244009933
1 <?php
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.
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/>.
18 /**
19 * format.php - Default format class for file imports/exports. Doesn't do
20 * everything on it's own -- it needs to be extended.
22 * Included by import.ph
24 * @package lesson
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 **/
29 /**
30 * Given some question info and some data about the the answers
31 * this function parses, organises and saves the question
33 * This is only used when IMPORTING questions and is only called
34 * from format.php
35 * Lifted from mod/quiz/lib.php -
36 * 1. all reference to oldanswers removed
37 * 2. all reference to quiz_multichoice table removed
38 * 3. In SHORTANSWER questions usecase is store in the qoption field
39 * 4. In NUMERIC questions store the range as two answers
40 * 5. TRUEFALSE options are ignored
41 * 6. For MULTICHOICE questions with more than one answer the qoption field is true
43 * @param opject $question Contains question data like question, type and answers.
44 * @return object Returns $result->error or $result->notice.
45 **/
46 function lesson_save_question_options($question, $lesson) {
47 global $DB;
49 // These lines are required to ensure that all page types have
50 // been loaded for the following switch
51 if (!($lesson instanceof lesson)) {
52 $lesson = new lesson($lesson);
54 $manager = lesson_page_type_manager::get($lesson);
56 $timenow = time();
57 switch ($question->qtype) {
58 case LESSON_PAGE_SHORTANSWER:
60 $answers = array();
61 $maxfraction = -1;
63 // Insert all the new answers
64 foreach ($question->answer as $key => $dataanswer) {
65 if ($dataanswer != "") {
66 $answer = new stdClass;
67 $answer->lessonid = $question->lessonid;
68 $answer->pageid = $question->id;
69 if ($question->fraction[$key] >=0.5) {
70 $answer->jumpto = LESSON_NEXTPAGE;
72 $answer->timecreated = $timenow;
73 $answer->grade = $question->fraction[$key] * 100;
74 $answer->answer = $dataanswer;
75 $answer->response = $question->feedback[$key];
76 $answer->id = $DB->insert_record("lesson_answers", $answer);
77 $answers[] = $answer->id;
78 if ($question->fraction[$key] > $maxfraction) {
79 $maxfraction = $question->fraction[$key];
85 /// Perform sanity checks on fractional grades
86 if ($maxfraction != 1) {
87 $maxfraction = $maxfraction * 100;
88 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
89 return $result;
91 break;
93 case LESSON_PAGE_NUMERICAL: // Note similarities to SHORTANSWER
95 $answers = array();
96 $maxfraction = -1;
99 // for each answer store the pair of min and max values even if they are the same
100 foreach ($question->answer as $key => $dataanswer) {
101 if ($dataanswer != "") {
102 $answer = new stdClass;
103 $answer->lessonid = $question->lessonid;
104 $answer->pageid = $question->id;
105 $answer->jumpto = LESSON_NEXTPAGE;
106 $answer->timecreated = $timenow;
107 $answer->grade = $question->fraction[$key] * 100;
108 $min = $question->answer[$key] - $question->tolerance[$key];
109 $max = $question->answer[$key] + $question->tolerance[$key];
110 $answer->answer = $min.":".$max;
111 // $answer->answer = $question->min[$key].":".$question->max[$key]; original line for min/max
112 $answer->response = $question->feedback[$key];
113 $answer->id = $DB->insert_record("lesson_answers", $answer);
115 $answers[] = $answer->id;
116 if ($question->fraction[$key] > $maxfraction) {
117 $maxfraction = $question->fraction[$key];
122 /// Perform sanity checks on fractional grades
123 if ($maxfraction != 1) {
124 $maxfraction = $maxfraction * 100;
125 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
126 return $result;
128 break;
131 case LESSON_PAGE_TRUEFALSE:
133 // the truth
134 $answer->lessonid = $question->lessonid;
135 $answer->pageid = $question->id;
136 $answer->timecreated = $timenow;
137 $answer->answer = get_string("true", "quiz");
138 $answer->grade = $question->answer * 100;
139 if ($answer->grade > 50 ) {
140 $answer->jumpto = LESSON_NEXTPAGE;
142 if (isset($question->feedbacktrue)) {
143 $answer->response = $question->feedbacktrue;
145 $true->id = $DB->insert_record("lesson_answers", $answer);
147 // the lie
148 $answer = new stdClass;
149 $answer->lessonid = $question->lessonid;
150 $answer->pageid = $question->id;
151 $answer->timecreated = $timenow;
152 $answer->answer = get_string("false", "quiz");
153 $answer->grade = (1 - (int)$question->answer) * 100;
154 if ($answer->grade > 50 ) {
155 $answer->jumpto = LESSON_NEXTPAGE;
157 if (isset($question->feedbackfalse)) {
158 $answer->response = $question->feedbackfalse;
160 $false->id = $DB->insert_record("lesson_answers", $answer);
162 break;
164 case LESSON_PAGE_MULTICHOICE:
166 $totalfraction = 0;
167 $maxfraction = -1;
169 $answers = array();
171 // Insert all the new answers
172 foreach ($question->answer as $key => $dataanswer) {
173 if ($dataanswer != "") {
174 $answer = new stdClass;
175 $answer->lessonid = $question->lessonid;
176 $answer->pageid = $question->id;
177 $answer->timecreated = $timenow;
178 $answer->grade = $question->fraction[$key] * 100;
179 // changed some defaults
180 /* Original Code
181 if ($answer->grade > 50 ) {
182 $answer->jumpto = LESSON_NEXTPAGE;
184 Replaced with: */
185 if ($answer->grade > 50 ) {
186 $answer->jumpto = LESSON_NEXTPAGE;
187 $answer->score = 1;
189 // end Replace
190 $answer->answer = $dataanswer;
191 $answer->response = $question->feedback[$key];
192 $answer->id = $DB->insert_record("lesson_answers", $answer);
193 // for Sanity checks
194 if ($question->fraction[$key] > 0) {
195 $totalfraction += $question->fraction[$key];
197 if ($question->fraction[$key] > $maxfraction) {
198 $maxfraction = $question->fraction[$key];
203 /// Perform sanity checks on fractional grades
204 if ($question->single) {
205 if ($maxfraction != 1) {
206 $maxfraction = $maxfraction * 100;
207 $result->notice = get_string("fractionsnomax", "quiz", $maxfraction);
208 return $result;
210 } else {
211 $totalfraction = round($totalfraction,2);
212 if ($totalfraction != 1) {
213 $totalfraction = $totalfraction * 100;
214 $result->notice = get_string("fractionsaddwrong", "quiz", $totalfraction);
215 return $result;
218 break;
220 case LESSON_PAGE_MATCHING:
222 $subquestions = array();
224 $defaultanswer = new stdClass;
225 $defaultanswer->lessonid = $question->lessonid;
226 $defaultanswer->pageid = $question->id;
227 $defaultanswer->timecreated = $timenow;
228 $defaultanswer->grade = 0;
230 // The first answer should always be the correct answer
231 $correctanswer = clone($defaultanswer);
232 $correctanswer->answer = get_string('thatsthecorrectanswer', 'lesson');
233 $DB->insert_record("lesson_answers", $correctanswer);
235 // The second answer should always be the wrong answer
236 $wronganswer = clone($defaultanswer);
237 $wronganswer->answer = get_string('thatsthewronganswer', 'lesson');
238 $DB->insert_record("lesson_answers", $wronganswer);
240 $i = 0;
241 // Insert all the new question+answer pairs
242 foreach ($question->subquestions as $key => $questiontext) {
243 $answertext = $question->subanswers[$key];
244 if (!empty($questiontext) and !empty($answertext)) {
245 $answer = clone($defaultanswer);
246 $answer->answer = $questiontext;
247 $answer->response = $answertext;
248 if ($i == 0) {
249 // first answer contains the correct answer jump
250 $answer->jumpto = LESSON_NEXTPAGE;
252 $subquestion->id = $DB->insert_record("lesson_answers", $answer);
253 $subquestions[] = $subquestion->id;
254 $i++;
258 if (count($subquestions) < 3) {
259 $result->notice = get_string("notenoughsubquestions", "quiz");
260 return $result;
262 break;
263 default:
264 $result->error = "Unsupported question type ($question->qtype)!";
265 return $result;
267 return true;
271 class qformat_default {
273 var $displayerrors = true;
274 var $category = NULL;
275 var $questionids = array();
276 var $qtypeconvert = array(NUMERICAL => LESSON_PAGE_NUMERICAL,
277 MULTICHOICE => LESSON_PAGE_MULTICHOICE,
278 TRUEFALSE => LESSON_PAGE_TRUEFALSE,
279 SHORTANSWER => LESSON_PAGE_SHORTANSWER,
280 MATCH => LESSON_PAGE_MATCHING
283 // Importing functions
284 function provide_import() {
285 return false;
288 function importpreprocess() {
289 // Does any pre-processing that may be desired
290 return true;
293 function importprocess($filename, $lesson, $pageid) {
294 global $DB, $OUTPUT;
296 /// Processes a given file. There's probably little need to change this
297 $timenow = time();
299 if (! $lines = $this->readdata($filename)) {
300 echo $OUTPUT->notification("File could not be read, or was empty");
301 return false;
304 if (! $questions = $this->readquestions($lines)) { // Extract all the questions
305 echo $OUTPUT->notification("There are no questions in this file!");
306 return false;
309 echo $OUTPUT->notification(get_string('importcount', 'lesson', sizeof($questions)));
311 $count = 0;
313 $unsupportedquestions = 0;
315 foreach ($questions as $question) { // Process and store each question
316 switch ($question->qtype) {
317 // the good ones
318 case SHORTANSWER :
319 case NUMERICAL :
320 case TRUEFALSE :
321 case MULTICHOICE :
322 case MATCH :
323 $count++;
325 echo "<hr><p><b>$count</b>. ".$question->questiontext."</p>";
326 $newpage = new stdClass;
327 $newpage->lessonid = $lesson->id;
328 $newpage->qtype = $this->qtypeconvert[$question->qtype];
329 switch ($question->qtype) {
330 case SHORTANSWER :
331 if (isset($question->usecase)) {
332 $newpage->qoption = $question->usecase;
334 break;
335 case MULTICHOICE :
336 if (isset($question->single)) {
337 $newpage->qoption = !$question->single;
339 break;
341 $newpage->timecreated = $timenow;
342 if ($question->name != $question->questiontext) {
343 $newpage->title = $question->name;
344 } else {
345 $newpage->title = "Page $count";
347 $newpage->contents = $question->questiontext;
349 // set up page links
350 if ($pageid) {
351 // the new page follows on from this page
352 if (!$page = $DB->get_record("lesson_pages", array("id" => $pageid))) {
353 print_error('invalidpageid', 'lesson');
355 $newpage->prevpageid = $pageid;
356 $newpage->nextpageid = $page->nextpageid;
357 // insert the page and reset $pageid
358 $newpageid = $DB->insert_record("lesson_pages", $newpage);
359 // update the linked list
360 $DB->set_field("lesson_pages", "nextpageid", $newpageid, array("id" => $pageid));
362 } else {
363 // new page is the first page
364 // get the existing (first) page (if any)
365 $params = array ("lessonid" => $lesson->id, "prevpageid" => 0);
366 if (!$page = $DB->get_record_select("lesson_pages", "lessonid = :lessonid AND prevpageid = :prevpageid", $params)) {
367 // there are no existing pages
368 $newpage->prevpageid = 0; // this is a first page
369 $newpage->nextpageid = 0; // this is the only page
370 $newpageid = $DB->insert_record("lesson_pages", $newpage);
371 } else {
372 // there are existing pages put this at the start
373 $newpage->prevpageid = 0; // this is a first page
374 $newpage->nextpageid = $page->id;
375 $newpageid = $DB->insert_record("lesson_pages", $newpage);
376 // update the linked list
377 $DB->set_field("lesson_pages", "prevpageid", $newpageid, array("id" => $page->id));
380 // reset $pageid and put the page ID in $question, used in save_question_option()
381 $pageid = $newpageid;
382 $question->id = $newpageid;
384 $this->questionids[] = $question->id;
386 // Now to save all the answers and type-specific options
388 $question->lessonid = $lesson->id; // needed for foreign key
389 $question->qtype = $this->qtypeconvert[$question->qtype];
390 $result = lesson_save_question_options($question, $lesson);
392 if (!empty($result->error)) {
393 echo $OUTPUT->notification($result->error);
394 return false;
397 if (!empty($result->notice)) {
398 echo $OUTPUT->notification($result->notice);
399 return true;
401 break;
402 // the Bad ones
403 default :
404 $unsupportedquestions++;
405 break;
409 if ($unsupportedquestions) {
410 echo $OUTPUT->notification(get_string('unknownqtypesnotimported', 'lesson', $unsupportedquestions));
412 return true;
416 function readdata($filename) {
417 /// Returns complete file with an array, one item per line
419 if (is_readable($filename)) {
420 $filearray = file($filename);
422 /// Check for Macintosh OS line returns (ie file on one line), and fix
423 if (preg_match("/\r/", $filearray[0]) AND !preg_match("/\n/", $filearray[0])) {
424 return explode("\r", $filearray[0]);
425 } else {
426 return $filearray;
429 return false;
432 function readquestions($lines) {
433 /// Parses an array of lines into an array of questions,
434 /// where each item is a question object as defined by
435 /// readquestion(). Questions are defined as anything
436 /// between blank lines.
438 $questions = array();
439 $currentquestion = array();
441 foreach ($lines as $line) {
442 $line = trim($line);
443 if (empty($line)) {
444 if (!empty($currentquestion)) {
445 if ($question = $this->readquestion($currentquestion)) {
446 $questions[] = $question;
448 $currentquestion = array();
450 } else {
451 $currentquestion[] = $line;
455 if (!empty($currentquestion)) { // There may be a final question
456 if ($question = $this->readquestion($currentquestion)) {
457 $questions[] = $question;
461 return $questions;
465 function readquestion($lines) {
466 /// Given an array of lines known to define a question in
467 /// this format, this function converts it into a question
468 /// object suitable for processing and insertion into Moodle.
470 echo "<p>This flash question format has not yet been completed!</p>";
472 return NULL;
475 function defaultquestion() {
476 // returns an "empty" question
477 // Somewhere to specify question parameters that are not handled
478 // by import but are required db fields.
479 // This should not be overridden.
480 global $CFG;
482 $question = new stdClass();
483 $question->shuffleanswers = get_config('quiz', 'shuffleanswers');
484 $question->defaultgrade = 1;
485 $question->image = "";
486 $question->usecase = 0;
487 $question->multiplier = array();
488 $question->generalfeedback = '';
489 $question->correctfeedback = '';
490 $question->partiallycorrectfeedback = '';
491 $question->incorrectfeedback = '';
492 $question->answernumbering = 'abc';
493 $question->penalty = 0.1;
494 $question->length = 1;
495 $question->qoption = 0;
496 $question->layout = 1;
498 return $question;
501 function importpostprocess() {
502 /// Does any post-processing that may be desired
503 /// Argument is a simple array of question ids that
504 /// have just been added.
505 return true;