From 7fcfc7ddf289eacb037fe29d0f0f70e6d45fc38f Mon Sep 17 00:00:00 2001 From: James C <5689414+james-cnz@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:04:09 +1300 Subject: [PATCH] MDL-83994 course: Respect course_can_delete_section() in AJAX --- course/format/classes/stateactions.php | 3 + .../tests/external/delete_section_test.php | 117 ++++++++++++++++++ .../fixtures/format_theunittestdelete.php | 63 ++++++++++ 3 files changed, 183 insertions(+) create mode 100644 course/format/tests/external/delete_section_test.php create mode 100644 course/format/tests/fixtures/format_theunittestdelete.php diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php index c43cc1919b8..dd3c0b31d68 100644 --- a/course/format/classes/stateactions.php +++ b/course/format/classes/stateactions.php @@ -366,6 +366,9 @@ class stateactions { // We need to get the latest modinfo on each iteration because the section numbers change. $modinfo = get_fast_modinfo($course); $section = $modinfo->get_section_info_by_id($sectionid, MUST_EXIST); + if (!course_can_delete_section($course, $section)) { + continue; + } // Send all activity deletions. if (!empty($modinfo->sections[$section->section])) { foreach ($modinfo->sections[$section->section] as $modnumber) { diff --git a/course/format/tests/external/delete_section_test.php b/course/format/tests/external/delete_section_test.php new file mode 100644 index 00000000000..4b60b8356eb --- /dev/null +++ b/course/format/tests/external/delete_section_test.php @@ -0,0 +1,117 @@ +. + +declare(strict_types=1); + +namespace core_courseformat\external; + +defined('MOODLE_INTERNAL') || die(); + +use core_courseformat\stateactions; +use core_courseformat\stateupdates; + +global $CFG; +require_once($CFG->dirroot . '/webservice/tests/helpers.php'); + +/** + * Tests for the delete section test class. + * + * @package core_courseformat + * @copyright 2025 Laurent David + * @category test + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \core_courseformat\stateactions + */ +final class delete_section_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_theunittestdelete.php'); + } + + /** + * Test the webservice can execute the section_delete action. + * + * @covers ::section_delete + * @dataProvider section_delete_provider + * @param int $sectionum + * @param string $format + * @param array $formatoptions + * @param int $expectedsectionum + * + * @throws \moodle_exception + */ + public function test_delete_section(int $sectionum, string $format, array $formatoptions, int $expectedsectionum): void { + $this->resetAfterTest(); + + $course = + $this->getDataGenerator()->create_course(['numsections' => $sectionum, 'format' => $format, ...$formatoptions]); + $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher'); + // Execute the method. + $courseformat = course_get_format($course->id); + $updates = new stateupdates($courseformat); + $modinfo = get_fast_modinfo($course); + $sections = $modinfo->get_section_info_all(); + $sectionsid = array_map(function ($section) { + return $section->id; + }, $sections); + $actions = new stateactions(); + $this->setUser($teacher); + $actions->section_delete( + $updates, + $course, + $sectionsid + ); + // Check result. + $modinfo = get_fast_modinfo($course); + $sections = $modinfo->get_section_info_all(); + $this->assertCount($expectedsectionum, $sections); + if ($format == 'theunittestdelete') { + $this->assertDebuggingCalled(); + } + } + + /** + * Data provider for the test_delete_section method. + * + * @return array + */ + public static function section_delete_provider(): array { + return [ + 'format topic' => [ + 'sectionum' => 4, + 'format' => 'topics', + 'formatoptions' => [], + 'expectedsectionum' => 1, + ], + 'format theunittestdelete' => [ + 'sectionum' => 4, + 'format' => 'theunittestdelete', + 'formatoptions' => [], + 'expectedsectionum' => 5, + ], + 'format theunittestdelete can delete' => [ + 'sectionum' => 4, + 'format' => 'theunittestdelete', + 'formatoptions' => ['can_delete_sections' => true], + 'expectedsectionum' => 1, + ], + ]; + } +} diff --git a/course/format/tests/fixtures/format_theunittestdelete.php b/course/format/tests/fixtures/format_theunittestdelete.php new file mode 100644 index 00000000000..edfae4a4082 --- /dev/null +++ b/course/format/tests/fixtures/format_theunittestdelete.php @@ -0,0 +1,63 @@ +. + +defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__ . '/format_theunittest.php'); + +/** + * Fixture for fake course format testing course format API. + * + * @package core_courseformat + * @copyright 2025 Laurent David + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class format_theunittestdelete extends format_theunittest { + + /** + * Definitions of the additional options that format uses + * + * @param bool $foreditform + * @return array of options + */ + public function course_format_options($foreditform = false) { + static $courseformatoptions = false; + if ($courseformatoptions === false) { + $courseformatoptions = parent::course_format_options(true); + $courseformatoptionsadditional = [ + 'can_delete_sections' => [ + 'default' => false, + 'type' => PARAM_BOOL, + ], + ]; + $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsadditional); + } + return $courseformatoptions; + } + + /** + * Whether this format allows to delete sections + * + * Here for test purpose we just can delete one section every two sections + * + * Do not call this function directly, instead use course_can_delete_section() + * + * @param int|stdClass|section_info $section + * @return bool + */ + public function can_delete_section($section) { + return $this->get_format_options()['can_delete_sections'] ?? false; + } +}