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
17defined('MOODLE_INTERNAL') || die();
18
19// Include all the needed stuff.
20global $CFG;
21require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
22require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
23
24
25/**
26 * Unit tests for how backup and restore handles role-related things.
27 *
28 * @package   core_backup
29 * @copyright 2021 The Open University
30 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31 */
32class roles_backup_restore_test extends advanced_testcase {
33
34    /**
35     * Create a course where the (non-editing) Teacher role is overridden
36     * to have 'moodle/user:loginas' and 'moodle/site:accessallgroups'.
37     *
38     * @return stdClass the new course.
39     */
40    protected function create_course_with_role_overrides(): stdClass {
41        $generator = $this->getDataGenerator();
42        $course = $generator->create_course();
43        $teacher = $generator->create_user();
44
45        $context = context_course::instance($course->id);
46        $generator->enrol_user($teacher->id, $course->id, 'teacher');
47
48        $editingteacherrole = $this->get_role('teacher');
49        role_change_permission($editingteacherrole->id, $context, 'moodle/user:loginas', CAP_ALLOW);
50        role_change_permission($editingteacherrole->id, $context, 'moodle/site:accessallgroups', CAP_ALLOW);
51
52        return $course;
53    }
54
55    /**
56     * Get the role id from a shortname.
57     *
58     * @param string $shortname the role shortname.
59     * @return stdClass the role from the DB.
60     */
61    protected function get_role(string $shortname): stdClass {
62        global $DB;
63        return $DB->get_record('role', ['shortname' => $shortname]);
64    }
65
66    /**
67     * Get an array capability => CAP_... constant for all the orverrides set for a given role on a given context.
68     *
69     * @param string $shortname role shortname.
70     * @param context $context context.
71     * @return array the overrides set here.
72     */
73    protected function get_overrides_for_role_on_context(string $shortname, context $context): array {
74        $overridedata = get_capabilities_from_role_on_context($this->get_role($shortname), $context);
75        $overrides = [];
76        foreach ($overridedata as $override) {
77            $overrides[$override->capability] = $override->permission;
78        }
79        return $overrides;
80    }
81
82    /**
83     * Makes a backup of the course.
84     *
85     * @param stdClass $course The course object.
86     * @return string Unique identifier for this backup.
87     */
88    protected function backup_course(\stdClass $course): string {
89        global $CFG, $USER;
90
91        // Turn off file logging, otherwise it can't delete the file (Windows).
92        $CFG->backup_file_logger_level = backup::LOG_NONE;
93
94        // Do backup with default settings. MODE_IMPORT means it will just
95        // create the directory and not zip it.
96        $bc = new \backup_controller(backup::TYPE_1COURSE, $course->id,
97                backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
98                $USER->id);
99        $backupid = $bc->get_backupid();
100        $bc->execute_plan();
101        $bc->destroy();
102
103        return $backupid;
104    }
105
106    /**
107     * Restores a backup that has been made earlier.
108     *
109     * @param string $backupid The unique identifier of the backup.
110     * @param string $asroleshortname Which role in the new cousre the restorer should have.
111     * @return int The new course id.
112     */
113    protected function restore_adding_to_course(string $backupid, string $asroleshortname): int {
114        global $CFG, $USER;
115
116        // Create course to restore into, and a user to do the restore.
117        $generator = $this->getDataGenerator();
118        $course = $generator->create_course();
119        $restorer = $generator->create_user();
120
121        $generator->enrol_user($restorer->id, $course->id, $asroleshortname);
122        $this->setUser($restorer);
123
124        // Turn off file logging, otherwise it can't delete the file (Windows).
125        $CFG->backup_file_logger_level = backup::LOG_NONE;
126
127        // Do restore to new course with default settings.
128        $rc = new \restore_controller($backupid, $course->id,
129                backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
130                backup::TARGET_CURRENT_ADDING);
131
132        $precheck = $rc->execute_precheck();
133        $this->assertTrue($precheck);
134        $rc->get_plan()->get_setting('role_assignments')->set_value(true);
135        $rc->execute_plan();
136        $rc->destroy();
137
138        return $course->id;
139    }
140
141    public function test_restore_role_overrides_as_manager(): void {
142        $this->resetAfterTest();
143        $this->setAdminUser();
144
145        // Create a course and back it up.
146        $course = $this->create_course_with_role_overrides();
147        $backupid = $this->backup_course($course);
148
149        // When manager restores, both role overrides should be restored.
150        $newcourseid = $this->restore_adding_to_course($backupid, 'manager');
151
152        // Verify.
153        $overrides = $this->get_overrides_for_role_on_context('teacher',
154                context_course::instance($newcourseid));
155        $this->assertArrayHasKey('moodle/user:loginas', $overrides);
156        $this->assertEquals(CAP_ALLOW, $overrides['moodle/user:loginas']);
157        $this->assertArrayHasKey('moodle/site:accessallgroups', $overrides);
158        $this->assertEquals(CAP_ALLOW, $overrides['moodle/site:accessallgroups']);
159    }
160
161    public function test_restore_role_overrides_as_teacher(): void {
162        $this->resetAfterTest();
163        $this->setAdminUser();
164
165        // Create a course and back it up.
166        $course = $this->create_course_with_role_overrides();
167        $backupid = $this->backup_course($course);
168
169        // When teacher restores, only the safe override should be restored.
170        $newcourseid = $this->restore_adding_to_course($backupid, 'editingteacher');
171
172        // Verify.
173        $overrides = $this->get_overrides_for_role_on_context('teacher',
174                context_course::instance($newcourseid));
175        $this->assertArrayNotHasKey('moodle/user:loginas', $overrides);
176        $this->assertArrayHasKey('moodle/site:accessallgroups', $overrides);
177        $this->assertEquals(CAP_ALLOW, $overrides['moodle/site:accessallgroups']);
178    }
179}
180