Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
}