mirror of
https://github.com/moodle/moodle.git
synced 2025-04-22 17:02:03 +02:00
Merge branch 'MDL-83469-405' of https://github.com/ferranrecio/moodle into MOODLE_405_STABLE
This commit is contained in:
commit
09c0b90348
2
course/amd/build/actions.min.js
vendored
2
course/amd/build/actions.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -65,7 +65,7 @@ define(
|
||||
const componentActions = [
|
||||
'moveSection', 'moveCm', 'addSection', 'deleteSection', 'cmDelete', 'cmDuplicate', 'sectionHide', 'sectionShow',
|
||||
'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight', 'cmMoveRight', 'cmMoveLeft',
|
||||
'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups', 'addModule',
|
||||
'cmNoGroups', 'cmVisibleGroups', 'cmSeparateGroups', 'addModule', 'newModule',
|
||||
];
|
||||
|
||||
// The course reactive instance.
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -469,6 +469,17 @@ export default class extends BaseComponent {
|
||||
this.reactive.dispatch('addModule', target.dataset.modname, target.dataset.sectionnum, target.dataset.beforemod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a new create subsection request.
|
||||
*
|
||||
* @param {Element} target the dispatch action element
|
||||
* @param {Event} event the triggered event
|
||||
*/
|
||||
async _requestNewModule(target, event) {
|
||||
event.preventDefault();
|
||||
this.reactive.dispatch('newModule', target.dataset.modname, target.dataset.sectionid, target.dataset.beforemod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a delete section request.
|
||||
*
|
||||
|
@ -75,21 +75,46 @@ export default class {
|
||||
* @param {number} targetSectionNum target section number
|
||||
* @param {number} targetCmId optional target cm id
|
||||
*/
|
||||
async _callAddModuleWebservice(courseId, modName, targetSectionNum, targetCmId) {
|
||||
const args = {
|
||||
courseid: courseId,
|
||||
modname: modName,
|
||||
targetsectionnum: targetSectionNum,
|
||||
};
|
||||
if (targetCmId) {
|
||||
args.targetcmid = targetCmId;
|
||||
}
|
||||
let ajaxresult = await ajax.call([{
|
||||
methodname: 'core_courseformat_create_module',
|
||||
args,
|
||||
}])[0];
|
||||
return JSON.parse(ajaxresult);
|
||||
async _callAddModuleWebservice(courseId, modName, targetSectionNum, targetCmId) {
|
||||
const args = {
|
||||
courseid: courseId,
|
||||
modname: modName,
|
||||
targetsectionnum: targetSectionNum,
|
||||
};
|
||||
if (targetCmId) {
|
||||
args.targetcmid = targetCmId;
|
||||
}
|
||||
let ajaxresult = await ajax.call([{
|
||||
methodname: 'core_courseformat_create_module',
|
||||
args,
|
||||
}])[0];
|
||||
return JSON.parse(ajaxresult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private method to call core_courseformat_new_module webservice.
|
||||
*
|
||||
* @method _callEditWebservice
|
||||
* @param {number} courseId
|
||||
* @param {string} modName module name
|
||||
* @param {number} targetSectionId target section number
|
||||
* @param {number} targetCmId optional target cm id
|
||||
*/
|
||||
async _callNewModuleWebservice(courseId, modName, targetSectionId, targetCmId) {
|
||||
const args = {
|
||||
courseid: courseId,
|
||||
modname: modName,
|
||||
targetsectionid: targetSectionId,
|
||||
};
|
||||
if (targetCmId) {
|
||||
args.targetcmid = targetCmId;
|
||||
}
|
||||
let ajaxresult = await ajax.call([{
|
||||
methodname: 'core_courseformat_new_module',
|
||||
args,
|
||||
}])[0];
|
||||
return JSON.parse(ajaxresult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a basic section state action.
|
||||
@ -439,6 +464,29 @@ export default class {
|
||||
stateManager.processUpdates(updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new module to a specific course section.
|
||||
*
|
||||
* @param {StateManager} stateManager the current state manager
|
||||
* @param {string} modName the modulename to add
|
||||
* @param {number} targetSectionId the target section id
|
||||
* @param {number} targetCmId optional the target cm id
|
||||
*/
|
||||
async newModule(stateManager, modName, targetSectionId, targetCmId) {
|
||||
if (!modName) {
|
||||
throw new Error(`Mutation newModule requires moduleName`);
|
||||
}
|
||||
if (!targetSectionId) {
|
||||
throw new Error(`Mutation newModule requires targetSectionId`);
|
||||
}
|
||||
if (!targetCmId) {
|
||||
targetCmId = 0;
|
||||
}
|
||||
const course = stateManager.get('course');
|
||||
const updates = await this._callNewModuleWebservice(course.id, modName, targetSectionId, targetCmId);
|
||||
stateManager.processUpdates(updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark or unmark course modules as dragging.
|
||||
*
|
||||
|
145
course/format/classes/external/new_module.php
vendored
Normal file
145
course/format/classes/external/new_module.php
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
<?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_courseformat\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_value;
|
||||
use moodle_exception;
|
||||
use coding_exception;
|
||||
use context_course;
|
||||
use core_courseformat\base as course_format;
|
||||
|
||||
/**
|
||||
* External service to create a new module instance in the course.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @copyright 2024 Mikel Martín <mikel@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class new_module extends external_api {
|
||||
|
||||
/**
|
||||
* Webservice parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'courseid' => new external_value(PARAM_INT, 'course id', VALUE_REQUIRED),
|
||||
'modname' => new external_value(PARAM_ALPHANUMEXT, 'module name', VALUE_REQUIRED),
|
||||
'targetsectionid' => new external_value(PARAM_INT, 'target section id', VALUE_REQUIRED, null),
|
||||
'targetcmid' => new external_value(PARAM_INT, 'Optional target cm id', VALUE_DEFAULT, null),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This webservice will execute the create_module action from the course editor.
|
||||
*
|
||||
* The action will register in a {@see \core_courseformat\stateupdates} all the affected
|
||||
* sections, cms and course attribute. This object (in JSON) will be sent back to the
|
||||
* frontend editor to refresh the updated state elements.
|
||||
*
|
||||
* By default, {@see \core_courseformat\stateupdates} will register only create, delete and update events
|
||||
* on cms, sections and the general course data. However, if some plugin needs adhoc messages for
|
||||
* its own mutation module, extend this class in format_XXX\course.
|
||||
*
|
||||
* @param int $courseid the course id
|
||||
* @param string $modname the module name
|
||||
* @param int $targetsectionid the target section id
|
||||
* @param int|null $targetcmid optional target cm id
|
||||
* @return string Course state in JSON
|
||||
*/
|
||||
public static function execute(
|
||||
int $courseid,
|
||||
string $modname,
|
||||
int $targetsectionid,
|
||||
?int $targetcmid = null
|
||||
): string {
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
|
||||
[
|
||||
'courseid' => $courseid,
|
||||
'modname' => $modname,
|
||||
'targetsectionid' => $targetsectionid,
|
||||
'targetcmid' => $targetcmid,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'courseid' => $courseid,
|
||||
'modname' => $modname,
|
||||
'targetsectionid' => $targetsectionid,
|
||||
'targetcmid' => $targetcmid,
|
||||
]);
|
||||
|
||||
self::validate_context(context_course::instance($courseid));
|
||||
|
||||
// Plugin needs to support quick creation and the course format needs to support components.
|
||||
// Formats using YUI modules should not be able to quick-create because the front end cannot react to the change.
|
||||
if (!plugin_supports('mod', $modname, FEATURE_QUICKCREATE) || !course_get_format($courseid)->supports_components()) {
|
||||
throw new moodle_exception("Module $modname does not support quick creation");
|
||||
}
|
||||
|
||||
$courseformat = course_get_format($courseid);
|
||||
|
||||
// Create a course changes tracker object.
|
||||
$defaultupdatesclass = 'core_courseformat\\stateupdates';
|
||||
$updatesclass = 'format_' . $courseformat->get_format() . '\\courseformat\\stateupdates';
|
||||
if (!class_exists($updatesclass)) {
|
||||
$updatesclass = $defaultupdatesclass;
|
||||
}
|
||||
$updates = new $updatesclass($courseformat);
|
||||
|
||||
if (!is_a($updates, $defaultupdatesclass)) {
|
||||
throw new coding_exception("The \"$updatesclass\" class must extend \"$defaultupdatesclass\"");
|
||||
}
|
||||
|
||||
// Get the actions class from the course format.
|
||||
$actionsclass = 'format_'. $courseformat->get_format().'\\courseformat\\stateactions';
|
||||
if (!class_exists($actionsclass)) {
|
||||
$actionsclass = 'core_courseformat\\stateactions';
|
||||
}
|
||||
/** @var \core_courseformat\stateactions $actions */
|
||||
$actions = new $actionsclass();
|
||||
|
||||
$action = 'new_module';
|
||||
if (!is_callable([$actions, $action])) {
|
||||
throw new moodle_exception("Invalid course state action $action in ".get_class($actions));
|
||||
}
|
||||
|
||||
$course = $courseformat->get_course();
|
||||
|
||||
// Execute the action.
|
||||
$actions->$action($updates, $course, $modname, $targetsectionid, $targetcmid);
|
||||
|
||||
// Any state action mark the state cache as dirty.
|
||||
course_format::session_cache_reset($course);
|
||||
|
||||
return json_encode($updates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Webservice returns.
|
||||
*
|
||||
* @return external_value
|
||||
*/
|
||||
public static function execute_returns(): external_value {
|
||||
return new external_value(PARAM_RAW, 'Encoded course update JSON');
|
||||
}
|
||||
}
|
@ -1211,4 +1211,38 @@ class stateactions {
|
||||
// Adding module affects section structure, and if the module has a delegated section even the course structure.
|
||||
$this->course_state($updates, $course);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new course module.
|
||||
*
|
||||
* @param stateupdates $updates the affected course elements track
|
||||
* @param stdClass $course the course object
|
||||
* @param string $modname the module name
|
||||
* @param int $targetsectionid target section id
|
||||
* @param int|null $targetcmid optional target cm id
|
||||
*/
|
||||
public function new_module(
|
||||
stateupdates $updates,
|
||||
stdClass $course,
|
||||
string $modname,
|
||||
int $targetsectionid,
|
||||
?int $targetcmid = null
|
||||
): void {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/course/modlib.php');
|
||||
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
require_capability('moodle/course:update', $coursecontext);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$section = $modinfo->get_section_info_by_id($targetsectionid, MUST_EXIST);
|
||||
|
||||
// Method "can_add_moduleinfo" called in "prepare_new_moduleinfo_data" will handle the capability checks.
|
||||
[, , , , $moduleinfo] = prepare_new_moduleinfo_data($course, $modname, $section->sectionnum);
|
||||
$moduleinfo->beforemod = $targetcmid;
|
||||
create_module((object) $moduleinfo);
|
||||
|
||||
// Adding module affects section structure, and if the module has a delegated section even the course structure.
|
||||
$this->course_state($updates, $course);
|
||||
}
|
||||
}
|
||||
|
164
course/format/tests/external/new_module_test.php
vendored
Normal file
164
course/format/tests/external/new_module_test.php
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
<?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/>.
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace core_courseformat\external;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use moodle_exception;
|
||||
use stdClass;
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
|
||||
|
||||
/**
|
||||
* Tests for the new_module class.
|
||||
*
|
||||
* @package core_courseformat
|
||||
* @category test
|
||||
* @copyright 2024 Mikel Martín <mikel@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @coversDefaultClass \core_courseformat\external\new_module
|
||||
*/
|
||||
final class new_module_test extends \externallib_advanced_testcase {
|
||||
|
||||
/**
|
||||
* Setup to ensure that fixtures are loaded.
|
||||
*/
|
||||
public static function setupBeforeClass(): void { // phpcs:ignore
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest.php');
|
||||
require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest_output_course_format_state.php');
|
||||
require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest_stateactions.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the webservice can execute the new_module action.
|
||||
*
|
||||
* @covers ::execute
|
||||
*/
|
||||
public function test_execute(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$modname = 'subsection';
|
||||
$manager = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$manager::enable_plugin($modname, 1);
|
||||
|
||||
// Create a course with an activity.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
$activity = $this->getDataGenerator()->create_module('book', ['course' => $course->id]);
|
||||
$targetsection = get_fast_modinfo($course->id)->get_section_info(1);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Execute course action.
|
||||
$results = json_decode(
|
||||
new_module::execute((int)$course->id, $modname, (int)$targetsection->id, (int)$activity->id),
|
||||
);
|
||||
|
||||
// Check result.
|
||||
$cmupdate = $this->find_update_by_fieldname($results, 'put', 'cm', get_string('quickcreatename', 'mod_' . $modname));
|
||||
$this->assertNotEmpty($cmupdate);
|
||||
$this->assertEquals($modname, $cmupdate->fields->module);
|
||||
$this->assertEquals($targetsection->id, $cmupdate->fields->sectionid);
|
||||
$this->assertEquals(1, $cmupdate->fields->sectionnumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the webservice can execute the new_module action with a format override.
|
||||
*
|
||||
* @covers ::execute
|
||||
*/
|
||||
public function test_execute_with_format_override(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$manager = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$manager::enable_plugin('subsection', 1);
|
||||
|
||||
// Create a course.
|
||||
$course = $this->getDataGenerator()->create_course(['format' => 'theunittest', 'numsections' => 1, 'initsections' => 1]);
|
||||
$targetsection = get_fast_modinfo($course->id)->get_section_info(1);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Execute course action.
|
||||
$modname = 'subsection';
|
||||
$results = json_decode(
|
||||
new_module::execute((int)$course->id, $modname, (int)$targetsection->id),
|
||||
);
|
||||
|
||||
// Some course formats doesn't have the renderer file, so a debugging message will be displayed.
|
||||
$this->assertDebuggingCalled();
|
||||
|
||||
// Check result.
|
||||
$this->assertEmpty($results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the webservice can execute the new_module action with an invalid module.
|
||||
*
|
||||
* @covers ::execute
|
||||
*/
|
||||
public function test_execute_with_invalid_module(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create a course.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
$targetsection = get_fast_modinfo($course->id)->get_section_info(1);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Expect exception. Book module doesn't support quickcreate feature.
|
||||
$this->expectException(moodle_exception::class);
|
||||
|
||||
// Execute course action.
|
||||
$modname = 'book';
|
||||
new_module::execute((int)$course->id, $modname, (int)$targetsection->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper methods to find a specific update in the updadelist.
|
||||
*
|
||||
* @param array $updatelist the update list
|
||||
* @param string $action the action to find
|
||||
* @param string $name the element name to find
|
||||
* @param string $fieldname the element identifiername
|
||||
* @return stdClass|null the object found, if any.
|
||||
*/
|
||||
private function find_update_by_fieldname(
|
||||
array $updatelist,
|
||||
string $action,
|
||||
string $name,
|
||||
string $fieldname,
|
||||
|
||||
): ?stdClass {
|
||||
foreach ($updatelist as $update) {
|
||||
if ($update->action != $action || $update->name != $name) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($update->fields->name)) {
|
||||
continue;
|
||||
}
|
||||
if ($update->fields->name == $fieldname) {
|
||||
return $update;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -54,10 +54,28 @@ class stateactions extends core_actions {
|
||||
* @param stateupdates $updates the affected course elements track
|
||||
* @param stdClass $course the course object
|
||||
* @param string $modname the module name
|
||||
* @param int $targetsectionid target section id
|
||||
* @param int $targetsectionnum target section number
|
||||
* @param int|null $targetcmid optional target cm id
|
||||
*/
|
||||
public function create_module(
|
||||
stateupdates $updates,
|
||||
stdClass $course,
|
||||
string $modname,
|
||||
int $targetsectionnum,
|
||||
?int $targetcmid = null
|
||||
): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative new_module state action for testing.
|
||||
*
|
||||
* @param stateupdates $updates the affected course elements track
|
||||
* @param stdClass $course the course object
|
||||
* @param string $modname the module name
|
||||
* @param int $targetsectionid target section id
|
||||
* @param int|null $targetcmid optional target cm id
|
||||
*/
|
||||
public function new_module(
|
||||
stateupdates $updates,
|
||||
stdClass $course,
|
||||
string $modname,
|
||||
|
@ -1712,8 +1712,6 @@ final class stateactions_test extends \advanced_testcase {
|
||||
|
||||
// Create a course with 1 section and 1 student.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
|
||||
$courseformat = course_get_format($course->id);
|
||||
$targetsection = $courseformat->get_modinfo()->get_section_info(1);
|
||||
|
||||
@ -1734,11 +1732,39 @@ final class stateactions_test extends \advanced_testcase {
|
||||
$this->assertEquals($modname, $cmupdate->module);
|
||||
$this->assertEquals($targetsection->id, $cmupdate->sectionid);
|
||||
$this->assertEquals(get_string('quickcreatename', 'mod_' . $modname), $cmupdate->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for create_module public method with no capabilities.
|
||||
*
|
||||
* @covers ::create_module
|
||||
*/
|
||||
public function test_create_module_no_capabilities(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$modname = 'subsection';
|
||||
$manager = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$manager::enable_plugin($modname, 1);
|
||||
|
||||
// Create a course with 1 section and 1 student.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
|
||||
$courseformat = course_get_format($course->id);
|
||||
$targetsection = $courseformat->get_modinfo()->get_section_info(1);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Sanity check.
|
||||
$this->assertEmpty($courseformat->get_modinfo()->get_cms());
|
||||
|
||||
// Change to a user without permission.
|
||||
$this->setUser($student);
|
||||
|
||||
// Validate that the method throws an exception.
|
||||
$actions = new stateactions();
|
||||
$updates = new stateupdates($courseformat);
|
||||
|
||||
$this->expectException(moodle_exception::class);
|
||||
$actions->create_module($updates, $course, $modname, $targetsection->sectionnum);
|
||||
}
|
||||
@ -1792,4 +1818,125 @@ final class stateactions_test extends \advanced_testcase {
|
||||
$modinfo->get_section_info(1)->sequence
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for new_module public method.
|
||||
*
|
||||
* @covers ::new_module
|
||||
*/
|
||||
public function test_new_module(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$modname = 'subsection';
|
||||
$manager = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$manager::enable_plugin($modname, 1);
|
||||
|
||||
// Create a course with 1 section and 1 student.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
$courseformat = course_get_format($course->id);
|
||||
$targetsection = $courseformat->get_modinfo()->get_section_info(1);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Sanity check.
|
||||
$this->assertEmpty($courseformat->get_modinfo()->get_cms());
|
||||
|
||||
// Execute given method.
|
||||
$actions = new stateactions();
|
||||
$updates = new stateupdates($courseformat);
|
||||
$actions->new_module($updates, $course, $modname, $targetsection->id);
|
||||
|
||||
// Validate cm was created and updates were generated.
|
||||
$results = $this->summarize_updates($updates);
|
||||
$cmupdate = reset($results['put']['cm']);
|
||||
$this->assertCount(1, $courseformat->get_modinfo()->get_cms());
|
||||
$this->assertEquals($modname, $cmupdate->module);
|
||||
$this->assertEquals($targetsection->id, $cmupdate->sectionid);
|
||||
$this->assertEquals(get_string('quickcreatename', 'mod_' . $modname), $cmupdate->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for new_module public method with no capabilities.
|
||||
*
|
||||
* @covers ::new_module
|
||||
*/
|
||||
public function test_new_module_no_capabilities(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$modname = 'subsection';
|
||||
$manager = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$manager::enable_plugin($modname, 1);
|
||||
|
||||
// Create a course with 1 section and 1 student.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
|
||||
$courseformat = course_get_format($course->id);
|
||||
$targetsection = $courseformat->get_modinfo()->get_section_info(1);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Sanity check.
|
||||
$this->assertEmpty($courseformat->get_modinfo()->get_cms());
|
||||
|
||||
// Change to a user without permission.
|
||||
$this->setUser($student);
|
||||
|
||||
// Validate that the method throws an exception.
|
||||
$actions = new stateactions();
|
||||
$updates = new stateupdates($courseformat);
|
||||
|
||||
$this->expectException(moodle_exception::class);
|
||||
$actions->new_module($updates, $course, $modname, $targetsection->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for new_module public method with targetcmid parameter.
|
||||
*
|
||||
* @covers ::new_module
|
||||
*/
|
||||
public function test_new_module_with_targetcmid(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$modname = 'subsection';
|
||||
$manager = \core_plugin_manager::resolve_plugininfo_class('mod');
|
||||
$manager::enable_plugin($modname, 1);
|
||||
|
||||
// Create a course with 1 section, 2 modules (forum and page) and 1 student.
|
||||
$course = $this->getDataGenerator()->create_course(['numsections' => 1]);
|
||||
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course], ['section' => 1]);
|
||||
$page = $this->getDataGenerator()->create_module('page', ['course' => $course], ['section' => 1]);
|
||||
$student = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
|
||||
$courseformat = course_get_format($course->id);
|
||||
$targetsection = $courseformat->get_modinfo()->get_section_info(1);
|
||||
|
||||
$this->setAdminUser();
|
||||
|
||||
// Sanity check.
|
||||
$this->assertCount(2, $courseformat->get_modinfo()->get_cms());
|
||||
|
||||
// Execute given method.
|
||||
$actions = new stateactions();
|
||||
$updates = new stateupdates($courseformat);
|
||||
$actions->new_module($updates, $course, $modname, $targetsection->id, $page->cmid);
|
||||
|
||||
$modinfo = $courseformat->get_modinfo();
|
||||
$cms = $modinfo->get_cms();
|
||||
$results = $this->summarize_updates($updates);
|
||||
$cmupdate = reset($results['put']['cm']);
|
||||
|
||||
// Validate updates were generated.
|
||||
$this->assertEquals($modname, $cmupdate->module);
|
||||
$this->assertEquals($targetsection->id, $cmupdate->sectionid);
|
||||
$this->assertEquals(get_string('quickcreatename', 'mod_' . $modname), $cmupdate->name);
|
||||
|
||||
// Validate that the new module was created between both modules.
|
||||
$this->assertCount(3, $cms);
|
||||
$this->assertArrayHasKey($cmupdate->id, $cms);
|
||||
$this->assertEquals(
|
||||
implode(',', [$forum->cmid, $cmupdate->id, $page->cmid]),
|
||||
$modinfo->get_section_info(1)->sequence
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -590,6 +590,14 @@ $functions = array(
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_courseformat_new_module' => [
|
||||
'classname' => 'core_courseformat\external\new_module',
|
||||
'methodname' => 'execute',
|
||||
'description' => 'Create a new module to course.',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
'capabilities' => 'moodle/course:manageactivities',
|
||||
],
|
||||
'core_courseformat_update_course' => [
|
||||
'classname' => 'core_courseformat\external\update_course',
|
||||
'methodname' => 'execute',
|
||||
|
@ -46,9 +46,10 @@ class before_activitychooserbutton_exported_handler {
|
||||
|
||||
$attributes = [
|
||||
'class' => 'dropdown-item',
|
||||
'data-action' => 'addModule',
|
||||
'data-action' => 'newModule',
|
||||
'data-modname' => 'subsection',
|
||||
'data-sectionnum' => $section->sectionnum,
|
||||
'data-sectionid' => $section->id,
|
||||
];
|
||||
if ($hook->get_cm()) {
|
||||
$attributes['data-beforemod'] = $hook->get_cm()->id;
|
||||
|
@ -110,3 +110,15 @@ Feature: Users view subsections on course page
|
||||
Then I should see "New forum" in the "Section 3" "section"
|
||||
And "New database" "text" should appear before "New forum" "text"
|
||||
And "New forum" "text" should appear before "New page" "text"
|
||||
|
||||
@javascript
|
||||
Scenario: Teacher can subsections after moving the parent section
|
||||
Given I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I open section "1" edit menu
|
||||
And I click on "Move" "link" in the "Section 1" "section"
|
||||
And I click on "Section 3" "link" in the "Move section" "dialogue"
|
||||
And "Section 1" "section" should appear after "Section 3" "section"
|
||||
When I click on "Add content" "button" in the "Section 1" "section"
|
||||
And I click on "Subsection" "link" in the ".dropdown-menu.show" "css_element"
|
||||
Then I should see "New subsection" in the "Section 1" "section"
|
||||
|
Loading…
x
Reference in New Issue
Block a user