MDL-80715 core_courseformat: delegate section and cm rename

This commit is contained in:
Ferran Recio 2024-02-06 16:59:42 +01:00 committed by ferranrecio
parent c74a5439f6
commit fbbb86a2a6
11 changed files with 285 additions and 1 deletions

View File

@ -104,7 +104,12 @@ $editoroptions = array(
);
$courseformat = course_get_format($course);
$defaultsectionname = $courseformat->get_default_section_name($section);
if ($sectioninfo->is_delegated()) {
$defaultsectionname = $sectioninfo->name;
} else {
$defaultsectionname = $courseformat->get_default_section_name($section);
}
$customdata = [
'cs' => $sectioninfo,

View File

@ -0,0 +1,77 @@
<?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\hook;
use core\hook\described_hook;
use cm_info;
/**
* Hook for course-module name edited.
*
* @package core_courseformat
* @copyright 2024 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class after_cm_name_edited implements described_hook {
/**
* Constructor.
*
* @param cm_info $cm the course module
* @param string $newname the new name
*/
public function __construct(
protected cm_info $cm,
protected string $newname,
) {
}
/**
* Describes the hook purpose.
*
* @return string
*/
public static function get_hook_description(): string {
return 'This hook is triggered when a course module name is edited.';
}
/**
* List of tags that describe this hook.
*
* @return string[]
*/
public static function get_hook_tags(): array {
return ['cm_name_edited'];
}
/**
* Get course module instance.
*
* @return cm_info
*/
public function get_cm(): cm_info {
return $this->cm;
}
/**
* Get new name.
* @return string
*/
public function get_newname(): string {
return $this->newname;
}
}

View File

@ -70,6 +70,11 @@ class cmactions extends baseactions {
course_modinfo::purge_course_module_cache($cm->course, $cm->id);
rebuild_course_cache($cm->course, false, true);
// Modules may add some logic to renaming.
$modinfo = get_fast_modinfo($cm->course);
$hook = new \core_courseformat\hook\after_cm_name_edited($modinfo->get_cm($cm->id), $name);
\core\hook\manager::get_instance()->dispatch($hook);
// Attempt to update the grade item if relevant.
$grademodule = $DB->get_record($cm->modname, ['id' => $cm->instance]);
$grademodule->cmidnumber = $cm->idnumber;

View File

@ -356,6 +356,9 @@ class sectionactions extends baseactions {
throw new \moodle_exception('maximumchars', 'moodle', '', 255);
}
// If the section is delegated to a component, it may control some section values.
$fields = $this->preprocess_delegated_section_fields($sectioninfo, $fields);
if (empty($fields)) {
return false;
}
@ -428,4 +431,22 @@ class sectionactions extends baseactions {
\course_modinfo::purge_course_modules_cache($this->course->id, $cmids);
rebuild_course_cache($this->course->id, false, true);
}
/**
* Preprocess the section fields before updating a delegated section.
*
* @param section_info $sectioninfo the section info or database record to update.
* @param array $fields the fields to update.
* @return array the updated fields
*/
protected function preprocess_delegated_section_fields(section_info $sectioninfo, array $fields): array {
$delegated = $sectioninfo->get_component_instance();
if (!$delegated) {
return $fields;
}
if (array_key_exists('name', $fields)) {
$fields['name'] = $delegated->preprocess_section_name($sectioninfo, $fields['name']);
}
return $fields;
}
}

View File

@ -17,6 +17,7 @@
namespace core_courseformat;
use section_info;
use core_courseformat\stateupdates;
/**
* Section delegate base class.
@ -77,4 +78,30 @@ abstract class sectiondelegate {
public static function has_delegate_class(string $pluginname): bool {
return self::get_delegate_class_name($pluginname) !== null;
}
/**
* Define the section final name.
*
* This method can process the section name and return the validated new name.
*
* @param section_info $section
* @param string|null $newname the new name value to store in the database
* @return string|null the name value to store in the database
*/
public function preprocess_section_name(section_info $section, ?string $newname): ?string {
return $newname;
}
/**
* Add extra state updates when put or create a section.
*
* This method is called every time the backend sends a delegated section
* state update to the UI.
*
* @param section_info $section the affected section.
* @param stateupdates $updates the state updates object to notify the UI.
*/
public function put_section_state_extra_updates(section_info $section, stateupdates $updates): void {
// By default, do nothing.
}
}

View File

