MDL-51802 course: allow to edit section names on the course page
authorMarina Glancy <marina@moodle.com>
Wed, 3 Feb 2016 09:28:56 +0000 (17:28 +0800)
committerMarina Glancy <marina@moodle.com>
Sat, 13 Feb 2016 03:07:46 +0000 (11:07 +0800)
17 files changed:
course/editsection.php
course/format/lib.php
course/format/renderer.php
course/format/topics/lang/en/format_topics.php
course/format/topics/lib.php
course/format/topics/renderer.php
course/format/topics/tests/behat/edit_delete_sections.feature
course/format/topics/tests/format_topics_test.php
course/format/upgrade.txt
course/format/weeks/lang/en/format_weeks.php
course/format/weeks/lib.php
course/format/weeks/renderer.php
course/format/weeks/tests/behat/edit_delete_sections.feature
course/format/weeks/tests/format_weeks_test.php
course/lib.php
lang/en/error.php
lang/en/moodle.php

index 1aff731..14b7493 100644 (file)
@@ -114,28 +114,7 @@ if ($mform->is_cancelled()){
             $data->availability = null;
         }
     }
-    $DB->update_record('course_sections', $data);
-    rebuild_course_cache($course->id, true);
-    if (isset($data->section)) {
-        // Usually edit form does not change relative section number but just in case.
-        $sectionnum = $data->section;
-    }
-    course_get_format($course->id)->update_section_format_options($data);
-
-    // Set section info, as this might not be present in form_data.
-    if (!isset($data->section))  {
-        $data->section = $sectionnum;
-    }
-    // Trigger an event for course section update.
-    $event = \core\event\course_section_updated::create(
-            array(
-                'objectid' => $data->id,
-                'courseid' => $course->id,
-                'context' => $context,
-                'other' => array('sectionnum' => $data->section)
-            )
-        );
-    $event->trigger();
+    course_update_section($course, $section, $data);
 
     $PAGE->navigation->clear_cache();
     redirect(course_get_url($course, $section, array('sr' => $sectionreturn)));
index d5423f7..8a4078c 100644 (file)
@@ -1030,6 +1030,76 @@ abstract class format_base {
 
         return true;
     }
