Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
namespace core_question;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
global $CFG;
22
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
23
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
24
 
25
/**
26
 * Class core_question_backup_testcase
27
 *
28
 * @package    core_question
29
 * @category   test
30
 * @copyright  2018 Shamim Rezaie <shamim@moodle.com>
31
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32
 */
33
class backup_test extends \advanced_testcase {
34
 
35
    /**
36
     * Makes a backup of the course.
37
     *
38
     * @param \stdClass $course The course object.
39
     * @return string Unique identifier for this backup.
40
     */
41
    protected function backup_course($course) {
42
        global $CFG, $USER;
43
 
44
        // Turn off file logging, otherwise it can't delete the file (Windows).
45
        $CFG->backup_file_logger_level = \backup::LOG_NONE;
46
 
47
        // Do backup with default settings. MODE_IMPORT means it will just
48
        // create the directory and not zip it.
49
        $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id,
50
                \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT,
51
                $USER->id);
52
        $backupid = $bc->get_backupid();
53
        $bc->execute_plan();
54
        $bc->destroy();
55
 
56
        return $backupid;
57
    }
58
 
59
    /**
60
     * 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.
64
     * @param string $shortname Short name of the new course that is going to be created.
65
     * @param int $categoryid The course category the backup is going to be restored in.
66
     * @param string[] $expectedprecheckwarning
67
     * @return int The new course id.
68
     */
69
    protected function restore_course($backupid, $fullname, $shortname, $categoryid, $expectedprecheckwarning = []) {
70
        global $CFG, $USER;
71
 
72
        // Turn off file logging, otherwise it can't delete the file (Windows).
73
        $CFG->backup_file_logger_level = \backup::LOG_NONE;
74
 
75
        // Do restore to new course with default settings.
76
        $newcourseid = \restore_dbops::create_new_course($fullname, $shortname, $categoryid);
77
        $rc = new \restore_controller($backupid, $newcourseid,
78
                \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
79
                \backup::TARGET_NEW_COURSE);
80
 
81
        $precheck = $rc->execute_precheck();
82
        if (!$expectedprecheckwarning) {
83
            $this->assertTrue($precheck);
84
        } else {
85
            $precheckresults = $rc->get_precheck_results();
86
            $this->assertEqualsCanonicalizing($expectedprecheckwarning, $precheckresults['warnings']);
87
            $this->assertCount(1, $precheckresults);
88
        }
89
        $rc->execute_plan();
90
        $rc->destroy();
91
 
92
        return $newcourseid;
93
    }
94
 
95
    /**
96
     * This function tests backup and restore of question tags and course level question tags.
97
     */
11 efrain 98
    public function test_backup_question_tags(): void {
1 efrain 99
        global $DB;
100
 
101
        $this->resetAfterTest();
102
        $this->setAdminUser();
103
 
104
        // Create a new course category and and a new course in that.
105
        $category1 = $this->getDataGenerator()->create_category();
106
        $course = $this->getDataGenerator()->create_course(['category' => $category1->id]);
107
        $courseshortname = $course->shortname;
108
        $coursefullname = $course->fullname;
109
 
110
        // Create 2 questions.
111
        $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
112
        $context = \context_coursecat::instance($category1->id);
113
        $qcat = $qgen->create_question_category(['contextid' => $context->id]);
114
        $question1 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q1']);
115
        $question2 = $qgen->create_question('shortanswer', null, ['category' => $qcat->id, 'idnumber' => 'q2']);
116
 
117
        // Tag the questions with 2 question tags and 2 course level question tags.
118
        $qcontext = \context::instance_by_id($qcat->contextid);
119
        $coursecontext = \context_course::instance($course->id);
120
        \core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $qcontext, ['qtag1', 'qtag2']);
121
        \core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $qcontext, ['qtag3', 'qtag4']);
122
        \core_tag_tag::set_item_tags('core_question', 'question', $question1->id, $coursecontext, ['ctag1', 'ctag2']);
123
        \core_tag_tag::set_item_tags('core_question', 'question', $question2->id, $coursecontext, ['ctag3', 'ctag4']);
124
 
125
        // Create a quiz and add one of the questions to that.
126
        $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
127
        quiz_add_quiz_question($question1->id, $quiz);
128
 
129
        // Backup the course twice for future use.
130
        $backupid1 = $this->backup_course($course);
131
        $backupid2 = $this->backup_course($course);
132
 
133
        // Now delete almost everything.
134
        delete_course($course, false);
135
        question_delete_question($question1->id);
136
        question_delete_question($question2->id);
137
 
138
        // Restore the backup we had made earlier into a new course.
139
        $courseid2 = $this->restore_course($backupid1, $coursefullname, $courseshortname . '_2', $category1->id);
140
 
141
        // The questions should remain in the question category they were which is
142
        // a question category belonging to a course category context.
143
        $sql = 'SELECT q.*,
144
                       qbe.idnumber
145
                  FROM {question} q
146
                  JOIN {question_versions} qv ON qv.questionid = q.id
147
                  JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
148
                 WHERE qbe.questioncategoryid = ?
