diff --git a/course/editsection.php b/course/editsection.php index 6b8ce0a5cfb..b1e01dbd7a4 100644 --- a/course/editsection.php +++ b/course/editsection.php @@ -29,6 +29,7 @@ require_once($CFG->libdir . '/formslib.php'); $id = required_param('id', PARAM_INT); // course_sections.id $sectionreturn = optional_param('sr', 0, PARAM_INT); +$deletesection = optional_param('delete', 0, PARAM_BOOL); $PAGE->set_url('/course/editsection.php', array('id'=>$id, 'sr'=> $sectionreturn)); @@ -43,6 +44,41 @@ require_capability('moodle/course:update', $context); // Get section_info object with all availability options. $sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum); +// Deleting the section. +if ($deletesection) { + $cancelurl = course_get_url($course, $sectioninfo, array('sr' => $sectionreturn)); + if (course_can_delete_section($course, $sectioninfo)) { + $confirm = optional_param('confirm', false, PARAM_BOOL) && confirm_sesskey(); + if ($confirm) { + course_delete_section($course, $sectioninfo, true); + $courseurl = course_get_url($course, 0, array('sr' => $sectionreturn)); + redirect($courseurl); + } else { + if (get_string_manager()->string_exists('deletesection', 'format_' . $course->format)) { + $strdelete = get_string('deletesection', 'format_' . $course->format); + } else { + $strdelete = get_string('deletesection'); + } + $PAGE->navbar->add($strdelete); + $PAGE->set_title($strdelete); + $PAGE->set_heading($course->fullname); + echo $OUTPUT->header(); + echo $OUTPUT->box_start('noticebox'); + $optionsyes = array('id' => $id, 'confirm' => 1, 'delete' => 1, 'sesskey' => sesskey()); + $deleteurl = new moodle_url('/course/editsection.php', $optionsyes); + $formcontinue = new single_button($deleteurl, get_string('yes')); + $formcancel = new single_button($cancelurl, get_string('no'), 'get'); + echo $OUTPUT->confirm(get_string('confirmdeletesection', '', + get_section_name($course, $sectioninfo)), $formcontinue, $formcancel); + echo $OUTPUT->box_end(); + echo $OUTPUT->footer(); + exit; + } + } else { + notice(get_string('nopermissions', 'error', get_string('deletesection')), $cancelurl); + } +} + $editoroptions = array('context'=>$context ,'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true); $mform = course_get_format($course->id)->editsection_form($PAGE->url, array('cs' => $sectioninfo, 'editoroptions' => $editoroptions)); diff --git a/course/format/lib.php b/course/format/lib.php index eaeaff7efae..51709b93efc 100644 --- a/course/format/lib.php +++ b/course/format/lib.php @@ -935,6 +935,85 @@ abstract class format_base { */ public function section_get_available_hook(section_info $section, &$available, &$availableinfo) { } + + /** + * Whether this format allows to delete sections + * + * If format supports deleting sections it is also recommended to define language string + * 'deletesection' inside the format. + * + * Do not call this function directly, instead use {@link course_can_delete_section()} + * + * @param int|stdClass|section_info $section + * @return bool + */ + public function can_delete_section($section) { + return false; + } + + /** + * Deletes a section + * + * Do not call this function directly, instead call {@link course_delete_section()} + * + * @param int|stdClass|section_info $section + * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it. + * @return bool whether section was deleted + */ + public function delete_section($section, $forcedeleteifnotempty = false) { + global $DB; + if (!$this->uses_sections()) { + // Not possible to delete section if sections are not used. + return false; + } + if (!is_object($section)) { + $section = $DB->get_record('course_sections', array('course' => $this->get_courseid(), 'section' => $section), + 'id,section,sequence'); + } + if (!$section || !$section->section) { + // Not possible to delete 0-section. + return false; + } + + if (!$forcedeleteifnotempty && !empty($section->sequence)) { + return false; + } + + $course = $this->get_course(); + + // Remove the marker if it points to this section. + if ($section->section == $course->marker) { + course_set_marker($course->id, 0); + } + + $lastsection = $DB->get_field_sql('SELECT max(section) from {course_sections} + WHERE course = ?', array($course->id)); + + // Find out if we need to descrease the 'numsections' property later. + $courseformathasnumsections = array_key_exists('numsections', + $this->get_format_options()); + $decreasenumsections = $courseformathasnumsections && ($section->section <= $course->numsections); + + // Move the section to the end. + move_section_to($course, $section->section, $lastsection, true); + + // Delete all modules from the section. + foreach (preg_split('/,/', $section->sequence, -1, PREG_SPLIT_NO_EMPTY) as $cmid) { + course_delete_module($cmid); + } + + // Delete section and it's format options. + $DB->delete_records('course_format_options', array('sectionid' => $section->id)); + $DB->delete_records('course_sections', array('id' => $section->id)); + rebuild_course_cache($course->id, true); + + // Descrease 'numsections' if needed. + if ($decreasenumsections) { + $this->update_course_format_options(array('numsections' => $course->numsections - 1)); + } + + return true; + } } /** diff --git a/course/format/renderer.php b/course/format/renderer.php index d9afaa0a04f..6002ff8508b 100644 --- a/course/format/renderer.php +++ b/course/format/renderer.php @@ -226,6 +226,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { } $coursecontext = context_course::instance($course->id); + $isstealth = isset($course->numsections) && ($section->section > $course->numsections); if ($onsectionpage) { $baseurl = course_get_url($course, $section->section); @@ -237,7 +238,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { $controls = array(); $url = clone($baseurl); - if (has_capability('moodle/course:sectionvisibility', $coursecontext)) { + if (!$isstealth && has_capability('moodle/course:sectionvisibility', $coursecontext)) { if ($section->visible) { // Show the hide/show eye. $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format); $url->param('hide', $section->section); @@ -255,7 +256,21 @@ abstract class format_section_renderer_base extends plugin_renderer_base { } } - if (!$onsectionpage && has_capability('moodle/course:movesections', $coursecontext)) { + if (course_can_delete_section($course, $section)) { + if (get_string_manager()->string_exists('deletesection', 'format_'.$course->format)) { + $strdelete = get_string('deletesection', 'format_'.$course->format); + } else { + $strdelete = get_string('deletesection'); + } + $url = new moodle_url('/course/editsection.php', array('id' => $section->id, + 'sr' => $onsectionpage ? $section->section : 0, 'delete' => 1)); + $controls[] = html_writer::link($url, + html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/delete'), + 'class' => 'iconsmall edit', 'alt' => $strdelete)), + array('title' => $strdelete)); + } + + if (!$isstealth && !$onsectionpage && has_capability('moodle/course:movesections', $coursecontext)) { $url = clone($baseurl); if ($section->section > 1) { // Add a arrow to move section up. $url->param('section', $section->section); @@ -530,7 +545,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base { $o = ''; $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix orphaned hidden')); $o.= html_writer::tag('div', '', array('class' => 'left side')); - $o.= html_writer::tag('div', '', array('class' => 'right side')); + $course = course_get_format($this->page->course)->get_course(); + $section = course_get_format($this->page->course)->get_section($sectionno); + $rightcontent = $this->section_right_content($section, $course, false); + $o.= html_writer::tag('div', $rightcontent, array('class' => 'right side')); $o.= html_writer::start_tag('div', array('class' => 'content')); $o.= $this->output->heading(get_string('orphanedactivitiesinsectionno', '', $sectionno), 3, 'sectionname'); return $o; diff --git a/course/format/topics/lang/en/format_topics.php b/course/format/topics/lang/en/format_topics.php index f9766d9a6e4..37e2f11d4be 100644 --- a/course/format/topics/lang/en/format_topics.php +++ b/course/format/topics/lang/en/format_topics.php @@ -24,6 +24,7 @@ */ $string['currentsection'] = 'This topic'; +$string['deletesection'] = 'Delete topic'; $string['sectionname'] = 'Topic'; $string['pluginname'] = 'Topics format'; $string['section0name'] = 'General'; diff --git a/course/format/topics/lib.php b/course/format/topics/lib.php index cb0a7edc344..f5a8067f7fb 100644 --- a/course/format/topics/lib.php +++ b/course/format/topics/lib.php @@ -339,4 +339,16 @@ class format_topics extends format_base { } return $this->update_format_options($data); } + + /** + * Whether this format allows to delete sections + * + * Do not call this function directly, instead use {@link course_can_delete_section()} + * + * @param int|stdClass|section_info $section + * @return bool + */ + public function can_delete_section($section) { + return true; + } } diff --git a/course/format/topics/renderer.php b/course/format/topics/renderer.php index ffc7be60026..6e2b43b4ce9 100644 --- a/course/format/topics/renderer.php +++ b/course/format/topics/renderer.php @@ -97,8 +97,9 @@ class format_topics_renderer extends format_section_renderer_base { } $url->param('sesskey', sesskey()); + $isstealth = $section->section > $course->numsections; $controls = array(); - if (has_capability('moodle/course:setcurrentsection', $coursecontext)) { + if (!$isstealth && has_capability('moodle/course:setcurrentsection', $coursecontext)) { if ($course->marker == $section->section) { // Show the "light globe" on/off. $url->param('marker', 0); $controls[] = html_writer::link($url, diff --git a/course/format/topics/tests/behat/edit_delete_sections.feature b/course/format/topics/tests/behat/edit_delete_sections.feature new file mode 100644 index 00000000000..81e8a254225 --- /dev/null +++ b/course/format/topics/tests/behat/edit_delete_sections.feature @@ -0,0 +1,85 @@ +@format @format_topics +Feature: Sections can be edited and deleted in topics format + In order to rearrange my course contents + As a teacher + I need to edit and Delete topics + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@asd.com | + And the following "courses" exist: + | fullname | shortname | format | coursedisplay | numsections | + | Course 1 | C1 | topics | 0 | 5 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Test assignment name | Test assignment description | C1 | assign1 | 0 | + | book | Test book name | Test book description | C1 | book1 | 1 | + | chat | Test chat name | Test chat description | C1 | chat1 | 4 | + | choice | Test choice name | Test choice description | C1 | choice1 | 5 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + + Scenario: Edit section summary in topics format + When I click on "Edit summary" "link" in the "li#section-2" "css_element" + And I set the following fields to these values: + | Summary | Welcome to section 2 | + And I press "Save changes" + Then I should see "Welcome to section 2" in the "li#section-2" "css_element" + + Scenario: Edit section default name in topics format + When I click on "Edit summary" "link" in the "li#section-2" "css_element" + And I set the following fields to these values: + | Use default section name | 0 | + | name | This is the second topic | + And I press "Save changes" + Then I should see "This is the second topic" in the "li#section-2" "css_element" + And I should not see "Topic 2" in the "li#section-2" "css_element" + + Scenario: Deleting the last section in topics format + When I click on "Delete topic" "link" in the "li#section-5" "css_element" + Then I should see "Are you absolutely sure you want to delete \"Topic 5\"? All activities will be also deleted" + And I press "Yes" + And I should not see "Topic 5" + And I navigate to "Edit settings" node in "Course administration" + And I expand all fieldsets + And the field "Number of sections" matches value "4" + + Scenario: Deleting the middle section in topics format + When I click on "Delete topic" "link" in the "li#section-4" "css_element" + And I press "Yes" + Then I should not see "Topic 5" + And I should not see "Test chat name" + And I should see "Test choice name" in the "li#section-4" "css_element" + And I navigate to "Edit settings" node in "Course administration" + And I expand all fieldsets + And the field "Number of sections" matches value "4" + + Scenario: Deleting the orphaned section in topics format + When I follow "Reduce the number of sections" + Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element" + And I click on "Delete topic" "link" in the "li#section-5" "css_element" + And I press "Yes" + And I should not see "Topic 5" + And I should not see "Orphaned activities" + And "li#section-5" "css_element" should not exist + And I navigate to "Edit settings" node in "Course administration" + And I expand all fieldsets + And the field "Number of sections" matches value "4" + + Scenario: Deleting a section when orphaned section is present in topics format + When I follow "Reduce the number of sections" + Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element" + And "li#section-5.orphaned" "css_element" should exist + And "li#section-4.orphaned" "css_element" should not exist + And I click on "Delete topic" "link" in the "li#section-1" "css_element" + And I press "Yes" + And I should not see "Test book name" + And I should see "Orphaned activities (section 4)" in the "li#section-4" "css_element" + And "li#section-5" "css_element" should not exist + And "li#section-4.orphaned" "css_element" should exist + And "li#section-3.orphaned" "css_element" should not exist diff --git a/course/format/upgrade.txt b/course/format/upgrade.txt index 79168540e32..691932036d4 100644 --- a/course/format/upgrade.txt +++ b/course/format/upgrade.txt @@ -2,6 +2,11 @@ This files describes API changes for course formats Overview of this plugin type at http://docs.moodle.org/dev/Course_formats +=== 2.9 === +* Course formats may support deleting sections, see MDL-10405 for more details. + format_section_renderer_base::section_edit_controls() is now also called for + stealth sections and it also returns "delete" control. + === 2.8 === * The activity chooser now uses M.course.format.get_sectionwrapperclass() to determine the section selector, rather than a hard-coded `li.section`. diff --git a/course/format/weeks/lang/en/format_weeks.php b/course/format/weeks/lang/en/format_weeks.php index fd6c24d09fa..86e24b75b15 100644 --- a/course/format/weeks/lang/en/format_weeks.php +++ b/course/format/weeks/lang/en/format_weeks.php @@ -24,6 +24,7 @@ */ $string['currentsection'] = 'This week'; +$string['deletesection'] = 'Delete week'; $string['sectionname'] = 'Week'; $string['pluginname'] = 'Weekly format'; $string['section0name'] = 'General'; diff --git a/course/format/weeks/lib.php b/course/format/weeks/lib.php index 66152d233b4..1e472d1f134 100644 --- a/course/format/weeks/lib.php +++ b/course/format/weeks/lib.php @@ -393,4 +393,16 @@ class format_weeks extends format_base { $dates = $this->get_section_dates($section); return (($timenow >= $dates->start) && ($timenow < $dates->end)); } + + /** + * Whether this format allows to delete sections + * + * Do not call this function directly, instead use {@link course_can_delete_section()} + * + * @param int|stdClass|section_info $section + * @return bool + */ + public function can_delete_section($section) { + return true; + } } diff --git a/course/format/weeks/tests/behat/edit_delete_sections.feature b/course/format/weeks/tests/behat/edit_delete_sections.feature new file mode 100644 index 00000000000..ae1524c72b1 --- /dev/null +++ b/course/format/weeks/tests/behat/edit_delete_sections.feature @@ -0,0 +1,88 @@ +@format @format_weeks +Feature: Sections can be edited and deleted in weeks format + In order to rearrange my course contents + As a teacher + I need to edit and Delete weeks + + Background: + Given the following "users" exist: + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@asd.com | + And the following "courses" exist: + | fullname | shortname | format | coursedisplay | numsections | startdate | + | Course 1 | C1 | weeks | 0 | 5 | 957139200 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | section | + | assign | Test assignment name | Test assignment description | C1 | assign1 | 0 | + | book | Test book name | Test book description | C1 | book1 | 1 | + | chat | Test chat name | Test chat description | C1 | chat1 | 4 | + | choice | Test choice name | Test choice description | C1 | choice1 | 5 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + + Scenario: Edit section summary in weeks format + When I click on "Edit summary" "link" in the "li#section-2" "css_element" + And I set the following fields to these values: + | Summary | Welcome to section 2 | + And I press "Save changes" + Then I should see "Welcome to section 2" in the "li#section-2" "css_element" + + Scenario: Edit section default name in weeks format + Given I should see "8 May - 14 May" in the "li#section-2" "css_element" + When I click on "Edit summary" "link" in the "li#section-2" "css_element" + And I set the following fields to these values: + | Use default section name | 0 | + | name | This is the second week | + And I press "Save changes" + Then I should see "This is the second week" in the "li#section-2" "css_element" + And I should not see "8 May - 14 May" in the "li#section-2" "css_element" + + Scenario: Deleting the last section in weeks format + Given I should see "29 May - 4 June" in the "li#section-5" "css_element" + When I click on "Delete week" "link" in the "li#section-5" "css_element" + Then I should see "Are you absolutely sure you want to delete \"29 May - 4 June\"? All activities will be also deleted" + And I press "Yes" + And I should not see "29 May - 4 June" + And I navigate to "Edit settings" node in "Course administration" + And I expand all fieldsets + And the field "Number of sections" matches value "4" + + Scenario: Deleting the middle section in weeks format + Given I should see "29 May - 4 June" in the "li#section-5" "css_element" + When I click on "Delete week" "link" in the "li#section-4" "css_element" + And I press "Yes" + Then I should not see "29 May - 4 June" + And I should not see "Test chat name" + And I should see "Test choice name" in the "li#section-4" "css_element" + And I navigate to "Edit settings" node in "Course administration" + And I expand all fieldsets + And the field "Number of sections" matches value "4" + + Scenario: Deleting the orphaned section in weeks format + When I follow "Reduce the number of sections" + Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element" + And I click on "Delete week" "link" in the "li#section-5" "css_element" + And I press "Yes" + And I should not see "29 May - 4 June" + And I should not see "Orphaned activities" + And "li#section-5" "css_element" should not exist + And I navigate to "Edit settings" node in "Course administration" + And I expand all fieldsets + And the field "Number of sections" matches value "4" + + Scenario: Deleting a section when orphaned section is present in weeks format + When I follow "Reduce the number of sections" + Then I should see "Orphaned activities (section 5)" in the "li#section-5" "css_element" + And "li#section-5.orphaned" "css_element" should exist + And "li#section-4.orphaned" "css_element" should not exist + And I click on "Delete week" "link" in the "li#section-1" "css_element" + And I press "Yes" + And I should not see "Test book name" + And I should see "Orphaned activities (section 4)" in the "li#section-4" "css_element" + And "li#section-5" "css_element" should not exist + And "li#section-4.orphaned" "css_element" should exist + And "li#section-3.orphaned" "css_element" should not exist diff --git a/course/lib.php b/course/lib.php index b8212d5b495..0dabb51ff7e 100644 --- a/course/lib.php +++ b/course/lib.php @@ -1771,9 +1771,10 @@ function delete_mod_from_section($modid, $sectionid) { * @param object $course * @param int $section Section number (not id!!!) * @param int $destination + * @param bool $ignorenumsections * @return boolean Result */ -function move_section_to($course, $section, $destination) { +function move_section_to($course, $section, $destination, $ignorenumsections = false) { /// Moves a whole course section up and down within the course global $USER, $DB; @@ -1783,7 +1784,7 @@ function move_section_to($course, $section, $destination) { // compartibility with course formats using field 'numsections' $courseformatoptions = course_get_format($course)->get_format_options(); - if ((array_key_exists('numsections', $courseformatoptions) && + if ((!$ignorenumsections && array_key_exists('numsections', $courseformatoptions) && ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) { return false; } @@ -1825,6 +1826,57 @@ function move_section_to($course, $section, $destination) { return true; } +/** + * This method will delete a course section and may delete all modules inside it. + * + * No permissions are checked here, use {@link course_can_delete_section()} to + * check if section can actually be deleted. + * + * @param int|stdClass $course + * @param int|stdClass|section_info $section + * @param bool $forcedeleteifnotempty if set to false section will not be deleted if it has modules in it. + * @return bool whether section was deleted + */ +function course_delete_section($course, $section, $forcedeleteifnotempty = true) { + return course_get_format($course)->delete_section($section, $forcedeleteifnotempty); +} + +/** + * Checks if the current user can delete a section (if course format allows it and user has proper permissions). + * + * @param int|stdClass $course + * @param int|stdClass|section_info $section + * @return bool + */ +function course_can_delete_section($course, $section) { + if (is_object($section)) { + $section = $section->section; + } + if (!$section) { + // Not possible to delete 0-section. + return false; + } + // Course format should allow to delete sections. + if (!course_get_format($course)->can_delete_section($section)) { + return false; + } + // Make sure user has capability to update course and move sections. + $context = context_course::instance(is_object($course) ? $course->id : $course); + if (!has_all_capabilities(array('moodle/course:movesections', 'moodle/course:update'), $context)) { + return false; + } + // Make sure user has capability to delete each activity in this section. + $modinfo = get_fast_modinfo($course); + if (!empty($modinfo->sections[$section])) { + foreach ($modinfo->sections[$section] as $cmid) { + if (!has_capability('moodle/course:manageactivities', context_module::instance($cmid))) { + return false; + } + } + } + return true; +} + /** * Reordering algorithm for course sections. Given an array of section->section indexed by section->id, * an original position number and a target position number, rebuilds the array so that the diff --git a/course/tests/courselib_test.php b/course/tests/courselib_test.php index b60e5ac604a..a40bea6f647 100644 --- a/course/tests/courselib_test.php +++ b/course/tests/courselib_test.php @@ -859,6 +859,130 @@ class core_course_courselib_testcase extends advanced_testcase { $this->assertEquals(3, $course->marker); } + public function test_course_can_delete_section() { + global $DB; + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + + $courseweeks = $generator->create_course( + array('numsections' => 5, 'format' => 'weeks'), + array('createsections' => true)); + $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1)); + $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2)); + + $coursetopics = $generator->create_course( + array('numsections' => 5, 'format' => 'topics'), + array('createsections' => true)); + + $coursesingleactivity = $generator->create_course( + array('format' => 'singleactivity'), + array('createsections' => true)); + + // Enrol student and teacher. + $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); + $student = $generator->create_user(); + $teacher = $generator->create_user(); + + $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']); + $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']); + + $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']); + $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']); + + $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']); + $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']); + + // Teacher should be able to delete sections (except for 0) in topics and weeks format. + $this->setUser($teacher); + + // For topics and weeks formats will return false for section 0 and true for any other section. + $this->assertFalse(course_can_delete_section($courseweeks, 0)); + $this->assertTrue(course_can_delete_section($courseweeks, 1)); + + $this->assertFalse(course_can_delete_section($coursetopics, 0)); + $this->assertTrue(course_can_delete_section($coursetopics, 1)); + + // For singleactivity course format no section can be deleted. + $this->assertFalse(course_can_delete_section($coursesingleactivity, 0)); + $this->assertFalse(course_can_delete_section($coursesingleactivity, 1)); + + // Now let's revoke a capability from teacher to manage activity in section 1. + $modulecontext = context_module::instance($assign1->cmid); + assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'], + $modulecontext); + $modulecontext->mark_dirty(); + $this->assertFalse(course_can_delete_section($courseweeks, 1)); + $this->assertTrue(course_can_delete_section($courseweeks, 2)); + + // Student does not have permissions to delete sections. + $this->setUser($student); + $this->assertFalse(course_can_delete_section($courseweeks, 1)); + $this->assertFalse(course_can_delete_section($coursetopics, 1)); + $this->assertFalse(course_can_delete_section($coursesingleactivity, 1)); + } + + public function test_course_delete_section() { + global $DB; + $this->resetAfterTest(true); + + $generator = $this->getDataGenerator(); + + $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'), + array('createsections' => true)); + $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0)); + $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1)); + $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2)); + $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2)); + $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3)); + $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5)); + $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6)); + + $this->setAdminUser(); + + // Attempt to delete non-existing section. + $this->assertFalse(course_delete_section($course, 10, false)); + $this->assertFalse(course_delete_section($course, 9, true)); + + // Attempt to delete 0-section. + $this->assertFalse(course_delete_section($course, 0, true)); + $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid))); + + // Delete last section. + $this->assertTrue(course_delete_section($course, 6, true)); + $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid))); + $this->assertEquals(5, course_get_format($course)->get_course()->numsections); + + // Delete empty section. + $this->assertTrue(course_delete_section($course, 4, false)); + $this->assertEquals(4, course_get_format($course)->get_course()->numsections); + + // Delete section in the middle (2). + $this->assertFalse(course_delete_section($course, 2, false)); + $this->assertTrue(course_delete_section($course, 2, true)); + $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid))); + $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid))); + $this->assertEquals(3, course_get_format($course)->get_course()->numsections); + $this->assertEquals(array(0 => array($assign0->cmid), + 1 => array($assign1->cmid), + 2 => array($assign3->cmid), + 3 => array($assign5->cmid)), get_fast_modinfo($course)->sections); + + // Make last section orphaned. + update_course((object)array('id' => $course->id, 'numsections' => 2)); + $this->assertEquals(2, course_get_format($course)->get_course()->numsections); + + // Remove orphaned section. + $this->assertTrue(course_delete_section($course, 3, true)); + $this->assertEquals(2, course_get_format($course)->get_course()->numsections); + + // Remove marked section. + course_set_marker($course->id, 1); + $this->assertTrue(course_get_format($course)->is_section_current(1)); + $this->assertTrue(course_delete_section($course, 1, true)); + $this->assertFalse(course_get_format($course)->is_section_current(1)); + } + public function test_get_course_display_name_for_list() { global $CFG; $this->resetAfterTest(true); diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 1bb70c93199..b9a5d7b6856 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -270,6 +270,7 @@ $string['complete'] = 'Complete'; $string['completereport'] = 'Complete report'; $string['configuration'] = 'Configuration'; $string['confirm'] = 'Confirm'; +$string['confirmdeletesection'] = 'Are you absolutely sure you want to delete "{$a}"? All activities will be also deleted'; $string['confirmed'] = 'Your registration has been confirmed'; $string['confirmednot'] = 'Your registration has not yet been confirmed!'; $string['confirmcheckfull'] = 'Are you absolutely sure you want to confirm {$a} ?'; @@ -480,6 +481,7 @@ $string['deletechecktypename'] = 'Are you sure that you want to delete the {$a-> $string['deletecheckfiles'] = 'Are you absolutely sure you want to delete these files?'; $string['deletecheckfull'] = 'Are you absolutely sure you want to completely delete {$a} ?'; $string['deletecheckwarning'] = 'You are about to delete these files'; +$string['deletesection'] = 'Delete section'; $string['deleteselected'] = 'Delete selected'; $string['deleteselectedkey'] = 'Delete selected key'; $string['deletingcourse'] = 'Deleting {$a}';