+
+    /**
+     * Prepares the templateable object to display section name
+     *
+     * @param \section_info|\stdClass $section
+     * @param bool $linkifneeded
+     * @param bool $editable
+     * @param null|lang_string|string $edithint
+     * @param null|lang_string|string $editlabel
+     * @return \core\output\inplace_editable
+     */
+    public function inplace_editable_render_section_name($section, $linkifneeded = true,
+                                                         $editable = null, $edithint = null, $editlabel = null) {
+        global $USER, $CFG;
+        require_once($CFG->dirroot.'/course/lib.php');
+
+        if ($editable === null) {
+            $editable = !empty($USER->editing) && has_capability('moodle/course:update',
+                    context_course::instance($section->course));
+        }
+
+        $displayvalue = $title = get_section_name($section->course, $section);
+        if ($linkifneeded) {
+            // Display link under the section name if the course format setting is to display one section per page.
+            $url = course_get_url($section->course, $section->section, array('navigation' => true));
+            if ($url) {
+                $displayvalue = html_writer::link($url, $title);
+            }
+            $itemtype = 'sectionname';
+        } else {
+            // If $linkifneeded==false, we never display the link (this is used when rendering the section header).
+            // Itemtype 'sectionnamenl' (nl=no link) will tell the callback that link should not be rendered -
+            // there is no other way callback can know where we display the section name.
+            $itemtype = 'sectionnamenl';
+        }
+        if (empty($edithint)) {
+            $edithint = new lang_string('editsectionname');
+        }
+        if (empty($editlabel)) {
+            $editlabel = new lang_string('newsectionname', '', $title);
+        }
+
+        return new \core\output\inplace_editable('format_' . $this->format, $itemtype, $section->id, $editable,
+            $displayvalue, $section->name, $edithint, $editlabel);
+    }
+
+    /**
+     * Updates the value in the database and modifies this object respectively.
+     *
+     * ALWAYS check user permissions before performing an update! Throw exceptions if permissions are not sufficient
+     * or value is not legit.
+     *
+     * @param stdClass $section
+     * @param string $itemtype
+     * @param mixed $newvalue
+     * @return \core\output\inplace_editable
+     */
+    public function inplace_editable_update_section_name($section, $itemtype, $newvalue) {
+        if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') {
+            require_login($section->course, false, null, true, true);
+            $context = context_course::instance($section->course);
+            require_capability('moodle/course:update', $context);
+
+            $newtitle = clean_param($newvalue, PARAM_TEXT);
+            if (strval($section->name) !== strval($newtitle)) {
+                course_update_section($section->course, $section, array('name' => $newtitle));
+            }
+            return $this->inplace_editable_render_section_name($section, ($itemtype === 'sectionname'), true);
+        }
+    }
 }
 
 /**
index 77a34a5..c88d14d 100644 (file)
@@ -71,7 +71,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
     abstract protected function page_title();
 
     /**
-     * Generate the section title
+     * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page
      *
      * @param stdClass $section The course_section entry from DB
      * @param stdClass $course The course entry from DB
@@ -86,6 +86,17 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         return $title;
     }
 
+    /**
+     * Generate the section title to be displayed on the section page, without a link
+     *
+     * @param stdClass $section The course_section entry from DB
+     * @param stdClass $course The course entry from DB
+     * @return string HTML to output.
+     */
+    public function section_title_without_link($section, $course) {
+        return get_section_name($course, $section);
+    }
+
     /**
      * Generate the edit control action menu
      *
@@ -193,7 +204,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             'aria-label'=> get_section_name($course, $section)));
 
         // Create a span that contains the section title to be used to create the keyboard section move menu.
-        $o .= html_writer::tag('span', $this->section_title($section, $course), array('class' => 'hidden sectionname'));
+        $o .= html_writer::tag('span', get_section_name($course, $section), array('class' => 'hidden sectionname'));
 
         $leftcontent = $this->section_left_content($section, $course, $onsectionpage);
         $o.= html_writer::tag('div', $leftcontent, array('class' => 'left side'));
@@ -788,7 +799,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         if (!$thissection->visible) {
             $classes .= ' dimmed_text';
         }
-        $sectionname = html_writer::tag('span', get_section_name($course, $displaysection));
+        $sectionname = html_writer::tag('span', $this->section_title_without_link($thissection, $course));
         $sectiontitle .= $this->output->heading($sectionname, 3, $classes);
 
         $sectiontitle .= html_writer::end_tag('div');
index 6518d5b..69856d4 100644 (file)
@@ -25,7 +25,9 @@
 
 $string['currentsection'] = 'This topic';
 $string['editsection'] = 'Edit topic';
+$string['editsectionname'] = 'Edit topic name';
 $string['deletesection'] = 'Delete topic';
+$string['newsectionname'] = 'New name for topic {$a}';
 $string['sectionname'] = 'Topic';
 $string['pluginname'] = 'Topics format';
 $string['section0name'] = 'General';
index 228b122..da48e81 100644 (file)
@@ -382,4 +382,45 @@ class format_topics extends format_base {
     public function can_delete_section($section) {
         return true;
     }
+
+    /**
+     * Prepares the templateable object to display section name
+     *
+     * @param \section_info|\stdClass $section
+     * @param bool $linkifneeded
+     * @param bool $editable
+     * @param null|lang_string|string $edithint
+     * @param null|lang_string|string $editlabel
+     * @return \core\output\inplace_editable
+     */
+    public function inplace_editable_render_section_name($section, $linkifneeded = true,
+                                                         $editable = null, $edithint = null, $editlabel = null) {
+        if (empty($edithint)) {
+            $edithint = new lang_string('editsectionname', 'format_topics');
+        }
+        if (empty($editlabel)) {
+            $title = get_section_name($section->course, $section);
+            $editlabel = new lang_string('newsectionname', 'format_topics', $title);
+        }
+        return parent::inplace_editable_render_section_name($section, $linkifneeded, $editable, $edithint, $editlabel);
+    }
+}
+
+/**
+ * Implements callback inplace_editable() allowing to edit values in-place
+ *
+ * @param string $itemtype
+ * @param int $itemid
+ * @param mixed $newvalue
+ * @return \core\output\inplace_editable
+ */
+function format_topics_inplace_editable($itemtype, $itemid, $newvalue) {
+    global $DB, $CFG;
+    require_once($CFG->dirroot . '/course/lib.php');
+    if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') {
+        $section = $DB->get_record_sql(
+            'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?',
+            array($itemid, 'topics'), MUST_EXIST);
+        return course_get_format($section->course)->inplace_editable_update_section_name($section, $itemtype, $newvalue);
+    }
 }
