Skip to content

Commit

Permalink
Merge branch 'MDL-40600-master-duplicate_section' of https://github.c…
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Mar 2, 2023
2 parents 6a36f59 + 01d6ba0 commit 0c8b374
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 11 deletions.
36 changes: 36 additions & 0 deletions course/format/classes/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -1727,4 +1727,40 @@ public function delete_format_data() {
// 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.

if (!empty($originalsection->name)) {
$newsection->name = get_string('duplicatedsection', 'moodle', $originalsection->name);
} else {
$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);
}
}
11 changes: 11 additions & 0 deletions course/format/classes/output/local/content/section/controlmenu.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ public function section_control_items() {
'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) {
Expand Down
39 changes: 39 additions & 0 deletions course/format/tests/base_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,45 @@ public function delete_format_data_provider(): array {
]
];
}

/**
* 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);
}
}

/**
Expand Down
34 changes: 34 additions & 0 deletions course/format/tests/behat/duplicate_section.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@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
When I click on "Duplicate" "link" in the "Topic 1" "section"
Then I should see "Activity sample 2" in the "Topic 2" "section"

@javascript
Scenario: Duplicate a named section
Given I set the field "Edit topic name" in the "Topic 1" "section" to "New name"
And I should see "New name" in the "New name" "section"
When I open section "1" edit menu
And I click on "Duplicate" "link" in the "New name" "section"
Then I should see "Activity sample 2" in the "New name (copy)" "section"
1 change: 1 addition & 0 deletions course/format/upgrade.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 19 additions & 11 deletions course/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -3369,6 +3369,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
Expand All @@ -3378,7 +3380,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');
Expand Down Expand Up @@ -3454,16 +3456,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]);

$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]);
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', ['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.
Expand Down
3 changes: 3 additions & 0 deletions course/upgrade.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ information provided here is intended especially for developers.
=== 4.2 ===
* course/mod.php now accepts parameter beforemod for adding course modules. It contains the course module id
of an existing course module. The new module is inserted before this module.
* The function duplicate_module() now has two new optional parameters:
- $sectionid to specify section the duplicated course module is placed in
- $changename to disable changing the name of the course module using the 'duplicatedmodule' lang string

=== 4.1 ===
* The function course_modchooser() has been finally deprecated and can not be used anymore. Please use
Expand Down
7 changes: 7 additions & 0 deletions course/view.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions lang/en/moodle.php
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,7 @@
$string['droptoupload'] = 'Drop files here to upload';
$string['duplicate'] = 'Duplicate';
$string['duplicatedmodule'] = '{$a} (copy)';
$string['duplicatedsection'] = '{$a} (copy)';
$string['edhelpaspellpath'] = 'To use spell-checking within the editor, you MUST have <strong>aspell 0.50</strong> or later installed on your server, and you must specify the correct path to access the aspell binary. On Unix/Linux systems, this path is usually <strong>/usr/bin/aspell</strong>, but it might be something else.';
$string['edhelpbgcolor'] = 'Define the edit area\'s background color.<br />Valid values are, for example: #FFFFFF or white';
$string['edhelpcleanword'] = 'This setting enables or disables Word-specific format filtering.';
Expand Down

0 comments on commit 0c8b374

Please sign in to comment.