MDL-16221
[moodle.git] / question / format.php
CommitLineData
271e6dec 1<?php // $Id$
4323d029 2/**
3 * Base class for question import and export formats.
4 *
5 * @author Martin Dougiamas, Howard Miller, and many others.
6 * {@link http://moodle.org}
7 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
8 * @package questionbank
9 * @subpackage importexport
4323d029 10 */
f5565b69 11class qformat_default {
aca318e1 12
f1abd39f 13 var $displayerrors = true;
aca318e1 14 var $category = NULL;
2c44a3d3 15 var $questions = array();
aca318e1 16 var $course = NULL;
08892d5b 17 var $filename = '';
18 var $matchgrades = 'error';
19 var $catfromfile = 0;
271e6dec 20 var $contextfromfile = 0;
f1abd39f 21 var $cattofile = 0;
271e6dec 22 var $contexttofile = 0;
aca318e1 23 var $questionids = array();
f3701561 24 var $importerrors = 0;
25 var $stoponerror = true;
271e6dec 26 var $translator = null;
1b8b535d 27 var $canaccessbackupdata = true;
271e6dec 28
aca318e1 29
30// functions to indicate import/export functionality
31// override to return true if implemented
32
33 function provide_import() {
34 return false;
35 }
36
37 function provide_export() {
38 return false;
39 }
40
08892d5b 41// Accessor methods
aca318e1 42
08892d5b 43 /**
44 * set the category
45 * @param object category the category object
46 */
47 function setCategory( $category ) {
2c44a3d3 48 if (count($this->questions)){
49 debugging('You shouldn\'t call setCategory after setQuestions');
50 }
08892d5b 51 $this->category = $category;
52 }
aca318e1 53
2c44a3d3 54 /**
55 * Set the specific questions to export. Should not include questions with
56 * parents (sub questions of cloze question type).
57 * Only used for question export.
58 * @param array of question objects
59 */
60 function setQuestions( $questions ) {
61 if ($this->category !== null){
62 debugging('You shouldn\'t call setQuestions after setCategory');
63 }
64 $this->questions = $questions;
65 }
66
08892d5b 67 /**
68 * set the course class variable
69 * @param course object Moodle course variable
70 */
71 function setCourse( $course ) {
aca318e1 72 $this->course = $course;
08892d5b 73 }
271e6dec 74 /**
75 * set an array of contexts.
76 * @param array $contexts Moodle course variable
77 */
78 function setContexts($contexts) {
79 $this->contexts = $contexts;
80 $this->translator = new context_to_string_translator($this->contexts);
81 }
aca318e1 82
08892d5b 83 /**
84 * set the filename
85 * @param string filename name of file to import/export
86 */
87 function setFilename( $filename ) {
88 $this->filename = $filename;
89 }
90
91 /**
92 * set matchgrades
93 * @param string matchgrades error or nearest for grades
94 */
95 function setMatchgrades( $matchgrades ) {
96 $this->matchgrades = $matchgrades;
aca318e1 97 }
98
76f0a334 99 /**
08892d5b 100 * set catfromfile
101 * @param bool catfromfile allow categories embedded in import file
76f0a334 102 */
08892d5b 103 function setCatfromfile( $catfromfile ) {
104 $this->catfromfile = $catfromfile;
105 }
271e6dec 106
107 /**
108 * set contextfromfile
109 * @param bool $contextfromfile allow contexts embedded in import file
110 */
111 function setContextfromfile($contextfromfile) {
112 $this->contextfromfile = $contextfromfile;
113 }
114
f1abd39f 115 /**
116 * set cattofile
117 * @param bool cattofile exports categories within export file
118 */
119 function setCattofile( $cattofile ) {
120 $this->cattofile = $cattofile;
271e6dec 121 }
122 /**
123 * set contexttofile
124 * @param bool cattofile exports categories within export file
125 */
126 function setContexttofile($contexttofile) {
127 $this->contexttofile = $contexttofile;
128 }
aca318e1 129
f3701561 130 /**
131 * set stoponerror
132 * @param bool stoponerror stops database write if any errors reported
133 */
134 function setStoponerror( $stoponerror ) {
135 $this->stoponerror = $stoponerror;
136 }
137
1b8b535d 138 /**
139 * @param boolean $canaccess Whether the current use can access the backup data folder. Determines
140 * where export files are saved.
141 */
142 function set_can_access_backupdata($canaccess) {
143 $this->canaccessbackupdata = $canaccess;
144 }
145
f3701561 146/***********************
147 * IMPORTING FUNCTIONS
148 ***********************/
149
150 /**
151 * Handle parsing error
152 */
153 function error( $message, $text='', $questionname='' ) {
cdeabc06 154 $importerrorquestion = get_string('importerrorquestion','quiz');
155
f3701561 156 echo "<div class=\"importerror\">\n";
cdeabc06 157 echo "<strong>$importerrorquestion $questionname</strong>";
f3701561 158 if (!empty($text)) {
159 $text = s($text);
160 echo "<blockquote>$text</blockquote>\n";
161 }
162 echo "<strong>$message</strong>\n";
163 echo "</div>";
164
165 $this->importerrors++;
166 }
08892d5b 167
271e6dec 168 /**
a41e3287 169 * Import for questiontype plugins
170 * Do not override.
171 * @param data mixed The segment of data containing the question
172 * @param question object processed (so far) by standard import code if appropriate
173 * @param extra mixed any additional format specific data that may be passed by the format
174 * @return object question object suitable for save_options() or false if cannot handle
175 */
176 function try_importing_using_qtypes( $data, $question=null, $extra=null ) {
177 global $QTYPES;
178
179 // work out what format we are using
180 $formatname = substr( get_class( $this ), strlen('qformat_'));
181 $methodname = "import_from_$formatname";
182
183 // loop through installed questiontypes checking for
184 // function to handle this question
185 foreach ($QTYPES as $qtype) {
186 if (method_exists( $qtype, $methodname)) {
187 if ($question = $qtype->$methodname( $data, $question, $this, $extra )) {
188 return $question;
189 }
190 }
271e6dec 191 }
192 return false;
a41e3287 193 }
194
08892d5b 195 /**
196 * Perform any required pre-processing
f3701561 197 * @return boolean success
08892d5b 198 */
199 function importpreprocess() {
200 return true;
201 }
202
203 /**
204 * Process the file
205 * This method should not normally be overidden
f3701561 206 * @return boolean success
08892d5b 207 */
208 function importprocess() {
f34488b2 209 global $USER, $DB;
f3701561 210
67c12527 211 // reset the timer in case file upload was slow
212 @set_time_limit();
213
f3701561 214 // STAGE 1: Parse the file
215 notify( get_string('parsingquestions','quiz') );
271e6dec 216
08892d5b 217 if (! $lines = $this->readdata($this->filename)) {
1e3d6fd8 218 notify( get_string('cannotread','quiz') );
aca318e1 219 return false;
220 }
221
222 if (! $questions = $this->readquestions($lines)) { // Extract all the questions
1e3d6fd8 223 notify( get_string('noquestionsinfile','quiz') );
aca318e1 224 return false;
225 }
226
f3701561 227 // STAGE 2: Write data to database
ce2df288 228 notify( get_string('importingquestions','quiz',$this->count_questions($questions)) );
aca318e1 229
f3701561 230 // check for errors before we continue
231 if ($this->stoponerror and ($this->importerrors>0)) {
232 return false;
233 }
234
76f0a334 235 // get list of valid answer grades
236 $grades = get_grade_options();
237 $gradeoptionsfull = $grades->gradeoptionsfull;
238
c1828e0b 239 // check answer grades are valid
240 // (now need to do this here because of 'stop on error': MDL-10689)
241 $gradeerrors = 0;
242 $goodquestions = array();
243 foreach ($questions as $question) {
244 if (!empty($question->fraction) and (is_array($question->fraction))) {
245 $fractions = $question->fraction;
246 $answersvalid = true; // in case they are!
247 foreach ($fractions as $key => $fraction) {
248 $newfraction = match_grade_options($gradeoptionsfull, $fraction, $this->matchgrades);
249 if ($newfraction===false) {
250 $answersvalid = false;
251 }
252 else {
253 $fractions[$key] = $newfraction;
254 }
255 }
256 if (!$answersvalid) {
257 notify(get_string('matcherror', 'quiz'));
258 ++$gradeerrors;
259 continue;
260 }
261 else {
262 $question->fraction = $fractions;
263 }
264 }
265 $goodquestions[] = $question;
266 }
267 $questions = $goodquestions;
268
269 // check for errors before we continue
270 if ($this->stoponerror and ($gradeerrors>0)) {
271 return false;
272 }
273
274 // count number of questions processed
aca318e1 275 $count = 0;
276
277 foreach ($questions as $question) { // Process and store each question
08892d5b 278
271e6dec 279 // reset the php timeout
67c12527 280 @set_time_limit();
281
08892d5b 282 // check for category modifiers
283 if ($question->qtype=='category') {
284 if ($this->catfromfile) {
285 // find/create category object
40e71443 286 $catpath = $question->category;
271e6dec 287 $newcategory = $this->create_category_path( $catpath, '/');
08892d5b 288 if (!empty($newcategory)) {
289 $this->category = $newcategory;
290 }
291 }
271e6dec 292 continue;
08892d5b 293 }
294
aca318e1 295 $count++;
296
5b0dc681 297 echo "<hr /><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
aca318e1 298
299 $question->category = $this->category->id;
300 $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
aca318e1 301
271e6dec 302 $question->createdby = $USER->id;
303 $question->timecreated = time();
304
f34488b2 305 if (!$question->id = $DB->insert_record("question", $question)) {
5a2a5331 306 print_error('cannotinsert','quiz');
aca318e1 307 }
308
309 $this->questionids[] = $question->id;
310
311 // Now to save all the answers and type-specific options
312
313 global $QTYPES;
314 $result = $QTYPES[$question->qtype]
315 ->save_question_options($question);
316
317 if (!empty($result->error)) {
318 notify($result->error);
319 return false;
320 }
321
322 if (!empty($result->notice)) {
323 notify($result->notice);
324 return true;
325 }
cbe20043 326
327 // Give the question a unique version stamp determined by question_hash()
e5d7d1dc 328 $DB->set_field('question', 'version', question_hash($question), array('id'=>$question->id));
aca318e1 329 }
330 return true;
331 }
ce2df288 332 /**
333 * Count all non-category questions in the questions array.
f34488b2 334 *
ce2df288 335 * @param array questions An array of question objects.
336 * @return int The count.
f34488b2 337 *
ce2df288 338 */
339 function count_questions($questions) {
340 $count = 0;
341 if (!is_array($questions)) {
342 return $count;
343 }
344 foreach ($questions as $question) {
345 if (!is_object($question) || !isset($question->qtype) || ($question->qtype == 'category')) {
346 continue;
347 }
348 $count++;
349 }
350 return $count;
351 }
352
271e6dec 353 /**
354 * find and/or create the category described by a delimited list
355 * e.g. $course$/tom/dick/harry or tom/dick/harry
356 *
357 * removes any context string no matter whether $getcontext is set
358 * but if $getcontext is set then ignore the context and use selected category context.
359 *
360 * @param string catpath delimited category path
361 * @param string delimiter path delimiting character
362 * @param int courseid course to search for categories
363 * @return mixed category object or null if fails
364 */
365 function create_category_path($catpath, $delimiter='/') {
f34488b2 366 global $DB;
271e6dec 367 $catpath = clean_param($catpath, PARAM_PATH);
368 $catnames = explode($delimiter, $catpath);
369 $parent = 0;
370 $category = null;
5ca9e32d 371
372 // check for context id in path, it might not be there in pre 1.9 exports
373 $matchcount = preg_match('/^\$([a-z]+)\$$/', $catnames[0], $matches);
374 if ($matchcount==1) {
271e6dec 375 $contextid = $this->translator->string_to_context($matches[1]);
376 array_shift($catnames);
377 } else {
378 $contextid = FALSE;
379 }
380 if ($this->contextfromfile && ($contextid !== FALSE)){
381 $context = get_context_instance_by_id($contextid);
382 require_capability('moodle/question:add', $context);
383 } else {
384 $context = get_context_instance_by_id($this->category->contextid);
385 }
386 foreach ($catnames as $catname) {
f34488b2 387 if ($category = $DB->get_record( 'question_categories', array('name' => $catname, 'contextid' => $context->id, 'parent' => $parent))) {
271e6dec 388 $parent = $category->id;
389 } else {
390 require_capability('moodle/question:managecategory', $context);
391 // create the new category
392 $category = new object;
393 $category->contextid = $context->id;
394 $category->name = $catname;
395 $category->info = '';
396 $category->parent = $parent;
397 $category->sortorder = 999;
398 $category->stamp = make_unique_id_code();
f34488b2 399 if (!($id = $DB->insert_record('question_categories', $category))) {
d9f30321 400 print_error("cannotcreatecategory");
271e6dec 401 }
402 $category->id = $id;
403 $parent = $id;
404 }
405 }
406 return $category;
407 }
3f5633df 408
f3701561 409 /**
410 * Return complete file within an array, one item per line
411 * @param string filename name of file
412 * @return mixed contents array or false on failure
413 */
aca318e1 414 function readdata($filename) {
aca318e1 415 if (is_readable($filename)) {
416 $filearray = file($filename);
417
418 /// Check for Macintosh OS line returns (ie file on one line), and fix
419 if (ereg("\r", $filearray[0]) AND !ereg("\n", $filearray[0])) {
420 return explode("\r", $filearray[0]);
421 } else {
422 return $filearray;
423 }
424 }
425 return false;
426 }
427
f3701561 428 /**
271e6dec 429 * Parses an array of lines into an array of questions,
430 * where each item is a question object as defined by
431 * readquestion(). Questions are defined as anything
f3701561 432 * between blank lines.
433 *
434 * If your format does not use blank lines as a delimiter
435 * then you will need to override this method. Even then
436 * try to use readquestion for each question
437 * @param array lines array of lines from readdata
438 * @return array array of question objects
439 */
aca318e1 440 function readquestions($lines) {
271e6dec 441
aca318e1 442 $questions = array();
443 $currentquestion = array();
444
445 foreach ($lines as $line) {
446 $line = trim($line);
447 if (empty($line)) {
448 if (!empty($currentquestion)) {
449 if ($question = $this->readquestion($currentquestion)) {
450 $questions[] = $question;
451 }
452 $currentquestion = array();
453 }
454 } else {
455 $currentquestion[] = $line;
456 }
457 }
458
459 if (!empty($currentquestion)) { // There may be a final question
460 if ($question = $this->readquestion($currentquestion)) {
461 $questions[] = $question;
462 }
463 }
464
465 return $questions;
466 }
467
468
f3701561 469 /**
470 * return an "empty" question
471 * Somewhere to specify question parameters that are not handled
472 * by import but are required db fields.
473 * This should not be overridden.
474 * @return object default question
271e6dec 475 */
aca318e1 476 function defaultquestion() {
b0679efa 477 global $CFG;
271e6dec 478
aca318e1 479 $question = new stdClass();
271e6dec 480 $question->shuffleanswers = $CFG->quiz_shuffleanswers;
aca318e1 481 $question->defaultgrade = 1;
482 $question->image = "";
483 $question->usecase = 0;
484 $question->multiplier = array();
172f6d95 485 $question->generalfeedback = '';
08892d5b 486 $question->correctfeedback = '';
487 $question->partiallycorrectfeedback = '';
488 $question->incorrectfeedback = '';
5931ea94 489 $question->answernumbering = 'abc';
46013523 490 $question->penalty = 0.1;
3f5633df 491 $question->length = 1;
aca318e1 492
5fd8f999 493 // this option in case the questiontypes class wants
494 // to know where the data came from
495 $question->export_process = true;
093414d2 496 $question->import_process = true;
5fd8f999 497
aca318e1 498 return $question;
499 }
500
f3701561 501 /**
271e6dec 502 * Given the data known to define a question in
503 * this format, this function converts it into a question
f3701561 504 * object suitable for processing and insertion into Moodle.
505 *
506 * If your format does not use blank lines to delimit questions
507 * (e.g. an XML format) you must override 'readquestions' too
508 * @param $lines mixed data that represents question
509 * @return object question object
510 */
aca318e1 511 function readquestion($lines) {
aca318e1 512
1e3d6fd8 513 $formatnotimplemented = get_string( 'formatnotimplemented','quiz' );
514 echo "<p>$formatnotimplemented</p>";
aca318e1 515
516 return NULL;
517 }
518
f3701561 519 /**
520 * Override if any post-processing is required
521 * @return boolean success
522 */
aca318e1 523 function importpostprocess() {
aca318e1 524 return true;
525 }
526
f3701561 527 /**
528 * Import an image file encoded in base64 format
529 * @param string path path (in course data) to store picture
530 * @param string base64 encoded picture
531 * @return string filename (nb. collisions are handled)
532 */
d08e16b2 533 function importimagefile( $path, $base64 ) {
d08e16b2 534 global $CFG;
535
536 // all this to get the destination directory
537 // and filename!
538 $fullpath = "{$CFG->dataroot}/{$this->course->id}/$path";
539 $path_parts = pathinfo( $fullpath );
540 $destination = $path_parts['dirname'];
541 $file = clean_filename( $path_parts['basename'] );
542
71735794 543 // check if path exists
544 check_dir_exists($destination, true, true );
545
d08e16b2 546 // detect and fix any filename collision - get unique filename
271e6dec 547 $newfiles = resolve_filename_collisions( $destination, array($file) );
d08e16b2 548 $newfile = $newfiles[0];
549
550 // convert and save file contents
551 if (!$content = base64_decode( $base64 )) {
46013523 552 return '';
d08e16b2 553 }
554 $newfullpath = "$destination/$newfile";
555 if (!$fh = fopen( $newfullpath, 'w' )) {
46013523 556 return '';
d08e16b2 557 }
558 if (!fwrite( $fh, $content )) {
46013523 559 return '';
d08e16b2 560 }
561 fclose( $fh );
562
563 // return the (possibly) new filename
71735794 564 $newfile = ereg_replace("{$CFG->dataroot}/{$this->course->id}/", '',$newfullpath);
d08e16b2 565 return $newfile;
566 }
567
3f5633df 568
f3701561 569/*******************
570 * EXPORT FUNCTIONS
571 *******************/
aca318e1 572
271e6dec 573 /**
a41e3287 574 * Provide export functionality for plugin questiontypes
575 * Do not override
576 * @param name questiontype name
271e6dec 577 * @param question object data to export
a41e3287 578 * @param extra mixed any addition format specific data needed
579 * @return string the data to append to export or false if error (or unhandled)
580 */
581 function try_exporting_using_qtypes( $name, $question, $extra=null ) {
582 global $QTYPES;
583
584 // work out the name of format in use
585 $formatname = substr( get_class( $this ), strlen( 'qformat_' ));
586 $methodname = "export_to_$formatname";
587
588 if (array_key_exists( $name, $QTYPES )) {
589 $qtype = $QTYPES[ $name ];
590 if (method_exists( $qtype, $methodname )) {
591 if ($data = $qtype->$methodname( $question, $this, $extra )) {
592 return $data;
593 }
594 }
595 }
596 return false;
597 }
598
f3701561 599 /**
600 * Return the files extension appropriate for this type
601 * override if you don't want .txt
602 * @return string file extension
603 */
aca318e1 604 function export_file_extension() {
aca318e1 605 return ".txt";
606 }
607
f3701561 608 /**
609 * Do any pre-processing that may be required
610 * @param boolean success
611 */
08892d5b 612 function exportpreprocess() {
aca318e1 613 return true;
614 }
615
f3701561 616 /**
617 * Enable any processing to be done on the content
618 * just prior to the file being saved
619 * default is to do nothing
620 * @param string output text
621 * @param string processed output text
622 */
aca318e1 623 function presave_process( $content ) {
aca318e1 624 return $content;
625 }
626
f3701561 627 /**
628 * Do the export
629 * For most types this should not need to be overrided
630 * @return boolean success
631 */
08892d5b 632 function exportprocess() {
aca318e1 633 global $CFG;
634
635 // create a directory for the exports (if not already existing)
1367cb8d 636 if (! $export_dir = make_upload_directory($this->question_get_export_dir())) {
5a2a5331 637 print_error('cannotcreatepath', 'quiz', $export_dir);
aca318e1 638 }
1367cb8d 639 $path = $CFG->dataroot.'/'.$this->question_get_export_dir();
aca318e1 640
641 // get the questions (from database) in this category
642 // only get q's with no parents (no cloze subquestions specifically)
2c44a3d3 643 if ($this->category){
644 $questions = get_questions_category( $this->category, true );
645 } else {
646 $questions = $this->questions;
647 }
aca318e1 648
1e3d6fd8 649 notify( get_string('exportingquestions','quiz') );
aca318e1 650 $count = 0;
651
652 // results are first written into string (and then to a file)
653 // so create/initialize the string here
654 $expout = "";
271e6dec 655
f1abd39f 656 // track which category questions are in
657 // if it changes we will record the category change in the output
658 // file if selected. 0 means that it will get printed before the 1st question
659 $trackcategory = 0;
aca318e1 660
661 // iterate through questions
662 foreach($questions as $question) {
271e6dec 663
a9b16aff 664 // do not export hidden questions
665 if (!empty($question->hidden)) {
666 continue;
667 }
668
669 // do not export random questions
670 if ($question->qtype==RANDOM) {
671 continue;
672 }
271e6dec 673
f1abd39f 674 // check if we need to record category change
675 if ($this->cattofile) {
676 if ($question->category != $trackcategory) {
271e6dec 677 $trackcategory = $question->category;
678 $categoryname = $this->get_category_path($trackcategory, '/', $this->contexttofile);
679
f1abd39f 680 // create 'dummy' question for category export
681 $dummyquestion = new object;
682 $dummyquestion->qtype = 'category';
683 $dummyquestion->category = $categoryname;
684 $dummyquestion->name = "switch category to $categoryname";
685 $dummyquestion->id = 0;
686 $dummyquestion->questiontextformat = '';
687 $expout .= $this->writequestion( $dummyquestion ) . "\n";
271e6dec 688 }
689 }
f1abd39f 690
691 // export the question displaying message
692 $count++;
5b0dc681 693 echo "<hr /><p><b>$count</b>. ".$this->format_question_text($question)."</p>";
0647e82b 694 if (question_has_capability_on($question, 'view', $question->category)){
695 $expout .= $this->writequestion( $question ) . "\n";
696 }
a9b16aff 697 }
aca318e1 698
2c6d2c88 699 // continue path for following error checks
700 $course = $this->course;
2c44a3d3 701 $continuepath = "$CFG->wwwroot/question/export.php?courseid=$course->id";
2c6d2c88 702
703 // did we actually process anything
704 if ($count==0) {
2c44a3d3 705 print_error( 'noquestions','quiz',$continuepath );
2c6d2c88 706 }
707
aca318e1 708 // final pre-process on exported data
709 $expout = $this->presave_process( $expout );
271e6dec 710
aca318e1 711 // write file
08892d5b 712 $filepath = $path."/".$this->filename . $this->export_file_extension();
aca318e1 713 if (!$fh=fopen($filepath,"w")) {
2c6d2c88 714 print_error( 'cannotopen','quiz',$continuepath,$filepath );
aca318e1 715 }
f1abd39f 716 if (!fwrite($fh, $expout, strlen($expout) )) {
2c6d2c88 717 print_error( 'cannotwrite','quiz',$continuepath,$filepath );
aca318e1 718 }
719 fclose($fh);
aca318e1 720 return true;
721 }
3f5633df 722
271e6dec 723 /**
724 * get the category as a path (e.g., tom/dick/harry)
725 * @param int id the id of the most nested catgory
726 * @param string delimiter the delimiter you want
727 * @return string the path
728 */
729 function get_category_path($id, $delimiter='/', $includecontext = true) {
f34488b2 730 global $DB;
271e6dec 731 $path = '';
f34488b2 732 if (!$firstcategory = $DB->get_record('question_categories',array('id' =>$id))) {
1e7386c9 733 print_error('cannotfindcategory', 'error', '', $id);
271e6dec 734 }
735 $category = $firstcategory;
736 $contextstring = $this->translator->context_to_string($category->contextid);
737 do {
738 $name = $category->name;
739 $id = $category->parent;
740 if (!empty($path)) {
741 $path = "{$name}{$delimiter}{$path}";
742 }
743 else {
744 $path = $name;
745 }
f34488b2 746 } while ($category = $DB->get_record( 'question_categories',array('id' =>$id )));
271e6dec 747
748 if ($includecontext){
749 $path = '$'.$contextstring.'$'."{$delimiter}{$path}";
750 }
751 return $path;
752 }
aca318e1 753
f3701561 754 /**
755 * Do an post-processing that may be required
756 * @return boolean success
757 */
aca318e1 758 function exportpostprocess() {
aca318e1 759 return true;
760 }
761
f3701561 762 /**
763 * convert a single question object into text output in the given
764 * format.
765 * This must be overriden
766 * @param object question question object
767 * @return mixed question export text or null if not implemented
768 */
aca318e1 769 function writequestion($question) {
1e3d6fd8 770 // if not overidden, then this is an error.
771 $formatnotimplemented = get_string( 'formatnotimplemented','quiz' );
772 echo "<p>$formatnotimplemented</p>";
aca318e1 773 return NULL;
774 }
775
f3701561 776 /**
271e6dec 777 * get directory into which export is going
f3701561 778 * @return string file path
779 */
1367cb8d 780 function question_get_export_dir() {
1b8b535d 781 global $USER;
782 if ($this->canaccessbackupdata) {
783 $dirname = get_string("exportfilename","quiz");
784 $path = $this->course->id.'/backupdata/'.$dirname; // backupdata is protected directory
785 } else {
786 $path = 'temp/questionexport/' . $USER->id;
787 }
1367cb8d 788 return $path;
789 }
790
f3701561 791 /**
792 * where question specifies a moodle (text) format this
793 * performs the conversion.
794 */
5b0dc681 795 function format_question_text($question) {
796 $formatoptions = new stdClass;
797 $formatoptions->noclean = true;
798 $formatoptions->para = false;
799 if (empty($question->questiontextformat)) {
800 $format = FORMAT_MOODLE;
801 } else {
802 $format = $question->questiontextformat;
803 }
294ce987 804 return format_text($question->questiontext, $format, $formatoptions);
5b0dc681 805 }
271e6dec 806
807
aca318e1 808}
809
810?>