149
                 ORDER BY qbe.idnumber';
150
        $questions = $DB->get_records_sql($sql, [$qcat->id]);
151
        $this->assertCount(2, $questions);
152
 
153
        // Retrieve tags for each question and check if they are assigned at the right context.
154
        $qcount = 1;
155
        foreach ($questions as $question) {
156
            $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'])) {
163
                    $expected = \context_course::instance($courseid2)->id;
164
                } else if (in_array($tag->name, ['qtag1', 'qtag2', 'qtag3', 'qtag4'])) {
165
                    $expected = $qcontext->id;
166
                }
167
                $this->assertEquals($expected, $tag->taginstancecontextid);
168
            }
169
 
170
            // Also check idnumbers have been backed up and restored.
171
            $this->assertEquals('q' . $qcount, $question->idnumber);
172
            $qcount++;
173
        }
174
 
175
        // Now, again, delete everything including the course category.
176
        delete_course($courseid2, false);
177
        foreach ($questions as $question) {
178
            question_delete_question($question->id);
179
        }
180
        $category1->delete_full(false);
181
 
182
        // Create a new course category to restore the backup file into it.
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
        ];
189
 
190
        // Restore to a new course in the new course category.
191
        $courseid3 = $this->restore_course($backupid2, $coursefullname, $courseshortname . '_3', $category2->id, $expectedwarnings);
192
        $coursecontext3 = \context_course::instance($courseid3);
193
 
194
        // The questions should have been moved to a question category that belongs to a course context.
195
        $questions = $DB->get_records_sql("SELECT q.*
196
                                                FROM {question} q
197
                                                JOIN {question_versions} qv ON qv.questionid = q.id
198
                                                JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
199
                                                JOIN {question_categories} qc ON qc.id = qbe.questioncategoryid
200
                                               WHERE qc.contextid = ?", [$coursecontext3->id]);
201
        $this->assertCount(2, $questions);
202
 
203
        // Now, retrieve tags for each question and check if they are assigned at the right context.
204
        foreach ($questions as $question) {
205
            $tags = \core_tag_tag::get_item_tags('core_question', 'question', $question->id);
206
 
207
            // Each question is tagged with 4 tags (all are course tags now).
208
            $this->assertCount(4, $tags);
209
 
210
            foreach ($tags as $tag) {
211
                $this->assertEquals($coursecontext3->id, $tag->taginstancecontextid);
212
            }
213
        }
214
 
215
    }
216
 
217
    /**
218
     * Test that the question author is retained when they are enrolled in to the course.
219
     */
11 efrain 220
    public function test_backup_question_author_retained_when_enrolled(): void {
1 efrain 221
        global $DB, $USER, $CFG;
222
        $this->resetAfterTest();
223
        $this->setAdminUser();
224
 
225
        // Create a course, a category and a user.
226
        $course = $this->getDataGenerator()->create_course();
227
        $category = $this->getDataGenerator()->create_category();
228
        $user = $this->getDataGenerator()->create_user();
229
 
230
        // Create a question.
231
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
232
        $questioncategory = $questiongenerator->create_question_category();
233
        $overrides = ['name' => 'Test question', 'category' => $questioncategory->id,
234
                'createdby' => $user->id, 'modifiedby' => $user->id];
235
        $question = $questiongenerator->create_question('truefalse', null, $overrides);
236
 
237
        // Create a quiz and a questions.
238
        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
239
        quiz_add_quiz_question($question->id, $quiz);
240
 
241
        // Enrol user with a teacher role.
242
        $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher']);
243
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id, 'manual');
244
 
245
        // Backup the course.
246
        $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
247
            \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id);
248
        $backupid = $bc->get_backupid();
249
        $bc->execute_plan();
250
        $results = $bc->get_results();
251
        $file = $results['backup_destination'];
252
        $fp = get_file_packer('application/vnd.moodle.backup');
253
        $filepath = $CFG->dataroot . '/temp/backup/' . $backupid;
254
        $file->extract_to_pathname($fp, $filepath);
255
        $bc->destroy();
256
 
257
        // Delete the original course and related question.
258
        delete_course($course, false);
259
        question_delete_question($question->id);
260
 
261
        // Restore the course.
262
        $restoredcourseid = \restore_dbops::create_new_course($course->fullname, $course->shortname . '_1', $category->id);
263
        $rc = new \restore_controller($backupid, $restoredcourseid, \backup::INTERACTIVE_NO,
264
            \backup::MODE_GENERAL, $USER->id, \backup::TARGET_NEW_COURSE);
265
        $rc->execute_precheck();
266
        $rc->execute_plan();
267
        $rc->destroy();
268
 
269
        // Test the question author.
270
        $questions = $DB->get_records('question', ['name' => 'Test question']);
271
        $this->assertCount(1, $questions);
272
        $question3 = array_shift($questions);
273
        $this->assertEquals($user->id, $question3->createdby);
274
        $this->assertEquals($user->id, $question3->modifiedby);
275
    }
276
 
277
    /**
278
     * Test that the question author is retained when they are not enrolled in to the course,
279
     * but we are restoring the backup at the same site.
280
     */
