diff --git a/.upgradenotes/MDL-82259-2024072314533865.yml b/.upgradenotes/MDL-82259-2024072314533865.yml
new file mode 100644
index 00000000000..038dd388d33
--- /dev/null
+++ b/.upgradenotes/MDL-82259-2024072314533865.yml
@@ -0,0 +1,10 @@
+issueNumber: MDL-82259
+notes:
+ core_course:
+ - message: >-
+ i_open_section_edit_menu(), i_show_section(), i_hide_section(),
+ i_wait_until_section_is_available(),
+ show_section_link_exists(), hide_section_link_exists() and
+ section_exists() functions have been improved to accept not only section
+ number but also section name.
+ type: improved
diff --git a/course/format/classes/local/cmactions.php b/course/format/classes/local/cmactions.php
index 3e9f1e54c69..fe300b1008c 100644
--- a/course/format/classes/local/cmactions.php
+++ b/course/format/classes/local/cmactions.php
@@ -17,6 +17,7 @@
namespace core_courseformat\local;
+use core_courseformat\sectiondelegatemodule;
use course_modinfo;
/**
* Course module course format actions.
@@ -26,8 +27,44 @@ use course_modinfo;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cmactions extends baseactions {
+ /**
+ * Update a course delegated section linked to the given module.
+ *
+ * @param \stdClass $cm
+ * @param array $sectionfields to change in section database record.
+ * @param bool $rebuildcache If true (default), perform a partial cache purge and rebuild.
+ * @return bool true if any delegated section has been updated, false otherwise.
+ */
+ protected function update_delegated(
+ \stdClass $cm,
+ array $sectionfields,
+ bool $rebuildcache = true
+ ): bool {
+
+ if (!sectiondelegatemodule::has_delegate_class('mod_' . $cm->modname)) {
+ return false;
+ }
+
+ // Propagate the changes to delegated section.
+ $cminfo = \cm_info::create($cm);
+ if (!$delegatedsection = $cminfo->get_delegated_section_info()) {
+ return false;
+ }
+
+ $sectionactions = new sectionactions($this->course);
+ $sectionactions->update($delegatedsection, $sectionfields);
+
+ if ($rebuildcache) {
+ course_modinfo::purge_course_section_cache_by_id($cm->course, $delegatedsection->id);
+ rebuild_course_cache($cm->course, false, true);
+ }
+
+ return true;
+ }
+
/**
* Rename a course module.
+ *
* @param int $cmid the course module id.
* @param string $name the new name.
* @return bool true if the course module was renamed, false otherwise.
@@ -64,12 +101,16 @@ class cmactions extends baseactions {
]
);
$cm->name = $name;
+ $fields = new \stdClass();
+ $fields->name = $name;
\core\event\course_module_updated::create_from_cm($cm)->trigger();
course_modinfo::purge_course_module_cache($cm->course, $cm->id);
rebuild_course_cache($cm->course, false, true);
+ $this->update_delegated($cm, ['name' => $name]);
+
// Modules may add some logic to renaming.
$modinfo = get_fast_modinfo($cm->course);
\core\di::get(\core\hook\manager::class)->dispatch(
@@ -87,4 +128,100 @@ class cmactions extends baseactions {
return true;
}
+
+ /**
+ * Update a course module.
+ *
+ * @param int $cmid the course module id.
+ * @param int $visible state of the module
+ * @param int $visibleoncoursepage state of the module on the course page
+ * @param bool $rebuildcache If true (default), perform a partial cache purge and rebuild.
+ * @return bool whether course module was updated
+ */
+ public function set_visibility(int $cmid, int $visible, int $visibleoncoursepage = 1, bool $rebuildcache = true): bool {
+ global $DB, $CFG;
+ require_once($CFG->libdir.'/gradelib.php');
+ require_once($CFG->dirroot.'/calendar/lib.php');
+
+ if (!$cm = get_coursemodule_from_id('', $cmid, 0, false, MUST_EXIST)) {
+ return false;
+ }
+
+ // Create events and propagate visibility to associated grade items if the value has changed.
+ // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
+ if ($cm->visible == $visible && $cm->visibleoncoursepage == $visibleoncoursepage) {
+ return true;
+ }
+
+ if (!$modulename = $DB->get_field('modules', 'name', ['id' => $cm->module])) {
+ return false;
+ }
+
+ // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
+ // affect visibleold to allow for an original visibility restore. See set_section_visible().
+ $cminfo = (object)[
+ 'id' => $cmid,
+ 'visible' => $visible,
+ 'visibleoncoursepage' => $visibleoncoursepage,
+ 'visibleold' => $visible,
+ ];
+
+ $DB->update_record('course_modules', $cminfo);
+ $DB->update_record(
+ $cm->modname,
+ (object)[
+ 'id' => $cm->instance,
+ 'timemodified' => time(),
+ ]
+ );
+
+ $fields = ['visible' => $visible, 'visibleold' => $visible];
+ $this->update_delegated($cm, $fields, false);
+
+ if ($rebuildcache) {
+ \course_modinfo::purge_course_module_cache($cm->course, $cm->id);
+ rebuild_course_cache($cm->course, false, true);
+ }
+
+ if ($cm->visible == $visible) {
+ // There is nothing else to change.
+ return true;
+ }
+
+ if ($events = $DB->get_records('event', ['instance' => $cm->instance, 'modulename' => $modulename])) {
+ foreach ($events as $event) {
+ if ($visible) {
+ $event = new \calendar_event($event);
+ $event->toggle_visibility(true);
+ } else {
+ $event = new \calendar_event($event);
+ $event->toggle_visibility(false);
+ }
+ }
+ }
+
+ // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
+ // Note that this must be done after updating the row in course_modules, in case
+ // the modules grade_item_update function needs to access $cm->visible.
+ $supportsgrade = plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
+ component_callback_exists('mod_' . $modulename, 'grade_item_update');
+ if ($supportsgrade) {
+ $instance = $DB->get_record($modulename, ['id' => $cm->instance], '*', MUST_EXIST);
+ component_callback('mod_' . $modulename, 'grade_item_update', [$instance]);
+ } else {
+ $gradeitems = \grade_item::fetch_all([
+ 'itemtype' => 'mod',
+ 'itemmodule' => $modulename,
+ 'iteminstance' => $cm->instance,
+ 'courseid' => $cm->course,
+ ]);
+ if ($gradeitems) {
+ foreach ($gradeitems as $gradeitem) {
+ $gradeitem->set_hidden(!$visible);
+ }
+ }
+ }
+
+ return true;
+ }
}
diff --git a/course/format/classes/local/sectionactions.php b/course/format/classes/local/sectionactions.php
index f0e5657a9e1..9730fcae82e 100644
--- a/course/format/classes/local/sectionactions.php
+++ b/course/format/classes/local/sectionactions.php
@@ -404,6 +404,18 @@ class sectionactions extends baseactions {
$modules = explode(',', $sectioninfo->sequence);
$cmids = [];
+
+ // In case the section is delegated by a module, we change also the visibility for the source module.
+ if ($sectioninfo->is_delegated()) {
+ $delegateinstance = $sectioninfo->get_component_instance();
+ // We only return sections delegated by course modules. Sections delegated to other
+ // types of components must implement their own methods to get the section.
+ if ($delegateinstance && ($delegateinstance instanceof \core_courseformat\sectiondelegatemodule)) {
+ $delegator = $delegateinstance->get_cm();
+ $modules[] = $delegator->id;
+ }
+ }
+
foreach ($modules as $moduleid) {
$cm = get_coursemodule_from_id(null, $moduleid, $this->course->id);
if (!$cm) {
diff --git a/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php b/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php
index 4a0a0e32ec5..3634eedd332 100644
--- a/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php
+++ b/course/format/classes/output/local/content/cm/delegatedcontrolmenu.php
@@ -121,6 +121,52 @@ class delegatedcontrolmenu extends basecontrolmenu {
];
}
+ // Hide/Show uses module functionality.
+ // Hide/Show options will be available for subsections inside visible sections only.
+ $parentsection = $cm->get_section_info();
+ $availablevisibility = has_capability('moodle/course:sectionvisibility', $coursecontext, $user) && $parentsection->visible;
+ if ($availablevisibility) {
+ $url = clone($baseurl);
+ if (!is_null($sectionreturn)) {
+ $url->param('sr', $format->get_sectionid());
+ }
+ $strhidefromothers = get_string('hidefromothers', 'format_' . $course->format);
+ $strshowfromothers = get_string('showfromothers', 'format_' . $course->format);
+ if ($section->visible) { // Show the hide/show eye.
+ $url->param('hide', $section->section);
+ $controls['visiblity'] = [
+ 'url' => $url,
+ 'icon' => 'i/show',
+ 'name' => $strhidefromothers,
+ 'pixattr' => ['class' => ''],
+ 'attr' => [
+ 'class' => 'editing_showhide',
+ 'data-sectionreturn' => $sectionreturn,
+ 'data-action' => ($usecomponents) ? 'sectionHide' : 'hide',
+ 'data-id' => $section->id,
+ 'data-swapname' => $strshowfromothers,
+ 'data-swapicon' => 'i/show',
+ ],
+ ];
+ } else {
+ $url->param('show', $section->section);
+ $controls['visiblity'] = [
+ 'url' => $url,
+ 'icon' => 'i/hide',
+ 'name' => $strshowfromothers,
+ 'pixattr' => ['class' => ''],
+ 'attr' => [
+ 'class' => 'editing_showhide',
+ 'data-sectionreturn' => $sectionreturn,
+ 'data-action' => ($usecomponents) ? 'sectionShow' : 'show',
+ 'data-id' => $section->id,
+ 'data-swapname' => $strhidefromothers,
+ 'data-swapicon' => 'i/hide',
+ ],
+ ];
+ }
+ }
+
// Delete deletes the module.
// Only show the view link if we are not already in the section view page.
if (!$isheadersection && $hasmanageactivities) {
diff --git a/course/format/classes/sectiondelegatemodule.php b/course/format/classes/sectiondelegatemodule.php
index c91ba739812..675de5bf233 100644
--- a/course/format/classes/sectiondelegatemodule.php
+++ b/course/format/classes/sectiondelegatemodule.php
@@ -149,7 +149,7 @@ abstract class sectiondelegatemodule extends sectiondelegate {
controlmenu $controlmenu,
renderer_base $output,
): ?action_menu {
- $controlmenuclass = $format->get_output_classname('content\\cm\\controlmenu');
+ $controlmenuclass = $format->get_output_classname('content\\cm\\delegatedcontrolmenu');
$controlmenu = new $controlmenuclass(
$format,
$this->sectioninfo,
diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php
index 8ee286a717c..ba93a2e778a 100644
--- a/course/format/classes/stateactions.php
+++ b/course/format/classes/stateactions.php
@@ -542,8 +542,18 @@ class stateactions {
course_modinfo::purge_course_modules_cache($course->id, $ids);
rebuild_course_cache($course->id, false, true);
+ $delegatedsections = [];
foreach ($cms as $cm) {
$updates->add_cm_put($cm->id);
+ if (!$delegatedsection = $cm->get_delegated_section_info()) {
+ continue;
+ }
+ if (!in_array($delegatedsection->id, $delegatedsections)) {
+ $delegatedsections[] = $delegatedsection->id;
+ }
+ }
+ foreach ($delegatedsections as $sectionid => $section) {
+ $updates->add_section_put($sectionid);
}
}
diff --git a/course/format/templates/local/content/cm/controlmenu.mustache b/course/format/templates/local/content/cm/controlmenu.mustache
index 9f3bcd82b13..7a9b5e6f3fe 100644
--- a/course/format/templates/local/content/cm/controlmenu.mustache
+++ b/course/format/templates/local/content/cm/controlmenu.mustache
@@ -21,7 +21,7 @@
Example context (json):
{
- "menu": "Edit",
+ "menu": "Edit",
"hasmenu": true
}
}}
diff --git a/course/lib.php b/course/lib.php
index 7115a7c78ce..8c9fd69c266 100644
--- a/course/lib.php
+++ b/course/lib.php
@@ -679,74 +679,15 @@ function set_downloadcontent(int $id, bool $downloadcontent): bool {
* and rebuilt as appropriate. Consider using this if set_coursemodule_visible is called multiple times
* (e.g. in a loop).
*
- * @param int $id of the module
+ * @param int $cmid course module id
* @param int $visible state of the module
* @param int $visibleoncoursepage state of the module on the course page
* @param bool $rebuildcache If true (default), perform a partial cache purge and rebuild.
* @return bool false when the module was not found, true otherwise
*/
-function set_coursemodule_visible($id, $visible, $visibleoncoursepage = 1, bool $rebuildcache = true) {
- global $DB, $CFG;
- require_once($CFG->libdir.'/gradelib.php');
- require_once($CFG->dirroot.'/calendar/lib.php');
-
- if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
- return false;
- }
-
- // Create events and propagate visibility to associated grade items if the value has changed.
- // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
- if ($cm->visible == $visible && $cm->visibleoncoursepage == $visibleoncoursepage) {
- return true;
- }
-
- if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
- return false;
- }
- if (($cm->visible != $visible) &&
- ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $modulename)))) {
- foreach($events as $event) {
- if ($visible) {
- $event = new calendar_event($event);
- $event->toggle_visibility(true);
- } else {
- $event = new calendar_event($event);
- $event->toggle_visibility(false);
- }
- }
- }
-
- // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
- // affect visibleold to allow for an original visibility restore. See set_section_visible().
- $cminfo = new stdClass();
- $cminfo->id = $id;
- $cminfo->visible = $visible;
- $cminfo->visibleoncoursepage = $visibleoncoursepage;
- $cminfo->visibleold = $visible;
- $DB->update_record('course_modules', $cminfo);
-
- // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
- // Note that this must be done after updating the row in course_modules, in case
- // the modules grade_item_update function needs to access $cm->visible.
- if ($cm->visible != $visible &&
- plugin_supports('mod', $modulename, FEATURE_CONTROLS_GRADE_VISIBILITY) &&
- component_callback_exists('mod_' . $modulename, 'grade_item_update')) {
- $instance = $DB->get_record($modulename, array('id' => $cm->instance), '*', MUST_EXIST);
- component_callback('mod_' . $modulename, 'grade_item_update', array($instance));
- } else if ($cm->visible != $visible) {
- $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
- if ($grade_items) {
- foreach ($grade_items as $grade_item) {
- $grade_item->set_hidden(!$visible);
- }
- }
- }
-
- if ($rebuildcache) {
- \course_modinfo::purge_course_module_cache($cm->course, $cm->id);
- rebuild_course_cache($cm->course, false, true);
- }
- return true;
+function set_coursemodule_visible($cmid, $visible, $visibleoncoursepage = 1, bool $rebuildcache = true) {
+ $coursecontext = context_module::instance($cmid)->get_course_context();
+ return formatactions::cm($coursecontext->instanceid)->set_visibility($cmid, $visible, $visibleoncoursepage, $rebuildcache);
}
/**
diff --git a/course/tests/behat/behat_course.php b/course/tests/behat/behat_course.php
index 28a0ca7eb1e..9b0a0ecd95e 100644
--- a/course/tests/behat/behat_course.php
+++ b/course/tests/behat/behat_course.php
@@ -305,26 +305,26 @@ class behat_course extends behat_base {
/**
* Opens a section edit menu if it is not already opened.
*
- * @Given /^I open section "(?P\d+)" edit menu$/
+ * @Given /^I open section "(?P(?:[^"]|\\")*)" edit menu$/
* @throws DriverException The step is not available when Javascript is disabled
- * @param string $sectionnumber
+ * @param string|int $section
*/
- public function i_open_section_edit_menu($sectionnumber) {
+ public function i_open_section_edit_menu($section) {
if (!$this->running_javascript()) {
throw new DriverException('Section edit menu not available when Javascript is disabled');
}
// Wait for section to be available, before clicking on the menu.
- $this->i_wait_until_section_is_available($sectionnumber);
+ $this->i_wait_until_section_is_available($section);
// If it is already opened we do nothing.
- $xpath = $this->section_exists($sectionnumber);
- $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@data-toggle, 'dropdown')]";
+ $xpath = $this->section_exists($section);
+ $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[@data-toggle='dropdown']";
- $exception = new ExpectationException('Section "' . $sectionnumber . '" was not found', $this->getSession());
+ $exception = new ExpectationException('Section "' . $section . '" was not found', $this->getSession());
$menu = $this->find('xpath', $xpath, $exception);
$menu->click();
- $this->i_wait_until_section_is_available($sectionnumber);
+ $this->i_wait_until_section_is_available($section);
}
/**
@@ -404,34 +404,45 @@ class behat_course extends behat_base {
/**
* Shows the specified hidden section. You need to be in the course page and on editing mode.
*
- * @Given /^I show section "(?P\d+)"$/
- * @param int $sectionnumber
+ * @Given /^I show section "(?P(?:[^"]|\\")*)"$/
+ * @param int|string $section
*/
- public function i_show_section($sectionnumber) {
- $showlink = $this->show_section_link_exists($sectionnumber);
+ public function i_show_section($section) {
+ // Ensures the section exists.
+ $xpath = $this->section_exists($section);
+ // We need to know the course format as the text strings depends on them.
+ $courseformat = $this->get_course_format();
+ $strshow = get_string('showfromothers', $courseformat);
- // Ensure section edit menu is open before interacting with it.
+
+ // If javascript is on, link is inside a menu.
if ($this->running_javascript()) {
- $this->i_open_section_edit_menu($sectionnumber);
+ $this->i_open_section_edit_menu($section);
}
- $showlink->click();
+
+ // Ensure the click is using the action menu and not the visibility badge.
+ $xpath .= "//*[@role='menu']";
+
+ // Click on hide link.
+ $this->execute('behat_general::i_click_on_in_the',
+ [$strshow, "link", $this->escape($xpath), "xpath_element"]
+ );
if ($this->running_javascript()) {
$this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
- $this->i_wait_until_section_is_available($sectionnumber);
+ $this->i_wait_until_section_is_available($section);
}
}
/**
* Hides the specified visible section. You need to be in the course page and on editing mode.
*
- * @Given /^I hide section "(?P\d+)"$/
- * @param int $sectionnumber
+ * @Given /^I hide section "(?P(?:[^"]|\\")*)"$/
+ * @param int|string $section
*/
- public function i_hide_section($sectionnumber) {
+ public function i_hide_section($section) {
// Ensures the section exists.
- $xpath = $this->section_exists($sectionnumber);
-
+ $xpath = $this->section_exists($section);
// We need to know the course format as the text strings depends on them.
$courseformat = $this->get_course_format();
if (get_string_manager()->string_exists('hidefromothers', $courseformat)) {
@@ -442,17 +453,17 @@ class behat_course extends behat_base {
// If javascript is on, link is inside a menu.
if ($this->running_javascript()) {
- $this->i_open_section_edit_menu($sectionnumber);
+ $this->i_open_section_edit_menu($section);
}
- // Click on delete link.
+ // Click on hide link.
$this->execute('behat_general::i_click_on_in_the',
array($strhide, "link", $this->escape($xpath), "xpath_element")
);
if ($this->running_javascript()) {
$this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS);
- $this->i_wait_until_section_is_available($sectionnumber);
+ $this->i_wait_until_section_is_available($section);
}
}
@@ -1234,14 +1245,14 @@ class behat_course extends behat_base {
* Hopefully we would not require test writers to use this step
* and we will manage it from other step definitions.
*
- * @Given /^I wait until section "(?P\d+)" is available$/
- * @param int $sectionnumber
+ * @Given /^I wait until section "(?P(?:[^"]|\\")*)" is available$/
+ * @param int|string $section
* @return void
*/
- public function i_wait_until_section_is_available($sectionnumber) {
+ public function i_wait_until_section_is_available($section) {
// Looks for a hidden lightbox or a non-existent lightbox in that section.
- $sectionxpath = $this->section_exists($sectionnumber);
+ $sectionxpath = $this->section_exists($section);
$hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" .
" | " .
$sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]";
@@ -1282,10 +1293,26 @@ class behat_course extends behat_base {
* Checks if the course section exists.
*
* @throws ElementNotFoundException Thrown by behat_base::find
+ * @param int|string $section Section number or name to look for.
+ * @return string The xpath of the section.
+ */
+ protected function section_exists($section) {
+
+ if (is_numeric($section)) {
+ return $this->section_number_exists($section);
+ }
+
+ return $this->section_name_exists($section);
+ }
+
+ /**
+ * Checks if the course section number exists.
+ *
+ * @throws ElementNotFoundException Thrown by behat_base::find
* @param int $sectionnumber
* @return string The xpath of the section.
*/
- protected function section_exists($sectionnumber) {
+ protected function section_number_exists(int $sectionnumber): string {
// Just to give more info in case it does not exist.
$xpath = "//li[@id='section-" . $sectionnumber . "']";
@@ -1295,17 +1322,39 @@ class behat_course extends behat_base {
return $xpath;
}
+ /**
+ * Checks if the section name exists.
+ *
+ * @throws ElementNotFoundException Thrown by behat_base::find
+ * @param string $sectionname
+ * @return string The xpath of the section.
+ */
+ protected function section_name_exists(string $sectionname): string {
+ // Let's try to find section or subsection in course page.
+ $xpath = "//li[@data-for='section']//*[@data-for='section_title' and contains(normalize-space(.), '" . $sectionname ."')]";
+ $exception = new ElementNotFoundException($this->getSession(), "Section $sectionname ");
+ try {
+ $this->find('xpath', $xpath, $exception);
+ } catch (ElementNotFoundException $e) {
+ // Let's try to find section in section page.
+ $xpath = "//header[@id='page-header' and contains(normalize-space(.), '" . $sectionname ."')]";
+ $this->find('xpath', $xpath, $exception);
+ }
+
+ return $xpath;
+ }
+
/**
* Returns the show section icon or throws an exception.
*
* @throws ElementNotFoundException Thrown by behat_base::find
- * @param int $sectionnumber
+ * @param int|string $section Section number or name to look for.
* @return NodeElement
*/
- protected function show_section_link_exists($sectionnumber) {
+ protected function show_section_link_exists($section) {
// Gets the section xpath and ensure it exists.
- $xpath = $this->section_exists($sectionnumber);
+ $xpath = $this->section_exists($section);
// We need to know the course format as the text strings depends on them.
$courseformat = $this->get_course_format();
@@ -1324,13 +1373,13 @@ class behat_course extends behat_base {
* Returns the hide section icon link if it exists or throws exception.
*
* @throws ElementNotFoundException Thrown by behat_base::find
- * @param int $sectionnumber
+ * @param int|string $section Section number or name to look for.
* @return NodeElement
*/
- protected function hide_section_link_exists($sectionnumber) {
+ protected function hide_section_link_exists($section) {
// Gets the section xpath and ensure it exists.
- $xpath = $this->section_exists($sectionnumber);
+ $xpath = $this->section_exists($section);
// We need to know the course format as the text strings depends on them.
$courseformat = $this->get_course_format();
@@ -1370,6 +1419,10 @@ class behat_course extends behat_base {
throw $exception;
}
+ if (strstr($bodyid, 'page-course-view-section-') !== false) {
+ return 'format_' . str_replace('page-course-view-section-', '', $bodyid);
+ }
+
return 'format_' . str_replace('page-course-view-', '', $bodyid);
}
diff --git a/mod/subsection/tests/behat/subsection_actionmenu.feature b/mod/subsection/tests/behat/subsection_actionmenu.feature
index 7c180323305..d0d5ff3cfe4 100644
--- a/mod/subsection/tests/behat/subsection_actionmenu.feature
+++ b/mod/subsection/tests/behat/subsection_actionmenu.feature
@@ -33,10 +33,10 @@ Feature: The module menu replaces the delegated section menu
And I should not see "Assign roles"
And I should not see "Highlight"
And I should see "Edit settings"
- # Duplicate, Move and Show/Hide are not implemented yet.
+ # Duplicate and Move are not implemented yet.
And I should not see "Move"
And I should not see "Duplicate"
- And I should not see "Hide"
+ And I should see "Hide"
# Delete option for subsection page is not implemented yet.
And I should not see "Delete"
And I should see "Permalink"
@@ -50,12 +50,11 @@ Feature: The module menu replaces the delegated section menu
And I should not see "Highlight"
And I should see "View"
And I should see "Edit settings"
- # Duplicate, Move and Show/Hide are not implemented yet.
+ # Duplicate and Move are not implemented yet.
And I should not see "Move"
And I should not see "Duplicate"
- And I should not see "Hide"
+ And I should see "Hide"
And I should see "Delete"
- And I should see "Permalink"
@javascript
Scenario: The action menu for subsection module in section page has less options than a regular activity
@@ -67,10 +66,10 @@ Feature: The module menu replaces the delegated section menu
And I should not see "Highlight"
And I should see "View"
And I should see "Edit settings"
- # Duplicate, Move and Show/Hide are not implemented yet.
+ # Duplicate and Move are not implemented yet.
And I should not see "Move"
And I should not see "Duplicate"
- And I should not see "Hide"
+ And I should see "Hide"
And I should see "Delete"
And I should see "Permalink"
@@ -157,3 +156,54 @@ Feature: The module menu replaces the delegated section menu
# Subsection page. Open the section header action menu.
And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element"
And "Delete" "link" should not exist in the "[data-region='header-actions-container']" "css_element"
+
+ @javascript
+ Scenario: Hide/Show option in subsection action menu
+ Given I turn editing mode on
+ And I should not see "Hidden from students"
+ And I open "Subsection1" actions menu
+ When I choose "Hide" in the open action menu
+ Then I should see "Hidden from students"
+ Given I am on the "C1 > Subsection1" "course > section" page
+ And I should see "Hidden from students"
+ # Subsection page. Open the section header action menu.
+ And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element"
+ And I choose "Show" in the open action menu
+ And I should not see "Hidden from students"
+ And I click on "Section 1" "link" in the ".breadcrumb" "css_element"
+ And I should not see "Hidden from students"
+ # Section page. Open Subsection1 module action menu.
+ And I open "Subsection1" actions menu
+ And I choose "Hide" in the open action menu
+ And I should see "Hidden from students"
+
+ @javascript
+ Scenario: Hide/Show option in course page action menu for subsections
+ Given I am on the "C1" "Course" page
+ And I turn editing mode on
+ When I hide section "Subsection1"
+ Then I should see "Hidden from students"
+ And I show section "Subsection1"
+ And I should not see "Hidden from students"
+
+ @javascript
+ Scenario: Hide/Show option in subsection page action menu for subsections
+ Given I am on the "C1 > Subsection1" "course > section" page
+ And I turn editing mode on
+ When I hide section "Subsection1"
+ Then I should see "Hidden from students"
+ And I show section "Subsection1"
+ And I should not see "Hidden from students"
+
+ @javascript
+ Scenario: Subsections can't change visibility in hidden sections.
+ Given I am on the "C1" "Course" page
+ And I turn editing mode on
+ And I hide section "Section 1"
+ When I open section "Subsection1" edit menu
+ Then I should not see "Hide"
+ And I should not see "Show"
+ And I am on the "C1 > Section 1" "course > section" page
+ And I open section "Subsection1" edit menu
+ And I should not see "Hide"
+ And I should not see "Show"
diff --git a/mod/subsection/tests/courseformat/sectiondelegate_test.php b/mod/subsection/tests/courseformat/sectiondelegate_test.php
index 5d3a5bf0ced..450b2bc8a66 100644
--- a/mod/subsection/tests/courseformat/sectiondelegate_test.php
+++ b/mod/subsection/tests/courseformat/sectiondelegate_test.php
@@ -66,7 +66,7 @@ final class sectiondelegate_test extends \advanced_testcase {
// Highlight is only present in section menu (not module), so they shouldn't be found in the result.
// Duplicate is not implemented yet, so they shouldn't be found in the result.
- // The possible options are: View, Edit, Delete and Permalink.
+ // The possible options are: View, Edit, Show, Hide, Delete and Permalink.
if (get_string_manager()->string_exists('editsection', 'format_'.$format->get_format())) {
$streditsection = get_string('editsection', 'format_'.$format->get_format());
} else {
@@ -75,9 +75,12 @@ final class sectiondelegate_test extends \advanced_testcase {
$allowedoptions = [
get_string('view'),
$streditsection,
+ get_string('hidefromothers', 'format_' . $course->format),
+ get_string('showfromothers', 'format_' . $course->format),
get_string('delete'),
get_string('sectionlink', 'course'),
];
+
// The default section menu should be different for the delegated section menu.
$result = $delegated->get_section_action_menu($format, $controlmenu, $renderer);
foreach ($result->get_secondary_actions() as $secondaryaction) {