index c23bd0c..9ed95bd 100644 (file)
@@ -73,6 +73,28 @@ class format_topics_renderer extends format_section_renderer_base {
         return get_string('topicoutline');
     }
 
+    /**
+     * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page
+     *
+     * @param stdClass $section The course_section entry from DB
+     * @param stdClass $course The course entry from DB
+     * @return string HTML to output.
+     */
+    public function section_title($section, $course) {
+        return $this->render(course_get_format($course)->inplace_editable_render_section_name($section));
+    }
+
+    /**
+     * Generate the section title to be displayed on the section page, without a link
+     *
+     * @param stdClass $section The course_section entry from DB
+     * @param stdClass $course The course entry from DB
+     * @return string HTML to output.
+     */
+    public function section_title_without_link($section, $course) {
+        return $this->render(course_get_format($course)->inplace_editable_render_section_name($section, false));
+    }
+
     /**
      * Generate the edit control items of a section
      *
index 150c5e3..1e6a7dd 100644 (file)
@@ -56,6 +56,18 @@ Feature: Sections can be edited and deleted in topics format
     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"
 
+  @javascript
+  Scenario: Inline edit section name in topics format
+    When I click on "Edit topic name" "link" in the "li#section-1" "css_element"
+    And I set the field "New name for topic Topic 1" to "Midterm evaluation"
+    And I press key "13" in the field "New name for topic Topic 1"
+    Then I should not see "Topic 1" in the "#region-main" "css_element"
+    And "New name for topic" "field" should not exist
+    And I should see "Midterm evaluation" in the "li#section-1" "css_element"
+    And I follow "Course 1"
+    And I should not see "Topic 1" in the "#region-main" "css_element"
+    And I should see "Midterm evaluation" in the "li#section-1" "css_element"
+
   Scenario: Deleting the last section in topics format
     When I delete section "5"
     Then I should see "Are you absolutely sure you want to completely delete \"Topic 5\" and all the activities it contains?"
index 3253e45..45e8c00 100644 (file)
@@ -146,4 +146,69 @@ class format_topics_testcase extends advanced_testcase {
             }
         }
     }
+
+    /**
+     * Test web service updating section name
+     */
+    public function test_update_inplace_editable() {
+        global $CFG, $DB, $PAGE;
+        require_once($CFG->dirroot . '/lib/external/externallib.php');
+
+        $this->resetAfterTest();
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 5, 'format' => 'topics'),
+            array('createsections' => true));
+        $section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
+
+        // Call webservice without necessary permissions.
+        try {
+            core_external::update_inplace_editable('format_topics', 'sectionname', $section->id, 'New section name');
+            $this->fail('Exception expected');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('Course or activity not accessible. (Not enrolled)',
+                    $e->getMessage());
+        }
+
+        // Change to teacher and make sure that section name can be updated using web service update_inplace_editable().
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
+
+        $res = core_external::update_inplace_editable('format_topics', 'sectionname', $section->id, 'New section name');
+        $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
+        $this->assertEquals('New section name', $res['value']);
+        $this->assertEquals('New section name', $DB->get_field('course_sections', 'name', array('id' => $section->id)));
+    }
+
+    /**
+     * Test callback updating section name
+     */
+    public function test_inplace_editable() {
+        global $DB, $PAGE;
+
+        $this->resetAfterTest();
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 5, 'format' => 'topics'),
+            array('createsections' => true));
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
+        $this->setUser($user);
+
+        $section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
+
+        // Call callback format_topics_inplace_editable() directly.
+        $tmpl = component_callback('format_topics', 'inplace_editable', array('sectionname', $section->id, 'Rename me again'));
+        $this->assertInstanceOf('core\output\inplace_editable', $tmpl);
+        $res = $tmpl->export_for_template($PAGE->get_renderer('core'));
+        $this->assertEquals('Rename me again', $res['value']);
+        $this->assertEquals('Rename me again', $DB->get_field('course_sections', 'name', array('id' => $section->id)));
+
+        // Try updating using callback from mismatching course format.
+        try {
+            $tmpl = component_callback('format_weeks', 'inplace_editable', array('sectionname', $section->id, 'New name'));
+            $this->fail('Exception expected');
+        } catch (moodle_exception $e) {
+            $this->assertEquals(1, preg_match('/^Can not find data record in database/', $e->getMessage()));
+        }
+    }
 }
