Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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
namespace mod_quiz\backup;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
use mod_quiz\quiz_settings;
22
use mod_quiz\structure;
23
 
24
global $CFG;
25
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
26
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
27
require_once($CFG->dirroot . '/question/engine/lib.php');
28
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
29
require_once($CFG->dirroot . '/course/lib.php');
30
require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php');
31
 
32
/**
33
 * Unit tests ensuring only required questions are included in backups.
34
 *
35
 * @package   mod_quiz
36
 * @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}
37
 * @author    Mark Johnson <mark.johnson@catalyst-eu.net>
38
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 * @covers \backup_questions_structure_step
40
 * @covers \backup_question_dbops
41
 * @covers \backup_quiz_activity_structure_step
42
 */
43
final class backup_question_selection_test extends \advanced_testcase {
44
    use \quiz_question_helper_test_trait;
45
 
46
    /**
47
     * Set up data to back up.
48
     *
49
     * A course contains a quiz and a qbank, and a second course contains a shared qbank.
50
     * Each of these contains a some categories and some questions.
51
     * The quiz uses 2 questions from its own question bank, plus 1 from the course qbank, 1 from the shared qbank,
52
     * and a random question selecting questions from a separate category in the shared qbank.
53
     * A user manages both courses.
54
     *
55
     * @return array The manager, quiz, questions and course records.
56
     */
57
    protected function create_quiz_and_questions() {
58
        $manager = $this->getDataGenerator()->create_user();
59
        $this->setUser($manager);
60
        $course = $this->getDataGenerator()->create_course();
61
        $sharedcourse = $this->getDataGenerator()->create_course();
62
        $this->getDataGenerator()->enrol_user($manager->id, $course->id, 'manager');
63
        $this->getDataGenerator()->enrol_user($manager->id, $sharedcourse->id, 'manager');
64
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
65
        // Create some question banks and a quiz with 2 categories each.
66
 
67
        $courseqbank = self::getDataGenerator()->create_module('qbank', ['course' => $course->id]);
68
        $coursequestions = $questiongenerator->create_categories_and_questions(
69
            \context_module::instance($courseqbank->cmid),
70
            [
71
                'courseparentcat' => [
72
                    'courseq1' => 'shortanswer',
73
                    'courseq2' => 'shortanswer',
74
                    'coursechildcat' => [
75
                        'courseq3' => 'shortanswer',
76
                        'courseq4' => 'shortanswer',
77
                    ],
78
                ],
79
            ]
80
        );
81
        $sharedqbank = self::getDataGenerator()->create_module('qbank', ['course' => $sharedcourse->id]);
82
        $sharedquestions = $questiongenerator->create_categories_and_questions(
83
            \context_module::instance($sharedqbank->cmid),
84
            [
85
                'sharedparentcat' => [
86
                    'sharedq1' => 'shortanswer',
87
                    'sharedq2' => 'shortanswer',
88
                    'sharedchildcat' => [
89
                        'sharedq3' => 'shortanswer',
90
                        'sharedq4' => 'shortanswer',
91
                    ],
92
                ],
93
                'tagcat' => [
94
                    'tagq1' => 'shortanswer',
95
                    'tagq2' => 'shortanswer',
96
                    'tagq3' => 'shortanswer',
97
                ],
98
            ]
99
        );
100
        $quiz = $this->create_test_quiz($course);
101
        $quizquestions = $questiongenerator->create_categories_and_questions(
102
            \context_module::instance($quiz->cmid),
103
            [
104
                'quizparentcat' => [
105
                    'quizq1' => 'shortanswer',
106
                    'quizq2' => 'shortanswer',
107
                    'quizchildcat' => [
108
                        'quizq3' => 'shortanswer',
109
                        'quizq4' => 'shortanswer',
110
                    ],
111
                ],
112
 
113
            ]
114
        );
115
 
116
        $questiongenerator->create_question_tag(['questionid' => $sharedquestions['tagcat']['tagq1']->id, 'tag' => 'mytag']);
117
        $questiongenerator->create_question_tag(['questionid' => $sharedquestions['tagcat']['tagq2']->id, 'tag' => 'mytag']);
118
 
119
        $tags = \core_tag_tag::get_item_tags('core_question', 'question', $sharedquestions['tagcat']['tagq1']->id);
120
        $mytag = reset($tags);
121
 
122
        // Add a question from the shared bank child category.
123
        quiz_add_quiz_question($sharedquestions['sharedparentcat']['sharedchildcat']['sharedq3']->id, $quiz);
124
        // Add a question from the course bank parent category.
125
        quiz_add_quiz_question($coursequestions['courseparentcat']['courseq2']->id, $quiz);
126
        // Add a question from the quiz bank categories.
127
        quiz_add_quiz_question($quizquestions['quizparentcat']['quizq1']->id, $quiz);
128
        // Add a random question to select tagged questions.
129
        $settings = quiz_settings::create($quiz->id);
130
        $structure = structure::create_for_quiz($settings);
131
        $structure->add_random_questions(1, 1, [
132
            'filter' => [
133
                'category' => [
134
                    'jointype' => \core\output\datafilter::JOINTYPE_ANY,
135
                    'values' => [$sharedquestions['tagcat']['tagq1']->category],
136
                    'filteroptions' => ['includesubcategories' => false],
137
                ],
138
                'qtagids' => [
139
                    'jointype' => \core\output\datafilter::JOINTYPE_ANY,
140
                    'values' => [$mytag->id],
141
                ],
142
            ],
143
        ]);
144
 
145
        return [
146
            $manager,
147
            $quiz,
148
            $quizquestions,
149
            $coursequestions,
150
            $sharedquestions,
151
            $course,
152
        ];
153
    }
154
 
155
    /**
156
     * Test that backing up a quiz only includes the questions owned or used by the quiz.
157
     */
158
    public function test_quiz_backup_excludes_unused_questions(): void {
159
        global $DB;
160
        $this->resetAfterTest();
161
 
162
        [
163
            $manager,
164
            $quiz,
165
            $quizquestions,
166
            $coursequestions,
167
            $sharedquestions,
168
        ] = $this->create_quiz_and_questions();
169
 
170
        // Backup the quiz.
171
        $bc = new \backup_controller(
172
            \backup::TYPE_1ACTIVITY,
173
            $quiz->cmid,
174
            \backup::FORMAT_MOODLE,
175
            \backup::INTERACTIVE_NO,
176
            \backup::MODE_IMPORT,
177
            $manager->id,
178
        );
179
        $backupid = $bc->get_backupid();
180
        $bc->execute_plan();
181
        $bc->destroy();
182
 
183
        $course2 = $this->getDataGenerator()->create_course();
184
        $this->getDataGenerator()->enrol_user($manager->id, $course2->id, 'manager');
185
        $rc = new \restore_controller($backupid, $course2->id, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT,
186
            $manager->id, \backup::TARGET_CURRENT_ADDING);
187
        $rc->execute_precheck();
188
        $backupquestions = $DB->get_records_menu('backup_ids_temp', ['itemname' => 'question'], '', 'id, itemid');
189
        // Backup should contain used questions from shared qbanks.
190
        $this->assertContains((string) $sharedquestions['sharedparentcat']['sharedchildcat']['sharedq3']->id, $backupquestions);
191
        $this->assertContains((string) $coursequestions['courseparentcat']['courseq2']->id, $backupquestions);
192
        // Backup should contain all questions from quiz's bank.
193
        $this->assertContains((string) $quizquestions['quizparentcat']['quizq1']->id, $backupquestions);
194
        $this->assertContains((string) $quizquestions['quizparentcat']['quizq2']->id, $backupquestions);
195
        $this->assertContains((string) $quizquestions['quizparentcat']['quizchildcat']['quizq3']->id, $backupquestions);
196
        $this->assertContains((string) $quizquestions['quizparentcat']['quizchildcat']['quizq4']->id, $backupquestions);
197
        // Backup should contain questions matched by random question filter.
198
        $this->assertContains((string) $sharedquestions['tagcat']['tagq1']->id, $backupquestions);
199
        $this->assertContains((string) $sharedquestions['tagcat']['tagq2']->id, $backupquestions);
200
        // All other questions should be excluded.
201
        $this->assertNotContains((string) $sharedquestions['sharedparentcat']['sharedq1']->id, $backupquestions);
202
        $this->assertNotContains((string) $sharedquestions['sharedparentcat']['sharedq2']->id, $backupquestions);
203
        $this->assertNotContains((string) $sharedquestions['sharedparentcat']['sharedchildcat']['sharedq4']->id, $backupquestions);
204
        $this->assertNotContains((string) $coursequestions['courseparentcat']['courseq1']->id, $backupquestions);
205
        $this->assertNotContains((string) $coursequestions['courseparentcat']['coursechildcat']['courseq3']->id, $backupquestions);
206
        $this->assertNotContains((string) $coursequestions['courseparentcat']['coursechildcat']['courseq4']->id, $backupquestions);
207
        $this->assertNotContains((string) $sharedquestions['tagcat']['tagq3']->id, $backupquestions);
208
        $this->assertCount(8, $backupquestions);
209
        // Clean up.
210
        $rc->execute_plan();
211
        $rc->destroy();
212
    }
213
 
214
    /**
215
     * Test that backing up a quiz only includes the questions used in or belonging to the course.
216
     *
217
     * This should include all questions in categories belonging to quizzes or qbanks on the course, plus questions from outside
218
     * the course used by quizzes.
219
     */
220
    public function test_course_backup_excludes_unused_questions(): void {
221
        global $DB;
222
        $this->resetAfterTest();
223
 
224
        [
225
            $manager,
226
            ,
227
            $quizquestions,
228
            $coursequestions,
229
            $sharedquestions,
230
            $course,
231
        ] = $this->create_quiz_and_questions();
232
 
233
        // Backup the course.
234
        $bc = new \backup_controller(
235
            \backup::TYPE_1COURSE,
236
            $course->id,
237
            \backup::FORMAT_MOODLE,
238
            \backup::INTERACTIVE_NO,
239
            \backup::MODE_IMPORT,
240
            $manager->id,
241
        );
242
        $backupid = $bc->get_backupid();
243
        $bc->execute_plan();
244
        $bc->destroy();
245
 
246
        $course2 = $this->getDataGenerator()->create_course();
247
        $this->getDataGenerator()->enrol_user($manager->id, $course2->id, 'manager');
248
        $rc = new \restore_controller($backupid, $course2->id, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT,
249
            $manager->id, \backup::TARGET_CURRENT_ADDING);
250
        $rc->execute_precheck();
251
        $backupquestions = $DB->get_records_menu('backup_ids_temp', ['itemname' => 'question'], '', 'id, itemid');
252
        // Backup should contain used questions from shared qbanks.
253
        $this->assertContains((string) $sharedquestions['sharedparentcat']['sharedchildcat']['sharedq3']->id, $backupquestions);
254
        // Backup should contain all questions from course qbanks.
255
        $this->assertContains((string) $coursequestions['courseparentcat']['courseq1']->id, $backupquestions);
256
        $this->assertContains((string) $coursequestions['courseparentcat']['courseq2']->id, $backupquestions);
257
        $this->assertContains((string) $coursequestions['courseparentcat']['coursechildcat']['courseq3']->id, $backupquestions);
258
        $this->assertContains((string) $coursequestions['courseparentcat']['coursechildcat']['courseq4']->id, $backupquestions);
259
        // Backup should contain all questions from quiz's bank.
260
        $this->assertContains((string) $quizquestions['quizparentcat']['quizq1']->id, $backupquestions);
261
        $this->assertContains((string) $quizquestions['quizparentcat']['quizq2']->id, $backupquestions);
262
        $this->assertContains((string) $quizquestions['quizparentcat']['quizchildcat']['quizq3']->id, $backupquestions);
263
        $this->assertContains((string) $quizquestions['quizparentcat']['quizchildcat']['quizq4']->id, $backupquestions);
264
        // Backup should contain questions matched by random question filter.
265
        $this->assertContains((string) $sharedquestions['tagcat']['tagq1']->id, $backupquestions);
266
        $this->assertContains((string) $sharedquestions['tagcat']['tagq2']->id, $backupquestions);
267
        // All other questions should be excluded.
268
        $this->assertNotContains((string) $sharedquestions['sharedparentcat']['sharedq1']->id, $backupquestions);
269
        $this->assertNotContains((string) $sharedquestions['sharedparentcat']['sharedq2']->id, $backupquestions);
270
        $this->assertNotContains((string) $sharedquestions['sharedparentcat']['sharedchildcat']['sharedq4']->id, $backupquestions);
271
        $this->assertNotContains((string) $sharedquestions['tagcat']['tagq3']->id, $backupquestions);
272
        $this->assertCount(11, $backupquestions);
273
        // Clean up.
274
        $rc->execute_plan();
275
        $rc->destroy();
276
    }
277
}
278