+ {{/displayonesection}}
{{/sitehome}}
{{/headerdisplaymultipage}}
diff --git a/course/format/tests/base_test.php b/course/format/tests/base_test.php
index 6517f8935fa..6852286d097 100644
--- a/course/format/tests/base_test.php
+++ b/course/format/tests/base_test.php
@@ -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());
+ }
}
/**
diff --git a/course/format/tests/behat/bulk_show_one_section_actions.feature b/course/format/tests/behat/bulk_show_one_section_actions.feature
index 1fe665a21f2..ad9facee5c7 100644
--- a/course/format/tests/behat/bulk_show_one_section_actions.feature
+++ b/course/format/tests/behat/bulk_show_one_section_actions.feature
@@ -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
diff --git a/course/format/tests/behat/course_manageactivities.feature b/course/format/tests/behat/course_manageactivities.feature
index d9245a9c844..1833a123e1f 100644
--- a/course/format/tests/behat/course_manageactivities.feature
+++ b/course/format/tests/behat/course_manageactivities.feature
@@ -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'.
diff --git a/course/format/tests/behat/section_page.feature b/course/format/tests/behat/section_page.feature
new file mode 100644
index 00000000000..366bec94d72
--- /dev/null
+++ b/course/format/tests/behat/section_page.feature
@@ -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"
diff --git a/course/format/topics/lib.php b/course/format/topics/lib.php
index b745164ca0d..c3a6181751c 100644
--- a/course/format/topics/lib.php
+++ b/course/format/topics/lib.php
@@ -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'];
+ }
}
/**
diff --git a/course/format/topics/tests/format_topics_test.php b/course/format/topics/tests/format_topics_test.php
index eca87a7f7cc..8581f9083ef 100644
--- a/course/format/topics/tests/format_topics_test.php
+++ b/course/format/topics/tests/format_topics_test.php
@@ -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());
}
}
diff --git a/course/format/upgrade.txt b/course/format/upgrade.txt
index f6b2c0eb073..5e4c0233f9d 100644
--- a/course/format/upgrade.txt
+++ b/course/format/upgrade.txt
@@ -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.
diff --git a/course/format/weeks/lib.php b/course/format/weeks/lib.php
index d0eb7436997..89529917993 100644
--- a/course/format/weeks/lib.php
+++ b/course/format/weeks/lib.php
@@ -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'];
+ }
}
/**
diff --git a/course/format/weeks/tests/format_weeks_test.php b/course/format/weeks/tests/format_weeks_test.php
index 82f82ced5a2..9daaacff752 100644
--- a/course/format/weeks/tests/format_weeks_test.php
+++ b/course/format/weeks/tests/format_weeks_test.php
@@ -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());
+ }
}
diff --git a/course/section.php b/course/section.php
new file mode 100644
index 00000000000..01e1aba09ea
--- /dev/null
+++ b/course/section.php
@@ -0,0 +1,193 @@
+.
+
+/**
+ * Display a course section.
+ *
+ * @package core_course
+ * @copyright 2023 Sara Arjona
+ * @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();
diff --git a/course/tests/behat/activity_resource_delete.feature b/course/tests/behat/activity_resource_delete.feature
index 7ccea2de327..874d0528dba 100644
--- a/course/tests/behat/activity_resource_delete.feature
+++ b/course/tests/behat/activity_resource_delete.feature
@@ -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
diff --git a/course/tests/behat/course_controls.feature b/course/tests/behat/course_controls.feature
index 8963446847d..38270953cb6 100644
--- a/course/tests/behat/course_controls.feature
+++ b/course/tests/behat/course_controls.feature
@@ -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 "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 "section" exist
- And "section" 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 "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 "section" exist
And section "1" should be hidden
And all activities in section "1" should be hidden
And I show section "1"
- And "section" 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 "section" 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 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
diff --git a/course/tests/behat/move_sections.feature b/course/tests/behat/move_sections.feature
index b8c5164788c..feb64c8ab3b 100644
--- a/course/tests/behat/move_sections.feature
+++ b/course/tests/behat/move_sections.feature
@@ -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"
diff --git a/course/tests/behat/paged_course_navigation.feature b/course/tests/behat/paged_course_navigation.feature
index ce6523ce746..fefa309bed1 100644
--- a/course/tests/behat/paged_course_navigation.feature
+++ b/course/tests/behat/paged_course_navigation.feature
@@ -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 "link" in the "section"
+ And I open section edit menu
+ And I click on "View" "link" in the "section"
And I should see in the "div.single-section" "css_element"
And I should see in the ".single-section div.nextsection" "css_element"
And I should not see in the ".single-section" "css_element"
@@ -57,9 +58,9 @@ Feature: Course paged mode
And I should not see 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:
diff --git a/lang/en/admin.php b/lang/en/admin.php
index ff66d1db2dc..3aea88af19f 100644
--- a/lang/en/admin.php
+++ b/lang/en/admin.php
@@ -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.';
diff --git a/lang/en/deprecated.txt b/lang/en/deprecated.txt
index ab6432d6f21..3db1a425c0b 100644
--- a/lang/en/deprecated.txt
+++ b/lang/en/deprecated.txt
@@ -115,3 +115,5 @@ taskdeletecachetext,core_admin
themesettings,core_admin
copycourseheading,core_backup
backupcourse,core_backup
+linkcoursesections,core_admin
+linkcoursesections_help,core_admin
diff --git a/lib/classes/output/icon_system_fontawesome.php b/lib/classes/output/icon_system_fontawesome.php
index 8a1f6cae02a..afd79775c3f 100644
--- a/lib/classes/output/icon_system_fontawesome.php
+++ b/lib/classes/output/icon_system_fontawesome.php
@@ -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',
diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php
index b805206224c..d0a47fcc39e 100644
--- a/lib/db/upgrade.php
+++ b/lib/db/upgrade.php
@@ -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;
}
diff --git a/pix/i/viewsection.svg b/pix/i/viewsection.svg
new file mode 100644
index 00000000000..31ec744bc5c
--- /dev/null
+++ b/pix/i/viewsection.svg
@@ -0,0 +1 @@
+
diff --git a/version.php b/version.php
index 5024b851df7..0b368dce6f1 100644
--- a/version.php
+++ b/version.php
@@ -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