index fce6e1c..8d82f27 100644 (file)
@@ -2,6 +2,10 @@ This files describes API changes for course formats
 
 Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
 
+=== 3.1 ===
+* Course format may use the inplace_editable template to allow quick editing of section names, see
+  https://docs.moodle.org/dev/Inplace_editable and MDL-51802 for example implementation.
+
 === 3.0 ===
 * Course formats should now use section_edit_control_items and use the returned array of controls items and their attributes to create a
   renderable menu or array of links.  Plugin calls to section_edit_controls will now include the section edit control in the returned array.
index 905e71a..61626e0 100644 (file)
@@ -25,7 +25,9 @@
 
 $string['currentsection'] = 'This week';
 $string['editsection'] = 'Edit week';
+$string['editsectionname'] = 'Edit week name';
 $string['deletesection'] = 'Delete week';
+$string['newsectionname'] = 'New name for week {$a}';
 $string['sectionname'] = 'Week';
 $string['pluginname'] = 'Weekly format';
 $string['section0name'] = 'General';
index 7bcf1f4..dcb9658 100644 (file)
@@ -432,4 +432,45 @@ class format_weeks extends format_base {
     public function can_delete_section($section) {
         return true;
     }
+
+    /**
+     * Prepares the templateable object to display section name
+     *
+     * @param \section_info|\stdClass $section
+     * @param bool $linkifneeded
+     * @param bool $editable
+     * @param null|lang_string|string $edithint
+     * @param null|lang_string|string $editlabel
+     * @return \core\output\inplace_editable
+     */
+    public function inplace_editable_render_section_name($section, $linkifneeded = true,
+                                                         $editable = null, $edithint = null, $editlabel = null) {
+        if (empty($edithint)) {
+            $edithint = new lang_string('editsectionname', 'format_weeks');
+        }
+        if (empty($editlabel)) {
+            $title = get_section_name($section->course, $section);
+            $editlabel = new lang_string('newsectionname', 'format_weeks', $title);
+        }
+        return parent::inplace_editable_render_section_name($section, $linkifneeded, $editable, $edithint, $editlabel);
+    }
+}
+
+/**
+ * Implements callback inplace_editable() allowing to edit values in-place
+ *
+ * @param string $itemtype
+ * @param int $itemid
+ * @param mixed $newvalue
+ * @return \core\output\inplace_editable
+ */
+function format_weeks_inplace_editable($itemtype, $itemid, $newvalue) {
+    global $DB, $CFG;
+    require_once($CFG->dirroot . '/course/lib.php');
+    if ($itemtype === 'sectionname' || $itemtype === 'sectionnamenl') {
+        $section = $DB->get_record_sql(
+            'SELECT s.* FROM {course_sections} s JOIN {course} c ON s.course = c.id WHERE s.id = ? AND c.format = ?',
+            array($itemid, 'weeks'), MUST_EXIST);
+        return course_get_format($section->course)->inplace_editable_update_section_name($section, $itemtype, $newvalue);
+    }
 }
index 33590fd..af45ae3 100644 (file)
@@ -59,4 +59,26 @@ class format_weeks_renderer extends format_section_renderer_base {
     protected function page_title() {
         return get_string('weeklyoutline');
     }
+
+    /**
+     * Generate the section title, wraps it in a link to the section page if page is to be displayed on a separate page
+     *
+     * @param stdClass $section The course_section entry from DB
+     * @param stdClass $course The course entry from DB
+     * @return string HTML to output.
+     */
+    public function section_title($section, $course) {
+        return $this->render(course_get_format($course)->inplace_editable_render_section_name($section));
+    }
+
+    /**
+     * Generate the section title to be displayed on the section page, without a link
+     *
+     * @param stdClass $section The course_section entry from DB
+     * @param stdClass $course The course entry from DB
+     * @return string HTML to output.
+     */
+    public function section_title_without_link($section, $course) {
+        return $this->render(course_get_format($course)->inplace_editable_render_section_name($section, false));
+    }
 }
