MDL-74989 core_courseformat: migrate cm delete to reactive

Migrate the course index delete activities to the new reactive actions
with batch deletion.
This commit is contained in:
Ferran Recio
2023-01-03 13:07:31 +01:00
parent d789209f3e
commit fe4784e60f
16 changed files with 204 additions and 28 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -59,7 +59,7 @@ define(
// component compatible formats and the default actions.js won't be necessary anymore.
// Meanwhile, we filter the migrated actions.
const componentActions = [
'moveSection', 'moveCm', 'addSection', 'deleteSection', 'sectionHide', 'sectionShow',
'moveSection', 'moveCm', 'addSection', 'deleteSection', 'cmDelete', 'sectionHide', 'sectionShow',
'cmHide', 'cmShow', 'cmStealth', 'sectionHighlight', 'sectionUnhighlight',
];
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
@@ -408,6 +408,50 @@ export default class extends BaseComponent {
this.reactive.dispatch(mutationName, [target.dataset.id]);
}
/**
* Handle a delete cm request.
*
* @param {Element} target the dispatch action element
* @param {Event} event the triggered event
*/
async _requestCmDelete(target, event) {
// Check we have an id.
const cmId = target.dataset.id;
if (!cmId) {
return;
}
const cmInfo = this.reactive.get('cm', cmId);
event.preventDefault();
const modalParams = {
title: getString('confirm', 'core'),
body: getString(
'deletechecktypename',
'moodle',
{
type: cmInfo.modname,
name: cmInfo.name,
}
),
saveButtonText: getString('delete', 'core'),
type: ModalFactory.types.SAVE_CANCEL,
};
const modal = await this._modalBodyRenderedPromise(modalParams);
modal.getRoot().on(
ModalEvents.save,
e => {
// Stop the default save button behaviour which is to close the modal.
e.preventDefault();
modal.destroy();
this.reactive.dispatch('cmDelete', [cmId]);
}
);
}
/**
* Disable all add sections actions.
*
@@ -246,6 +246,19 @@ export default class {
stateManager.processUpdates(updates);
}
/**
* Delete cms.
* @param {StateManager} stateManager the current state manager
* @param {array} cmIds the list of section ids
*/
async cmDelete(stateManager, cmIds) {
const course = stateManager.get('course');
this.cmLock(stateManager, cmIds, true);
const updates = await this._callEditWebservice('cm_delete', course.id, cmIds);
this.cmLock(stateManager, cmIds, false);
stateManager.processUpdates(updates);
}
/**
* Mark or unmark course modules as dragging.
*
+14
View File
@@ -26,6 +26,7 @@ namespace core_courseformat;
use navigation_node;
use moodle_page;
use cm_info;
use core_component;
use course_modinfo;
use html_writer;
@@ -1473,6 +1474,19 @@ abstract class base {
return true;
}
/**
* Wrapper for course_delete_module method.
*
* Format plugins can override this method to provide their own implementation of course_delete_module.
*
* @param cm_info $cm the course module information
* @param bool $async whether or not to try to delete the module using an adhoc task. Async also depends on a plugin hook.
* @throws moodle_exception
*/
public function delete_module(cm_info $cm, bool $async = false) {
course_delete_module($cm->id, $async);
}
/**
* Prepares the templateable object to display section name
*
@@ -90,6 +90,7 @@ class cm implements renderable {
'sectionnumber' => $section->section,
'uservisible' => $cm->uservisible,
'hascmrestrictions' => $this->get_has_restrictions(),
'modname' => get_string('pluginname', 'mod_' . $cm->modname),
];
// Check the user access type to this cm.
+42
View File
@@ -422,6 +422,48 @@ class stateactions {
}
}
/**
* Delete course cms.
*
* @param stateupdates $updates the affected course elements track
* @param stdClass $course the course object
* @param int[] $ids section ids
* @param int $targetsectionid not used
* @param int $targetcmid not used
*/
public function cm_delete(
stateupdates $updates,
stdClass $course,
array $ids = [],
?int $targetsectionid = null,
?int $targetcmid = null
): void {
$this->validate_cms($course, $ids, __FUNCTION__);
// Check capabilities on every activity context.
foreach ($ids as $cmid) {
$modcontext = context_module::instance($cmid);
require_capability('moodle/course:manageactivities', $modcontext);
}
$format = course_get_format($course->id);
$modinfo = get_fast_modinfo($course);
$affectedsections = [];
$cms = $this->get_cm_info($modinfo, $ids);
foreach ($cms as $cm) {
$section = $cm->get_section_info();
$affectedsections[$section->id] = $section;
$format->delete_module($cm, true);
$updates->add_cm_remove($cm->id);
}
foreach ($affectedsections as $sectionid => $section) {
$updates->add_section_put($sectionid);
}
}
/**
* Extract several cm_info from the course_modinfo.
*
+72 -17
View File
@@ -594,21 +594,21 @@ class stateactions_test extends \advanced_testcase {
* @param string $role the user role
* @param string[] $idrefs the sections or cms id references to be used as method params
* @param bool $expectedexception whether the call should throw an exception
* @param int $expectedtotal the expected total number of state puts
* @param int[] $expectedtotal the expected total number of state indexed by put, remove and create
* @param string|null $coursefield the course field to check
* @param int|string|null $coursevalue the section field value
* @param string|null $sectionfield the section field to check
* @param int|string|null $sectionvalue the section field value
* @param string|null $cmfield the cm field to check
* @param int|string|null $cmvalue the cm field value
* @return array the state update summary
* @return array an array of elements to do extra validations (course, references, results)
*/
protected function basic_state_text(
string $method = 'section_hide',
string $role = 'editingteacher',
array $idrefs = [],
bool $expectedexception = false,
int $expectedtotal = 0,
array $expectedtotals = [],
?string $coursefield = null,
$coursevalue = 0,
?string $sectionfield = null,
@@ -632,6 +632,8 @@ class stateactions_test extends \advanced_testcase {
$references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
$references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
$references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
$references["cm4"] = $this->create_activity($course->id, 'forum', 2, false);
$references["cm5"] = $this->create_activity($course->id, 'wiki', 2, false);
if ($expectedexception) {
$this->expectException(moodle_exception::class);
@@ -652,13 +654,10 @@ class stateactions_test extends \advanced_testcase {
// Format results in a way we can compare easily.
$results = $this->summarize_updates($updates);
// Most state actions does not use create or remove actions because they are designed
// to refresh parts of the state.
$this->assertEquals(0, $results['create']['count']);
$this->assertEquals(0, $results['remove']['count']);
// Validate we have all the expected entries.
$this->assertEquals($expectedtotal, $results['put']['count']);
$this->assertEquals($expectedtotals['create'] ?? 0, $results['create']['count']);
$this->assertEquals($expectedtotals['remove'] ?? 0, $results['remove']['count']);
$this->assertEquals($expectedtotals['put'] ?? 0, $results['put']['count']);
// Validate course, section and cm.
if (!empty($coursefield)) {
@@ -676,7 +675,11 @@ class stateactions_test extends \advanced_testcase {
$this->assertEquals($cmvalue, $cm->$cmfield);
}
}
return $results;
return [
'course' => $course,
'references' => $references,
'results' => $results,
];
}
/**
@@ -696,7 +699,7 @@ class stateactions_test extends \advanced_testcase {
$role,
['section1', 'section2', 'section3'],
$expectedexception,
7,
['put' => 9],
null,
null,
'visible',
@@ -723,7 +726,7 @@ class stateactions_test extends \advanced_testcase {
$role,
['section1', 'section2', 'section3'],
$expectedexception,
7,
['put' => 9],
null,
null,
'visible',
@@ -750,7 +753,7 @@ class stateactions_test extends \advanced_testcase {
$role,
['cm0', 'cm1', 'cm2', 'cm3'],
$expectedexception,
4,
['put' => 4],
null,
null,
null,
@@ -777,7 +780,7 @@ class stateactions_test extends \advanced_testcase {
$role,
['cm0', 'cm1', 'cm2', 'cm3'],
$expectedexception,
4,
['put' => 4],
null,
null,
null,
@@ -805,7 +808,7 @@ class stateactions_test extends \advanced_testcase {
$role,
['cm0', 'cm1', 'cm2', 'cm3'],
$expectedexception,
4,
['put' => 4],
null,
null,
null,
@@ -822,7 +825,7 @@ class stateactions_test extends \advanced_testcase {
$role,
['cm0', 'cm1'],
$expectedexception,
2,
['put' => 2],
null,
null,
null,
@@ -835,7 +838,7 @@ class stateactions_test extends \advanced_testcase {
$role,
['cm2', 'cm3'],
$expectedexception,
2,
['put' => 2],
null,
null,
null,
@@ -870,4 +873,56 @@ class stateactions_test extends \advanced_testcase {
],
];
}
/**
* Test for cm_delete
*
* @covers ::cm_delete
* @dataProvider basic_role_provider
* @param string $role the user role
* @param bool $expectedexception if it will expect an exception.
*/
public function test_cm_delete(
string $role = 'editingteacher',
bool $expectedexception = false
): void {
$this->resetAfterTest();
// We want modules to be deleted for good.
set_config('coursebinenable', 0, 'tool_recyclebin');
$info = $this->basic_state_text(
'cm_delete',
$role,
['cm2', 'cm3'],
$expectedexception,
['remove' => 2, 'put' => 1],
);
$course = $info['course'];
$references = $info['references'];
$results = $info['results'];
$courseformat = course_get_format($course->id);
$this->assertArrayNotHasKey($references['cm0'], $results['remove']['cm']);
$this->assertArrayNotHasKey($references['cm1'], $results['remove']['cm']);
$this->assertArrayHasKey($references['cm2'], $results['remove']['cm']);
$this->assertArrayHasKey($references['cm3'], $results['remove']['cm']);
$this->assertArrayNotHasKey($references['cm4'], $results['remove']['cm']);
$this->assertArrayNotHasKey($references['cm5'], $results['remove']['cm']);
// Check the new section cm list.
$newcmlist = $this->translate_references($references, ['cm4', 'cm5']);
$section = $results['put']['section'][$references['section2']];
$this->assertEquals($newcmlist, $section->cmlist);
// Check activities are deleted.
$modinfo = $courseformat->get_modinfo();
$cms = $modinfo->get_cms();
$this->assertArrayHasKey($references['cm0'], $cms);
$this->assertArrayHasKey($references['cm1'], $cms);
$this->assertArrayNotHasKey($references['cm2'], $cms);
$this->assertArrayNotHasKey($references['cm3'], $cms);
$this->assertArrayHasKey($references['cm4'], $cms);
$this->assertArrayHasKey($references['cm5'], $cms);
}
}
+2
View File
@@ -4,6 +4,8 @@ Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
=== 4.2 ===
* New core_courseformat\base::get_context() to get the course context directly from the format instance.
* New core_courseformat\base::delete_module() method. Now format plugins can extend the activity deletion logic
by overriding this method.
=== 4.1 ===
* New \core_courseformat\stateupdates methods add_section_remove() and add_cm_remove() have been added to replace
+8 -3
View File
@@ -1836,10 +1836,15 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
// Delete.
if ($hasmanageactivities) {
$actions['delete'] = new action_menu_link_secondary(
new moodle_url($baseurl, array('delete' => $mod->id)),
new pix_icon('t/delete', '', 'moodle', array('class' => 'iconsmall')),
new moodle_url($baseurl, ['delete' => $mod->id]),
new pix_icon('t/delete', '', 'moodle', ['class' => 'iconsmall']),
$str->delete,
array('class' => 'editing_delete', 'data-action' => 'delete', 'data-sectionreturn' => $sr)
[
'class' => 'editing_delete',
'data-action' => ($usecomponents) ? 'cmDelete' : 'delete',
'data-sectionreturn' => $sr,
'data-id' => $mod->id,
]
);
}
+1 -1
View File
@@ -1039,7 +1039,7 @@ class behat_course extends behat_base {
// JS modal windows and avoiding interacting them at the same time.
if ($this->running_javascript()) {
$this->execute('behat_general::i_click_on_in_the',
array(get_string('yes'), "button", "Confirm", "dialogue")
array(get_string('delete'), "button", "Confirm", "dialogue")
);
} else {
$this->execute("behat_forms::press_button", get_string('yes'));