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 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * mod_lesson data generator. 19 * 20 * @package mod_lesson 21 * @category test 22 * @copyright 2013 Marina Glancy 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 require_once($CFG->dirroot.'/mod/lesson/locallib.php'); 29 30 /** 31 * mod_lesson data generator class. 32 * 33 * @package mod_lesson 34 * @category test 35 * @copyright 2013 Marina Glancy 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class mod_lesson_generator extends testing_module_generator { 39 40 /** 41 * @var int keep track of how many pages have been created. 42 */ 43 protected $pagecount = 0; 44 45 /** 46 * @var array list of candidate pages to be created when all answers have been added. 47 */ 48 protected $candidatepages = []; 49 50 /** 51 * @var array map of readable jumpto to integer value. 52 */ 53 protected $jumptomap = [ 54 'This page' => LESSON_THISPAGE, 55 'Next page' => LESSON_NEXTPAGE, 56 'Previous page' => LESSON_PREVIOUSPAGE, 57 'End of lesson' => LESSON_EOL, 58 'Unseen question within a content page' => LESSON_UNSEENBRANCHPAGE, 59 'Random question within a content page' => LESSON_RANDOMPAGE, 60 'Random content page' => LESSON_RANDOMBRANCH, 61 'Unseen question within a cluster' => LESSON_CLUSTERJUMP, 62 ]; 63 64 /** 65 * To be called from data reset code only, 66 * do not use in tests. 67 * @return void 68 */ 69 public function reset() { 70 $this->pagecount = 0; 71 $this->candidatepages = []; 72 parent::reset(); 73 } 74 75 /** 76 * Creates a lesson instance for testing purposes. 77 * 78 * @param null|array|stdClass $record data for module being generated. 79 * @param null|array $options general options for course module. 80 * @return stdClass record from module-defined table with additional field cmid (corresponding id in course_modules table) 81 */ 82 public function create_instance($record = null, array $options = null) { 83 global $CFG; 84 85 // Add default values for lesson. 86 $lessonconfig = get_config('mod_lesson'); 87 $record = (array)$record + array( 88 'progressbar' => $lessonconfig->progressbar, 89 'ongoing' => $lessonconfig->ongoing, 90 'displayleft' => $lessonconfig->displayleftmenu, 91 'displayleftif' => $lessonconfig->displayleftif, 92 'slideshow' => $lessonconfig->slideshow, 93 'maxanswers' => $lessonconfig->maxanswers, 94 'feedback' => $lessonconfig->defaultfeedback, 95 'activitylink' => 0, 96 'available' => 0, 97 'deadline' => 0, 98 'usepassword' => 0, 99 'password' => '', 100 'dependency' => 0, 101 'timespent' => 0, 102 'completed' => 0, 103 'gradebetterthan' => 0, 104 'modattempts' => $lessonconfig->modattempts, 105 'review' => $lessonconfig->displayreview, 106 'maxattempts' => $lessonconfig->maximumnumberofattempts, 107 'nextpagedefault' => $lessonconfig->defaultnextpage, 108 'maxpages' => $lessonconfig->numberofpagestoshow, 109 'practice' => $lessonconfig->practice, 110 'custom' => $lessonconfig->customscoring, 111 'retake' => $lessonconfig->retakesallowed, 112 'usemaxgrade' => $lessonconfig->handlingofretakes, 113 'minquestions' => $lessonconfig->minimumnumberofquestions, 114 'grade' => 100, 115 ); 116 if (!isset($record['mediafile'])) { 117 require_once($CFG->libdir.'/filelib.php'); 118 $record['mediafile'] = file_get_unused_draft_itemid(); 119 } 120 121 return parent::create_instance($record, (array)$options); 122 } 123 124 /** 125 * Creates a page for testing purposes. The page will be created when answers are added. 126 * 127 * @param null|array|stdClass $record data for page being generated. 128 * @param null|array $options general options. 129 */ 130 public function create_page($record = null, array $options = null) { 131 $record = (array) $record; 132 133 // Pages require answers to work. Add it as a candidate page to be created once answers have been added. 134 $record['answer_editor'] = []; 135 $record['response_editor'] = []; 136 $record['jumpto'] = []; 137 $record['score'] = []; 138 139 if (!isset($record['previouspage']) || $record['previouspage'] === '') { 140 // Previous page not set, set it to the last candidate page (if any). 141 $record['previouspage'] = empty($this->candidatepages) ? '0' : end($this->candidatepages)['title']; 142 } 143 144 $this->candidatepages[] = $record; 145 } 146 147 /** 148 * Creates a page and its answers for testing purposes. 149 * 150 * @param array $record data for page being generated. 151 * @return stdClass created page, null if couldn't be created because it has a jump to a page that doesn't exist. 152 * @throws coding_exception 153 */ 154 private function perform_create_page(array $record): ?stdClass { 155 global $DB; 156 157 $lesson = $DB->get_record('lesson', ['id' => $record['lessonid']], '*', MUST_EXIST); 158 $cm = get_coursemodule_from_instance('lesson', $lesson->id); 159 $lesson->cmid = $cm->id; 160 $qtype = $record['qtype']; 161 162 unset($record['qtype']); 163 unset($record['lessonid']); 164 165 if (isset($record['content'])) { 166 $record['contents_editor'] = [ 167 'text' => $record['content'], 168 'format' => FORMAT_MOODLE, 169 'itemid' => 0, 170 ]; 171 unset($record['content']); 172 } 173 174 $record['pageid'] = $this->get_previouspage_id($lesson->id, $record['previouspage']); 175 unset($record['previouspage']); 176 177 try { 178 $record['jumpto'] = $this->convert_page_jumpto($lesson->id, $record['jumpto']); 179 } catch (coding_exception $e) { 180 // This page has a jump to a page that hasn't been created yet. 181 return null; 182 } 183 184 switch ($qtype) { 185 case 'content': 186 case 'cluster': 187 case 'endofcluster': 188 case 'endofbranch': 189 $funcname = "create_{$qtype}"; 190 break; 191 default: 192 $funcname = "create_question_{$qtype}"; 193 } 194 195 if (!method_exists($this, $funcname)) { 196 throw new coding_exception('The page '.$record['title']." has an invalid qtype: $qtype"); 197 } 198 199 return $this->{$funcname}($lesson, $record); 200 } 201 202 /** 203 * Creates a content page for testing purposes. 204 * 205 * @param stdClass $lesson instance where to create the page. 206 * @param array|stdClass $record data for page being generated. 207 * @return stdClass page record. 208 */ 209 public function create_content($lesson, $record = array()) { 210 global $DB, $CFG; 211 $now = time(); 212 $this->pagecount++; 213 $record = (array)$record + array( 214 'lessonid' => $lesson->id, 215 'title' => 'Lesson page '.$this->pagecount, 216 'timecreated' => $now, 217 'qtype' => 20, // LESSON_PAGE_BRANCHTABLE 218 'pageid' => 0, // By default insert in the beginning. 219 ); 220 if (!isset($record['contents_editor'])) { 221 $record['contents_editor'] = array( 222 'text' => 'Contents of lesson page '.$this->pagecount, 223 'format' => FORMAT_MOODLE, 224 'itemid' => 0, 225 ); 226 } 227 $context = context_module::instance($lesson->cmid); 228 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 229 return $DB->get_record('lesson_pages', array('id' => $page->id), '*', MUST_EXIST); 230 } 231 232 /** 233 * Create True/false question pages. 234 * @param object $lesson 235 * @param array $record 236 * @return stdClass page record. 237 */ 238 public function create_question_truefalse($lesson, $record = array()) { 239 global $DB, $CFG; 240 $now = time(); 241 $this->pagecount++; 242 $record = (array)$record + array( 243 'lessonid' => $lesson->id, 244 'title' => 'Lesson TF question '.$this->pagecount, 245 'timecreated' => $now, 246 'qtype' => 2, // LESSON_PAGE_TRUEFALSE. 247 'pageid' => 0, // By default insert in the beginning. 248 ); 249 if (!isset($record['contents_editor'])) { 250 $record['contents_editor'] = array( 251 'text' => 'The answer is TRUE '.$this->pagecount, 252 'format' => FORMAT_HTML, 253 'itemid' => 0 254 ); 255 } 256 257 // First Answer (TRUE). 258 if (!isset($record['answer_editor'][0])) { 259 $record['answer_editor'][0] = array( 260 'text' => 'TRUE answer for '.$this->pagecount, 261 'format' => FORMAT_HTML 262 ); 263 } 264 if (!isset($record['jumpto'][0])) { 265 $record['jumpto'][0] = LESSON_NEXTPAGE; 266 } 267 268 // Second Answer (FALSE). 269 if (!isset($record['answer_editor'][1])) { 270 $record['answer_editor'][1] = array( 271 'text' => 'FALSE answer for '.$this->pagecount, 272 'format' => FORMAT_HTML 273 ); 274 } 275 if (!isset($record['jumpto'][1])) { 276 $record['jumpto'][1] = LESSON_THISPAGE; 277 } 278 279 $context = context_module::instance($lesson->cmid); 280 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 281 return $DB->get_record('lesson_pages', array('id' => $page->id), '*', MUST_EXIST); 282 } 283 284 /** 285 * Create multichoice question pages. 286 * @param object $lesson 287 * @param array $record 288 * @return stdClass page record. 289 */ 290 public function create_question_multichoice($lesson, $record = array()) { 291 global $DB, $CFG; 292 $now = time(); 293 $this->pagecount++; 294 $record = (array)$record + array( 295 'lessonid' => $lesson->id, 296 'title' => 'Lesson multichoice question '.$this->pagecount, 297 'timecreated' => $now, 298 'qtype' => 3, // LESSON_PAGE_MULTICHOICE. 299 'pageid' => 0, // By default insert in the beginning. 300 ); 301 if (!isset($record['contents_editor'])) { 302 $record['contents_editor'] = array( 303 'text' => 'Pick the correct answer '.$this->pagecount, 304 'format' => FORMAT_HTML, 305 'itemid' => 0 306 ); 307 } 308 309 // First Answer (correct). 310 if (!isset($record['answer_editor'][0])) { 311 $record['answer_editor'][0] = array( 312 'text' => 'correct answer for '.$this->pagecount, 313 'format' => FORMAT_HTML 314 ); 315 } 316 if (!isset($record['jumpto'][0])) { 317 $record['jumpto'][0] = LESSON_NEXTPAGE; 318 } 319 320 // Second Answer (incorrect). 321 if (!isset($record['answer_editor'][1])) { 322 $record['answer_editor'][1] = array( 323 'text' => 'correct answer for '.$this->pagecount, 324 'format' => FORMAT_HTML 325 ); 326 } 327 if (!isset($record['jumpto'][1])) { 328 $record['jumpto'][1] = LESSON_THISPAGE; 329 } 330 331 $context = context_module::instance($lesson->cmid); 332 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 333 return $DB->get_record('lesson_pages', array('id' => $page->id), '*', MUST_EXIST); 334 } 335 336 /** 337 * Create essay question pages. 338 * @param object $lesson 339 * @param array $record 340 * @return stdClass page record. 341 */ 342 public function create_question_essay($lesson, $record = array()) { 343 global $DB, $CFG; 344 $now = time(); 345 $this->pagecount++; 346 $record = (array)$record + array( 347 'lessonid' => $lesson->id, 348 'title' => 'Lesson Essay question '.$this->pagecount, 349 'timecreated' => $now, 350 'qtype' => 10, // LESSON_PAGE_ESSAY. 351 'pageid' => 0, // By default insert in the beginning. 352 ); 353 if (!isset($record['contents_editor'])) { 354 $record['contents_editor'] = array( 355 'text' => 'Write an Essay '.$this->pagecount, 356 'format' => FORMAT_HTML, 357 'itemid' => 0 358 ); 359 } 360 361 // Essays have an answer of NULL. 362 if (!isset($record['answer_editor'][0])) { 363 $record['answer_editor'][0] = array( 364 'text' => null, 365 'format' => FORMAT_MOODLE 366 ); 367 } 368 if (!isset($record['jumpto'][0])) { 369 $record['jumpto'][0] = LESSON_NEXTPAGE; 370 } 371 372 $context = context_module::instance($lesson->cmid); 373 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 374 return $DB->get_record('lesson_pages', array('id' => $page->id), '*', MUST_EXIST); 375 } 376 377 /** 378 * Create matching question pages. 379 * @param object $lesson 380 * @param array $record 381 * @return stdClass page record. 382 */ 383 public function create_question_matching($lesson, $record = array()) { 384 global $DB, $CFG; 385 $now = time(); 386 $this->pagecount++; 387 $record = (array)$record + array( 388 'lessonid' => $lesson->id, 389 'title' => 'Lesson Matching question '.$this->pagecount, 390 'timecreated' => $now, 391 'qtype' => 5, // LESSON_PAGE_MATCHING. 392 'pageid' => 0, // By default insert in the beginning. 393 ); 394 if (!isset($record['contents_editor'])) { 395 $record['contents_editor'] = array( 396 'text' => 'Match the values '.$this->pagecount, 397 'format' => FORMAT_HTML, 398 'itemid' => 0 399 ); 400 } 401 // Feedback for correct result. 402 if (!isset($record['answer_editor'][0])) { 403 $record['answer_editor'][0] = array( 404 'text' => '', 405 'format' => FORMAT_HTML 406 ); 407 } 408 // Feedback for wrong result. 409 if (!isset($record['answer_editor'][1])) { 410 $record['answer_editor'][1] = array( 411 'text' => '', 412 'format' => FORMAT_HTML 413 ); 414 } 415 // First answer value. 416 if (!isset($record['answer_editor'][2])) { 417 $record['answer_editor'][2] = array( 418 'text' => 'Match value 1', 419 'format' => FORMAT_HTML 420 ); 421 } 422 // First response value. 423 if (!isset($record['response_editor'][2])) { 424 $record['response_editor'][2] = 'Match answer 1'; 425 } 426 // Second Matching value. 427 if (!isset($record['answer_editor'][3])) { 428 $record['answer_editor'][3] = array( 429 'text' => 'Match value 2', 430 'format' => FORMAT_HTML 431 ); 432 } 433 // Second Matching answer. 434 if (!isset($record['response_editor'][3])) { 435 $record['response_editor'][3] = 'Match answer 2'; 436 } 437 438 // Jump Values. 439 if (!isset($record['jumpto'][0])) { 440 $record['jumpto'][0] = LESSON_NEXTPAGE; 441 } 442 if (!isset($record['jumpto'][1])) { 443 $record['jumpto'][1] = LESSON_THISPAGE; 444 } 445 446 // Mark the correct values. 447 if (!isset($record['score'][0])) { 448 $record['score'][0] = 1; 449 } 450 $context = context_module::instance($lesson->cmid); 451 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 452 return $DB->get_record('lesson_pages', array('id' => $page->id), '*', MUST_EXIST); 453 } 454 455 /** 456 * Create shortanswer question pages. 457 * @param object $lesson 458 * @param array $record 459 * @return stdClass page record. 460 */ 461 public function create_question_shortanswer($lesson, $record = array()) { 462 global $DB, $CFG; 463 $now = time(); 464 $this->pagecount++; 465 $record = (array)$record + array( 466 'lessonid' => $lesson->id, 467 'title' => 'Lesson Shortanswer question '.$this->pagecount, 468 'timecreated' => $now, 469 'qtype' => 1, // LESSON_PAGE_SHORTANSWER. 470 'pageid' => 0, // By default insert in the beginning. 471 ); 472 if (!isset($record['contents_editor'])) { 473 $record['contents_editor'] = array( 474 'text' => 'Fill in the blank '.$this->pagecount, 475 'format' => FORMAT_HTML, 476 'itemid' => 0 477 ); 478 } 479 480 // First Answer (correct). 481 if (!isset($record['answer_editor'][0])) { 482 $record['answer_editor'][0] = array( 483 'text' => 'answer'.$this->pagecount, 484 'format' => FORMAT_MOODLE 485 ); 486 } 487 if (!isset($record['jumpto'][0])) { 488 $record['jumpto'][0] = LESSON_NEXTPAGE; 489 } 490 491 $context = context_module::instance($lesson->cmid); 492 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 493 return $DB->get_record('lesson_pages', array('id' => $page->id), '*', MUST_EXIST); 494 } 495 496 /** 497 * Create shortanswer question pages. 498 * @param object $lesson 499 * @param array $record 500 * @return stdClass page record. 501 */ 502 public function create_question_numeric($lesson, $record = array()) { 503 global $DB, $CFG; 504 $now = time(); 505 $this->pagecount++; 506 $record = (array)$record + array( 507 'lessonid' => $lesson->id, 508 'title' => 'Lesson numerical question '.$this->pagecount, 509 'timecreated' => $now, 510 'qtype' => 8, // LESSON_PAGE_NUMERICAL. 511 'pageid' => 0, // By default insert in the beginning. 512 ); 513 if (!isset($record['contents_editor'])) { 514 $record['contents_editor'] = array( 515 'text' => 'Numerical question '.$this->pagecount, 516 'format' => FORMAT_HTML, 517 'itemid' => 0 518 ); 519 } 520 521 // First Answer (correct). 522 if (!isset($record['answer_editor'][0])) { 523 $record['answer_editor'][0] = array( 524 'text' => $this->pagecount, 525 'format' => FORMAT_MOODLE 526 ); 527 } 528 if (!isset($record['jumpto'][0])) { 529 $record['jumpto'][0] = LESSON_NEXTPAGE; 530 } 531 532 $context = context_module::instance($lesson->cmid); 533 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 534 return $DB->get_record('lesson_pages', array('id' => $page->id), '*', MUST_EXIST); 535 } 536 537 /** 538 * Creates a cluster page for testing purposes. 539 * 540 * @param stdClass $lesson instance where to create the page. 541 * @param array $record data for page being generated. 542 * @return stdClass page record. 543 */ 544 public function create_cluster(stdClass $lesson, array $record = []): stdClass { 545 global $DB, $CFG; 546 $now = time(); 547 $this->pagecount++; 548 $record = $record + [ 549 'lessonid' => $lesson->id, 550 'title' => 'Cluster '.$this->pagecount, 551 'timecreated' => $now, 552 'qtype' => 30, // LESSON_PAGE_CLUSTER. 553 'pageid' => 0, // By default insert in the beginning. 554 ]; 555 if (!isset($record['contents_editor'])) { 556 $record['contents_editor'] = [ 557 'text' => 'Cluster '.$this->pagecount, 558 'format' => FORMAT_MOODLE, 559 'itemid' => 0, 560 ]; 561 } 562 $context = context_module::instance($lesson->cmid); 563 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 564 return $DB->get_record('lesson_pages', ['id' => $page->id], '*', MUST_EXIST); 565 } 566 567 /** 568 * Creates a end of cluster page for testing purposes. 569 * 570 * @param stdClass $lesson instance where to create the page. 571 * @param array $record data for page being generated. 572 * @return stdClass page record. 573 */ 574 public function create_endofcluster(stdClass $lesson, array $record = []): stdClass { 575 global $DB, $CFG; 576 $now = time(); 577 $this->pagecount++; 578 $record = $record + [ 579 'lessonid' => $lesson->id, 580 'title' => 'End of cluster '.$this->pagecount, 581 'timecreated' => $now, 582 'qtype' => 31, // LESSON_PAGE_ENDOFCLUSTER. 583 'pageid' => 0, // By default insert in the beginning. 584 ]; 585 if (!isset($record['contents_editor'])) { 586 $record['contents_editor'] = [ 587 'text' => 'End of cluster '.$this->pagecount, 588 'format' => FORMAT_MOODLE, 589 'itemid' => 0, 590 ]; 591 } 592 $context = context_module::instance($lesson->cmid); 593 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 594 return $DB->get_record('lesson_pages', ['id' => $page->id], '*', MUST_EXIST); 595 } 596 597 /** 598 * Creates a end of branch page for testing purposes. 599 * 600 * @param stdClass $lesson instance where to create the page. 601 * @param array $record data for page being generated. 602 * @return stdClass page record. 603 */ 604 public function create_endofbranch(stdClass $lesson, array $record = []): stdClass { 605 global $DB, $CFG; 606 $now = time(); 607 $this->pagecount++; 608 $record = $record + [ 609 'lessonid' => $lesson->id, 610 'title' => 'End of branch '.$this->pagecount, 611 'timecreated' => $now, 612 'qtype' => 21, // LESSON_PAGE_ENDOFBRANCH. 613 'pageid' => 0, // By default insert in the beginning. 614 ]; 615 if (!isset($record['contents_editor'])) { 616 $record['contents_editor'] = [ 617 'text' => 'End of branch '.$this->pagecount, 618 'format' => FORMAT_MOODLE, 619 'itemid' => 0, 620 ]; 621 } 622 $context = context_module::instance($lesson->cmid); 623 $page = lesson_page::create((object)$record, new lesson($lesson), $context, $CFG->maxbytes); 624 return $DB->get_record('lesson_pages', ['id' => $page->id], '*', MUST_EXIST); 625 } 626 627 /** 628 * Create a lesson override (either user or group). 629 * 630 * @param array $data must specify lessonid, and one of userid or groupid. 631 * @throws coding_exception 632 */ 633 public function create_override(array $data): void { 634 global $DB; 635 636 if (!isset($data['lessonid'])) { 637 throw new coding_exception('Must specify lessonid when creating a lesson override.'); 638 } 639 640 if (!isset($data['userid']) && !isset($data['groupid'])) { 641 throw new coding_exception('Must specify one of userid or groupid when creating a lesson override.'); 642 } 643 644 if (isset($data['userid']) && isset($data['groupid'])) { 645 throw new coding_exception('Cannot specify both userid and groupid when creating a lesson override.'); 646 } 647 648 $DB->insert_record('lesson_overrides', (object) $data); 649 } 650 651 /** 652 * Creates an answer in a page for testing purposes. 653 * 654 * @param null|array|stdClass $record data for module being generated. 655 * @param null|array $options general options. 656 * @throws coding_exception 657 */ 658 public function create_answer($record = null, array $options = null) { 659 $record = (array) $record; 660 661 $candidatepage = null; 662 $pagetitle = $record['page']; 663 $found = false; 664 foreach ($this->candidatepages as &$candidatepage) { 665 if ($candidatepage['title'] === $pagetitle) { 666 $found = true; 667 break; 668 } 669 } 670 671 if (!$found) { 672 throw new coding_exception("Page '$pagetitle' not found in candidate pages. Please make sure the page exists " 673 . 'and all answers are in the same table.'); 674 } 675 676 if (isset($record['answer'])) { 677 $candidatepage['answer_editor'][] = [ 678 'text' => $record['answer'], 679 'format' => FORMAT_HTML, 680 ]; 681 } else { 682 $candidatepage['answer_editor'][] = null; 683 } 684 685 if (isset($record['response'])) { 686 $candidatepage['response_editor'][] = [ 687 'text' => $record['response'], 688 'format' => FORMAT_HTML, 689 ]; 690 } else { 691 $candidatepage['response_editor'][] = null; 692 } 693 694 $candidatepage['jumpto'][] = $record['jumpto'] ?? LESSON_THISPAGE; 695 $candidatepage['score'][] = $record['score'] ?? 0; 696 } 697 698 /** 699 * All answers in a table have been generated, create the pages. 700 */ 701 public function finish_generate_answer() { 702 $this->create_candidate_pages(); 703 } 704 705 /** 706 * Create candidate pages. 707 * 708 * @throws coding_exception 709 */ 710 protected function create_candidate_pages(): void { 711 // For performance reasons it would be better to use a topological sort algorithm. But since test cases shouldn't have 712 // a lot of paged and complex jumps it was implemented using a simpler approach. 713 $consecutiveblocked = 0; 714 715 while (count($this->candidatepages) > 0) { 716 $page = array_shift($this->candidatepages); 717 $id = $this->perform_create_page($page); 718 719 if ($id === null) { 720 // Page cannot be created yet because of jumpto. Move it to the end of list. 721 $consecutiveblocked++; 722 $this->candidatepages[] = $page; 723 724 if ($consecutiveblocked === count($this->candidatepages)) { 725 throw new coding_exception('There is a circular dependency in pages jumps.'); 726 } 727 } else { 728 $consecutiveblocked = 0; 729 } 730 } 731 } 732 733 /** 734 * Calculate the previous page id. 735 * If no page title is supplied, use the last page created in the lesson (0 if no pages). 736 * If page title is supplied, search it in DB and the list of candidate pages. 737 * 738 * @param int $lessonid the lesson id. 739 * @param string $pagetitle the page title, for example 'Test page'. '0' if no previous page. 740 * @return int corresponding id. 0 if no previous page. 741 * @throws coding_exception 742 */ 743 protected function get_previouspage_id(int $lessonid, string $pagetitle): int { 744 global $DB; 745 746 if (is_numeric($pagetitle) && intval($pagetitle) === 0) { 747 return 0; 748 } 749 750 $pages = $DB->get_records('lesson_pages', ['lessonid' => $lessonid, 'title' => $pagetitle], 'id ASC', 'id, title'); 751 752 if (count($pages) > 1) { 753 throw new coding_exception("More than one page with '$pagetitle' found"); 754 } else if (!empty($pages)) { 755 return current($pages)->id; 756 } 757 758 // Page doesn't exist, search if it's a candidate page. If it is, use its previous page instead. 759 foreach ($this->candidatepages as $candidatepage) { 760 if ($candidatepage['title'] === $pagetitle) { 761 return $this->get_previouspage_id($lessonid, $candidatepage['previouspage']); 762 } 763 } 764 765 throw new coding_exception("Page '$pagetitle' not found"); 766 } 767 768 /** 769 * Convert the jumpto using a string to an integer value. 770 * The jumpto can contain a page name or one of our predefined values. 771 * 772 * @param int $lessonid the lesson id. 773 * @param array|null $jumptolist list of jumpto to treat. 774 * @return array|null list of jumpto already treated. 775 * @throws coding_exception 776 */ 777 protected function convert_page_jumpto(int $lessonid, ?array $jumptolist): ?array { 778 global $DB; 779 780 if (empty($jumptolist)) { 781 return $jumptolist; 782 } 783 784 foreach ($jumptolist as $i => $jumpto) { 785 if (empty($jumpto) || is_numeric($jumpto)) { 786 continue; 787 } 788 789 if (isset($this->jumptomap[$jumpto])) { 790 $jumptolist[$i] = $this->jumptomap[$jumpto]; 791 792 continue; 793 } 794 795 $page = $DB->get_record('lesson_pages', ['lessonid' => $lessonid, 'title' => $jumpto], 'id'); 796 if ($page === false) { 797 throw new coding_exception("Jump '$jumpto' not found in pages."); 798 } 799 800 $jumptolist[$i] = $page->id; 801 } 802 803 return $jumptolist; 804 } 805 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body