index 0e296c7..355059a 100644 (file)
@@ -57,6 +57,18 @@ Feature: Sections can be edited and deleted in weeks format
     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"
 
+  @javascript
+  Scenario: Inline edit section name in weeks format
+    When I click on "Edit week name" "link" in the "li#section-1" "css_element"
+    And I set the field "New name for week 1 May - 7 May" to "Midterm evaluation"
+    And I press key "13" in the field "New name for week 1 May - 7 May"
+    Then I should not see "1 May - 7 May" in the "#region-main" "css_element"
+    And "New name for week" "field" should not exist
+    And I should see "Midterm evaluation" in the "li#section-1" "css_element"
+    And I follow "Course 1"
+    And I should not see "1 May - 7 May" in the "#region-main" "css_element"
+    And I should see "Midterm evaluation" in the "li#section-1" "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 delete section "5"
index ad014fc..a22ce02 100644 (file)
@@ -152,4 +152,69 @@ class format_weeks_testcase extends advanced_testcase {
             }
         }
     }
+
+    /**
+     * Test web service updating section name
+     */
+    public function test_update_inplace_editable() {
+        global $CFG, $DB, $PAGE;
+        require_once($CFG->dirroot . '/lib/external/externallib.php');
+
+        $this->resetAfterTest();
+        $user = $this->getDataGenerator()->create_user();
+        $this->setUser($user);
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 5, 'format' => 'weeks'),
+            array('createsections' => true));
+        $section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
+
+        // Call webservice without necessary permissions.
+        try {
+            core_external::update_inplace_editable('format_weeks', 'sectionname', $section->id, 'New section name');
+            $this->fail('Exception expected');
+        } catch (moodle_exception $e) {
+            $this->assertEquals('Course or activity not accessible. (Not enrolled)',
+                    $e->getMessage());
+        }
+
+        // Change to teacher and make sure that section name can be updated using web service update_inplace_editable().
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
+
+        $res = core_external::update_inplace_editable('format_weeks', 'sectionname', $section->id, 'New section name');
+        $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
+        $this->assertEquals('New section name', $res['value']);
+        $this->assertEquals('New section name', $DB->get_field('course_sections', 'name', array('id' => $section->id)));
+    }
+
+    /**
+     * Test callback updating section name
+     */
+    public function test_inplace_editable() {
+        global $CFG, $DB, $PAGE;
+
+        $this->resetAfterTest();
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 5, 'format' => 'weeks'),
+            array('createsections' => true));
+        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
+        $this->setUser($user);
+
+        $section = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 2));
+
+        // Call callback format_weeks_inplace_editable() directly.
+        $tmpl = component_callback('format_weeks', 'inplace_editable', array('sectionname', $section->id, 'Rename me again'));
+        $this->assertInstanceOf('core\output\inplace_editable', $tmpl);
+        $res = $tmpl->export_for_template($PAGE->get_renderer('core'));
+        $this->assertEquals('Rename me again', $res['value']);
+        $this->assertEquals('Rename me again', $DB->get_field('course_sections', 'name', array('id' => $section->id)));
+
+        // Try updating using callback from mismatching course format.
+        try {
+            $tmpl = component_callback('format_topics', 'inplace_editable', array('sectionname', $section->id, 'New name'));
+            $this->fail('Exception expected');
+        } catch (moodle_exception $e) {
+            $this->assertEquals(1, preg_match('/^Can not find data record in database/', $e->getMessage()));
+        }
+    }
 }
