MDL-40600 course: Add ability to duplicate section

This commit is contained in:
Alex Morris
2023-02-27 09:10:36 +13:00
parent 9ee4f8db8b
commit f1c4cc73df
7 changed files with 134 additions and 10 deletions
+32
View File
@@ -1735,4 +1735,36 @@ abstract class base {
// By default, formats store some most display specifics in a user preference.
$DB->delete_records('user_preferences', ['name' => 'coursesectionspreferences_' . $course->id]);
}
/**
* Duplicate a section
*
* @param section_info $originalsection The section to be duplicated
* @return section_info The new duplicated section
* @since Moodle 4.2
*/
public function duplicate_section(section_info $originalsection): section_info {
if (!$this->uses_sections()) {
throw new moodle_exception('sectionsnotsupported', 'core_courseformat');
}
$course = $this->get_course();
$oldsectioninfo = get_fast_modinfo($course)->get_section_info($originalsection->section);
$newsection = course_create_section($course, $oldsectioninfo->section + 1); // Place new section after existing one.
$newsection->name = $originalsection->name;
$newsection->summary = $originalsection->summary;
$newsection->summaryformat = $originalsection->summaryformat;
$newsection->visible = $originalsection->visible;
$newsection->availability = $originalsection->availability;
course_update_section($course, $newsection, $newsection);
$modinfo = $this->get_modinfo();
foreach ($modinfo->sections[$originalsection->section] as $modnumber) {
$originalcm = $modinfo->cms[$modnumber];
duplicate_module($course, $originalcm, $newsection->id, false);
}
return get_fast_modinfo($course)->get_section_info_by_id($newsection->id);
}
}
@@ -152,6 +152,17 @@ class controlmenu implements named_templatable, renderable {
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon edit'],
];
$duplicatesectionurl = clone($baseurl);
$duplicatesectionurl->param('section', $section->section);
$duplicatesectionurl->param('duplicatesection', $section->section);
$controls['duplicate'] = [
'url' => $duplicatesectionurl,
'icon' => 't/copy',
'name' => get_string('duplicate'),
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon duplicate'],
];
}
if ($section->section) {
+39
View File
@@ -416,6 +416,45 @@ class base_test extends advanced_testcase {
]
];
}
/**
* Test duplicate_section()
* @covers ::duplicate_section
*/
public function test_duplicate_section() {
global $DB;
$this->setAdminUser();
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$format = course_get_format($course);
$originalsection = $DB->get_record('course_sections', ['course' => $course->id, 'section' => 1], '*', MUST_EXIST);
$generator->create_module('page', ['course' => $course, 'section' => $originalsection->section]);
$generator->create_module('page', ['course' => $course, 'section' => $originalsection->section]);
$generator->create_module('page', ['course' => $course, 'section' => $originalsection->section]);
$originalmodcount = $DB->count_records('course_modules', ['course' => $course->id, 'section' => $originalsection->id]);
$this->assertEquals(3, $originalmodcount);
$modinfo = get_fast_modinfo($course);
$sectioninfo = $modinfo->get_section_info($originalsection->section, MUST_EXIST);
$newsection = $format->duplicate_section($sectioninfo);
// Verify properties are the same.
foreach ($originalsection as $prop => $value) {
if ($prop == 'id' || $prop == 'sequence' || $prop == 'section' || $prop == 'timemodified') {
continue;
}
$this->assertEquals($value, $newsection->$prop);
}
$newmodcount = $DB->count_records('course_modules', ['course' => $course->id, 'section' => $newsection->id]);
$this->assertEquals($originalmodcount, $newmodcount);
}
}
/**
@@ -0,0 +1,26 @@
@core @core_course @core_courseformat
Feature: Duplicate a section
In order to set up my course contents quickly
As a teacher
I need to duplicate sections inside the same course
Background:
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| enablecompletion | 1 |
| numsections | 4 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 |
| book | Activity sample 2 | Test book description | C1 | sample2 | 1 |
| choice | Activity sample 3 | Test choice description | C1 | sample3 | 2 |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
@javascript
Scenario: Duplicate section
Given I open section "1" edit menu
And I click on "Duplicate" "link" in the "Topic 1" "section"
Then I should see "Activity sample 2" in the "Topic 2" "section"
+1
View File
@@ -16,6 +16,7 @@ Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
- Renderer method: core_courseformat\output\section_renderer::bulk_editing_button
- New overridable checkboxes: content/cm/bulkselect.mustache and content/section/bulkselect.mustache
* Plugins can use the CSS class "bulk-hidden" to hide elements when the bulk editing is enabled.
* New core_courseformat\base::duplicate_section method to duplicate course sections and their modules within a course.
=== 4.1 ===
* New \core_courseformat\stateupdates methods add_section_remove() and add_cm_remove() have been added to replace
+18 -10
View File
@@ -3357,6 +3357,8 @@ function mod_duplicate_activity($course, $cm, $sr = null) {
*
* @param object $course course object.
* @param object $cm course module object to be duplicated.
* @param int $sectionid section ID new course module will be placed in.
* @param bool $changename updates module name with text from duplicatedmodule lang string.
* @since Moodle 2.8
*
* @throws Exception
@@ -3366,7 +3368,7 @@ function mod_duplicate_activity($course, $cm, $sr = null) {
*
* @return cm_info|null cminfo object if we sucessfully duplicated the mod and found the new cm.
*/
function duplicate_module($course, $cm) {
function duplicate_module($course, $cm, int $sectionid = null, bool $changename = true): ?cm_info {
global $CFG, $DB, $USER;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
@@ -3442,16 +3444,22 @@ function duplicate_module($course, $cm) {
// Proceed with activity renaming before everything else. We don't use APIs here to avoid
// triggering a lot of create/update duplicated events.
$newcm = get_coursemodule_from_id($cm->modname, $newcmid, $cm->course);
// Add ' (copy)' to duplicates. Note we don't cleanup or validate lengths here. It comes
// from original name that was valid, so the copy should be too.
$newname = get_string('duplicatedmodule', 'moodle', $newcm->name);
$DB->set_field($cm->modname, 'name', $newname, ['id' => $newcm->instance]);
if ($changename) {
// Add ' (copy)' to duplicates. Note we don't cleanup or validate lengths here. It comes
// from original name that was valid, so the copy should be too.
$newname = get_string('duplicatedmodule', 'moodle', $newcm->name);
$DB->set_field($cm->modname, 'name', $newname, ['id' => $newcm->instance]);
}
$section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course));
$modarray = explode(",", trim($section->sequence));
$cmindex = array_search($cm->id, $modarray);
if ($cmindex !== false && $cmindex < count($modarray) - 1) {
moveto_module($newcm, $section, $modarray[$cmindex + 1]);
$section = $DB->get_record('course_sections', ['id' => $sectionid ?? $cm->section, 'course' => $cm->course]);
if (isset($sectionid)) {
moveto_module($newcm, $section);
} else {
$modarray = explode(",", trim($section->sequence));
$cmindex = array_search($cm->id, $modarray);
if ($cmindex !== false && $cmindex < count($modarray) - 1) {
moveto_module($newcm, $section, $modarray[$cmindex + 1]);
}
}
// Update calendar events with the duplicated module.
+7
View File
@@ -13,6 +13,7 @@
$edit = optional_param('edit', -1, PARAM_BOOL);
$hide = optional_param('hide', 0, PARAM_INT);
$show = optional_param('show', 0, PARAM_INT);
$duplicatesection = optional_param('duplicatesection', 0, PARAM_INT);
$idnumber = optional_param('idnumber', '', PARAM_RAW);
$sectionid = optional_param('sectionid', 0, PARAM_INT);
$section = optional_param('section', 0, PARAM_INT);
@@ -187,6 +188,12 @@
}
}
if (!empty($section) && !empty($coursesections) && !empty($duplicatesection)
&& has_capability('moodle/course:update', $context) && confirm_sesskey()) {
$newsection = $format->duplicate_section($coursesections);
redirect(course_get_url($course, $newsection->section));
}
if (!empty($section) && !empty($move) &&
has_capability('moodle/course:movesections', $context) && confirm_sesskey()) {
$destsection = $section + $move;