Proyectos de Subversion Moodle

Rev

Rev 11 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 11 Rev 1441
Línea 14... Línea 14...
14
// You should have received a copy of the GNU General Public License
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/>.
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
Línea 16... Línea 16...
16
 
16
 
Línea -... Línea 17...
-
 
17
namespace core_question;
-
 
18
 
-
 
19
use context_course;
-
 
20
use mod_quiz\quiz_settings;
-
 
21
use moodle_url;
17
namespace core_question;
22
use question_bank;
Línea -... Línea 23...
-
 
23
 
-
 
24
defined('MOODLE_INTERNAL') || die();
-
 
25
 
-
 
26
use backup;
-
 
27
use core_question\local\bank\question_bank_helper;
18
 
28
use restore_controller;
19
defined('MOODLE_INTERNAL') || die();
29
use restore_dbops;
20
 
30
 
Línea 21... Línea 31...
21
global $CFG;
31
global $CFG;
Línea 27... Línea 37...
27
 *
37
 *
28
 * @package    core_question
38
 * @package    core_question
29
 * @category   test
39
 * @category   test
30
 * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
40
 * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
31
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
-
 
42
 * @covers     \restore_qtype_plugin
-
 
43
 * @covers     \restore_create_categories_and_questions
-
 
44
 * @covers     \restore_move_module_questions_categories
32
 */
45
 */
