MDL-80190 core_courseformat: fix activity creation on delegated sections

The method for checking if the user can create an activity executed
create_if_missing wihtout checking the section exists or not. This is
a problem for delegated sections because create_if_missing will create a
regular section and push down all delegate ones. With the patch the
method first checks if the section exists.
This commit is contained in:
Ferran Recio 2024-01-26 11:08:31 +01:00
parent 97a7958aee
commit 15db7d9d98
3 changed files with 204 additions and 7 deletions

View File

@ -172,6 +172,10 @@ class sectionactions extends baseactions {
/**
* Create course sections if they are not created yet.
*
* The calculations will ignore sections delegated to components.
* If the section is created, all delegated sections will be pushed down.
*
* @param int[] $sectionnums the section numbers to create
* @return bool whether any section was created
*/

View File

@ -28,6 +28,7 @@
defined('MOODLE_INTERNAL') || die;
use \core_grades\component_gradeitems;
use core_courseformat\formatactions;
require_once($CFG->dirroot.'/course/lib.php');
@ -497,27 +498,33 @@ function set_moduleinfo_defaults($moduleinfo) {
* The fucntion create the course section if it doesn't exist.
*
* @param object $course the course of the module
* @param object $modulename the module name
* @param object $section the section of the module
* @param string $modulename the module name
* @param int $sectionnum the section of the module
* @return array list containing module, context, course section.
* @throws moodle_exception if user is not allowed to perform the action or module is not allowed in this course
*/
function can_add_moduleinfo($course, $modulename, $section) {
function can_add_moduleinfo($course, $modulename, $sectionnum) {
global $DB;
$module = $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
$module = $DB->get_record('modules', ['name' => $modulename], '*', MUST_EXIST);
$context = context_course::instance($course->id);
require_capability('moodle/course:manageactivities', $context);
course_create_sections_if_missing($course, $section);
$cw = get_fast_modinfo($course)->get_section_info($section);
// If the $sectionnum is a delegated section, we cannot execute create_if_missing
// because it only works to create regular sections. To prevent that from happening, we
// check if the section is already there, no matter if it is delegated or not.
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
if (!$sectioninfo) {
formatactions::section($course)->create_if_missing([$sectionnum]);
$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
}
if (!course_allowed_module($course, $module->name)) {
throw new \moodle_exception('moduledisable');
}
return array($module, $context, $cw);
return [$module, $context, $sectioninfo];
}
/**

View File

@ -16,6 +16,8 @@
namespace core_course;
use core_courseformat\formatactions;
defined('MOODLE_INTERNAL') || die();
global $CFG;
@ -276,4 +278,188 @@ class modlib_test extends \advanced_testcase {
$this->assertEquals($expectedorder, $modinfo->get_sections()[$sectionnumber]);
}
/**
* Test for can_add_moduleinfo on a non-existing module.
*
* @covers \can_add_moduleinfo
*/
public function test_can_add_moduleinfo_invalid_module(): void {
global $DB;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course(
['numsections' => 2, 'enablecompletion' => 1],
['createsections' => true]
);
$modinfo = get_fast_modinfo($course);
$section = $modinfo->get_section_info(2);
$this->setAdminUser();
$this->expectException(\dml_missing_record_exception::class);
can_add_moduleinfo($course, 'non-existent-module!', $section->section);
}
/**
* Test for can_add_moduleinfo when the user does not have addinstance capability.
*
* @covers \can_add_moduleinfo
*/
public function test_can_add_moduleinfo_deny_add_instance(): void {
global $DB;
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course(
['numsections' => 2, 'enablecompletion' => 1],
['createsections' => true]
);
$modinfo = get_fast_modinfo($course);
$section = $modinfo->get_section_info(2);
// The can_add_moduleinfo uses course_allowed_module to check if the module is allowed in the course.
// This method uses capabilities like the specific module addinstance capability.
$teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher'], '*', MUST_EXIST);
role_change_permission(
$teacherrole->id,
\context_course::instance($course->id),
'mod/label:addinstance',
CAP_PROHIBIT
);
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher');
$this->setUser($user);
$this->expectException(\moodle_exception::class);
can_add_moduleinfo($course, 'label', $section->section);
}
/**
* Test for can_add_moduleinfo.
*
* @dataProvider provider_can_add_moduleinfo
* @covers \can_add_moduleinfo
* @param string $rolename
* @param bool $hascapability
*/
public function test_can_add_moduleinfo_capability(string $rolename, bool $hascapability): void {
global $DB;
$this->resetAfterTest(true);
$module = $DB->get_record('modules', ['name' => 'label'], '*', MUST_EXIST);
$course = $this->getDataGenerator()->create_course(
['numsections' => 2, 'enablecompletion' => 1],
['createsections' => true]
);
$modinfo = get_fast_modinfo($course);
$section = $modinfo->get_section_info(2);
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename);
$this->setUser($user);
if (!$hascapability) {
$this->expectException(\required_capability_exception::class);
}
$result = can_add_moduleinfo($course, 'label', $section->section);
$this->assertEquals($module, $result[0]);
$this->assertEquals(
\context_course::instance($course->id),
$result[1]
);
$this->assertEquals($section->id, $result[2]->id);
}
/**
* Test for can_add_moduleinfo returns true on a delegate section.
*
* @dataProvider provider_can_add_moduleinfo
* @covers \can_add_moduleinfo
* @param string $rolename
* @param bool $hascapability
*/
public function test_can_add_moduleinfo_delegate_section(string $rolename, bool $hascapability): void {
global $DB;
$this->resetAfterTest(true);
$module = $DB->get_record('modules', ['name' => 'label'], '*', MUST_EXIST);
$course = $this->getDataGenerator()->create_course(
['numsections' => 2, 'enablecompletion' => 1],
['createsections' => true]
);
$section = formatactions::section($course)->create_delegated('mod_label', 0);
$modinfo = get_fast_modinfo($course);
$this->assertCount(4, $modinfo->get_section_info_all());
$this->assertCount(3, $modinfo->get_listed_section_info_all());
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename);
$this->setUser($user);
if (!$hascapability) {
$this->expectException(\required_capability_exception::class);
}
$result = can_add_moduleinfo($course, 'label', $section->section);
$this->assertEquals($module, $result[0]);
$this->assertEquals(
\context_course::instance($course->id),
$result[1]
);
$this->assertEquals($section->id, $result[2]->id);
// Validate no section has been created.
$modinfo = get_fast_modinfo($course);
$this->assertCount(4, $modinfo->get_section_info_all());
$this->assertCount(3, $modinfo->get_listed_section_info_all());
$this->assertEquals(
$section->section,
$modinfo->get_section_info_by_id($section->id)->section
);
}
/**
* Data provider for test_can_add_moduleinfo.
* @return array
*/
public static function provider_can_add_moduleinfo(): array {
return [
'Editing teacher' => [
'rolename' => 'editingteacher',
'hascapability' => true,
],
'Manager' => [
'rolename' => 'manager',
'hascapability' => true,
],
'Course creator' => [
'rolename' => 'coursecreator',
'hascapability' => false,
],
'Non-editing teacher' => [
'rolename' => 'teacher',
'hascapability' => false,
],
'Student' => [
'rolename' => 'student',
'hascapability' => false,
],
'Guest' => [
'rolename' => 'guest',
'hascapability' => false,
],
];
}
}