MDL-80715 core_courseformat: new section update action
This commit is contained in:
committed by
ferranrecio
parent
757be30c39
commit
9d3bbe6c02
@@ -18,6 +18,7 @@ namespace core_courseformat\local;
|
||||
|
||||
use section_info;
|
||||
use stdClass;
|
||||
use core\event\course_module_updated;
|
||||
use core\event\course_section_deleted;
|
||||
|
||||
/**
|
||||
@@ -336,4 +337,95 @@ class sectionactions extends baseactions {
|
||||
$sectioninfo = $this->get_section_info($sectioninfo->id);
|
||||
return $this->delete_format_data($sectioninfo, $forcedeleteifnotempty, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a course section.
|
||||
*
|
||||
* @param section_info $sectioninfo the section info or database record to update.
|
||||
* @param array|stdClass $fields the fields to update.
|
||||
* @return bool whether section was updated
|
||||
*/
|
||||
public function update(section_info $sectioninfo, array|stdClass $fields): bool {
|
||||
global $DB;
|
||||
|
||||
$courseid = $this->course->id;
|
||||
|
||||
// Some fields can not be updated using this method.
|
||||
$fields = array_diff_key((array) $fields, array_flip(['id', 'course', 'section', 'sequence']));
|
||||
if (array_key_exists('name', $fields) && \core_text::strlen($fields['name']) > 255) {
|
||||
throw new \moodle_exception('maximumchars', 'moodle', '', 255);
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields['id'] = $sectioninfo->id;
|
||||
$fields['timemodified'] = time();
|
||||
$DB->update_record('course_sections', $fields);
|
||||
|
||||
// We need to update the section cache before the format options are updated.
|
||||
\course_modinfo::purge_course_section_cache_by_id($courseid, $sectioninfo->id);
|
||||
rebuild_course_cache($courseid, false, true);
|
||||
|
||||
course_get_format($courseid)->update_section_format_options($fields);
|
||||
|
||||
$event = \core\event\course_section_updated::create(
|
||||
[
|
||||
'objectid' => $sectioninfo->id,
|
||||
'courseid' => $courseid,
|
||||
'context' => \context_course::instance($courseid),
|
||||
'other' => ['sectionnum' => $sectioninfo->section],
|
||||
]
|
||||
);
|
||||
$event->trigger();
|
||||
|
||||
if (isset($fields['visible'])) {
|
||||
$this->transfer_visibility_to_cms($sectioninfo, (bool) $fields['visible']);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer the visibility of the section to the course modules.
|
||||
*
|
||||
* @param section_info $sectioninfo the section info or database record to update.
|
||||
* @param bool $visibility the new visibility of the section.
|
||||
*/
|
||||
protected function transfer_visibility_to_cms(section_info $sectioninfo, bool $visibility): void {
|
||||
global $DB;
|
||||
|
||||
if (empty($sectioninfo->sequence) || $visibility == (bool) $sectioninfo->visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
$modules = explode(',', $sectioninfo->sequence);
|
||||
$cmids = [];
|
||||
foreach ($modules as $moduleid) {
|
||||
$cm = get_coursemodule_from_id(null, $moduleid, $this->course->id);
|
||||
if (!$cm) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$modupdated = false;
|
||||
if ($visibility) {
|
||||
// As we unhide the section, we use the previously saved visibility stored in visibleold.
|
||||
$modupdated = set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage, false);
|
||||
} else {
|
||||
// We hide the section, so we hide the module but we store the original state in visibleold.
|
||||
$modupdated = set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage, false);
|
||||
if ($modupdated) {
|
||||
$DB->set_field('course_modules', 'visibleold', $cm->visible, ['id' => $moduleid]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($modupdated) {
|
||||
$cmids[] = $cm->id;
|
||||
course_module_updated::create_from_cm($cm)->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
\course_modinfo::purge_course_modules_cache($this->course->id, $cmids);
|
||||
rebuild_course_cache($this->course->id, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,4 +541,325 @@ class sectionactions_test extends \advanced_testcase {
|
||||
}
|
||||
$this->assertEquals(3, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test section update method.
|
||||
*
|
||||
* @covers ::update
|
||||
* @dataProvider update_provider
|
||||
* @param string $fieldname the name of the field to update
|
||||
* @param int|string $value the value to set
|
||||
* @param int|string $expected the expected value after the update ('=' to specify the same value as original field)
|
||||
* @param bool $expectexception if the method should throw an exception
|
||||
*/
|
||||
public function test_update(
|
||||
string $fieldname,
|
||||
int|string $value,
|
||||
int|string $expected,
|
||||
bool $expectexception
|
||||
): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['format' => 'topics', 'numsections' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
$section = get_fast_modinfo($course)->get_section_info(1);
|
||||
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$this->assertNotEquals($value, $sectionrecord->$fieldname);
|
||||
$this->assertNotEquals($value, $section->$fieldname);
|
||||
|
||||
if ($expectexception) {
|
||||
$this->expectException(\moodle_exception::class);
|
||||
}
|
||||
|
||||
if ($expected === '=') {
|
||||
$expected = $section->$fieldname;
|
||||
}
|
||||
|
||||
$sectionactions = new sectionactions($course);
|
||||
$sectionactions->update($section, [$fieldname => $value]);
|
||||
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$this->assertEquals($expected, $sectionrecord->$fieldname);
|
||||
|
||||
$section = get_fast_modinfo($course)->get_section_info(1);
|
||||
$this->assertEquals($expected, $section->$fieldname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_update.
|
||||
* @return array
|
||||
*/
|
||||
public static function update_provider(): array {
|
||||
return [
|
||||
'Id will not be updated' => [
|
||||
'fieldname' => 'id',
|
||||
'value' => -1,
|
||||
'expected' => '=',
|
||||
'expectexception' => false,
|
||||
],
|
||||
'Course will not be updated' => [
|
||||
'fieldname' => 'course',
|
||||
'value' => -1,
|
||||
'expected' => '=',
|
||||
'expectexception' => false,
|
||||
],
|
||||
'Section number will not be updated' => [
|
||||
'fieldname' => 'section',
|
||||
'value' => -1,
|
||||
'expected' => '=',
|
||||
'expectexception' => false,
|
||||
],
|
||||
'Sequence will be updated' => [
|
||||
'fieldname' => 'name',
|
||||
'value' => 'new name',
|
||||
'expected' => 'new name',
|
||||
'expectexception' => false,
|
||||
],
|
||||
'Summary can be updated' => [
|
||||
'fieldname' => 'summary',
|
||||
'value' => 'new summary',
|
||||
'expected' => 'new summary',
|
||||
'expectexception' => false,
|
||||
],
|
||||
'Visible can be updated' => [
|
||||
'fieldname' => 'visible',
|
||||
'value' => 0,
|
||||
'expected' => 0,
|
||||
'expectexception' => false,
|
||||
],
|
||||
'component can be updated' => [
|
||||
'fieldname' => 'component',
|
||||
'value' => 'mod_assign',
|
||||
'expected' => 'mod_assign',
|
||||
'expectexception' => false,
|
||||
],
|
||||
'itemid can be updated' => [
|
||||
'fieldname' => 'itemid',
|
||||
'value' => 1,
|
||||
'expected' => 1,
|
||||
'expectexception' => false,
|
||||
],
|
||||
'Long names throws and exception' => [
|
||||
'fieldname' => 'name',
|
||||
'value' => str_repeat('a', 256),
|
||||
'expected' => '=',
|
||||
'expectexception' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test section update method updating several values at once.
|
||||
*
|
||||
* @covers ::update
|
||||
*/
|
||||
public function test_update_multiple_fields(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['format' => 'topics', 'numsections' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
$section = get_fast_modinfo($course)->get_section_info(1);
|
||||
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$this->assertEquals(1, $sectionrecord->visible);
|
||||
$this->assertNull($section->name);
|
||||
|
||||
$sectionactions = new sectionactions($course);
|
||||
$sectionactions->update($section, ['name' => 'New name', 'visible' => 0]);
|
||||
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$this->assertEquals('New name', $sectionrecord->name);
|
||||
$this->assertEquals(0, $sectionrecord->visible);
|
||||
|
||||
$section = get_fast_modinfo($course)->get_section_info(1);
|
||||
$this->assertEquals('New name', $section->name);
|
||||
$this->assertEquals(0, $section->visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updating a section trigger a course section update log event.
|
||||
*
|
||||
* @covers ::update
|
||||
*/
|
||||
public function test_course_section_updated_event(): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['format' => 'topics', 'numsections' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
$section = get_fast_modinfo($course)->get_section_info(1);
|
||||
|
||||
$sink = $this->redirectEvents();
|
||||
|
||||
$sectionactions = new sectionactions($course);
|
||||
$sectionactions->update($section, ['name' => 'New name', 'visible' => 0]);
|
||||
|
||||
$events = $sink->get_events();
|
||||
$event = reset($events);
|
||||
|
||||
// Check that the event data is valid.
|
||||
$this->assertInstanceOf('\core\event\course_section_updated', $event);
|
||||
$data = $event->get_data();
|
||||
$this->assertEquals(\context_course::instance($course->id), $event->get_context());
|
||||
$this->assertEquals($section->id, $data['objectid']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test section update change the modified date.
|
||||
*
|
||||
* @covers ::update
|
||||
*/
|
||||
public function test_update_time_modified(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the course with sections.
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['format' => 'topics', 'numsections' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
$section = get_fast_modinfo($course)->get_section_info(1);
|
||||
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$oldtimemodified = $sectionrecord->timemodified;
|
||||
|
||||
$sectionactions = new sectionactions($course);
|
||||
|
||||
// Ensuring that the section update occurs at a different timestamp.
|
||||
$this->waitForSecond();
|
||||
|
||||
// The timemodified should only be updated if the section is actually updated.
|
||||
$result = $sectionactions->update($section, []);
|
||||
$this->assertFalse($result);
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$this->assertEquals($oldtimemodified, $sectionrecord->timemodified);
|
||||
|
||||
// Now update something to prove timemodified changes.
|
||||
$result = $sectionactions->update($section, ['name' => 'New name']);
|
||||
$this->assertTrue($result);
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$this->assertGreaterThan($oldtimemodified, $sectionrecord->timemodified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test section updating visibility will hide or show section activities.
|
||||
*
|
||||
* @covers ::update
|
||||
*/
|
||||
public function test_update_hide_section_activities(): void {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create 4 activities (visible, visible, hidden, hidden).
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['format' => 'topics', 'numsections' => 1],
|
||||
['createsections' => true]
|
||||
);
|
||||
$activity1 = $this->getDataGenerator()->create_module(
|
||||
'assign',
|
||||
['course' => $course->id, 'section' => 1]
|
||||
);
|
||||
$activity2 = $this->getDataGenerator()->create_module(
|
||||
'assign',
|
||||
['course' => $course->id, 'section' => 1]
|
||||
);
|
||||
$activity3 = $this->getDataGenerator()->create_module(
|
||||
'assign',
|
||||
['course' => $course->id, 'section' => 1, 'visible' => 0]
|
||||
);
|
||||
$activity4 = $this->getDataGenerator()->create_module(
|
||||
'assign',
|
||||
['course' => $course->id, 'section' => 1, 'visible' => 0]
|
||||
);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm1 = $modinfo->get_cm($activity1->cmid);
|
||||
$cm2 = $modinfo->get_cm($activity2->cmid);
|
||||
$cm3 = $modinfo->get_cm($activity3->cmid);
|
||||
$cm4 = $modinfo->get_cm($activity4->cmid);
|
||||
$this->assertEquals(1, $cm1->visible);
|
||||
$this->assertEquals(1, $cm2->visible);
|
||||
$this->assertEquals(0, $cm3->visible);
|
||||
$this->assertEquals(0, $cm4->visible);
|
||||
|
||||
$sectionactions = new sectionactions($course);
|
||||
|
||||
// Validate hidding section hides all activities.
|
||||
$section = $modinfo->get_section_info(1);
|
||||
$sectionactions->update($section, ['visible' => 0]);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm1 = $modinfo->get_cm($activity1->cmid);
|
||||
$cm2 = $modinfo->get_cm($activity2->cmid);
|
||||
$cm3 = $modinfo->get_cm($activity3->cmid);
|
||||
$cm4 = $modinfo->get_cm($activity4->cmid);
|
||||
$this->assertEquals(0, $cm1->visible);
|
||||
$this->assertEquals(0, $cm2->visible);
|
||||
$this->assertEquals(0, $cm3->visible);
|
||||
$this->assertEquals(0, $cm4->visible);
|
||||
|
||||
// Validate showing the section restores the previous visibility.
|
||||
$section = $modinfo->get_section_info(1);
|
||||
$sectionactions->update($section, ['visible' => 1]);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm1 = $modinfo->get_cm($activity1->cmid);
|
||||
$cm2 = $modinfo->get_cm($activity2->cmid);
|
||||
$cm3 = $modinfo->get_cm($activity3->cmid);
|
||||
$cm4 = $modinfo->get_cm($activity4->cmid);
|
||||
$this->assertEquals(1, $cm1->visible);
|
||||
$this->assertEquals(1, $cm2->visible);
|
||||
$this->assertEquals(0, $cm3->visible);
|
||||
$this->assertEquals(0, $cm4->visible);
|
||||
|
||||
// Swap two activities visibility to alter visible values.
|
||||
set_coursemodule_visible($cm2->id, 0, 0, true);
|
||||
set_coursemodule_visible($cm4->id, 1, 1, true);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm1 = $modinfo->get_cm($activity1->cmid);
|
||||
$cm2 = $modinfo->get_cm($activity2->cmid);
|
||||
$cm3 = $modinfo->get_cm($activity3->cmid);
|
||||
$cm4 = $modinfo->get_cm($activity4->cmid);
|
||||
$this->assertEquals(1, $cm1->visible);
|
||||
$this->assertEquals(0, $cm2->visible);
|
||||
$this->assertEquals(0, $cm3->visible);
|
||||
$this->assertEquals(1, $cm4->visible);
|
||||
|
||||
// Validate hidding the section again.
|
||||
$section = $modinfo->get_section_info(1);
|
||||
$sectionactions->update($section, ['visible' => 0]);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm1 = $modinfo->get_cm($activity1->cmid);
|
||||
$cm2 = $modinfo->get_cm($activity2->cmid);
|
||||
$cm3 = $modinfo->get_cm($activity3->cmid);
|
||||
$cm4 = $modinfo->get_cm($activity4->cmid);
|
||||
$this->assertEquals(0, $cm1->visible);
|
||||
$this->assertEquals(0, $cm2->visible);
|
||||
$this->assertEquals(0, $cm3->visible);
|
||||
$this->assertEquals(0, $cm4->visible);
|
||||
|
||||
// Validate showing the section once more to check previous state is restored.
|
||||
$section = $modinfo->get_section_info(1);
|
||||
$sectionactions->update($section, ['visible' => 1]);
|
||||
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm1 = $modinfo->get_cm($activity1->cmid);
|
||||
$cm2 = $modinfo->get_cm($activity2->cmid);
|
||||
$cm3 = $modinfo->get_cm($activity3->cmid);
|
||||
$cm4 = $modinfo->get_cm($activity4->cmid);
|
||||
$this->assertEquals(1, $cm1->visible);
|
||||
$this->assertEquals(0, $cm2->visible);
|
||||
$this->assertEquals(0, $cm3->visible);
|
||||
$this->assertEquals(1, $cm4->visible);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-55
@@ -1277,70 +1277,21 @@ function course_delete_section_async($section, $forcedeleteifnotempty = true) {
|
||||
*
|
||||
* This function does not check permissions or clean values - this has to be done prior to calling it.
|
||||
*
|
||||
* @param int|stdClass $course
|
||||
* @param int|stdClass $courseorid
|
||||
* @param stdClass $section record from course_sections table - it will be updated with the new values
|
||||
* @param array|stdClass $data
|
||||
*/
|
||||
function course_update_section($course, $section, $data) {
|
||||
global $DB;
|
||||
function course_update_section($courseorid, $section, $data) {
|
||||
$sectioninfo = get_fast_modinfo($courseorid)->get_section_info_by_id($section->id);
|
||||
formatactions::section($courseorid)->update($sectioninfo, $data);
|
||||
|
||||
$courseid = (is_object($course)) ? $course->id : (int)$course;
|
||||
|
||||
// Some fields can not be updated using this method.
|
||||
$data = array_diff_key((array)$data, array('id', 'course', 'section', 'sequence'));
|
||||
$changevisibility = (array_key_exists('visible', $data) && (bool)$data['visible'] != (bool)$section->visible);
|
||||
if (array_key_exists('name', $data) && \core_text::strlen($data['name']) > 255) {
|
||||
throw new moodle_exception('maximumchars', 'moodle', '', 255);
|
||||
}
|
||||
|
||||
// Update record in the DB and course format options.
|
||||
$data['id'] = $section->id;
|
||||
$data['timemodified'] = time();
|
||||
$DB->update_record('course_sections', $data);
|
||||
// Invalidate the section cache by given section id.
|
||||
course_modinfo::purge_course_section_cache_by_id($courseid, $section->id);
|
||||
rebuild_course_cache($courseid, false, true);
|
||||
course_get_format($courseid)->update_section_format_options($data);
|
||||
|
||||
// Update fields of the $section object.
|
||||
// Update $section object fields (for legacy compatibility).
|
||||
$data = array_diff_key((array) $data, array_flip(['id', 'course', 'section', 'sequence']));
|
||||
foreach ($data as $key => $value) {
|
||||
if (property_exists($section, $key)) {
|
||||
$section->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger an event for course section update.
|
||||
$event = \core\event\course_section_updated::create(
|
||||
array(
|
||||
'objectid' => $section->id,
|
||||
'courseid' => $courseid,
|
||||
'context' => context_course::instance($courseid),
|
||||
'other' => array('sectionnum' => $section->section)
|
||||
)
|
||||
);
|
||||
$event->trigger();
|
||||
|
||||
// If section visibility was changed, hide the modules in this section too.
|
||||
if ($changevisibility && !empty($section->sequence)) {
|
||||
$modules = explode(',', $section->sequence);
|
||||
$cmids = [];
|
||||
foreach ($modules as $moduleid) {
|
||||
if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
|
||||
$cmids[] = $cm->id;
|
||||
if ($data['visible']) {
|
||||
// As we unhide the section, we use the previously saved visibility stored in visibleold.
|
||||
set_coursemodule_visible($moduleid, $cm->visibleold, $cm->visibleoncoursepage, false);
|
||||
} else {
|
||||
// We hide the section, so we hide the module but we store the original state in visibleold.
|
||||
set_coursemodule_visible($moduleid, 0, $cm->visibleoncoursepage, false);
|
||||
$DB->set_field('course_modules', 'visibleold', $cm->visible, ['id' => $moduleid]);
|
||||
}
|
||||
\core\event\course_module_updated::create_from_cm($cm)->trigger();
|
||||
}
|
||||
}
|
||||
\course_modinfo::purge_course_modules_cache($courseid, $cmids);
|
||||
rebuild_course_cache($courseid, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -782,19 +782,27 @@ class courselib_test extends advanced_testcase {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create the course with sections.
|
||||
$course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
|
||||
$sections = $DB->get_records('course_sections', array('course' => $course->id));
|
||||
$course = $this->getDataGenerator()->create_course(
|
||||
['numsections' => 10],
|
||||
['createsections' => true]
|
||||
);
|
||||
$sections = $DB->get_records('course_sections', ['course' => $course->id]);
|
||||
|
||||
// Get the last section's time modified value.
|
||||
$section = array_pop($sections);
|
||||
$oldtimemodified = $section->timemodified;
|
||||
|
||||
// Update the section.
|
||||
$this->waitForSecond(); // Ensuring that the section update occurs at a different timestamp.
|
||||
course_update_section($course, $section, array());
|
||||
// Ensuring that the section update occurs at a different timestamp.
|
||||
$this->waitForSecond();
|
||||
|
||||
// Check that the time has changed.
|
||||
$section = $DB->get_record('course_sections', array('id' => $section->id));
|
||||
// The timemodified should only be updated if the section is actually updated.
|
||||
course_update_section($course, $section, []);
|
||||
$sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$this->assertEquals($oldtimemodified, $sectionrecord->timemodified);
|
||||
|
||||
// Now update something to prove timemodified changes.
|
||||
course_update_section($course, $section, ['name' => 'New name']);
|
||||
$section = $DB->get_record('course_sections', ['id' => $section->id]);
|
||||
$newtimemodified = $section->timemodified;
|
||||
$this->assertGreaterThan($oldtimemodified, $newtimemodified);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user