Merge branch 'MDL-79986-main' of https://github.com/sarjona/moodle

This commit is contained in:
Jun Pataleta 2023-12-04 16:01:37 +08:00
commit 1d5ee36809
No known key found for this signature in database
GPG Key ID: F83510526D99E2C7
36 changed files with 710 additions and 225 deletions

View File

@ -181,7 +181,6 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) { // sp
new lang_string('confignavcourselimit', 'admin'), 10, PARAM_INT));
$temp->add(new admin_setting_configcheckbox('usesitenameforsitepages', new lang_string('usesitenameforsitepages', 'admin'), new lang_string('configusesitenameforsitepages', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('linkadmincategories', new lang_string('linkadmincategories', 'admin'), new lang_string('linkadmincategories_help', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('linkcoursesections', new lang_string('linkcoursesections', 'admin'), new lang_string('linkcoursesections_help', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('navshowfrontpagemods', new lang_string('navshowfrontpagemods', 'admin'), new lang_string('navshowfrontpagemods_help', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('navadduserpostslinks', new lang_string('navadduserpostslinks', 'admin'), new lang_string('navadduserpostslinks_help', 'admin'), 1));

File diff suppressed because one or more lines are too long

View File

@ -57,7 +57,11 @@ class block_section_links_renderer extends plugin_renderer_base {
if ($section->highlight) {
$sectiontext = html_writer::tag('strong', $sectiontext);
}
$html .= html_writer::link(course_get_url($course, $section->section), $sectiontext, $attributes);
$html .= html_writer::link(
course_get_url($course, $section->section, ['navigation' => true]),
$sectiontext,
$attributes
);
$html .= html_writer::end_tag('li').' ';
}
$html .= html_writer::end_tag('ol');
@ -78,4 +82,4 @@ class block_section_links_renderer extends plugin_renderer_base {
return $html;
}
}
}

View File

@ -711,15 +711,14 @@ abstract class base {
* @param int|stdClass $section Section object from database or just field course_sections.section
* if null the course view page is returned
* @param array $options options for view URL. At the moment core uses:
* 'navigation' (bool) if true and section has no separate page, the function returns null
* 'sr' (int) used by multipage formats to specify to which section to return
* 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page.
* 'sr' (int) used by course formats to specify to which section to return
* 'expanded' (bool) if true the section will be shown expanded, true by default
* @return null|moodle_url
*/
public function get_view_url($section, $options = array()) {
global $CFG;
$course = $this->get_course();
$url = new moodle_url('/course/view.php', array('id' => $course->id));
$url = new moodle_url('/course/view.php', ['id' => $course->id]);
if (array_key_exists('sr', $options)) {
$sectionno = $options['sr'];
@ -728,9 +727,10 @@ abstract class base {
} else {
$sectionno = $section;
}
if (empty($CFG->linkcoursesections) && !empty($options['navigation']) && $sectionno !== null) {
// By default assume that sections are never displayed on separate pages.
return null;
if ((!empty($options['navigation']) || array_key_exists('sr', $options)) && $sectionno !== null) {
// Display section on separate page.
$sectioninfo = $this->get_section($sectionno);
return new moodle_url('/course/section.php', ['id' => $sectioninfo->id]);
}
if ($this->uses_sections() && $sectionno !== null) {
// The url includes the parameter to expand the section by default.
@ -743,6 +743,7 @@ abstract class base {
}
$url->set_anchor('section-'.$sectionno);
}
return $url;
}
@ -1915,4 +1916,13 @@ abstract class base {
return get_fast_modinfo($course)->get_section_info_by_id($newsection->id);
}
/**
* Get the required javascript files for the course format.
*
* @return array The list of javascript files required by the course format.
*/
public function get_required_jsfiles(): array {
return [];
}
}

View File

@ -86,12 +86,8 @@ class content implements named_templatable, renderable {
global $PAGE;
$format = $this->format;
// Most formats uses section 0 as a separate section so we remove from the list.
$sections = $this->export_sections($output);
$initialsection = '';
if (!empty($sections)) {
$initialsection = array_shift($sections);
}
$data = (object)[
'title' => $format->page_title(), // This method should be in the course_format class.
@ -188,7 +184,6 @@ class content implements named_templatable, renderable {
$singlesection = $this->format->get_section_number();
if ($singlesection) {
return [
$modinfo->get_section_info(0),
$modinfo->get_section_info($singlesection),
];
}

View File

@ -147,7 +147,8 @@ class section implements named_templatable, renderable {
'summary' => $summary->export_for_template($output),
'highlightedlabel' => $format->get_section_highlighted_name(),
'sitehome' => $course->id == SITEID,
'editing' => $PAGE->user_is_editing()
'editing' => $PAGE->user_is_editing(),
'displayonesection' => ($course->id != SITEID && $format->get_section_number() !== 0),
];
$haspartials = [];

View File

@ -135,7 +135,13 @@ class controlmenu implements named_templatable, renderable {
$baseurl = course_get_url($course, $sectionreturn);
$baseurl->param('sesskey', sesskey());
$controls = [];
$controls['view'] = [
'url' => new moodle_url('/course/section.php', ['id' => $section->id]),
'icon' => 'i/viewsection',
'name' => get_string('view'),
'pixattr' => ['class' => ''],
'attr' => ['class' => 'icon view'],
];
if (!$isstealth && has_capability('moodle/course:update', $coursecontext, $user)) {
if ($section->section > 0
@ -291,9 +297,8 @@ class controlmenu implements named_templatable, renderable {
], $coursecontext)
) {
$sectionlink = new moodle_url(
'/course/view.php',
['id' => $course->id],
"sectionid-{$section->id}-title"
'/course/section.php',
['id' => $section->id]
);
$controls['permalink'] = [
'url' => $sectionlink,

View File

@ -76,14 +76,28 @@ class header implements named_templatable, renderable {
'id' => $section->id,
];
$data->title = $output->section_title_without_link($section, $course);
$data->editing = $format->show_editor();
if ($course->id == SITEID) {
$data->title = $output->section_title_without_link($section, $course);
$data->sitehome = true;
} else {
if ($format->get_section_number() === 0) {
// All sections are displayed.
if (!$data->editing) {
$data->title = $output->section_title($section, $course);
} else {
$data->title = $output->section_title_without_link($section, $course);
}
} else {
// Only one section is displayed.
$data->displayonesection = true;
$data->title = $output->section_title_without_link($section, $course);
}
}
$coursedisplay = $format->get_course_display();
$data->headerdisplaymultipage = false;
if ($coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
$data->headerdisplaymultipage = true;
$data->title = $output->section_title($section, $course);
}
$data->headerdisplaymultipage = ($coursedisplay == COURSE_DISPLAY_MULTIPAGE);
if ($section->section > $format->get_last_section_number()) {
// Stealth sections (orphaned) has special title.
@ -94,16 +108,8 @@ class header implements named_templatable, renderable {
$data->ishidden = true;
}
if ($course->id == SITEID) {
$data->sitehome = true;
}
$data->editing = $format->show_editor();
if (!$format->show_editor() && $coursedisplay == COURSE_DISPLAY_MULTIPAGE && empty($data->issinglesection)) {
if ($section->uservisible) {
$data->url = course_get_url($course, $section->section);
}
if (!$data->editing && $section->uservisible) {
$data->url = course_get_url($course, $section->section, ['navigation' => true]);
}
$data->name = get_section_name($course, $section);
$data->selecttext = $format->get_format_string('selectsection', $data->name);

View File

@ -100,7 +100,7 @@ class sectionnavigation implements named_templatable, renderable {
$data->previoushidden = true;
}
$data->previousname = get_section_name($course, $sections[$back]);
$data->previousurl = course_get_url($course, $back);
$data->previousurl = course_get_url($course, $back, ['navigation' => true]);
$data->hasprevious = true;
}
$back--;
@ -114,7 +114,7 @@ class sectionnavigation implements named_templatable, renderable {
$data->nexthidden = true;
}
$data->nextname = get_section_name($course, $sections[$forward]);
$data->nexturl = course_get_url($course, $forward);
$data->nexturl = course_get_url($course, $forward, ['navigation' => true]);
$data->hasnext = true;
}
$forward++;

View File

@ -83,7 +83,7 @@ class section implements renderable {
'rawtitle' => $section->name,
'cmlist' => [],
'visible' => !empty($section->visible),
'sectionurl' => course_get_url($course, $section->section)->out(),
'sectionurl' => course_get_url($course, $section->section, ['navigation' => true])->out(),
'current' => $format->is_section_current($section),
'indexcollapsed' => $indexcollapsed,
'contentcollapsed' => $contentcollapsed,

View File

@ -45,22 +45,12 @@ class format_singleactivity extends core_courseformat\base {
* @param int|stdClass $section Section object from database or just field course_sections.section
* if null the course view page is returned
* @param array $options options for view URL. At the moment core uses:
* 'navigation' (bool) if true and section has no separate page, the function returns null
* 'sr' (int) used by multipage formats to specify to which section to return
* 'navigation' (bool) ignored by this format
* 'sr' (int) ignored by this format
* @return null|moodle_url
*/
public function get_view_url($section, $options = array()) {
$sectionnum = $section;
if (is_object($sectionnum)) {
$sectionnum = $section->section;
}
if ($sectionnum == 1) {
return new moodle_url('/course/view.php', array('id' => $this->courseid, 'section' => 1));
}
if (!empty($options['navigation']) && $section !== null) {
return null;
}
return new moodle_url('/course/view.php', array('id' => $this->courseid));
return new moodle_url('/course/view.php', ['id' => $this->courseid]);
}
/**

View File

@ -0,0 +1,77 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace format_singleactivity;
/**
* Single activity course format related unit tests.
*
* @package format_singleactivity
* @copyright 2023 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \format_singleactivity
*/
class format_singleactivity_test extends \advanced_testcase {
/**
* Test for get_view_url().
*
* @covers ::get_view_url
*/
public function test_get_view_url(): void {
global $CFG;
$this->resetAfterTest();
// Generate a course with two sections (0 and 1) and two modules.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course(['format' => 'singleactivity']);
course_create_sections_if_missing($course1, [0, 1]);
$data = (object)['id' => $course1->id];
$format = course_get_format($course1);
$format->update_course_format_options($data);
// In page.
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
// Navigation.
$this->assertStringContainsString('course/view.php', $format->get_view_url(0));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1, ['navigation' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0, ['sr' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1, ['sr' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0, ['sr' => 0]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1, ['sr' => 0]));
}
/**
* Test get_required_jsfiles().
*
* @covers ::get_required_jsfiles
*/
public function test_get_required_jsfiles(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(['format' => 'singleactivity']);
$format = course_get_format($course);
$this->assertEmpty($format->get_required_jsfiles());
}
}

View File

@ -41,15 +41,12 @@ class format_social extends core_courseformat\base {
* @param int|stdClass $section Section object from database or just field course_sections.section
* if null the course view page is returned
* @param array $options options for view URL. At the moment core uses:
* 'navigation' (bool) if true and section has no separate page, the function returns null
* 'sr' (int) used by multipage formats to specify to which section to return
* 'navigation' (bool) ignored by this format
* 'sr' (int) ignored by this format
* @return null|moodle_url
*/
public function get_view_url($section, $options = array()) {
if (!empty($options['navigation']) && $section !== null) {
return null;
}
return new moodle_url('/course/view.php', array('id' => $this->courseid));
return new moodle_url('/course/view.php', ['id' => $this->courseid]);
}
/**

View File

@ -0,0 +1,77 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace format_social;
/**
* Social course format related unit tests.
*
* @package format_social
* @copyright 2023 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \format_social
*/
class format_social_test extends \advanced_testcase {
/**
* Test for get_view_url().
*
* @covers ::get_view_url
*/
public function test_get_view_url(): void {
global $CFG;
$this->resetAfterTest();
// Generate a course with two sections (0 and 1) and two modules.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course(['format' => 'social']);
course_create_sections_if_missing($course1, [0, 1]);
$data = (object)['id' => $course1->id];
$format = course_get_format($course1);
$format->update_course_format_options($data);
// In page.
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
// Navigation.
$this->assertStringContainsString('course/view.php', $format->get_view_url(0));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1, ['navigation' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0, ['sr' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1, ['sr' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0, ['sr' => 0]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1, ['sr' => 0]));
}
/**
* Test get_required_jsfiles().
*
* @covers ::get_required_jsfiles
*/
public function test_get_required_jsfiles(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(['format' => 'social']);
$format = course_get_format($course);
$this->assertEmpty($format->get_required_jsfiles());
}
}

View File

@ -148,7 +148,7 @@
{{/header}}
</div>
<div id="coursecontentcollapse{{num}}"
class="content {{^iscoursedisplaymultipage}}{{^sitehome}}course-content-item-content collapse {{^contentcollapsed}}show{{/contentcollapsed}}{{/sitehome}}{{/iscoursedisplaymultipage}}">
class="content {{^iscoursedisplaymultipage}}{{^sitehome}}{{^displayonesection}}course-content-item-content collapse {{^contentcollapsed}}show{{/contentcollapsed}}{{/displayonesection}}{{/sitehome}}{{/iscoursedisplaymultipage}}">
<div class="{{#hasavailability}}description{{/hasavailability}} my-3" data-for="sectioninfo">
{{#summary}}
{{$ core_courseformat/local/content/section/summary }}

View File

@ -47,30 +47,36 @@
</h2>
{{/sitehome}}
{{^sitehome}}
<div class="d-flex align-items-start position-relative">
<a role="button"
data-toggle="collapse"
data-for="sectiontoggler"
href="#coursecontentcollapse{{num}}"
id="collapssesection{{num}}"
aria-expanded="{{^contentcollapsed}}true{{/contentcollapsed}}{{#contentcollapsed}}false{{/contentcollapsed}}"
aria-controls="coursecontentcollapse{{num}}"
class="btn btn-icon mr-1 icons-collapse-expand justify-content-center
{{^editing}} stretched-link {{/editing}}
{{#contentcollapsed}} collapsed {{/contentcollapsed}}"
aria-label="{{name}}">
<span class="expanded-icon icon-no-margin p-2" title="{{#str}} collapse, core {{/str}}">
{{#pix}} t/expandedchevron, core {{/pix}}
</span>
<span class="collapsed-icon icon-no-margin p-2" title="{{#str}} expand, core {{/str}}">
<span class="dir-rtl-hide">{{#pix}} t/collapsedchevron, core {{/pix}}</span>
<span class="dir-ltr-hide">{{#pix}} t/collapsedchevron_rtl, core {{/pix}}</span>
</span>
</a>
<h3 class="sectionname course-content-item d-flex align-self-stretch align-items-center mb-0"
id="sectionid-{{id}}-title" data-for="section_title" data-id="{{id}}" data-number="{{num}}">
{{#displayonesection}}
<h3 id="sectionid-{{id}}-title" class="sectionname">
{{{title}}}
</h3>
</div>
{{/displayonesection}}
{{^displayonesection}}
<div class="d-flex align-items-start position-relative">
<a role="button"
data-toggle="collapse"
data-for="sectiontoggler"
href="#coursecontentcollapse{{num}}"
id="collapssesection{{num}}"
aria-expanded="{{^contentcollapsed}}true{{/contentcollapsed}}{{#contentcollapsed}}false{{/contentcollapsed}}"
aria-controls="coursecontentcollapse{{num}}"
class="btn btn-icon mr-1 icons-collapse-expand justify-content-center
{{#contentcollapsed}} collapsed {{/contentcollapsed}}"
aria-label="{{name}}">
<span class="expanded-icon icon-no-margin p-2" title="{{#str}} collapse, core {{/str}}">
{{#pix}} t/expandedchevron, core {{/pix}}
</span>
<span class="collapsed-icon icon-no-margin p-2" title="{{#str}} expand, core {{/str}}">
<span class="dir-rtl-hide">{{#pix}} t/collapsedchevron, core {{/pix}}</span>
<span class="dir-ltr-hide">{{#pix}} t/collapsedchevron_rtl, core {{/pix}}</span>
</span>
</a>
<h3 class="sectionname course-content-item d-flex align-self-stretch align-items-center mb-0"
id="sectionid-{{id}}-title" data-for="section_title" data-id="{{id}}" data-number="{{num}}">
{{{title}}}
</h3>
</div>
{{/displayonesection}}
{{/sitehome}}
{{/headerdisplaymultipage}}

View File

@ -20,6 +20,7 @@
* @package core_course
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \core_courseformat\base
* @coversDefaultClass \core_courseformat\base
*/
class base_test extends advanced_testcase {
@ -199,14 +200,14 @@ class base_test extends advanced_testcase {
}
/**
* Test for get_view_url() to ensure that the url is only given for the correct cases
* Test for get_view_url().
*
* @covers ::get_view_url
*/
public function test_get_view_url() {
public function test_get_view_url(): void {
global $CFG;
$this->resetAfterTest();
$linkcoursesections = $CFG->linkcoursesections;
// Generate a course with two sections (0 and 1) and two modules. Course format is set to 'testformat'.
// This will allow us to test the default implementation of get_view_url.
$generator = $this->getDataGenerator();
@ -218,22 +219,20 @@ class base_test extends advanced_testcase {
$format->update_course_format_options($data);
// In page.
$CFG->linkcoursesections = 0;
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
$CFG->linkcoursesections = 1;
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
// Navigation.
$CFG->linkcoursesections = 0;
$this->assertNull($format->get_view_url(1, ['navigation' => 1]));
$this->assertNull($format->get_view_url(0, ['navigation' => 1]));
$CFG->linkcoursesections = 1;
$this->assertNotEmpty($format->get_view_url(1, ['navigation' => 1]));
$this->assertNotEmpty($format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['navigation' => 1]));
// When sr parameter is defined, the section.php page should be returned.
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 0]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 0]));
// Expand section.
// The current course format $format uses the format 'testformat' which does not use sections.
@ -756,6 +755,21 @@ class base_test extends advanced_testcase {
],
];
}
/**
* Test get_required_jsfiles().
*
* @covers ::get_required_jsfiles
*/
public function test_get_required_jsfiles(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(['format' => 'testformat']);
$format = course_get_format($course);
$this->assertEmpty($format->get_required_jsfiles());
}
}
/**

View File

@ -1,5 +1,5 @@
@core @core_courseformat @core_course @show_editor @javascript
Feature: Bulk course section actions one section per page.
Feature: Bulk course section actions one section per page
In order to edit the course section in one section per page setting
As a teacher
I need to be able to edit sections in bulk in both display modes.
@ -33,8 +33,10 @@ Feature: Bulk course section actions one section per page.
Scenario: Bulk section edit is only available when multiple sections are displayed
Given I click on "Select topic Topic 1" "checkbox"
And I should see "1 selected" in the "sticky-footer" "region"
And I click on "Close bulk actions" "button" in the "sticky-footer" "region"
# Move to single topic page.
When I click on "Topic 1" "link" in the "region-main" "region"
And I open section "1" edit menu
When I click on "View" "link" in the "Topic 1" "section"
And I click on "Bulk actions" "button"
Then "Select topic Topic 1" "checkbox" should not exist

View File

@ -1,5 +1,5 @@
@core @core_courseformat @show_editor
Feature: Verify edit utils availability.
Feature: Verify edit utils availability
In order to edit the course activities
As a student with capability 'moodle/course:manageactivities'
I need to be able to use the edit utils.
@ -49,16 +49,17 @@ Feature: Verify edit utils availability.
Then I should not see "Edit mode"
@javascript
Scenario: Edit tools should be available to students with the capability 'moodle/course:manageactivities',
but should not be allowed to add and edit sections without having 'moodle/course:update'
Scenario: Edit tools should be available to students with manageactivities capability but not allowed to add sections without course:update
Given I log in as "author1"
When I am on "Course 1" course homepage
And I turn editing mode on
Then I should see "Add an activity or resource"
And I should not see "Add topic"
But I should not see "Add topic"
And I open "Activity sample 1" actions menu
And I should see "Edit settings"
And ".section_action_menu" "css_element" should not exist in the "Topic 1" "section"
And I open section "1" edit menu
And I should not see "Edit settings"
And I should see "View"
@javascript
Scenario: Section adding should be available to students if they also have the capability 'moodle/course:update'.

View File

@ -0,0 +1,82 @@
@core @core_courseformat
Feature: Single section course page
In order to improve the course page
As a user
I need to be able to see a section in a single page
Background:
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "activities" exist:
| activity | name | course | idnumber | section |
| assign | Activity sample 0.1 | C1 | sample1 | 0 |
| assign | Activity sample 1.1 | C1 | sample1 | 1 |
| assign | Activity sample 1.2 | C1 | sample2 | 1 |
| assign | Activity sample 1.3 | C1 | sample3 | 1 |
| assign | Activity sample 2.1 | C1 | sample3 | 2 |
| assign | Activity sample 2.2 | C1 | sample3 | 2 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
Given I am on the "C1" "Course" page logged in as "teacher1"
@javascript
Scenario: Collapsed sections are always expanded in the single section page
Given I press "Collapse all"
And I should not see "Activity sample 1.1" in the "region-main" "region"
When I click on "Topic 1" "link" in the "region-main" "region"
Then I should see "Activity sample 1.1"
And I should see "Activity sample 1.2"
And I should see "Activity sample 1.3"
And I should not see "Activity sample 2.1" in the "region-main" "region"
And I should not see "Activity sample 2.1" in the "region-main" "region"
Scenario: General section is not displayed in the single section page
When I click on "Topic 1" "link" in the "region-main" "region"
Then I should not see "General" in the "region-main" "region"
And I should not see "Activity sample 0.1" in the "region-main" "region"
And I should see "Activity sample 1.1"
And I should see "Activity sample 1.2"
And I should see "Activity sample 1.3"
And I should not see "Activity sample 2.1" in the "region-main" "region"
And I should not see "Activity sample 2.1" in the "region-main" "region"
@javascript
Scenario: The view action for sections displays the single section page
Given I turn editing mode on
And I open section "1" edit menu
When I click on "View" "link" in the "Topic 1" "section"
Then I should not see "General" in the "region-main" "region"
And I should not see "Activity sample 0.1" in the "region-main" "region"
And I should see "Activity sample 1.1"
And I should see "Activity sample 1.2"
And I should see "Activity sample 1.3"
And I should not see "Activity sample 2.1" in the "region-main" "region"
And I should not see "Activity sample 2.1" in the "region-main" "region"
And I am on "Course 1" course homepage
And I open section "2" edit menu
And I click on "View" "link" in the "Topic 2" "section"
And I should not see "General" in the "region-main" "region"
And I should not see "Activity sample 0.1" in the "region-main" "region"
And I should not see "Activity sample 1.1"
And I should not see "Activity sample 1.2"
And I should not see "Activity sample 1.3"
And I should see "Activity sample 2.1" in the "region-main" "region"
And I should see "Activity sample 2.1" in the "region-main" "region"
# The following steps will need to be changed in MDL-80248, when the General section will be displayed in isolation.
But I am on "Course 1" course homepage
And I open section "0" edit menu
And I click on "View" "link" in the "General" "section"
And I should see "General" in the "region-main" "region"
And I should see "Activity sample 0.1" in the "region-main" "region"
And I should see "Activity sample 1.1"
And I should see "Activity sample 1.2"
And I should see "Activity sample 1.3"
And I should see "Activity sample 2.1" in the "region-main" "region"
And I should see "Activity sample 2.1" in the "region-main" "region"

View File

@ -108,45 +108,26 @@ class format_topics extends core_courseformat\base {
* @param int|stdClass $section Section object from database or just field course_sections.section
* if omitted the course view page is returned
* @param array $options options for view URL. At the moment core uses:
* 'navigation' (bool) if true and section has no separate page, the function returns null
* 'sr' (int) used by multipage formats to specify to which section to return
* 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page.
* 'sr' (int) used by course formats to specify to which section to return
* @return null|moodle_url
*/
public function get_view_url($section, $options = []) {
global $CFG;
$course = $this->get_course();
$url = new moodle_url('/course/view.php', ['id' => $course->id]);
$sr = null;
if (array_key_exists('sr', $options)) {
$sr = $options['sr'];
}
if (is_object($section)) {
$sectionno = $options['sr'];
} else if (is_object($section)) {
$sectionno = $section->section;
} else {
$sectionno = $section;
}
if ($sectionno !== null) {
if ($sr !== null) {
if ($sr) {
$usercoursedisplay = COURSE_DISPLAY_MULTIPAGE;
$sectionno = $sr;
} else {
$usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE;
}
} else {
$usercoursedisplay = $course->coursedisplay ?? COURSE_DISPLAY_SINGLEPAGE;
}
if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
$url->param('section', $sectionno);
} else {
if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) {
return null;
}
$url->set_anchor('section-'.$sectionno);
}
if ((!empty($options['navigation']) || array_key_exists('sr', $options)) && $sectionno !== null) {
// Display section on separate page.
$sectioninfo = $this->get_section($sectionno);
return new moodle_url('/course/section.php', ['id' => $sectioninfo->id]);
}
return $url;
return new moodle_url('/course/view.php', ['id' => $course->id]);
}
/**
@ -457,6 +438,15 @@ class format_topics extends core_courseformat\base {
$formatoptions['indentation'] = get_config('format_topics', 'indentation');
return $formatoptions;
}
/**
* Get the required javascript files for the course format.
*
* @return array The list of javascript files required by the course format.
*/
public function get_required_jsfiles(): array {
return ['/course/format/topics/format.js'];
}
}
/**

View File

@ -29,6 +29,7 @@ require_once($CFG->dirroot . '/course/lib.php');
* @package format_topics
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \format_topics
*/
class format_topics_test extends \advanced_testcase {
@ -232,16 +233,14 @@ class format_topics_test extends \advanced_testcase {
}
/**
* Test for get_view_url() to ensure that the url is only given for the correct cases.
* Test for get_view_url().
*
* @return void
* @covers ::get_view_url
*/
public function test_get_view_url() {
public function test_get_view_url(): void {
global $CFG;
$this->resetAfterTest();
$linkcoursesections = $CFG->linkcoursesections;
// Generate a course with two sections (0 and 1) and two modules.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course(['format' => 'topics']);
@ -252,21 +251,34 @@ class format_topics_test extends \advanced_testcase {
$format->update_course_format_options($data);
// In page.
$CFG->linkcoursesections = 0;
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
$CFG->linkcoursesections = 1;
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
// Navigation.
$CFG->linkcoursesections = 0;
$this->assertNull($format->get_view_url(1, ['navigation' => 1]));
$this->assertNull($format->get_view_url(0, ['navigation' => 1]));
$CFG->linkcoursesections = 1;
$this->assertNotEmpty($format->get_view_url(1, ['navigation' => 1]));
$this->assertNotEmpty($format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['navigation' => 1]));
// When sr parameter is defined, the section.php page should be returned.
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 0]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 0]));
}
/**
* Test get_required_jsfiles().
*
* @covers ::get_required_jsfiles
*/
public function test_get_required_jsfiles(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(['format' => 'topics']);
$format = course_get_format($course);
$this->assertNotEmpty($format->get_required_jsfiles());
}
}

View File

@ -7,6 +7,8 @@ Overview of this plugin type at https://moodledev.io/docs/apis/plugintypes/forma
valid section move mutation.
* The state action core_courseformat\stateactions::section_move is deprecated and
replaced by core_courseformat\stateactions::section_move_after.
* $CFG->linkcoursesections setting has been completely removed because it's not required anymore. From now on, sections will be
always linked because a new page, section.php, has been created to display any single section.
=== 4.3 ===
* New core_courseformat\output\activitybadge class that can be extended by any module to display content near the activity name.

View File

@ -118,45 +118,26 @@ class format_weeks extends core_courseformat\base {
* @param int|stdClass $section Section object from database or just field course_sections.section
* if omitted the course view page is returned
* @param array $options options for view URL. At the moment core uses:
* 'navigation' (bool) if true and section has no separate page, the function returns null
* 'sr' (int) used by multipage formats to specify to which section to return
* 'navigation' (bool) if true and section not empty, the function returns section page; otherwise, it returns course page.
* 'sr' (int) used by course formats to specify to which section to return
* @return null|moodle_url
*/
public function get_view_url($section, $options = array()) {
global $CFG;
$course = $this->get_course();
$url = new moodle_url('/course/view.php', array('id' => $course->id));
$sr = null;
if (array_key_exists('sr', $options)) {
$sr = $options['sr'];
}
if (is_object($section)) {
$sectionno = $options['sr'];
} else if (is_object($section)) {
$sectionno = $section->section;
} else {
$sectionno = $section;
}
if ($sectionno !== null) {
if ($sr !== null) {
if ($sr) {
$usercoursedisplay = COURSE_DISPLAY_MULTIPAGE;
$sectionno = $sr;
} else {
$usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE;
}
} else {
$usercoursedisplay = $course->coursedisplay ?? COURSE_DISPLAY_SINGLEPAGE;
}
if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
$url->param('section', $sectionno);
} else {
if (empty($CFG->linkcoursesections) && !empty($options['navigation'])) {
return null;
}
$url->set_anchor('section-'.$sectionno);
}
if ((!empty($options['navigation']) || array_key_exists('sr', $options)) && $sectionno !== null) {
// Display section on separate page.
$sectioninfo = $this->get_section($sectionno);
return new moodle_url('/course/section.php', ['id' => $sectioninfo->id]);
}
return $url;
return new moodle_url('/course/view.php', ['id' => $course->id]);
}
/**
@ -631,6 +612,15 @@ class format_weeks extends core_courseformat\base {
$formatoptions['indentation'] = get_config('format_weeks', 'indentation');
return $formatoptions;
}
/**
* Get the required javascript files for the course format.
*
* @return array The list of javascript files required by the course format.
*/
public function get_required_jsfiles(): array {
return ['/course/format/weeks/format.js'];
}
}
/**

View File

@ -29,6 +29,7 @@ require_once($CFG->dirroot . '/course/lib.php');
* @package format_weeks
* @copyright 2015 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \format_weeks
*/
class format_weeks_test extends \advanced_testcase {
@ -229,14 +230,14 @@ class format_weeks_test extends \advanced_testcase {
}
/**
* Test for get_view_url() to ensure that the url is only given for the correct cases
* Test for get_view_url().
*
* @covers ::get_view_url
*/
public function test_get_view_url() {
public function test_get_view_url(): void {
global $CFG;
$this->resetAfterTest();
$linkcoursesections = $CFG->linkcoursesections;
// Generate a course with two sections (0 and 1) and two modules.
$generator = $this->getDataGenerator();
$course1 = $generator->create_course(array('format' => 'weeks'));
@ -247,22 +248,34 @@ class format_weeks_test extends \advanced_testcase {
$format->update_course_format_options($data);
// In page.
$CFG->linkcoursesections = 0;
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
$CFG->linkcoursesections = 1;
$this->assertNotEmpty($format->get_view_url(null));
$this->assertNotEmpty($format->get_view_url(0));
$this->assertNotEmpty($format->get_view_url(1));
// Navigation.
$CFG->linkcoursesections = 0;
$this->assertNull($format->get_view_url(1, ['navigation' => 1]));
$this->assertNull($format->get_view_url(0, ['navigation' => 1]));
$CFG->linkcoursesections = 1;
$this->assertNotEmpty($format->get_view_url(1, ['navigation' => 1]));
$this->assertNotEmpty($format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/view.php', $format->get_view_url(0));
$this->assertStringContainsString('course/view.php', $format->get_view_url(1));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['navigation' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['navigation' => 1]));
// When sr parameter is defined, the section.php page should be returned.
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 1]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(0, ['sr' => 0]));
$this->assertStringContainsString('course/section.php', $format->get_view_url(1, ['sr' => 0]));
}
/**
* Test get_required_jsfiles().
*
* @covers ::get_required_jsfiles
*/
public function test_get_required_jsfiles(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$course = $generator->create_course(['format' => 'weeks']);
$format = course_get_format($course);
$this->assertNotEmpty($format->get_required_jsfiles());
}
}

193
course/section.php Normal file
View File

@ -0,0 +1,193 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Display a course section.
*
* @package core_course
* @copyright 2023 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once('lib.php');
require_once($CFG->libdir.'/completionlib.php');
redirect_if_major_upgrade_required();
$sectionid = required_param('id', PARAM_INT);
// This parameter is used by the classic theme to force editing on.
$edit = optional_param('edit', -1, PARAM_BOOL);
$section = $DB->get_record('course_sections', ['id' => $sectionid], '*', MUST_EXIST);
// Defined here to avoid notices on errors.
$PAGE->set_url('/course/section.php', ['id' => $sectionid]);
if ($section->course == SITEID) {
// The home page is not a real course.
redirect($CFG->wwwroot .'/?redirect=0');
}
$course = get_course($section->course);
// Fix course format if it is no longer installed.
$format = course_get_format($course);
$course->format = $format->get_format();
// When the course format doesn't support sections, redirect to course page.
if (!course_format_uses_sections($course->format)) {
redirect(new moodle_url('/course/view.php', ['id' => $course->id]));
}
// Prevent caching of this page to stop confusion when changing page after making AJAX changes.
$PAGE->set_cacheable(false);
context_helper::preload_course($course->id);
$context = context_course::instance($course->id, MUST_EXIST);
require_login($course);
// Must set layout before getting section info. See MDL-47555.
$PAGE->set_pagelayout('course');
$PAGE->add_body_class('limitedwidth');
// Get section details and check it exists.
$modinfo = get_fast_modinfo($course);
$coursesections = $modinfo->get_section_info($section->section, MUST_EXIST);
// Check user is allowed to see it.
if (!$coursesections->uservisible) {
// Check if coursesection has conditions affecting availability and if
// so, output availability info.
if ($coursesections->visible && $coursesections->availableinfo) {
$sectionname = get_section_name($course, $coursesections);
$message = get_string('notavailablecourse', '', $sectionname);
redirect(course_get_url($course), $message, null, \core\output\notification::NOTIFY_ERROR);
} else {
// Note: We actually already know they don't have this capability
// or uservisible would have been true; this is just to get the
// correct error message shown.
require_capability('moodle/course:viewhiddensections', $context);
}
}
$PAGE->set_pagetype('course-view-' . $course->format);
$PAGE->set_other_editing_capability('moodle/course:update');
$PAGE->set_other_editing_capability('moodle/course:manageactivities');
$PAGE->set_other_editing_capability('moodle/course:activityvisibility');
$PAGE->set_other_editing_capability('moodle/course:sectionvisibility');
$PAGE->set_other_editing_capability('moodle/course:movesections');
$renderer = $PAGE->get_renderer('format_' . $course->format);
// This is used by the Classic theme to change the editing mode based on the 'edit' parameter value.
if (!isset($USER->editing)) {
$USER->editing = 0;
}
if ($PAGE->user_allowed_editing()) {
if (($edit == 1) && confirm_sesskey()) {
$USER->editing = 1;
$url = new moodle_url($PAGE->url, ['notifyeditingon' => 1]);
redirect($url);
} else if (($edit == 0) && confirm_sesskey()) {
$USER->editing = 0;
if (!empty($USER->activitycopy) && $USER->activitycopycourse == $course->id) {
$USER->activitycopy = false;
$USER->activitycopycourse = null;
}
redirect($PAGE->url);
}
}
// This is used by the Classic theme, to display the Turn editing on/off button.
// We are currently keeping the button here from 1.x to help new teachers figure out what to do, even though the link also appears
// in the course admin block. It also means you can back out of a situation where you removed the admin block.
if ($PAGE->user_allowed_editing()) {
$buttons = $OUTPUT->edit_button($PAGE->url);
$PAGE->set_button($buttons);
}
// Make the title more specific when editing, for accessibility reasons.
$editingtitle = '';
if ($PAGE->user_is_editing()) {
$editingtitle = 'editing';
}
$sectionname = get_string('sectionname', "format_$course->format");
$sectiontitle = get_section_name($course, $section);
$PAGE->set_title(
get_string(
'coursesectiontitle' . $editingtitle,
'moodle',
['course' => $course->fullname, 'sectiontitle' => $sectiontitle, 'sectionname' => $sectionname]
)
);
// Add bulk editing control.
$bulkbutton = $renderer->bulk_editing_button($format);
if (!empty($bulkbutton)) {
$PAGE->add_header_action($bulkbutton);
}
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
// Show communication room status notification.
if (core_communication\api::is_available() && has_capability('moodle/course:update', $context)) {
$communication = \core_communication\api::load_by_instance(
$context,
'core_course',
'coursecommunication',
$course->id
);
$communication->show_communication_room_status_notification();
}
// Display a warning if asynchronous backups are pending for this course.
if ($PAGE->user_is_editing()) {
require_once($CFG->dirroot . '/backup/util/helper/async_helper.class.php');
if (async_helper::is_async_pending($course->id, 'course', 'backup')) {
echo $OUTPUT->notification(get_string('pendingasyncedit', 'backup'), 'warning');
}
}
echo $renderer->container_start('course-content');
// Include course AJAX.
include_course_ajax($course, $modinfo->get_used_module_names());
$format->set_section_number($section->section);
$outputclass = $format->get_output_classname('content');
$widget = new $outputclass($format);
echo $renderer->render($widget);
// Include course format javascript files.
$jsfiles = $format->get_required_jsfiles();
foreach ($jsfiles as $jsfile) {
$PAGE->requires->js($jsfile);
}
echo $renderer->container_end();
// Trigger course viewed event.
course_view($context, $section->section);
// Load the view JS module if completion tracking is enabled for this course.
$completion = new completion_info($course);
if ($completion->is_enabled()) {
$PAGE->requires->js_call_amd('core_course/view', 'init');
}
echo $OUTPUT->footer();

View File

@ -31,7 +31,7 @@ Feature: Delete activity and resource works correctly
And I open "Glossary 1" actions menu
And I click on "Delete" "link" in the "Glossary 1" activity
And I click on "Delete" "button" in the "Delete activity?" "dialogue"
# Confirm that glossary is successfully deleted
# Confirm that glossary is successfully deleted.
And I should not see "Glossary 1"
# Reload the page and confirm that both the label and glossary are really deleted
And I reload the page

View File

@ -16,7 +16,7 @@ Feature: Course activity controls works as expected
# * Course controls with paged mode in a section's page
@javascript @_cross_browser
Scenario Outline: General activities course controls using topics and weeks formats, and paged mode and not paged mode works as expected
Scenario Outline: Check activities using topics and weeks formats, and paged mode and not paged mode
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
@ -30,15 +30,14 @@ Feature: Course activity controls works as expected
| activity | course | section | name |
| forum | C1 | 1 | Test forum name 1 |
| forum | C1 | 1 | Test forum name 2 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I am on the "Course 1" course page logged in as "teacher1"
When I click on <targetpage> "link" in the "region-main" "region"
And I turn editing mode on
And I add the "Recent activity" block
And I open the action menu in "Recent activity" "block"
And I click on "Delete Recent activity block" "link"
And I click on "Delete" "button" in the "Delete block?" "dialogue"
And <belowpage> "section" <should_see_other_sections> exist
And <belowpage> "section" <should_see_other_sections> exist
And I open "Test forum name 1" actions menu
And I click on "Edit settings" "link" in the "Test forum name 1" activity
And I should see "Updating Forum"
@ -76,14 +75,16 @@ Feature: Course activity controls works as expected
Examples:
| courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage |
| topics | 0 | "General" | should | should | "Topic 2" |
| topics | 1 | "Topic 1" | should not | should not | "Topic 2" |
| topics | 0 | "General" | should | should not | "Topic 2" |
| topics | 1 | "General" | should | should not | "Topic 2" |
| weeks | 0 | "General" | should | should | "8 January - 14 January" |
| weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
| topics | 0 | "Topic 1" | should not | should not | "Topic 2" |
| topics | 1 | "Topic 1" | should not | should not | "Topic 2" |
| weeks | 0 | "General" | should | should not | "8 January - 14 January" |
| weeks | 1 | "General" | should | should not | "8 January - 14 January" |
| weeks | 0 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
| weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
Scenario Outline: General activities course controls using topics and weeks formats, and paged mode and not paged mode works as expected without javascript
Scenario Outline: Check, without javascript, activities using topics and weeks formats, and paged mode and not paged mode
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
@ -97,9 +98,9 @@ Feature: Course activity controls works as expected
| activity | name | course | idnumber | section |
| forum | Test forum name 1 | C1 | 0001 | 1 |
| forum | Test forum name 2 | C1 | 0002 | 1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I am on the "Course 1" course page logged in as "teacher1"
When I click on <targetpage> "link" in the "region-main" "region"
And I turn editing mode on
And I add the "Recent activity" block
And I open the action menu in "Recent activity" "block"
And I click on "Delete Recent activity block" "link"
@ -121,28 +122,27 @@ Feature: Course activity controls works as expected
And I should see "Test forum name 2"
And I should see "Edited test forum name 2"
And I hide section "1"
And <belowpage> "section" <should_see_other_sections> exist
And section "1" should be hidden
And all activities in section "1" should be hidden
And I show section "1"
And <belowpage> "section" <should_see_other_sections> exist
And section "1" should be visible
And the following config values are set as admin:
| unaddableblocks | | theme_boost|
And I add the "Section links" block
And <belowpage> "section" <should_see_other_sections> exist
And I should see "1 2 3 4 5" in the "Section links" "block"
And I click on "2" "link" in the "Section links" "block"
And I <should_see_other_sections_following_block_sections_links> see "Test forum name 2"
Examples:
| courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage |
| topics | 0 | "General" | should | should | "Topic 2" |
| topics | 1 | "Topic 1" | should not | should not | "Topic 2" |
| topics | 0 | "General" | should | should not | "Topic 2" |
| topics | 1 | "General" | should | should not | "Topic 2" |
| weeks | 0 | "General" | should | should | "8 January - 14 January" |
| weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
| topics | 0 | "Topic 1" | should not | should not | "Topic 2" |
| topics | 1 | "Topic 1" | should not | should not | "Topic 2" |
| weeks | 0 | "General" | should | should not | "8 January - 14 January" |
| weeks | 1 | "General" | should | should not | "8 January - 14 January" |
| weeks | 0 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
| weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" |
@javascript
Scenario Outline: Indentation should allow one level only

View File

@ -17,17 +17,19 @@ Feature: Sections can be moved
And the following "activities" exist:
| activity | name | course | idnumber | section |
| forum | Test forum name | C1 | forum1 | 1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
Scenario: Move up and down a section with Javascript disabled in a single page course
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I move down section "1"
Then I should see "Test forum name" in the "Topic 2" "section"
And I move up section "2"
And I should see "Test forum name" in the "Topic 1" "section"
Scenario: Move up and down a section with Javascript disabled in the course home of a course using paged mode
Given I navigate to "Settings" in current page administration
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Course layout | Show one section per page |
And I press "Save and display"
@ -37,11 +39,13 @@ Feature: Sections can be moved
And I should see "Test forum name" in the "Topic 1" "section"
Scenario: Sections can not be moved with Javascript disabled in a section page of a course using paged mode
Given I navigate to "Settings" in current page administration
Given I am on the "Course 1" course page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Course layout | Show one section per page |
And I press "Save and display"
When I follow "Topic 2"
When I click on "Topic 2" "link" in the "region-main" "region"
And I turn editing mode on
Then "Topic 1" "section" should not exist
And "Topic 3" "section" should not exist
And "Move down" "link" should not exist
@ -49,6 +53,8 @@ Feature: Sections can be moved
@javascript
Scenario: Move section with javascript
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I open section "1" edit menu
And I click on "Move" "link" in the "Topic 1" "section"
And I click on "Topic 3" "link" in the ".modal-body" "css_element"

View File

@ -48,7 +48,8 @@ Feature: Course paged mode
| chat | C1 | Chat room |
When I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I click on <section1> "link" in the <section1> "section"
And I open section <sectionnumber1> edit menu
And I click on "View" "link" in the <section1> "section"
And I should see <section1> in the "div.single-section" "css_element"
And I should see <section2> in the ".single-section div.nextsection" "css_element"
And I should not see <prevunexistingsection> in the ".single-section" "css_element"
@ -57,9 +58,9 @@ Feature: Course paged mode
And I should not see <prevunexistingsection> in the ".single-section" "css_element"
Examples:
| courseformat | section1 | section2 | prevunexistingsection |
| topics | "Topic 1" | "Topic 2" | "Topic 0" |
| weeks | "1 January - 7 January" | "8 January - 14 January" | "25 December - 31 December" |
| courseformat | section1 | sectionnumber1 | section2 | prevunexistingsection |
| topics | "Topic 1" | "1" | "Topic 2" | "Topic 0" |
| weeks | "1 January - 7 January" | "1" | "8 January - 14 January" | "25 December - 31 December" |
Scenario Outline: Weekly and topics course formats with Javascript disabled
Given the following "courses" exist:

View File

@ -793,8 +793,6 @@ $string['libcurlwarning'] = 'It has been detected that libcurl doesn\'t have CUR
$string['licensesettings'] = 'Licence settings';
$string['linkadmincategories'] = 'Link admin categories';
$string['linkadmincategories_help'] = 'If enabled admin setting categories will be displayed as links in the navigation and will lead to the admin category pages.';
$string['linkcoursesections'] = 'Always link course sections';
$string['linkcoursesections_help'] = 'Always try to provide a link for course sections. Course sections are usually only shown as links if the course format displays a single section per page. If this setting is enabled a link will always be provided.';
$string['loading'] = 'Loading';
$string['localetext'] = 'Sitewide locale';
$string['localstringcustomization'] = 'Local string customization';
@ -1638,3 +1636,5 @@ $string['unsettheme'] = 'Unset theme';
// Deprecated since Moodle 4.4.
$string['taskdeletecachetext'] = 'Delete old text cache records';
$string['themesettings'] = 'Theme settings';
$string['linkcoursesections'] = 'Always link course sections';
$string['linkcoursesections_help'] = 'Always try to provide a link for course sections. Course sections are usually only shown as links if the course format displays a single section per page. If this setting is enabled a link will always be provided.';

View File

@ -115,3 +115,5 @@ taskdeletecachetext,core_admin
themesettings,core_admin
copycourseheading,core_backup
backupcourse,core_backup
linkcoursesections,core_admin
linkcoursesections_help,core_admin

View File

@ -346,6 +346,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:i/user' => 'fa-user',
'core:i/users' => 'fa-users',
'core:i/valid' => 'fa-check text-success',
'core:i/viewsection' => 'fa-pager',
'core:i/warning' => 'fa-exclamation text-warning',
'core:i/window_close' => 'fa-window-close',
'core:i/withsubcat' => 'fa-plus-square',

View File

@ -856,5 +856,13 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2023110900.00);
}
if ($oldversion < 2023120100.01) {
// The $CFG->linkcoursesections setting has been removed because it's not required anymore.
// From now, sections will be always linked because a new page, section.php, has been created to display a single section.
unset_config('linkcoursesections');
upgrade_main_savepoint(true, 2023120100.01);
}
return true;
}

1
pix/i/viewsection.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M0 128C0 92.7 28.7 64 64 64H448c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V128zm64 32v64c0 17.7 14.3 32 32 32H416c17.7 0 32-14.3 32-32V160c0-17.7-14.3-32-32-32H96c-17.7 0-32 14.3-32 32zM80 320c-13.3 0-24 10.7-24 24s10.7 24 24 24h56c13.3 0 24-10.7 24-24s-10.7-24-24-24H80zm136 0c-13.3 0-24 10.7-24 24s10.7 24 24 24h48c13.3 0 24-10.7 24-24s-10.7-24-24-24H216z"/></svg>

After

Width:  |  Height:  |  Size: 648 B

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2023120100.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2023120100.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '4.4dev (Build: 20231201)'; // Human-friendly version name