See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 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 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. 21 * 22 * Included by import.ph 23 * 24 * @package mod_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 **/ 28 29 defined('MOODLE_INTERNAL') || die(); 30 31 /** 32 * Import files embedded into answer or response 33 * 34 * @param string $field nfield name (answer or response) 35 * @param array $data imported data 36 * @param object $answer answer object 37 * @param int $contextid 38 **/ 39 function lesson_import_question_files($field, $data, $answer, $contextid) { 40 global $DB; 41 if (!isset($data['itemid'])) { 42 return; 43 } 44 $text = file_save_draft_area_files($data['itemid'], 45 $contextid, 'mod_lesson', 'page_' . $field . 's', $answer->id, 46 array('subdirs' => false, 'maxfiles' => -1, 'maxbytes' => 0), 47 $answer->$field); 48 49 $DB->set_field("lesson_answers", $field, $text, array("id" => $answer->id)); 50 } 51 52 /** 53 * Given some question info and some data about the the answers 54 * this function parses, organises and saves the question 55 * 56 * This is only used when IMPORTING questions and is only called 57 * from format.php 58 * Lifted from mod/quiz/lib.php - 59 * 1. all reference to oldanswers removed 60 * 2. all reference to quiz_multichoice table removed 61 * 3. In shortanswer questions usecase is store in the qoption field 62 * 4. In numeric questions store the range as two answers 63 * 5. truefalse options are ignored 64 * 6. For multichoice questions with more than one answer the qoption field is true 65 * 66 * @param object $question Contains question data like question, type and answers. 67 * @param object $lesson 68 * @param int $contextid 69 * @return object Returns $result->error or $result->notice. 70 **/ 71 function lesson_save_question_options($question, $lesson, $contextid) { 72 global $DB; 73 74 // These lines are required to ensure that all page types have 75 // been loaded for the following switch 76 if (!($lesson instanceof lesson)) { 77 $lesson = new lesson($lesson); 78 } 79 $manager = lesson_page_type_manager::get($lesson); 80 81 $timenow = time(); 82 $result = new stdClass(); 83 84 // Default answer to avoid code duplication. 85 $defaultanswer = new stdClass(); 86 $defaultanswer->lessonid = $question->lessonid; 87 $defaultanswer->pageid = $question->id; 88 $defaultanswer->timecreated = $timenow; 89 $defaultanswer->answerformat = FORMAT_HTML; 90 $defaultanswer->jumpto = LESSON_THISPAGE; 91 $defaultanswer->grade = 0; 92 $defaultanswer->score = 0; 93 94 switch ($question->qtype) { 95 case LESSON_PAGE_SHORTANSWER: 96 97 $answers = array(); 98 $maxfraction = -1; 99 100 // Insert all the new answers 101 foreach ($question->answer as $key => $dataanswer) { 102 if ($dataanswer != "") { 103 $answer = clone($defaultanswer); 104 if ($question->fraction[$key] >=0.5) { 105 $answer->jumpto = LESSON_NEXTPAGE; 106 $answer->score = 1; 107 } 108 $answer->grade = round($question->fraction[$key] * 100); 109 $answer->answer = $dataanswer; 110 $answer->response = $question->feedback[$key]['text']; 111 $answer->responseformat = $question->feedback[$key]['format']; 112 $answer->id = $DB->insert_record("lesson_answers", $answer); 113 lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid); 114 $answers[] = $answer->id; 115 if ($question->fraction[$key] > $maxfraction) { 116 $maxfraction = $question->fraction[$key]; 117 } 118 } 119 } 120 121 122 /// Perform sanity checks on fractional grades 123 if ($maxfraction != 1) { 124 $maxfraction = $maxfraction * 100; 125 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction); 126 return $result; 127 } 128 break; 129 130 case LESSON_PAGE_NUMERICAL: // Note similarities to shortanswer. 131 132 $answers = array(); 133 $maxfraction = -1; 134 135 136 // for each answer store the pair of min and max values even if they are the same 137 foreach ($question->answer as $key => $dataanswer) { 138 if ($dataanswer != "") { 139 $answer = clone($defaultanswer); 140 if ($question->fraction[$key] >= 0.5) { 141 $answer->jumpto = LESSON_NEXTPAGE; 142 $answer->score = 1; 143 } 144 $answer->grade = round($question->fraction[$key] * 100); 145 $min = $question->answer[$key] - $question->tolerance[$key]; 146 $max = $question->answer[$key] + $question->tolerance[$key]; 147 $answer->answer = $min.":".$max; 148 $answer->response = $question->feedback[$key]['text']; 149 $answer->responseformat = $question->feedback[$key]['format']; 150 $answer->id = $DB->insert_record("lesson_answers", $answer); 151 lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid); 152 153 $answers[] = $answer->id; 154 if ($question->fraction[$key] > $maxfraction) { 155 $maxfraction = $question->fraction[$key]; 156 } 157 } 158 } 159 160 /// Perform sanity checks on fractional grades 161 if ($maxfraction != 1) { 162 $maxfraction = $maxfraction * 100; 163 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction); 164 return $result; 165 } 166 break; 167 168 169 case LESSON_PAGE_TRUEFALSE: 170 171 // In lesson the correct answer always come first, as it was the case 172 // in question bank exports years ago. 173 $answer = clone($defaultanswer); 174 $answer->grade = 100; 175 $answer->jumpto = LESSON_NEXTPAGE; 176 $answer->score = 1; 177 if ($question->correctanswer) { 178 $answer->answer = get_string("true", "lesson"); 179 if (isset($question->feedbacktrue)) { 180 $answer->response = $question->feedbacktrue['text']; 181 $answer->responseformat = $question->feedbacktrue['format']; 182 $answer->id = $DB->insert_record("lesson_answers", $answer); 183 lesson_import_question_files('response', $question->feedbacktrue, $answer, $contextid); 184 } 185 } else { 186 $answer->answer = get_string("false", "lesson"); 187 if (isset($question->feedbackfalse)) { 188 $answer->response = $question->feedbackfalse['text']; 189 $answer->responseformat = $question->feedbackfalse['format']; 190 $answer->id = $DB->insert_record("lesson_answers", $answer); 191 lesson_import_question_files('response', $question->feedbackfalse, $answer, $contextid); 192 } 193 } 194 195 // Now the wrong answer. 196 $answer = clone($defaultanswer); 197 if ($question->correctanswer) { 198 $answer->answer = get_string("false", "lesson"); 199 if (isset($question->feedbackfalse)) { 200 $answer->response = $question->feedbackfalse['text']; 201 $answer->responseformat = $question->feedbackfalse['format']; 202 $answer->id = $DB->insert_record("lesson_answers", $answer); 203 lesson_import_question_files('response', $question->feedbackfalse, $answer, $contextid); 204 } 205 } else { 206 $answer->answer = get_string("true", "lesson"); 207 if (isset($question->feedbacktrue)) { 208 $answer->response = $question->feedbacktrue['text']; 209 $answer->responseformat = $question->feedbacktrue['format']; 210 $answer->id = $DB->insert_record("lesson_answers", $answer); 211 lesson_import_question_files('response', $question->feedbacktrue, $answer, $contextid); 212 } 213 } 214 215 break; 216 217 case LESSON_PAGE_MULTICHOICE: 218 219 $totalfraction = 0; 220 $maxfraction = -1; 221 222 $answers = array(); 223 224 // Insert all the new answers 225 foreach ($question->answer as $key => $dataanswer) { 226 if ($dataanswer != "") { 227 $answer = clone($defaultanswer); 228 $answer->grade = round($question->fraction[$key] * 100); 229 230 if ($question->single) { 231 if ($answer->grade > 50) { 232 $answer->jumpto = LESSON_NEXTPAGE; 233 $answer->score = 1; 234 } 235 } else { 236 // If multi answer allowed, any answer with fraction > 0 is considered correct. 237 if ($question->fraction[$key] > 0) { 238 $answer->jumpto = LESSON_NEXTPAGE; 239 $answer->score = 1; 240 } 241 } 242 $answer->answer = $dataanswer['text']; 243 $answer->answerformat = $dataanswer['format']; 244 $answer->response = $question->feedback[$key]['text']; 245 $answer->responseformat = $question->feedback[$key]['format']; 246 $answer->id = $DB->insert_record("lesson_answers", $answer); 247 lesson_import_question_files('answer', $dataanswer, $answer, $contextid); 248 lesson_import_question_files('response', $question->feedback[$key], $answer, $contextid); 249 250 // for Sanity checks 251 if ($question->fraction[$key] > 0) { 252 $totalfraction += $question->fraction[$key]; 253 } 254 if ($question->fraction[$key] > $maxfraction) { 255 $maxfraction = $question->fraction[$key]; 256 } 257 } 258 } 259 260 /// Perform sanity checks on fractional grades 261 if ($question->single) { 262 if ($maxfraction != 1) { 263 $maxfraction = $maxfraction * 100; 264 $result->notice = get_string("fractionsnomax", "lesson", $maxfraction); 265 return $result; 266 } 267 } else { 268 $totalfraction = round($totalfraction,2); 269 if ($totalfraction != 1) { 270 $totalfraction = $totalfraction * 100; 271 $result->notice = get_string("fractionsaddwrong", "lesson", $totalfraction); 272 return $result; 273 } 274 } 275 break; 276 277 case LESSON_PAGE_MATCHING: 278 279 $subquestions = array(); 280 281 // The first answer should always be the correct answer 282 $correctanswer = clone($defaultanswer); 283 $correctanswer->answer = get_string('thatsthecorrectanswer', 'lesson'); 284 $correctanswer->jumpto = LESSON_NEXTPAGE; 285 $correctanswer->score = 1; 286 $DB->insert_record("lesson_answers", $correctanswer); 287 288 // The second answer should always be the wrong answer 289 $wronganswer = clone($defaultanswer); 290 $wronganswer->answer = get_string('thatsthewronganswer', 'lesson'); 291 $DB->insert_record("lesson_answers", $wronganswer); 292 293 $i = 0; 294 // Insert all the new question+answer pairs 295 foreach ($question->subquestions as $key => $questiontext) { 296 $answertext = $question->subanswers[$key]; 297 if (!empty($questiontext) and !empty($answertext)) { 298 $answer = clone($defaultanswer); 299 $answer->answer = $questiontext['text']; 300 $answer->answerformat = $questiontext['format']; 301 $answer->response = $answertext; 302 if ($i == 0) { 303 // first answer contains the correct answer jump 304 $answer->jumpto = LESSON_NEXTPAGE; 305 } 306 $answer->id = $DB->insert_record("lesson_answers", $answer); 307 lesson_import_question_files('answer', $questiontext, $answer, $contextid); 308 $subquestions[] = $answer->id; 309 $i++; 310 } 311 } 312 313 if (count($subquestions) < 3) { 314 $result->notice = get_string("notenoughsubquestions", "lesson"); 315 return $result; 316 } 317 break; 318 319 case LESSON_PAGE_ESSAY: 320 $answer = new stdClass(); 321 $answer->lessonid = $question->lessonid; 322 $answer->pageid = $question->id; 323 $answer->timecreated = $timenow; 324 $answer->answer = null; 325 $answer->answerformat = FORMAT_MOODLE; 326 $answer->grade = 0; 327 $answer->score = 1; 328 $answer->jumpto = LESSON_NEXTPAGE; 329 $answer->response = null; 330 $answer->responseformat = FORMAT_MOODLE; 331 $answer->id = $DB->insert_record("lesson_answers", $answer); 332 break; 333 default: 334 $result->error = "Unsupported question type ($question->qtype)!"; 335 return $result; 336 } 337 return true; 338 } 339 340 341 class qformat_default { 342 343 var $displayerrors = true; 344 var $category = null; 345 var $questionids = array(); 346 protected $importcontext = null; 347 var $qtypeconvert = array('numerical' => LESSON_PAGE_NUMERICAL, 348 'multichoice' => LESSON_PAGE_MULTICHOICE, 349 'truefalse' => LESSON_PAGE_TRUEFALSE, 350 'shortanswer' => LESSON_PAGE_SHORTANSWER, 351 'match' => LESSON_PAGE_MATCHING, 352 'essay' => LESSON_PAGE_ESSAY 353 ); 354 355 // Importing functions 356 function provide_import() { 357 return false; 358 } 359 360 function set_importcontext($context) { 361 $this->importcontext = $context; 362 } 363 364 /** 365 * Handle parsing error 366 * 367 * @param string $message information about error 368 * @param string $text imported text that triggered the error 369 * @param string $questionname imported question name 370 */ 371 protected function error($message, $text='', $questionname='') { 372 $importerrorquestion = get_string('importerrorquestion', 'question'); 373 374 echo "<div class=\"importerror\">\n"; 375 echo "<strong>$importerrorquestion $questionname</strong>"; 376 if (!empty($text)) { 377 $text = s($text); 378 echo "<blockquote>$text</blockquote>\n"; 379 } 380 echo "<strong>$message</strong>\n"; 381 echo "</div>"; 382 } 383 384 /** 385 * Import for questiontype plugins 386 * @param mixed $data The segment of data containing the question 387 * @param object $question processed (so far) by standard import code if appropriate 388 * @param object $extra mixed any additional format specific data that may be passed by the format 389 * @param string $qtypehint hint about a question type from format 390 * @return object question object suitable for save_options() or false if cannot handle 391 */ 392 public function try_importing_using_qtypes($data, $question = null, $extra = null, 393 $qtypehint = '') { 394 395 return false; 396 } 397 398 function importpreprocess() { 399 // Does any pre-processing that may be desired 400 return true; 401 } 402 403 function importprocess($filename, $lesson, $pageid) { 404 global $DB, $OUTPUT; 405 406 /// Processes a given file. There's probably little need to change this 407 $timenow = time(); 408 409 if (! $lines = $this->readdata($filename)) { 410 echo $OUTPUT->notification("File could not be read, or was empty"); 411 return false; 412 } 413 414 if (! $questions = $this->readquestions($lines)) { // Extract all the questions 415 echo $OUTPUT->notification("There are no questions in this file!"); 416 return false; 417 } 418 419 //Avoid category as question type 420 echo $OUTPUT->notification(get_string('importcount', 'lesson', 421 $this->count_questions($questions)), 'notifysuccess'); 422 423 $count = 0; 424 $addquestionontop = false; 425 if ($pageid == 0) { 426 $addquestionontop = true; 427 $updatelessonpage = $DB->get_record('lesson_pages', array('lessonid' => $lesson->id, 'prevpageid' => 0)); 428 } else { 429 $updatelessonpage = $DB->get_record('lesson_pages', array('lessonid' => $lesson->id, 'id' => $pageid)); 430 } 431 432 $unsupportedquestions = 0; 433 434 foreach ($questions as $question) { // Process and store each question 435 switch ($question->qtype) { 436 //TODO: Bad way to bypass category in data... Quickfix for MDL-27964 437 case 'category': 438 break; 439 // the good ones 440 case 'shortanswer' : 441 case 'numerical' : 442 case 'truefalse' : 443 case 'multichoice' : 444 case 'match' : 445 case 'essay' : 446 $count++; 447 448 //Show nice formated question in one line. 449 echo "<hr><p><b>$count</b>. ".$this->format_question_text($question)."</p>"; 450 451 $newpage = new stdClass; 452 $newpage->lessonid = $lesson->id; 453 $newpage->qtype = $this->qtypeconvert[$question->qtype]; 454 switch ($question->qtype) { 455 case 'shortanswer' : 456 if (isset($question->usecase)) { 457 $newpage->qoption = $question->usecase; 458 } 459 break; 460 case 'multichoice' : 461 if (isset($question->single)) { 462 $newpage->qoption = !$question->single; 463 } 464 break; 465 } 466 $newpage->timecreated = $timenow; 467 if ($question->name != $question->questiontext) { 468 $newpage->title = $question->name; 469 } else { 470 $newpage->title = "Page $count"; 471 } 472 $newpage->contents = $question->questiontext; 473 $newpage->contentsformat = isset($question->questionformat) ? $question->questionformat : FORMAT_HTML; 474 475 // set up page links 476 if ($pageid) { 477 // the new page follows on from this page 478 if (!$page = $DB->get_record("lesson_pages", array("id" => $pageid))) { 479 throw new \moodle_exception('invalidpageid', 'lesson'); 480 } 481 $newpage->prevpageid = $pageid; 482 $newpage->nextpageid = $page->nextpageid; 483 // insert the page and reset $pageid 484 $newpageid = $DB->insert_record("lesson_pages", $newpage); 485 // update the linked list 486 $DB->set_field("lesson_pages", "nextpageid", $newpageid, array("id" => $pageid)); 487 } else { 488 // new page is the first page 489 // get the existing (first) page (if any) 490 $params = array ("lessonid" => $lesson->id, "prevpageid" => 0); 491 if (!$page = $DB->get_record_select("lesson_pages", "lessonid = :lessonid AND prevpageid = :prevpageid", $params)) { 492 // there are no existing pages 493 $newpage->prevpageid = 0; // this is a first page 494 $newpage->nextpageid = 0; // this is the only page 495 $newpageid = $DB->insert_record("lesson_pages", $newpage); 496 } else { 497 // there are existing pages put this at the start 498 $newpage->prevpageid = 0; // this is a first page 499 $newpage->nextpageid = $page->id; 500 $newpageid = $DB->insert_record("lesson_pages", $newpage); 501 // update the linked list 502 $DB->set_field("lesson_pages", "prevpageid", $newpageid, array("id" => $page->id)); 503 } 504 } 505 506 // reset $pageid and put the page ID in $question, used in save_question_option() 507 $pageid = $newpageid; 508 $question->id = $newpageid; 509 510 $this->questionids[] = $question->id; 511 512 // Import images in question text. 513 if (isset($question->questiontextitemid)) { 514 $questiontext = file_save_draft_area_files($question->questiontextitemid, 515 $this->importcontext->id, 'mod_lesson', 'page_contents', $newpageid, 516 null , $question->questiontext); 517 // Update content with recoded urls. 518 $DB->set_field("lesson_pages", "contents", $questiontext, array("id" => $newpageid)); 519 } 520 521 // Now to save all the answers and type-specific options 522 523 $question->lessonid = $lesson->id; // needed for foreign key 524 $question->qtype = $this->qtypeconvert[$question->qtype]; 525 $result = lesson_save_question_options($question, $lesson, $this->importcontext->id); 526 527 if (!empty($result->error)) { 528 echo $OUTPUT->notification($result->error); 529 return false; 530 } 531 532 if (!empty($result->notice)) { 533 echo $OUTPUT->notification($result->notice); 534 return true; 535 } 536 break; 537 // the Bad ones 538 default : 539 $unsupportedquestions++; 540 break; 541 } 542 } 543 // Update the prev links if there were existing pages. 544 if (!empty($updatelessonpage)) { 545 if ($addquestionontop) { 546 $DB->set_field("lesson_pages", "prevpageid", $pageid, array("id" => $updatelessonpage->id)); 547 } else { 548 $DB->set_field("lesson_pages", "prevpageid", $pageid, array("id" => $updatelessonpage->nextpageid)); 549 } 550 } 551 if ($unsupportedquestions) { 552 echo $OUTPUT->notification(get_string('unknownqtypesnotimported', 'lesson', $unsupportedquestions)); 553 } 554 return true; 555 } 556 557 /** 558 * Count all non-category questions in the questions array. 559 * 560 * @param array questions An array of question objects. 561 * @return int The count. 562 * 563 */ 564 protected function count_questions($questions) { 565 $count = 0; 566 if (!is_array($questions)) { 567 return $count; 568 } 569 foreach ($questions as $question) { 570 if (!is_object($question) || !isset($question->qtype) || 571 ($question->qtype == 'category')) { 572 continue; 573 } 574 $count++; 575 } 576 return $count; 577 } 578 579 function readdata($filename) { 580 /// Returns complete file with an array, one item per line 581 582 if (is_readable($filename)) { 583 $filearray = file($filename); 584 585 /// Check for Macintosh OS line returns (ie file on one line), and fix 586 if (preg_match("/\r/", $filearray[0]) AND !preg_match("/\n/", $filearray[0])) { 587 return explode("\r", $filearray[0]); 588 } else { 589 return $filearray; 590 } 591 } 592 return false; 593 } 594 595 protected function readquestions($lines) { 596 /// Parses an array of lines into an array of questions, 597 /// where each item is a question object as defined by 598 /// readquestion(). Questions are defined as anything 599 /// between blank lines. 600 601 $questions = array(); 602 $currentquestion = array(); 603 604 foreach ($lines as $line) { 605 $line = trim($line); 606 if (empty($line)) { 607 if (!empty($currentquestion)) { 608 if ($question = $this->readquestion($currentquestion)) { 609 $questions[] = $question; 610 } 611 $currentquestion = array(); 612 } 613 } else { 614 $currentquestion[] = $line; 615 } 616 } 617 618 if (!empty($currentquestion)) { // There may be a final question 619 if ($question = $this->readquestion($currentquestion)) { 620 $questions[] = $question; 621 } 622 } 623 624 return $questions; 625 } 626 627 628 protected function readquestion($lines) { 629 /// Given an array of lines known to define a question in 630 /// this format, this function converts it into a question 631 /// object suitable for processing and insertion into Moodle. 632 633 // We should never get there unless the qformat plugin is broken. 634 throw new coding_exception('Question format plugin is missing important code: readquestion.'); 635 636 return null; 637 } 638 639 /** 640 * Construct a reasonable default question name, based on the start of the question text. 641 * @param string $questiontext the question text. 642 * @param string $default default question name to use if the constructed one comes out blank. 643 * @return string a reasonable question name. 644 */ 645 public function create_default_question_name($questiontext, $default) { 646 $name = $this->clean_question_name(shorten_text($questiontext, 80)); 647 if ($name) { 648 return $name; 649 } else { 650 return $default; 651 } 652 } 653 654 /** 655 * Ensure that a question name does not contain anything nasty, and will fit in the DB field. 656 * @param string $name the raw question name. 657 * @return string a safe question name. 658 */ 659 public function clean_question_name($name) { 660 $name = clean_param($name, PARAM_TEXT); // Matches what the question editing form does. 661 $name = trim($name); 662 $trimlength = 251; 663 while (core_text::strlen($name) > 255 && $trimlength > 0) { 664 $name = shorten_text($name, $trimlength); 665 $trimlength -= 10; 666 } 667 return $name; 668 } 669 670 /** 671 * return an "empty" question 672 * Somewhere to specify question parameters that are not handled 673 * by import but are required db fields. 674 * This should not be overridden. 675 * @return object default question 676 */ 677 protected function defaultquestion() { 678 global $CFG; 679 static $defaultshuffleanswers = null; 680 if (is_null($defaultshuffleanswers)) { 681 $defaultshuffleanswers = get_config('quiz', 'shuffleanswers'); 682 } 683 684 $question = new stdClass(); 685 $question->shuffleanswers = $defaultshuffleanswers; 686 $question->defaultmark = 1; 687 $question->image = ""; 688 $question->usecase = 0; 689 $question->multiplier = array(); 690 $question->questiontextformat = FORMAT_MOODLE; 691 $question->generalfeedback = ''; 692 $question->generalfeedbackformat = FORMAT_MOODLE; 693 $question->correctfeedback = ''; 694 $question->partiallycorrectfeedback = ''; 695 $question->incorrectfeedback = ''; 696 $question->answernumbering = 'abc'; 697 $question->penalty = 0.3333333; 698 $question->length = 1; 699 $question->qoption = 0; 700 $question->layout = 1; 701 702 // this option in case the questiontypes class wants 703 // to know where the data came from 704 $question->export_process = true; 705 $question->import_process = true; 706 707 return $question; 708 } 709 710 function importpostprocess() { 711 /// Does any post-processing that may be desired 712 /// Argument is a simple array of question ids that 713 /// have just been added. 714 return true; 715 } 716 717 /** 718 * Convert the question text to plain text, so it can safely be displayed 719 * during import to let the user see roughly what is going on. 720 */ 721 protected function format_question_text($question) { 722 $formatoptions = new stdClass(); 723 $formatoptions->noclean = true; 724 // The html_to_text call strips out all URLs, but format_text complains 725 // if it finds @@PLUGINFILE@@ tokens. So, we need to replace 726 // @@PLUGINFILE@@ with a real URL, but it doesn't matter what. 727 // We use http://example.com/. 728 $text = str_replace('@@PLUGINFILE@@/', 'http://example.com/', $question->questiontext); 729 return s(html_to_text(format_text($text, 730 $question->questiontextformat, $formatoptions), 0, false)); 731 } 732 733 /** 734 * Since the lesson module tries to re-use the question bank import classes in 735 * a crazy way, this is necessary to stop things breaking. 736 */ 737 protected function add_blank_combined_feedback($question) { 738 return $question; 739 } 740 } 741 742 743 /** 744 * Since the lesson module tries to re-use the question bank import classes in 745 * a crazy way, this is necessary to stop things breaking. This should be exactly 746 * the same as the class defined in question/format.php. 747 */ 748 class qformat_based_on_xml extends qformat_default { 749 /** 750 * A lot of imported files contain unwanted entities. 751 * This method tries to clean up all known problems. 752 * @param string str string to correct 753 * @return string the corrected string 754 */ 755 public function cleaninput($str) { 756 757 $html_code_list = array( 758 "'" => "'", 759 "’" => "'", 760 "“" => "\"", 761 "”" => "\"", 762 "–" => "-", 763 "—" => "-", 764 ); 765 $str = strtr($str, $html_code_list); 766 // Use core_text entities_to_utf8 function to convert only numerical entities. 767 $str = core_text::entities_to_utf8($str, false); 768 return $str; 769 } 770 771 /** 772 * Return the array moodle is expecting 773 * for an HTML text. No processing is done on $text. 774 * qformat classes that want to process $text 775 * for instance to import external images files 776 * and recode urls in $text must overwrite this method. 777 * @param array $text some HTML text string 778 * @return array with keys text, format and files. 779 */ 780 public function text_field($text) { 781 return array( 782 'text' => trim($text), 783 'format' => FORMAT_HTML, 784 'files' => array(), 785 ); 786 } 787 788 /** 789 * Return the value of a node, given a path to the node 790 * if it doesn't exist return the default value. 791 * @param array xml data to read 792 * @param array path path to node expressed as array 793 * @param mixed default 794 * @param bool istext process as text 795 * @param string error if set value must exist, return false and issue message if not 796 * @return mixed value 797 */ 798 public function getpath($xml, $path, $default, $istext=false, $error='') { 799 foreach ($path as $index) { 800 if (!isset($xml[$index])) { 801 if (!empty($error)) { 802 $this->error($error); 803 return false; 804 } else { 805 return $default; 806 } 807 } 808 809 $xml = $xml[$index]; 810 } 811 812 if ($istext) { 813 if (!is_string($xml)) { 814 $this->error(get_string('invalidxml', 'qformat_xml')); 815 } 816 $xml = trim($xml); 817 } 818 819 return $xml; 820 } 821 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body