MDL-40600 course: Add ability to duplicate section
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user