@ -123,6 +123,12 @@ class stateupdates implements JsonSerializable {
$currentstate = new $sectionclass($this->format, $section);
$this->add_update('section', $action, $currentstate->export_for_template($this->output));
// If the section is delegated to a component, give the component oportunity to add updates.
$delegated = $section->get_component_instance();
if ($delegated) {
$delegated->put_section_state_extra_updates($section, $this);
}
}
/**

View File

@ -16,6 +16,8 @@
namespace core_courseformat\local;
use core_courseformat\hook\after_cm_name_edited;
/**
* Course module format actions class tests.
*
@ -191,4 +193,32 @@ final class cmactions_test extends \advanced_testcase {
$this->assertInstanceOf('\core\event\course_module_updated', $event);
$this->assertEquals(\context_module::instance($activity->cmid), $event->get_context());
}
/**
* Test renaming an activity triggers the after_cm_name_edited hook.
* @covers ::rename
*/
public function test_rename_after_cm_name_edited_hook(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(
'assign',
['course' => $course->id, 'name' => 'Old name']
);
$executedhook = null;
$testcallback = function(after_cm_name_edited $hook) use (&$executedhook): void {
$executedhook = $hook;
};
$this->redirectHook(after_cm_name_edited::class, $testcallback);
$cmactions = new cmactions($course);
$result = $cmactions->rename($activity->cmid, 'New name');
$this->assertTrue($result);
$this->assertEquals($activity->cmid, $executedhook->get_cm()->id);
$this->assertEquals('New name', $executedhook->get_newname());
}
}

View File

@ -862,4 +862,43 @@ class sectionactions_test extends \advanced_testcase {
$this->assertEquals(0, $cm3->visible);
$this->assertEquals(1, $cm4->visible);
}
/**
* Test that the preprocess_section_name method can alter the section rename value.
*
* @covers ::update
* @covers ::preprocess_delegated_section_fields
*/
public function test_preprocess_section_name(): void {
global $DB, $CFG;
$this->resetAfterTest();
require_once($CFG->libdir . '/tests/fixtures/sectiondelegatetest.php');
$course = $this->getDataGenerator()->create_course();
$sectionactions = new sectionactions($course);
$section = $sectionactions->create_delegated('test_component', 1);
$result = $sectionactions->update($section, ['name' => 'new_name']);
$this->assertTrue($result);
$section = $DB->get_record('course_sections', ['id' => $section->id]);
$this->assertEquals('new_name_suffix', $section->name);
$sectioninfo = get_fast_modinfo($course->id)->get_section_info_by_id($section->id);
$this->assertEquals('new_name_suffix', $sectioninfo->name);
// Validate null name.
$section = $sectionactions->create_delegated('test_component', 1, (object)['name' => 'sample']);
$result = $sectionactions->update($section, ['name' => null]);
$this->assertTrue($result);
$section = $DB->get_record('course_sections', ['id' => $section->id]);
$this->assertEquals('null_name', $section->name);
$sectioninfo = get_fast_modinfo($course->id)->get_section_info_by_id($section->id);
$this->assertEquals('null_name', $sectioninfo->name);
}
}

View File

@ -383,4 +383,43 @@ class stateupdates_test extends \advanced_testcase {
],
];
}
/**
* Test components can add data to delegated section state updates.
* @covers ::add_section_put
*/
public function test_put_section_state_extra_updates(): void {
global $DB, $CFG;
$this->resetAfterTest();
require_once($CFG->libdir . '/tests/fixtures/sectiondelegatetest.php');
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module(
'assign',
['course' => $course->id]
);
// The test component section delegate will add the activity cm info into the state.
$section = formatactions::section($course)->create_delegated('test_component', $activity->cmid);
$format = course_get_format($course);
$updates = new \core_courseformat\stateupdates($format);
$updates->add_section_put($section->id);
$data = $updates->jsonSerialize();
$this->assertCount(2, $data);
$sectiondata = $data[0];
$this->assertEquals('section', $sectiondata->name);
$this->assertEquals('put', $sectiondata->action);
$this->assertEquals($section->id, $sectiondata->fields->id);
$cmdata = $data[1];
$this->assertEquals('cm', $cmdata->name);
$this->assertEquals('put', $cmdata->action);
$this->assertEquals($activity->cmid, $cmdata->fields->id);
}
}

View File

@ -723,6 +723,15 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
$cminfo = cm_info::create($cm);
$completion->reset_all_state($cminfo);
}
if ($cm->name != $moduleinfo->name) {
$hook = new \core_courseformat\hook\after_cm_name_edited(
get_fast_modinfo($course)->get_cm($cm->id),
$moduleinfo->name
);
\core\hook\manager::get_instance()->dispatch($hook);
}
$cm->name = $moduleinfo->name;
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();

View File

@ -17,6 +17,8 @@
namespace test_component\courseformat;
use core_courseformat\sectiondelegate as sectiondelegatebase;
use core_courseformat\stateupdates;
use section_info;
/**
* Test class for section delegate.
@ -26,4 +28,28 @@ use core_courseformat\sectiondelegate as sectiondelegatebase;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sectiondelegate extends sectiondelegatebase {
/**
* Test method to fake preprocesses the section name by appending a suffix to it.
*
* @param section_info $section The section information.
* @param string|null $newname The new name for the section.
* @return string|null The preprocessed section name with the suffix appended.
*/
public function preprocess_section_name(section_info $section, ?string $newname): ?string {
if (empty($newname)) {
return 'null_name';
}
return $newname . '_suffix';
}
/**
* Test method to add state updates of a section with additional information.
*
* @param section_info $section The section to update.
* @param stateupdates $updates The state updates to apply.
* @return void
*/
public function put_section_state_extra_updates(section_info $section, stateupdates $updates): void {
$updates->add_cm_put($section->itemid);
}
}