11 efrain 281
    public function test_backup_question_author_retained_when_not_enrolled(): void {
1 efrain 282
        global $DB, $USER, $CFG;
283
        $this->resetAfterTest();
284
        $this->setAdminUser();
285
 
286
        // Create a course, a category and a user.
287
        $course = $this->getDataGenerator()->create_course();
288
        $category = $this->getDataGenerator()->create_category();
289
        $user = $this->getDataGenerator()->create_user();
290
 
291
        // Create a question.
292
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
293
        $questioncategory = $questiongenerator->create_question_category();
294
        $overrides = ['name' => 'Test question', 'category' => $questioncategory->id,
295
                'createdby' => $user->id, 'modifiedby' => $user->id];
296
        $question = $questiongenerator->create_question('truefalse', null, $overrides);
297
 
298
        // Create a quiz and a questions.
299
        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
300
        quiz_add_quiz_question($question->id, $quiz);
301
 
302
        // Backup the course.
303
        $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
304
            \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id);
305
        $backupid = $bc->get_backupid();
306
        $bc->execute_plan();
307
        $results = $bc->get_results();
308
        $file = $results['backup_destination'];
309
        $fp = get_file_packer('application/vnd.moodle.backup');
310
        $filepath = $CFG->dataroot . '/temp/backup/' . $backupid;
311
        $file->extract_to_pathname($fp, $filepath);
312
        $bc->destroy();
313
 
314
        // Delete the original course and related question.
315
        delete_course($course, false);
316
        question_delete_question($question->id);
317
 
318
        // Restore the course.
319
        $restoredcourseid = \restore_dbops::create_new_course($course->fullname, $course->shortname . '_1', $category->id);
320
        $rc = new \restore_controller($backupid, $restoredcourseid, \backup::INTERACTIVE_NO,
321
            \backup::MODE_GENERAL, $USER->id, \backup::TARGET_NEW_COURSE);
322
        $rc->execute_precheck();
323
        $rc->execute_plan();
324
        $rc->destroy();
325
 
326
        // Test the question author.
327
        $questions = $DB->get_records('question', ['name' => 'Test question']);
328
        $this->assertCount(1, $questions);
329
        $question = array_shift($questions);
330
        $this->assertEquals($user->id, $question->createdby);
331
        $this->assertEquals($user->id, $question->modifiedby);
332
    }
333
 
334
    /**
335
     * Test that the current user is set as a question author when we are restoring the backup
336
     * at the another site and the question author is not enrolled in to the course.
337
     */
11 efrain 338
    public function test_backup_question_author_reset(): void {
1 efrain 339
        global $DB, $USER, $CFG;
340
        $this->resetAfterTest();
341
        $this->setAdminUser();
342
 
343
        // Create a course, a category and a user.
344
        $course = $this->getDataGenerator()->create_course();
345
        $category = $this->getDataGenerator()->create_category();
346
        $user = $this->getDataGenerator()->create_user();
347
 
348
        // Create a question.
349
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
350
        $questioncategory = $questiongenerator->create_question_category();
351
        $overrides = ['name' => 'Test question', 'category' => $questioncategory->id,
352
                'createdby' => $user->id, 'modifiedby' => $user->id];
353
        $question = $questiongenerator->create_question('truefalse', null, $overrides);
354
 
355
        // Create a quiz and a questions.
356
        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
357
        quiz_add_quiz_question($question->id, $quiz);
358
 
359
        // Backup the course.
360
        $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id, \backup::FORMAT_MOODLE,
361
            \backup::INTERACTIVE_NO, \backup::MODE_SAMESITE, $USER->id);
362
        $backupid = $bc->get_backupid();
363
        $bc->execute_plan();
364
        $results = $bc->get_results();
365
        $file = $results['backup_destination'];
366
        $fp = get_file_packer('application/vnd.moodle.backup');
367
        $filepath = $CFG->dataroot . '/temp/backup/' . $backupid;
368
        $file->extract_to_pathname($fp, $filepath);
369
        $bc->destroy();
370
 
371
        // Delete the original course and related question.
372
        delete_course($course, false);
373
        question_delete_question($question->id);
374
 
375
        // Emulate restoring to a different site.
376
        set_config('siteidentifier', random_string(32) . 'not the same site');
377
 
378
        // Restore the course.
379
        $restoredcourseid = \restore_dbops::create_new_course($course->fullname, $course->shortname . '_1', $category->id);
380
        $rc = new \restore_controller($backupid, $restoredcourseid, \backup::INTERACTIVE_NO,
381
            \backup::MODE_SAMESITE, $USER->id, \backup::TARGET_NEW_COURSE);
382
        $rc->execute_precheck();
383
        $rc->execute_plan();
384
        $rc->destroy();
385
 
386
        // Test the question author.
387
        $questions = $DB->get_records('question', ['name' => 'Test question']);
388
        $this->assertCount(1, $questions);
389
        $question = array_shift($questions);
390
        $this->assertEquals($USER->id, $question->createdby);
391
        $this->assertEquals($USER->id, $question->modifiedby);
392
    }
393
}