index 0c2acd3..4cb1e52 100644 (file)
@@ -1198,37 +1198,10 @@ function set_section_visible($courseid, $sectionnumber, $visibility) {
 
     $resourcestotoggle = array();
     if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
-        $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
-
-        $event = \core\event\course_section_updated::create(array(
-            'context' => context_course::instance($courseid),
-            'objectid' => $section->id,
-            'other' => array(
-                'sectionnum' => $sectionnumber
-            )
-        ));
-        $event->add_record_snapshot('course_sections', $section);
-        $event->trigger();
-
-        if (!empty($section->sequence)) {
-            $modules = explode(",", $section->sequence);
-            foreach ($modules as $moduleid) {
-                if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
-                    if ($visibility) {
-                        // As we unhide the section, we use the previously saved visibility stored in visibleold.
-                        set_coursemodule_visible($moduleid, $cm->visibleold);
-                    } else {
-                        // We hide the section, so we hide the module but we store the original state in visibleold.
-                        set_coursemodule_visible($moduleid, 0);
-                        $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
-                    }
-                    \core\event\course_module_updated::create_from_cm($cm)->trigger();
-                }
-            }
-        }
-        rebuild_course_cache($courseid, true);
+        course_update_section($courseid, $section, array('visible' => $visibility));
 
         // Determine which modules are visible for AJAX update
+        $modules = !empty($section->sequence) ? explode(',', $section->sequence) : array();
         if (!empty($modules)) {
             list($insql, $params) = $DB->get_in_or_equal($modules);
             $select = 'id ' . $insql . ' AND visible = ?';
@@ -1877,6 +1850,70 @@ function course_delete_section($course, $section, $forcedeleteifnotempty = true)
     return $result;
 }
 
+/**
+ * Updates the course section
+ *
+ * This function does not check permissions or clean values - this has to be done prior to calling it.
+ *
+ * @param int|stdClass $course
+ * @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;
+
+    $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;
+    $DB->update_record('course_sections', $data);
+    rebuild_course_cache($courseid, true);
+    course_get_format($courseid)->update_section_format_options($data);
+
+    // Update fields of the $section object.
+    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);
+        foreach ($modules as $moduleid) {
+            if ($cm = get_coursemodule_from_id(null, $moduleid, $courseid)) {
+                if ($data['visible']) {
+                    // As we unhide the section, we use the previously saved visibility stored in visibleold.
+                    set_coursemodule_visible($moduleid, $cm->visibleold);
+                } else {
+                    // We hide the section, so we hide the module but we store the original state in visibleold.
+                    set_coursemodule_visible($moduleid, 0);
+                    $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
+                }
+                \core\event\course_module_updated::create_from_cm($cm)->trigger();
+            }
+        }
+    }
+}
+
 /**
  * Checks if the current user can delete a section (if course format allows it and user has proper permissions).
  *
index 407b548..a90f109 100644 (file)
@@ -302,6 +302,7 @@ $string['invalidcontext'] = 'Invalid context';
 $string['invalidcourse'] = 'Invalid course';
 $string['invalidcourseid'] = 'You are trying to use an invalid course ID';
 $string['invalidcourselevel'] = 'Incorrect context level';
+$string['invalidcourseformat'] = 'Invalid course format';
 $string['invalidcoursemodule'] = 'Invalid course module ID';
 $string['invalidcoursenameshort'] = 'Invalid short course name';
 $string['invaliddata'] = 'Data submitted is invalid';
index b126d79..23c0c52 100644 (file)
@@ -557,6 +557,7 @@ $string['editorresettodefaults'] = 'Reset to default values';
 $string['editorsettings'] = 'Editor settings';
 $string['editorshortcutkeys'] = 'Editor shortcut keys';
 $string['editsection'] = 'Edit section';
+$string['editsectionname'] = 'Edit section name';
 $string['editsummary'] = 'Edit summary';
 $string['edittitle'] = 'Edit title';
 $string['edittitleinstructions'] = 'Escape to cancel, Enter when finished';
@@ -1261,6 +1262,7 @@ Cheers from the \'{$a->sitename}\' administrator,
 {$a->signoff}';
 $string['newpicture'] = 'New picture';
 $string['newpicture_help'] = 'To add a new picture, browse and select an image (in JPG or PNG format) then click "Update profile". The image will be cropped to a square and resized to 100x100 pixels.';
+$string['newsectionname'] = 'New name for section {$a}';
 $string['newsitem'] = 'news item';
 $string['newsitems'] = 'news items';
 $string['newsitemsnumber'] = 'News items to show';