Merge branch 'MDL-88061-main' of https://github.com/aanabit/moodle
This commit is contained in:
@@ -66,6 +66,24 @@ class restricted_module {
|
||||
$format = course_get_format($course);
|
||||
$course->format = $format->get_format();
|
||||
|
||||
// Confirm the module has a view page. Redirect to section page otherwise.
|
||||
if (!$cmurl = $cminfo->get_url()) {
|
||||
$cmurl = $format->get_view_url($sectioninfo->section, ['navigation' => true]);
|
||||
$cmurl->set_anchor('module-' . $cmdata->id);
|
||||
return $this->redirect($response, $cmurl);
|
||||
}
|
||||
|
||||
// Confirm the module is actually restricted. Redirect to module page otherwise.
|
||||
if ($cminfo->get_user_visible()) {
|
||||
return $this->redirect($response, $cmurl);
|
||||
}
|
||||
|
||||
// Confirm the module is not stealth and restrictions are visible.
|
||||
// Redirect to module page to require_login() otherwise.
|
||||
if (!$cminfo->is_visible_on_course_page()) {
|
||||
return $this->redirect($response, $cmurl);
|
||||
}
|
||||
|
||||
$url = \core\router\util::get_path_for_callable(
|
||||
[self::class, 'restricted_module_page'],
|
||||
['cm' => $cmdata->id],
|
||||
@@ -89,9 +107,13 @@ class restricted_module {
|
||||
);
|
||||
$renderer = $format->get_renderer($PAGE);
|
||||
|
||||
$response->getBody()->write($OUTPUT->header());
|
||||
if ($header = $OUTPUT->header()) {
|
||||
$response->getBody()->write($header);
|
||||
}
|
||||
$response->getBody()->write($renderer->render($cmoutput));
|
||||
$response->getBody()->write($OUTPUT->footer());
|
||||
if ($footer = $OUTPUT->footer()) {
|
||||
$response->getBody()->write($footer);
|
||||
}
|
||||
|
||||
$eventdata = [
|
||||
'objectid' => $cmdata->id,
|
||||
|
||||
@@ -59,6 +59,13 @@ class restricted_section {
|
||||
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
|
||||
// Confirm the section is actually restricted. Redirect to section page otherwise.
|
||||
$modinfo = get_fast_modinfo($section->course);
|
||||
$sectioninfo = $modinfo->get_section_info($section->section, MUST_EXIST);
|
||||
if ($sectioninfo->uservisible) {
|
||||
$url = course_get_url($section->course, $section->section, ['navigation' => true]);
|
||||
return $this->redirect($response, $url);
|
||||
}
|
||||
$course = get_course($section->course);
|
||||
$context = \context_course::instance($course->id);
|
||||
$format = course_get_format($course->id);
|
||||
@@ -81,9 +88,13 @@ class restricted_section {
|
||||
$PAGE->set_heading($sectiontitle);
|
||||
$PAGE->set_secondary_navigation(false);
|
||||
$renderer = $format->get_renderer($PAGE);
|
||||
$response->getBody()->write($OUTPUT->header());
|
||||
if ($header = $OUTPUT->header()) {
|
||||
$response->getBody()->write($header);
|
||||
}
|
||||
$response->getBody()->write($renderer->render($sectionoutput));
|
||||
$response->getBody()->write($OUTPUT->footer());
|
||||
if ($footer = $OUTPUT->footer()) {
|
||||
$response->getBody()->write($footer);
|
||||
}
|
||||
|
||||
// Trigger section viewed event.
|
||||
course_section_view($context, $section->id, true);
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_course\route\controller;
|
||||
|
||||
use core\router\route_loader_interface;
|
||||
use core\tests\router\route_testcase;
|
||||
use core\url;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
/**
|
||||
* Restricted module controller tests.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2026 Amaia Anabitarte <[email protected]>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
**/
|
||||
#[CoversClass(\core_course\route\controller\restricted_module::class)]
|
||||
final class restricted_module_test extends route_testcase {
|
||||
/**
|
||||
* Test the restricted module in different conditions.
|
||||
*
|
||||
* @param string $role
|
||||
* @param string $modulename
|
||||
* @param int $timedifference Positive number for not filled requirement,
|
||||
* and negative for filled requirement.
|
||||
* @param bool $showrestriction Whether the restriction should be available or not.
|
||||
* @param bool $visible 'visible' value for hidden modules.
|
||||
* @param bool $visibleoncourse 'visibleoncoursepage' value for stealth modules.
|
||||
* @param int $expectedstatus Expected response status code.
|
||||
* @param string $redirection Expected redirection URL.
|
||||
*/
|
||||
#[DataProvider('restricted_module_provider')]
|
||||
public function test_restricted_module(
|
||||
string $role = 'student',
|
||||
string $modulename = 'page',
|
||||
int $timedifference = 3600,
|
||||
bool $showrestriction = true,
|
||||
bool $visible = true,
|
||||
bool $visibleoncourse = true,
|
||||
int $expectedstatus = 200,
|
||||
string $redirection = 'empty',
|
||||
): void {
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
|
||||
$restriction = json_encode(\core_availability\tree::get_root_json(
|
||||
[
|
||||
\availability_date\condition::get_json(
|
||||
\availability_date\condition::DIRECTION_FROM,
|
||||
time() + $timedifference,
|
||||
),
|
||||
],
|
||||
'&',
|
||||
$showrestriction,
|
||||
));
|
||||
$module = $this->getDataGenerator()->create_module($modulename, [
|
||||
'course' => $course->id,
|
||||
'visible' => $visible,
|
||||
'visibleoncourse' => $visibleoncourse,
|
||||
'availability' => $restriction,
|
||||
]);
|
||||
|
||||
$user = $generator->create_and_enrol($course, $role);
|
||||
$this->setUser($user);
|
||||
|
||||
$response = $this->process_request(
|
||||
'GET',
|
||||
'course/cms/' . $module->cmid . '/restricted',
|
||||
route_loader_interface::ROUTE_GROUP_PAGE
|
||||
);
|
||||
|
||||
$this->assert_valid_response($response, $expectedstatus);
|
||||
$location = $response->getHeader('Location');
|
||||
switch ($redirection) {
|
||||
case 'empty':
|
||||
$this->assertEmpty($location, 'There is no redirection.');
|
||||
break;
|
||||
case 'module':
|
||||
$this->assertNotEmpty($location[0]);
|
||||
$this->assertEquals(
|
||||
new url("/mod/{$modulename}/view.php", ['id' => $module->cmid]),
|
||||
new url($location[0])
|
||||
);
|
||||
break;
|
||||
case 'section':
|
||||
$this->assertNotEmpty($location[0]);
|
||||
$this->assertStringContainsString('course/section.php', $location[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_restricted_module.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public static function restricted_module_provider(): \Generator {
|
||||
yield 'Teacher skipping restrictions' => [
|
||||
'role' => 'teacher',
|
||||
'modulename' => 'page',
|
||||
'timedifference' => 3600,
|
||||
'showrestriction' => true,
|
||||
'expectedstatus' => 302,
|
||||
'redirection' => 'module',
|
||||
];
|
||||
yield 'Applied restriction (student)' => [
|
||||
'role' => 'student',
|
||||
'modulename' => 'page',
|
||||
'timedifference' => 3600,
|
||||
'showrestriction' => true,
|
||||
'expectedstatus' => 200,
|
||||
'redirection' => 'empty',
|
||||
];
|
||||
yield 'Not applied restriction (student)' => [
|
||||
'role' => 'student',
|
||||
'modulename' => 'page',
|
||||
'timedifference' => -3600,
|
||||
'showrestriction' => true,
|
||||
'expectedstatus' => 302,
|
||||
'redirection' => 'module',
|
||||
];
|
||||
yield 'Modules with no url - Text and media (student)' => [
|
||||
// Redirect to section page and scroll to the module.
|
||||
'role' => 'student',
|
||||
'modulename' => 'label',
|
||||
'timedifference' => 3600,
|
||||
'showrestriction' => true,
|
||||
'expectedstatus' => 302,
|
||||
'redirection' => 'section',
|
||||
];
|
||||
yield 'Stealth restricted activities (student)' => [
|
||||
// Show restricted page for stealth restricted pages.
|
||||
'role' => 'student',
|
||||
'modulename' => 'page',
|
||||
'timedifference' => 3600,
|
||||
'showrestriction' => true,
|
||||
'visibleoncourse' => false,
|
||||
'expectedstatus' => 200,
|
||||
'redirection' => 'empty',
|
||||
];
|
||||
yield 'Hidden restricted activities (student)' => [
|
||||
// Redirect to view page of hidden modules to let
|
||||
// view.php and require_login() to take care of them.
|
||||
'role' => 'student',
|
||||
'modulename' => 'page',
|
||||
'timedifference' => 3600,
|
||||
'showrestriction' => true,
|
||||
'visible' => false,
|
||||
'visibleoncourse' => false,
|
||||
'expectedstatus' => 302,
|
||||
'redirection' => 'module',
|
||||
];
|
||||
yield 'Hidden restrictions (student)' => [
|
||||
// Redirect to view page of hidden restriction modules to let
|
||||
// view.php and require_login() to take care of them.
|
||||
'role' => 'student',
|
||||
'modulename' => 'page',
|
||||
'timedifference' => 3600,
|
||||
'showrestriction' => false,
|
||||
'expectedstatus' => 302,
|
||||
'redirection' => 'module',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core_course\route\controller;
|
||||
|
||||
use core\router\route_loader_interface;
|
||||
use core\tests\router\route_testcase;
|
||||
use core\url;
|
||||
use core_courseformat\formatactions;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
/**
|
||||
* Restricted section controller tests.
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2026 Amaia Anabitarte <[email protected]>
|
||||
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
**/
|
||||
#[CoversClass(\core_course\route\controller\restricted_section::class)]
|
||||
final class restricted_section_test extends route_testcase {
|
||||
/**
|
||||
* Test the restricted section URL redirections for none restricted sections.
|
||||
*
|
||||
* The reason why the section is not restricted for the user
|
||||
* (whether the user has enough permission to see the section page or
|
||||
* whether the restrictions don't apply to the user) is not important.
|
||||
* So we are testing only the case where the uservisible is true
|
||||
* (because the user has enough permisssio) and uservisible is false
|
||||
* (because the restriction applies to a student with no enough permission)
|
||||
*
|
||||
* @param string $role
|
||||
* @param int $expectedstatus Expected response status code.
|
||||
* @param bool $redirection Whether redirection should happen or not.
|
||||
*/
|
||||
#[DataProvider('restricted_section_provider')]
|
||||
public function test_restricted_section(
|
||||
string $role,
|
||||
int $expectedstatus,
|
||||
bool $redirection,
|
||||
): void {
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course(['numsections' => 2]);
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
|
||||
$restriction = json_encode(\core_availability\tree::get_root_json(
|
||||
[
|
||||
\availability_date\condition::get_json(
|
||||
\availability_date\condition::DIRECTION_FROM,
|
||||
time() + 3600,
|
||||
),
|
||||
],
|
||||
'&',
|
||||
true,
|
||||
));
|
||||
|
||||
// Restrict Section 1.
|
||||
formatactions::section($course)->update(
|
||||
$modinfo->get_section_info(1),
|
||||
['availability' => $restriction],
|
||||
);
|
||||
|
||||
$user = $generator->create_and_enrol($course, $role);
|
||||
$this->setUser($user);
|
||||
$restrictedsection = $modinfo->get_section_info(1);
|
||||
$response = $this->process_request(
|
||||
'GET',
|
||||
'course/sections/' . $restrictedsection->id . '/restricted',
|
||||
route_loader_interface::ROUTE_GROUP_PAGE
|
||||
);
|
||||
|
||||
$this->assert_valid_response($response, $expectedstatus);
|
||||
$location = $response->getHeader('Location'); // Just to consume the header if any.
|
||||
if ($redirection) {
|
||||
$this->assertNotEmpty($location, 'The redirection header should be present.');
|
||||
$this->assertEquals(
|
||||
new url('/course/section.php', ['id' => $restrictedsection->id]),
|
||||
new url($location[0])
|
||||
);
|
||||
} else {
|
||||
$this->assertEmpty($location, 'There is no redirection.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_restricted_section.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public static function restricted_section_provider(): \Generator {
|
||||
yield 'Teacher - Permission to see restricted page' => [
|
||||
'role' => 'teacher',
|
||||
'expectedstatus' => 302,
|
||||
'redirection' => true,
|
||||
];
|
||||
yield 'Student - Stays in the restricted page' => [
|
||||
'role' => 'student',
|
||||
'expectedstatus' => 200,
|
||||
'redirection' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user