33
class backup_test extends \advanced_testcase {
46
final class backup_test extends \advanced_testcase {
Línea 34... Línea 47...
34
 
47
 
35
    /**
48
    /**
36
     * Makes a backup of the course.
49
     * Makes a backup of the course.
37
     *
50
     *
Línea 55... Línea 68...
55
 
68
 
56
        return $backupid;
69
        return $backupid;
Línea 57... Línea 70...
57
    }
70
    }
-
 
71
 
-
 
72
    /**
-
 
73
     * Makes a backup of a course module.
-
 
74
     *
-
 
75
     * @param int $modid The course_module id.
-
 
76
     * @return string Unique identifier for this backup.
-
 
77
     */
-
 
78
    protected function backup_course_module(int $modid) {
-
 
79
        global $CFG, $USER;
-
 
80
 
-
 
81
        // Turn off file logging, otherwise it can't delete the file (Windows).
-
 
82
        $CFG->backup_file_logger_level = \backup::LOG_NONE;
-
 
83
 
-
 
84
        // Do backup with default settings. MODE_IMPORT means it will just
-
 
85
        // create the directory and not zip it.
-
 
86
        $bc = new \backup_controller(\backup::TYPE_1ACTIVITY, $modid,
-
 
87
            \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT,
-
 
88
            $USER->id);
-
 
89
        $backupid = $bc->get_backupid();
-
 
90
        $bc->execute_plan();
-
 
91
        $bc->destroy();
-
 
92
 
-
 
93
        return $backupid;
-
 
94
    }
58
 
95
 
59
    /**
96
    /**
60
     * Restores a backup that has been made earlier.
97
     * Restores a backup that has been made earlier.
61
     *
-
 
62
     * @param string $backupid The unique identifier of the backup.
-
 
63
     * @param string $fullname Full name of the new course that is going to be created.
98
     *
64
     * @param string $shortname Short name of the new course that is going to be created.
99
     * @param string $backupid The unique identifier of the backup.
65
     * @param int $categoryid The course category the backup is going to be restored in.
-
 
66
     * @param string[] $expectedprecheckwarning
100
     * @param int $courseid Course id of where the restore is happening.
67
     * @return int The new course id.
101
     * @param string[] $expectedprecheckwarning
68
     */
102
     */
Línea 69... Línea 103...
69
    protected function restore_course($backupid, $fullname, $shortname, $categoryid, $expectedprecheckwarning = []) {
103
    protected function restore_to_course(string $backupid, int $courseid, array $expectedprecheckwarning = []): void {
70
        global $CFG, $USER;
104
        global $CFG, $USER;
Línea 71... Línea -...
71
 
-
 
72
        // Turn off file logging, otherwise it can't delete the file (Windows).
-
 
73
        $CFG->backup_file_logger_level = \backup::LOG_NONE;
105
 
74
 
106
        // Turn off file logging, otherwise it can't delete the file (Windows).
75
        // Do restore to new course with default settings.
107
        $CFG->backup_file_logger_level = \backup::LOG_NONE;
Línea 76... Línea 108...
76
        $newcourseid = \restore_dbops::create_new_course($fullname, $shortname, $categoryid);
108
 
77
        $rc = new \restore_controller($backupid, $newcourseid,
109
        $rc = new \restore_controller($backupid, $courseid,
Línea 86... Línea 118...
86
            $this->assertEqualsCanonicalizing($expectedprecheckwarning, $precheckresults['warnings']);
118
            $this->assertEqualsCanonicalizing($expectedprecheckwarning, $precheckresults['warnings']);
87
            $this->assertCount(1, $precheckresults);
119
            $this->assertCount(1, $precheckresults);
88
        }
120
        }
89
        $rc->execute_plan();
121
        $rc->execute_plan();
90
        $rc->destroy();
122
        $rc->destroy();
91
 
-
 
92
        return $newcourseid;
-
 
93
    }
123
    }
Línea 94... Línea 124...
94
 
124
 
95
    /**
125
    /**
96
     * This function tests backup and restore of question tags and course level question tags.
126
     * This function tests backup and restore of question tags.
97
     */
127
     */
98
    public function test_backup_question_tags(): void {
128
    public function test_backup_question_tags(): void {
Línea 99... Línea 129...
99
        global $DB;
129
        global $DB;
100
 
130
 
Línea 101... Línea 131...
101
        $this->resetAfterTest();
131
        $this->resetAfterTest();
102
        $this->setAdminUser();
132
        $this->setAdminUser();
103
 
133
 
104
        // Create a new course category and and a new course in that.
134
        // Create a new course category and a new course in that.
105
        $category1 = $this->getDataGenerator()->create_category();
135
        $category1 = $this->getDataGenerator()->create_category();
Línea 106... Línea 136...
106
        $course = $this->getDataGenerator()->create_course(['category' => $category1->id]);
136
        $course = $this->getDataGenerator()->create_course(['category' => $category1->id]);
107
        $courseshortname = $course->shortname;
137
        $courseshortname = $course->shortname;
-
 
138
        $coursefullname = $course->fullname;
108
        $coursefullname = $course->fullname;
139
 
109
 
140
        // Create 2 questions.
110
        // Create 2 questions.
141
        $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
111
        $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
142
        $qbank = $this->getDataGenerator()->create_module('qbank', ['course' => $course->id]);
Línea 112... Línea 143...
112
        $context = \context_coursecat::instance($category1->id);
143
        $context = \context_module::instance($qbank->cmid);
113
        $qcat = $qgen->create_question_category(['contextid' => $context->id]);
144
        $qcat = question_get_default_category($context->id);
114
        $question1 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q1']);
145
        $question1 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q1']);
115
        $question2 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q2']);
146
        $question2 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q2']);
116
 
147
 
117
        // Tag the questions with 2 question tags and 2 course level question tags.
-
 
118
        $qcontext = \context::instance_by_id($qcat->contextid);
-
 
Línea 119... Línea 148...
119
        $coursecontext = \context_course::instance($course->id);
148
        // Tag the questions with 2 question tags.
120
        \core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['qtag1', 'qtag2']);
149
        $qcontext = \context::instance_by_id($qcat->contextid);
121
        \core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['qtag3', 'qtag4']);
150
        $coursecontext = context_course::instance($course->id);
Línea 134... Línea 163...
134
        delete_course($course, false);
163
        delete_course($course, false);
135
        question_delete_question($question1->id);
164
        question_delete_question($question1->id);
136
        question_delete_question($question2->id);
165
        question_delete_question($question2->id);
Línea 137... Línea 166...
137
 
166
 
-
 
167
        // Restore the backup we had made earlier into a new course.
138
        // Restore the backup we had made earlier into a new course.
168
        // Do restore to new course with default settings.
-
 
169
        $courseid2 = \restore_dbops::create_new_course($coursefullname, $courseshortname . '_2', $category1->id);
-
 
170
        $this->restore_to_course($backupid1, $courseid2);
-
 
171
        $modinfo = get_fast_modinfo($courseid2);
-
 
172
        $qbanks = $modinfo->get_instances_of('qbank');
-
 
173
        $qbanks = array_filter($qbanks, static fn($qbank) => $qbank->get_name() === 'Question bank 1');
-
 
174
        $this->assertCount(1, $qbanks);
-
 
175
        $qbank = reset($qbanks);
-
 
176
        $qbankcontext = \context_module::instance($qbank->id);
-
 
177
        $cats = $DB->get_records_select('question_categories', 'parent <> 0 AND contextid = ?', [$qbankcontext->id]);
-
 
178
        $this->assertCount(1, $cats);
Línea 139... Línea 179...
139
        $courseid2 = $this->restore_course($backupid1, $coursefullname, $courseshortname . '_2', $category1->id);
179
        $cat = reset($cats);
140
 
-
 
141
        // The questions should remain in the question category they were which is
180
 
142
        // a question category belonging to a course category context.
181
        // The questions should be restored to a mod_qbank context in the new course.
143
        $sql = 'SELECT q.*,
182
        $sql = 'SELECT q.*,
144
                       qbe.idnumber
183
                       qbe.idnumber
145
                  FROM {question} q
184
                  FROM {question} q
146
                  JOIN {question_versions} qv ON qv.questionid = q.id
185
                  JOIN {question_versions} qv ON qv.questionid = q.id
147
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
186
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
148
                 WHERE qbe.questioncategoryid = ?
187
                 WHERE qbe.questioncategoryid = ?
149
                 ORDER BY qbe.idnumber';
188
                 ORDER BY qbe.idnumber';
Línea 150... Línea 189...
150
        $questions = $DB->get_records_sql($sql, [$qcat->id]);
189
        $questions = $DB->get_records_sql($sql, [$cat->id]);
151
        $this->assertCount(2, $questions);
190
        $this->assertCount(2, $questions);
152
 
191
 
153
        // Retrieve tags for each question and check if they are assigned at the right context.
192
        // Retrieve tags for each question and check if they are assigned at the right context.
Línea 154... Línea 193...
154
        $qcount = 1;
193
        $qcount = 1;
155
        foreach ($questions as $question) {
194
        foreach ($questions as $question) {
Línea 156... Línea 195...
156
            $tags = \core_tag_tag::get_item_tags('core_question', 'question', $question->id);
195
            $tags = \core_tag_tag::get_item_tags('core_question', 'question', $question->id);
157
 
-
 
158
            // Each question is tagged with 4 tags (2 question tags + 2 course tags).
-
 
159
            $this->assertCount(4, $tags);
-
 
160
 
-
 
161
            foreach ($tags as $tag) {
-
 
162
                if (in_array($tag->name, ['ctag1', 'ctag2', 'ctag3', 'ctag4'])) {
196
 
163
                    $expected = \context_course::instance($courseid2)->id;
197
            // Each question is tagged with 4 tags (2 question tags + 2 course tags).
Línea 164... Línea 198...
164
                } else if (in_array($tag->name, ['qtag1', 'qtag2', 'qtag3', 'qtag4'])) {
198
            $this->assertCount(2, $tags);
165
                    $expected = $qcontext->id;
199
 
166
                }
200
            foreach ($tags as $tag) {
Línea 180... Línea 214...
180
        $category1->delete_full(false);
214
        $category1->delete_full(false);
Línea 181... Línea 215...
181
 
215
 
182
        // Create a new course category to restore the backup file into it.
216
        // Create a new course category to restore the backup file into it.
Línea 183... Línea -...
183
        $category2 = $this->getDataGenerator()->create_category();
-
 
184
 
-
 
185
        $expectedwarnings = [
-
 
186
                get_string('qcategory2coursefallback', 'backup', (object) ['name' => 'top']),
-
 
187
                get_string('qcategory2coursefallback', 'backup', (object) ['name' => $qcat->name])
-
 
188
        ];
217
        $category2 = $this->getDataGenerator()->create_category();
189
 
218
 
-
 
219
        // Restore to a new course in the new course category.
-
 
220
        $courseid3 = \restore_dbops::create_new_course($coursefullname, $courseshortname . '_3', $category2->id);
-
 
221
        $this->restore_to_course($backupid2, $courseid3);
-
 
222
        $modinfo = get_fast_modinfo($courseid3);
-
 
223
        $qbanks = $modinfo->get_instances_of('qbank');
-
 
224
        $qbanks = array_filter($qbanks, static fn($qbank) => $qbank->get_name() === 'Question bank 1');
190
        // Restore to a new course in the new course category.
225
        $this->assertCount(1, $qbanks);
Línea 191... Línea 226...
191
        $courseid3 = $this->restore_course($backupid2, $coursefullname, $courseshortname . '_3', $category2->id, $expectedwarnings);
226
        $qbank = reset($qbanks);
192
        $coursecontext3 = \context_course::instance($courseid3);
227
        $context = \context_module::instance($qbank->id);
193
 
228
 
194
        // The questions should have been moved to a question category that belongs to a course context.
229
        // The questions should have been moved to a question category that belongs to a course context.
195
        $questions = $DB->get_records_sql("SELECT q.*
230
        $questions = $DB->get_records_sql("SELECT q.*
196
                                                FROM {question} q
231
                                                FROM {question} q
197
                                                JOIN {question_versions} qv ON qv.questionid = q.id
232
                                                JOIN {question_versions} qv ON qv.questionid = q.id
198
                                                JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
233
                                                JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
Línea 199... Línea 234...
199
                                                JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
234
                                                JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
200
                                               WHERE qc.contextid = ?", [$coursecontext3->id]);
235
                                               WHERE qc.contextid = ?", [$context->id]);
201
        $this->assertCount(2, $questions);
236
        $this->assertCount(2, $questions);
Línea 202... Línea 237...
202
 
237
 
203
        // Now, retrieve tags for each question and check if they are assigned at the right context.
238
        // Now, retrieve tags for each question and check if they are assigned at the right context.
Línea 204... Línea 239...
204
        foreach ($questions as $question) {
239
        foreach ($questions as $question) {
205
            $tags = \core_tag_tag::get_item_tags('core_question', 'question', $question->id);
240
            $tags = \core_tag_tag::get_item_tags('core_question', 'question', $question->id);
206
 
241
 
207
            // Each question is tagged with 4 tags (all are course tags now).
242
            // Each question is tagged with 2 tags (all are question context tags now).
Línea 208... Línea 243...
208
            $this->assertCount(4, $tags);
243
            $this->assertCount(2, $tags);
Línea 388... Línea 423...
388
        $this->assertCount(1, $questions);
423
        $this->assertCount(1, $questions);
389
        $question = array_shift($questions);
424
        $question = array_shift($questions);
390
        $this->assertEquals($USER->id, $question->createdby);
425
        $this->assertEquals($USER->id, $question->createdby);
391
        $this->assertEquals($USER->id, $question->modifiedby);
426
        $this->assertEquals($USER->id, $question->modifiedby);
392
    }
427
    }
-
 
428
 
-
 
429
    public function test_backup_and_restore_recodes_links_in_questions(): void {
-
 
430
        global $DB, $USER, $CFG;
-
 
431
        $this->resetAfterTest();
-
 
432
        $this->setAdminUser();
-
 
433
 
-
 
434
        // Create a course and a category.
-
 
435
        $course = $this->getDataGenerator()->create_course();
-
 
436
        $qbank = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
-
 
437
        $category = $this->getDataGenerator()->create_category();
-
 
438
 
-
 
439
        // Create a question with links in all the places that should be recoded.
-
 
440
        $testlink = new moodle_url('/course/view.php', ['id' => $course->id]);
-
 
441
        $testcontent = 'Look at <a href="' . $testlink . '">the course</a>.';
-
 
442
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
-
 
443
        $questioncategory = $questiongenerator->create_question_category(
-
 
444
            ['contextid' => \context_module::instance($qbank->cmid)->id]);
-
 
445
        $question = $questiongenerator->create_question('multichoice', null, [
-
 
446
            'name' => 'Test question',
-
 
447
            'category' => $questioncategory->id,
-
 
448
            'questiontext' => ['text' => 'This is the question. ' . $testcontent],
-
 
449
            'generalfeedback' => ['text' => 'Why is this right? ' . $testcontent],
-
 
450
            'answer' => [
-
 
451
                '0' => ['text' => 'Choose me! ' . $testcontent],
-
 
452
            ],
-
 
453
            'feedback' => [
-
 
454
                '0' => ['text' => 'The reason: ' . $testcontent],
-
 
455
            ],
-
 
456
            'hint' => [
-
 
457
                '0' => ['text' => 'Hint: ' . $testcontent],
-
 
458
            ],
-
 
459
        ]);
-
 
460
 
-
 
461
        // Create a quiz and add the question.
-
 
462
        $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
-
 
463
        quiz_add_quiz_question($question->id, $quiz);
-
 
464
 
-
 
465
        // Backup and restore the course.
-
 
466
        $backupid = $this->backup_course($course);
-
 
467
        $newcourse = $this->getDataGenerator()->create_course();
-
 
468
        $this->restore_to_course($backupid, $newcourse->id);
-
 
469
        $modinfo = get_fast_modinfo($newcourse);
-
 
470
        $qbanks = $modinfo->get_instances_of('qbank');
-
 
471
        $qbank = reset($qbanks);
-
 
472
 
-
 
473
        // Get the question from the restored course - we are expecting just one, but that is not the real test here.
-
 
474
        $restoredquestions = $DB->get_records_sql("
-
 
475
                SELECT q.id, q.name
-
 
476
                  FROM {question} q
-
 
477
                  JOIN {question_versions} qv ON qv.questionid = q.id
-
 
478
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
-
 
479
                  JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
-
 
480
                 WHERE qc.contextid = ?
-
 
481
            ", [\context_module::instance($qbank->id)->id]);
-
 
482
        $this->assertCount(1, $restoredquestions);
-
 
483
        $questionid = array_key_first($restoredquestions);
-
 
484
        $this->assertEquals('Test question', $restoredquestions[$questionid]->name);
-
 
485
 
-
 
486
        // Verify the links have been recoded.
-
 
487
        $restoredquestion = question_bank::load_question_data($questionid);
-
 
488
        $recodedlink = new moodle_url('/course/view.php', ['id' => $newcourse->id]);
-
 
489
        $recodedcontent = 'Look at <a href="' . $recodedlink . '">the course</a>.';
-
 
490
        $firstanswerid = array_key_first($restoredquestion->options->answers);
-
 
491
        $firsthintid = array_key_first($restoredquestion->hints);
-
 
492
 
-
 
493
        $this->assertEquals('This is the question. ' . $recodedcontent, $restoredquestion->questiontext);
-
 
494
        $this->assertEquals('Why is this right? ' . $recodedcontent, $restoredquestion->generalfeedback);
-
 
495
        $this->assertEquals('Choose me! ' . $recodedcontent, $restoredquestion->options->answers[$firstanswerid]->answer);
-
 
496
        $this->assertEquals('The reason: ' . $recodedcontent, $restoredquestion->options->answers[$firstanswerid]->feedback);
-
 
497
        $this->assertEquals('Hint: ' . $recodedcontent, $restoredquestion->hints[$firsthintid]->hint);
-
 
498
    }
-
 
499
 
-
 
500
    /**
-
 
501
     * Boilerplate setup for the tests. Creates a course, a quiz, and a qbank module. It adds a category to each module context
-
 
502
     * and adds a question to each category. Finally, it adds the 2 questions to the quiz.
-
 
503
     *
-
 
504
     * @return \stdClass
-
 
505
     */
-
 
506
    private function add_course_quiz_and_qbank() {
-
 
507
        $qgen = self::getDataGenerator()->get_plugin_generator('core_question');
-
 
508
 
-
 
509
        // Create a new course.
-
 
510
        $course = self::getDataGenerator()->create_course();
-
 
511
 
-
 
512
        // Create a question bank module instance, a category for that module, and a question for that category.
-
 
513
        $qbank = self::getDataGenerator()->create_module(
-
 
514
            'qbank',
-
 
515
            ['type' => question_bank_helper::TYPE_STANDARD, 'course' => $course->id]
-
 
516
        );
-
 
517
        $qbankcontext = \context_module::instance($qbank->cmid);
-
 
518
        $bankqcat = question_get_default_category($qbankcontext->id);
-
 
519
        $bankquestion = $qgen->create_question('shortanswer',
-
 
520
            null,
-
 
521
            ['name' => 'bank question', 'category' => $bankqcat->id, 'idnumber' => 'bankq1']
-
 
522
        );
-
 
523
 
-
 
524
        // Create a quiz module instance, a category for that module, and a question for that category.
-
 
525
        $quiz = self::getDataGenerator()->create_module('quiz', ['course' => $course->id]);
-
 
526
        $quizcontext = \context_module::instance($quiz->cmid);
-
 
527
        $quizqcat = question_get_default_category($quizcontext->id);
-
 
528
        $quizquestion = $qgen->create_question('shortanswer',
-
 
529
            null,
-
 
530
            ['name' => 'quiz question', 'category' => $quizqcat->id, 'idnumber' => 'quizq1']
-
 
531
        );
-
 
532
 
-
 
533
        quiz_add_quiz_question($bankquestion->id, $quiz);
-
 
534
        quiz_add_quiz_question($quizquestion->id, $quiz);
-
 
535
 
-
 
536
        $data = new \stdClass();
-
 
537
        $data->course = $course;
-
 
538
        $data->qbank = $qbank;
-
 
539
        $data->qbankcategory = $bankqcat;
-
 
540
        $data->qbankquestion = $bankquestion;
-
 
541
        $data->quiz = $quiz;
-
 
542
        $data->quizcategory = $quizqcat;
-
 
543
        $data->quizquestion = $quizquestion;
-
 
544
 
-
 
545
        return $data;
-
 
546
    }
-
 
547
 
-
 
548
    /**
-
 
549
     * If the backup contains ONLY a quiz but that quiz uses questions from a qbank module and itself,
-
 
550
     * and the original course does not exist on the target system,
-
 
551
     * then the non-quiz context categories and questions should restore to a default qbank module on the new course
-
 
552
     * if the old qbank no longer exists.
-
 
553
     */
-
 
554
    public function test_quiz_activity_restore_to_new_course(): void {
-
 
555
        global $DB;
-
 
556
 
-
 
557
        $this->resetAfterTest();
-
 
558
        self::setAdminUser();
-
 
559
 
-
 
560
        // Create a course to make a backup.
-
 
561
        $data = $this->add_course_quiz_and_qbank();
-
 
562
        $oldquiz = $data->quiz;
-
 
563
 
-
 
564
        // Backup ONLY the quiz module.
-
 
565
        $backupid = $this->backup_course_module($oldquiz->cmid);
-
 
566
 
-
 
567
        // Create a new course to restore to.
-
 
568
        $newcourse = self::getDataGenerator()->create_course();
-
 
569
        delete_course($data->course->id, false);
-
 
570
 
-
 
571
        $this->restore_to_course($backupid, $newcourse->id);
-
 
572
        $modinfo = get_fast_modinfo($newcourse);
-
 
573
 
-
 
574
        // Assert we have our quiz including the category and question.
-
 
575
        $newquizzes = $modinfo->get_instances_of('quiz');
-
 
576
        $this->assertCount(1, $newquizzes);
-
 
577
        $newquiz = reset($newquizzes);
-
 
578
        $newquizcontext = \context_module::instance($newquiz->id);
-
 
579
 
-
 
580
        $quizcats = $DB->get_records_select('question_categories',
-
 
581
            'parent <> 0 AND contextid = :contextid',
-
 
582
            ['contextid' => $newquizcontext->id]
-
 
583
        );
-
 
584
        $this->assertCount(1, $quizcats);
-
 
585
        $quizcat = reset($quizcats);
-
 
586
        $quizcatqs = get_questions_category($quizcat, false);
-
 
587
        $this->assertCount(1, $quizcatqs);
-
 
588
        $quizq = reset($quizcatqs);
-
 
589
        $this->assertEquals('quiz question', $quizq->name);
-
 
590
 
-
 
591
        // The backup did not contain the qbank that held the categories, but it is dependant.
-
 
592
        // So make sure the categories and questions got restored to a 'system' type default qbank module on the course.
-
 
593
        $defaultbanks = $modinfo->get_instances_of('qbank');
-
 
594
        $this->assertCount(1, $defaultbanks);
-
 
595
        $defaultbank = reset($defaultbanks);
-
 
596
        $defaultbankcontext = \context_module::instance($defaultbank->id);
-
 
597
        $bankcats = $DB->get_records_select('question_categories',
-
 
598
            'parent <> 0 AND contextid = :contextid',
-
 
599
            ['contextid' => $defaultbankcontext->id]
-
 
600
        );
-
 
601
        $bankcat = reset($bankcats);
-
 
602
        $bankqs = get_questions_category($bankcat, false);
-
 
603
        $this->assertCount(1, $bankqs);
-
 
604
        $bankq = reset($bankqs);
-
 
605
        $this->assertEquals('bank question', $bankq->name);
-
 
606
    }
-
 
607
 
-
 
608
    /**
-
 
609
     * If the backup contains ONLY a quiz but that quiz uses questions from a qbank module and itself,
-
 
610
     * and the original course does exist on the target system but you dont have permission to view the original qbank,
-
 
611
     * then the non-quiz context categories and questions should restore to a default qbank module on the new course
-
 
612
     * if the old qbank no longer exists.
-
 
613
     */
-
 
614
    public function test_quiz_activity_restore_to_new_course_no_permission(): void {
-
 
615
        global $DB;
-
 
616
 
-
 
617
        $this->resetAfterTest();
-
 
618
        self::setAdminUser();
-
 
619
 
-
 
620
        // Create a course to make a backup.
-
 
621
        $data = $this->add_course_quiz_and_qbank();
-
 
622
        $oldquiz = $data->quiz;
-
 
623
 
-
 
624
        // Backup ONLY the quiz module.
-
 
625
        $backupid = $this->backup_course_module($oldquiz->cmid);
-
 
626
 
-
 
627
        // Create a new course to restore to.
-
 
628
        $newcourse = self::getDataGenerator()->create_course();
-
 
629
        $restoreuser = self::getDataGenerator()->create_user();
-
 
630
        self::getDataGenerator()->enrol_user($restoreuser->id, $newcourse->id, 'manager');
-
 
631
        $this->setUser($restoreuser);
-
 
632
 
-
 
633
        $this->restore_to_course($backupid, $newcourse->id);
-
 
634
        $modinfo = get_fast_modinfo($newcourse);
-
 
635
 
-
 
636
        // Assert we have our quiz including the category and question.
-
 
637
        $newquizzes = $modinfo->get_instances_of('quiz');
-
 
638
        $this->assertCount(1, $newquizzes);
-
 
639
        $newquiz = reset($newquizzes);
-
 
640
        $newquizcontext = \context_module::instance($newquiz->id);
-
 
641
 
-
 
642
        $quizcats = $DB->get_records_select('question_categories',
-
 
643
            'parent <> 0 AND contextid = :contextid',
-
 
644
            ['contextid' => $newquizcontext->id]
-
 
645
        );
-
 
646
        $this->assertCount(1, $quizcats);
-
 
647
        $quizcat = reset($quizcats);
-
 
648
        $quizcatqs = get_questions_category($quizcat, false);
-
 
649
        $this->assertCount(1, $quizcatqs);
-
 
650
        $quizq = reset($quizcatqs);
-
 
651
        $this->assertEquals('quiz question', $quizq->name);
-
 
652
 
-
 
653
        // The backup did not contain the qbank that held the categories, but it is dependant.
-
 
654
        // So make sure the categories and questions got restored to a qbank module on the course.
-
 
655
        $defaultbanks = $modinfo->get_instances_of('qbank');
-
 
656
        $this->assertCount(1, $defaultbanks);
-
 
657
        $defaultbank = reset($defaultbanks);
-
 
658
        $defaultbankcontext = \context_module::instance($defaultbank->id);
-
 
659
        $bankcats = $DB->get_records_select('question_categories',
-
 
660
            'parent <> 0 AND contextid = :contextid',
-
 
661
            ['contextid' => $defaultbankcontext->id]
-
 
662
        );
-
 
663
        $bankcat = reset($bankcats);
-
 
664
        $bankqs = get_questions_category($bankcat, false);
-
 
665
        $this->assertCount(1, $bankqs);
-
 
666
        $bankq = reset($bankqs);
-
 
667
        $this->assertEquals('bank question', $bankq->name);
-
 
668
    }
-
 
669
 
-
 
670
    /**
-
 
671
     * If the backup contains ONLY a quiz but that quiz uses questions from a qbank module and itself,
-
 
672
     * and that qbank still exists on the system, and the restoring user can access that qbank, then
-
 
673
     * the quiz should be restored with a copy of the quiz question, and a reference to the original qbank question.
-
 
674
     */
-
 
675
    public function test_quiz_activity_restore_to_new_course_by_reference(): void {
-
 
676
        global $DB;
-
 
677
 
-
 
678
        $this->resetAfterTest();
-
 
679
        self::setAdminUser();
-
 
680
 
-
 
681
        // Create a course to make a backup.
-
 
682
        $data = $this->add_course_quiz_and_qbank();
-
 
683
        $oldquiz = $data->quiz;
-
 
684
 
-
 
685
        // Backup ONLY the quiz module.
-
 
686
        $backupid = $this->backup_course_module($oldquiz->cmid);
-
 
687
 
-
 
688
        // Create a new course to restore to.
-
 
689
        $newcourse = self::getDataGenerator()->create_course();
-
 
690
 
-
 
691
        $this->restore_to_course($backupid, $newcourse->id);
-
 
692
        $modinfo = get_fast_modinfo($newcourse);
-
 
693
 
-
 
694
        // Assert we have our new quiz with the expected questions.
-
 
695
        $newquizzes = $modinfo->get_instances_of('quiz');
-
 
696
        $this->assertCount(1, $newquizzes);
-
 
697
        /** @var \cm_info $newquiz */
-
 
698
        $newquiz = reset($newquizzes);
-
 
699
        $quiz = $DB->get_record('quiz', ['id' => $newquiz->instance], '*', MUST_EXIST);
-
 
700
        [$course, $cm] = get_course_and_cm_from_instance($quiz, 'quiz');
-
 
701
        $newquizsettings = new quiz_settings($quiz, $cm, $course);
-
 
702
        $newq1 = $newquizsettings->get_structure()->get_question_in_slot(1);
-
 
703
        $newq2 = $newquizsettings->get_structure()->get_question_in_slot(2);
-
 
704
 
-
 
705
        $newquizcontext = \context_module::instance($newquiz->id);
-
 
706
        $qbankcontext = \context_module::instance($data->qbank->cmid);
-
 
707
 
-
 
708
        // Check we've got a copy of the quiz question in the new context.
-
 
709
        $this->assertEquals($data->quizquestion->name, $newq2->name);
-
 
710
        $this->assertEquals($newquizcontext->id, $newq2->contextid);
-
 
711
        // Check we've got a reference to the qbank question in the original context.
-
 
712
        $this->assertEquals($data->qbankquestion->name, $newq1->name);
-
 
713
        $this->assertEquals($qbankcontext->id, $newq1->contextid);
-
 
714
        // Check we have the expected restored categories.
-
 
715
        $this->assertEquals(2, $DB->count_records('question_categories', ['stamp' => $data->quizcategory->stamp]));
-
 
716
        $this->assertEquals(1, $DB->count_records('question_categories', ['stamp' => $data->qbankcategory->stamp]));
-
 
717
    }
-
 
718
 
-
 
719
    /**
-
 
720
     * If the backup contains BOTH a quiz and a qbank module and the quiz uses questions from the qbank module and itself,
-
 
721
     * then we need to restore the categories and questions to the qbank and quiz modules included in the backup on the new course.
-
 
722
     *
-
 
723
     * @return void
-
 
724
     * @covers \restore_controller::execute_plan()
-
 
725
     */
-
 
726
    public function test_bank_and_quiz_activity_restore_to_new_course(): void {
-
 
727
        // Create a new course.
-
 
728
        global $DB;
-
 
729
 
-
 
730
        $this->resetAfterTest();
-
 
731
        self::setAdminUser();
-
 
732
 
-
 
733
        // Create a course to make a backup from.
-
 
734
        $data = $this->add_course_quiz_and_qbank();
-
 
735
        $oldcourse = $data->course;
-
 
736
 
-
 
737
        // Backup the course.
-
 
738
        $backupid = $this->backup_course($oldcourse);
-
 
739
 
-
 
740
        // Create a new course to restore to.
-
 
741
        $newcourse = self::getDataGenerator()->create_course();
-
 
742
 
-
 
743
        // Restore it.
-
 
744
        $this->restore_to_course($backupid, $newcourse->id);
-
 
745
 
-
 
746
        // Assert the quiz got its question catregories restored.
-
 
747
        $modinfo = get_fast_modinfo($newcourse);
-
 
748
        $newquizzes = $modinfo->get_instances_of('quiz');
-
 
749
        $this->assertCount(1, $newquizzes);
-
 
750
        $newquiz = reset($newquizzes);
-
 
751
        $newquizcontext = \context_module::instance($newquiz->id);
-
 
752
        $quizcats = $DB->get_records_select('question_categories',
-
 
753
            'parent <> 0 AND contextid = :contextid',
-
 
754
            ['contextid' => $newquizcontext->id]
-
 
755
        );
-
 
756
        $quizcat = reset($quizcats);
-
 
757
        $quizcatqs = get_questions_category($quizcat, false);
-
 
758
        $this->assertCount(1, $quizcatqs);
-
 
759
        $quizcatq = reset($quizcatqs);
-
 
760
        $this->assertEquals('quiz question', $quizcatq->name);
-
 
761
 
-
 
762
        // Assert the qbank got its questions restored to the module in the backup.
-
 
763
        $qbanks = $modinfo->get_instances_of('qbank');
-
 
764
        $qbanks = array_filter($qbanks, static function($bank) {
-
 
765
            global $DB;
-
 
766
            $modrecord = $DB->get_record('qbank', ['id' => $bank->instance]);
-
 
767
            return $modrecord->type === question_bank_helper::TYPE_STANDARD;
-
 
768
        });
-
 
769
        $this->assertCount(1, $qbanks);
-
 
770
        $qbank = reset($qbanks);
-
 
771
        $bankcats = $DB->get_records_select('question_categories',
-
 
772
            'parent <> 0 AND contextid = :contextid',
-
 
773
            ['contextid' => \context_module::instance($qbank->id)->id]
-
 
774
        );
-
 
775
        $bankcat = reset($bankcats);
-
 
776
        $bankqs = get_questions_category($bankcat, false);
-
 
777
        $this->assertCount(1, $bankqs);
-
 
778
        $bankq = reset($bankqs);
-
 
779
        $this->assertEquals('bank question', $bankq->name);
-
 
780
    }
-
 
781
 
-
 
782
    /**
-
 
783
     * The course backup file contains question banks and a quiz module.
-
 
784
     * There is 1 question bank category per deprecated context level i.e. CONTEXT_SYSTEM, CONTEXT_COURSECAT, and CONTEXT_COURSE.
-
 
785
     * The quiz included in the backup uses a question in each category.
-
 
786
     *
-
 
787
     * @return void
-
 
788
     * @covers \restore_controller::execute_plan()
-
 
789
     */
-
 
790
    public function test_pre_46_course_restore_to_new_course(): void {
-
 
791
        global $DB, $USER;
-
 
792
        self::setAdminUser();
-
 
793
        $this->resetAfterTest();
-
 
794
 
-
 
795
        $backupid = 'question_category_45_format';
-
 
796
        $backuppath = make_backup_temp_directory($backupid);
-
 
797
        get_file_packer('application/vnd.moodle.backup')->extract_to_pathname(
-
 
798
            __DIR__ . "/fixtures/{$backupid}.mbz",
-
 
799
            $backuppath
-
 
800
        );
-
 
801
 
-
 
802
        // Do restore to new course with default settings.
-
 
803
        $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
-
 
804
        $newcourseid = restore_dbops::create_new_course('Test fullname', 'Test shortname', $categoryid);
-
 
805
        $rc = new restore_controller($backupid, $newcourseid,
-
 
806
            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
-
 
807
            backup::TARGET_NEW_COURSE
-
 
808
        );
-
 
809
 
-
 
810
        $rc->execute_precheck();
-
 
811
        $rc->execute_plan();
-
 
812
        $rc->destroy();
-
 
813
 
-
 
814
        $modinfo = get_fast_modinfo($newcourseid);
-
 
815
 
-
 
816
        $qbanks = $modinfo->get_instances_of('qbank');
-
 
817
        $qbanks = array_filter($qbanks, static function($bank) {
-
 
818
            global $DB;
-
 
819
            $modrecord = $DB->get_record('qbank', ['id' => $bank->instance]);
-
 
820
            return $modrecord->type === question_bank_helper::TYPE_SYSTEM;
-
 
821
        });
-
 
822
        $this->assertCount(1, $qbanks);
-
 
823
        $qbank = reset($qbanks);
-
 
824
        $qbankcontext = \context_module::instance($qbank->id);
-
 
825
        $bankcats = $DB->get_records_select('question_categories',
-
 
826
            'parent <> 0 AND contextid = :contextid',
-
 
827
            ['contextid' => $qbankcontext->id],
-
 
828
            'name ASC'
-
 
829
        );
-
 
830
        // The categories and questions in the 3 deprecated contexts
-
 
831
        // all got moved to the new default qbank module instance on the new course.
-
 
832
        $this->assertCount(3, $bankcats);
-
 
833
        $expectedidentifiers = [
-
 
834
            'Default for Category 1',
-
 
835
            'Default for System',
-
 
836
            'Default for Test Course 1',
-
 
837
            'Default for Quiz',
-
 
838
        ];
-
 
839
        $i = 0;
-
 
840
 
-
 
841
        foreach ($bankcats as $bankcat) {
-
 
842
            $identifer = $expectedidentifiers[$i];
-
 
843
            $this->assertEquals($identifer, $bankcat->name);
-
 
844
            $bankcatqs = get_questions_category($bankcat, false);
-
 
845
            $this->assertCount(1, $bankcatqs);
-
 
846
            $bankcatq = reset($bankcatqs);
-
 
847
            $this->assertEquals($identifer, $bankcatq->name);
-
 
848
            $i++;
-
 
849
        }
-
 
850
 
-
 
851
        // The question category and question attached to the quiz got restored to its own context correctly.
-
 
852
        $newquizzes = $modinfo->get_instances_of('quiz');
-
 
853
        $this->assertCount(1, $newquizzes);
-
 
854
        $newquiz = reset($newquizzes);
-
 
855
        $newquizcontext = \context_module::instance($newquiz->id);
-
 
856
        $quizcats = $DB->get_records_select('question_categories',
-
 
857
            'parent <> 0 AND contextid = :contextid',
-
 
858
            ['contextid' => $newquizcontext->id]
-
 
859
        );
-
 
860
        $quizcat = reset($quizcats);
-
 
861
        $quizcatqs = get_questions_category($quizcat, false);
-
 
862
        $this->assertCount(1, $quizcatqs);
-
 
863
        $quizcatq = reset($quizcatqs);
-
 
864
        $this->assertEquals($expectedidentifiers[$i], $quizcatq->name);
-
 
865
    }
393
}
866
}