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

This commit is contained in:
Amaia Anabitarte
2025-06-17 16:05:30 +02:00
27 changed files with 463 additions and 349 deletions
@@ -0,0 +1,9 @@
issueNumber: MDL-85433
notes:
core_course:
- message: >-
The 'Show description' checkbox is now present in all course formats.
Activity descriptions can be displayed via the Additional activities block
(formerly the Main menu block), regardless of whether the course
format's has_view_page() function returns false.
type: changed
@@ -0,0 +1,8 @@
issueNumber: MDL-85433
notes:
core_courseformat:
- message: >-
A new interface, `main_activity_interface`, is now available. Course
format plugins should implement it if they intend to display only a
single activity in the course page.
type: improved
@@ -0,0 +1,8 @@
issueNumber: MDL-85433
notes:
core_block:
- message: >-
Subcontext visibility is now turned on by default when adding blocks.
This change makes it much easier to manage blocks, for example, in
courses that lack a view page.
type: changed
+10
View File
@@ -0,0 +1,10 @@
define("block_site_main_menu/mainsection",["exports","core_courseformat/local/content","core_courseformat/courseeditor"],(function(_exports,_content,_courseeditor){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_content=(obj=_content)&&obj.__esModule?obj:{default:obj};const selectors_block=".block_site_main_menu";
/**
* Main section component for the site main menu block.
*
* @module block_site_main_menu/mainsection
* @copyright 2025 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/class _default extends _content.default{static init(query){let element=document.querySelector(query);const courseId=element.getAttribute("data-courseid");return element=element.closest(selectors_block),new _content.default({element:element,reactive:(0,_courseeditor.getCourseEditor)(courseId)})}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=mainsection.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"mainsection.min.js","sources":["../src/mainsection.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\nimport ContentComponent from 'core_courseformat/local/content';\nimport {getCourseEditor} from 'core_courseformat/courseeditor';\n\nconst selectors = {\n block: '.block_site_main_menu',\n};\n\n/**\n * Main section component for the site main menu block.\n *\n * @module block_site_main_menu/mainsection\n * @copyright 2025 Ferran Recio <[email protected]>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends ContentComponent {\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {string} query the DOM main element query selector.\n * @return {ContentComponent}\n */\n static init(query) {\n let element = document.querySelector(query);\n const courseId = element.getAttribute('data-courseid');\n // We need to include the full block because some controls are located in the block footer.\n element = element.closest(selectors.block);\n return new ContentComponent({\n element,\n reactive: getCourseEditor(courseId),\n });\n }\n}\n"],"names":["selectors","ContentComponent","query","element","document","querySelector","courseId","getAttribute","closest","reactive"],"mappings":"4TAkBMA,gBACK;;;;;;;4BAUkBC,6BAObC,WACJC,QAAUC,SAASC,cAAcH,aAC/BI,SAAWH,QAAQI,aAAa,wBAEtCJ,QAAUA,QAAQK,QAAQR,iBACnB,IAAIC,iBAAiB,CACxBE,QAAAA,QACAM,UAAU,iCAAgBH"}
@@ -0,0 +1,47 @@
// 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/>.
import ContentComponent from 'core_courseformat/local/content';
import {getCourseEditor} from 'core_courseformat/courseeditor';
const selectors = {
block: '.block_site_main_menu',
};
/**
* Main section component for the site main menu block.
*
* @module block_site_main_menu/mainsection
* @copyright 2025 Ferran Recio <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default class extends ContentComponent {
/**
* Static method to create a component instance form the mustache template.
*
* @param {string} query the DOM main element query selector.
* @return {ContentComponent}
*/
static init(query) {
let element = document.querySelector(query);
const courseId = element.getAttribute('data-courseid');
// We need to include the full block because some controls are located in the block footer.
element = element.closest(selectors.block);
return new ContentComponent({
element,
reactive: getCourseEditor(courseId),
});
}
}
@@ -76,6 +76,10 @@ class block_site_main_menu extends block_base {
$modinfo = $format->get_modinfo();
$section = $modinfo->get_section_info(0);
if ($format->supports_ajax()) {
include_course_ajax($course);
}
$courserenderer = $format->get_renderer($this->page);
$output = new block_site_main_menu\output\mainsection($format, $section);
@@ -61,7 +61,7 @@ class mainsection implements renderable, templatable {
$cmlist = $output->render($sectionoutput);
return [
'siteid' => $course->id,
'courseid' => $course->id,
'cmlist' => $cmlist,
'sectionid' => $section->id,
'sectionname' => $format->get_section_name($section),
@@ -24,7 +24,7 @@
Example context (json):
{
"cmlist": "Sample",
"siteid": 1,
"courseid": 1,
"sectionid": 1,
"sectionname": "Sample",
"sectionnum": 1,
@@ -32,8 +32,8 @@
}
}}
<div
id="block_site_main_menu_section"
class="course-content"
class="course-content block_site_main_menu_section"
data-courseid="{{courseid}}"
>
{{! The section list is used by the content module to init the sections.}}
<div
@@ -66,19 +66,9 @@
{{! The block should be fast to load, we only load the editor when needed.}}
{{#editing}}
require(
[
'core_courseformat/local/content',
'core_courseformat/courseeditor'
],
function(
Component,
Courseeditor
) {
{{! The block could be included static in other courses so we use Courseeditor.getCourseEditor. }}
new Component({
element: document.getElementById('block_site_main_menu_section'),
reactive: Courseeditor.getCourseEditor({{siteid}}),
});
['block_site_main_menu/mainsection'],
function(Component) {
Component.init('.block_site_main_menu_section[data-courseid="{{courseid}}"]');
}
);
{{/editing}}
@@ -69,7 +69,7 @@ Feature: Edit activities in Additional activities block
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
And I should see "My forum name" in the "block_site_main_menu_section" "region"
And I should see "My forum name" in the "Additional activities" "block"
And I should not see "My forum name" in the "region-main" "region"
When I open "My forum name" actions menu
And I click on "Move" "link" in the "My forum name" activity
@@ -78,7 +78,7 @@ Feature: Edit activities in Additional activities block
And I should see "Site" in the "Move activity" "dialogue"
And I click on "Site" "link" in the "Move activity" "dialogue"
Then I should see "My forum name" in the "region-main" "region"
And I should not see "My forum name" in the "block_site_main_menu_section" "region"
And I should not see "My forum name" in the "Additional activities" "block"
@javascript
Scenario: The move activity modal allow to move from the main content to the Additional activities block
@@ -94,7 +94,7 @@ Feature: Edit activities in Additional activities block
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
And I should not see "My forum name" in the "block_site_main_menu_section" "region"
And I should not see "My forum name" in the "Additional activities" "block"
And I should see "My forum name" in the "region-main" "region"
When I open "My forum name" actions menu
And I click on "Move" "link" in the "My forum name" activity
@@ -103,7 +103,7 @@ Feature: Edit activities in Additional activities block
And I should see "Site" in the "Move activity" "dialogue"
And I click on "Block" "link" in the "Move activity" "dialogue"
Then I should not see "My forum name" in the "region-main" "region"
And I should see "My forum name" in the "block_site_main_menu_section" "region"
And I should see "My forum name" in the "Additional activities" "block"
@javascript
Scenario: Admin can delete an activity in the Additional activities block
@@ -118,11 +118,11 @@ Feature: Edit activities in Additional activities block
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
And I should see "My forum name" in the "block_site_main_menu_section" "region"
And I should see "My forum name" in the "Additional activities" "block"
When I open "My forum name" actions menu
And I choose "Delete" in the open action menu
And I click on "Delete" "button" in the "Delete activity?" "dialogue"
Then I should not see "My forum name" in the "block_site_main_menu_section" "region"
Then I should not see "My forum name" in the "Additional activities" "block"
@javascript
Scenario: Admin can duplicate an activity in the Additional activities block
@@ -137,10 +137,10 @@ Feature: Edit activities in Additional activities block
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
And I should see "My forum name" in the "block_site_main_menu_section" "region"
And I should see "My forum name" in the "Additional activities" "block"
When I open "My forum name" actions menu
And I choose "Duplicate" in the open action menu
Then I should see "My forum name (copy)" in the "block_site_main_menu_section" "region"
Then I should see "My forum name (copy)" in the "Additional activities" "block"
@javascript
Scenario: Admin can move right and left an activity in the Additional activities block
@@ -155,7 +155,7 @@ Feature: Edit activities in Additional activities block
And I log in as "admin"
And I am on site homepage
And I turn editing mode on
And I should see "My forum name" in the "block_site_main_menu_section" "region"
And I should see "My forum name" in the "Additional activities" "block"
When I open "My forum name" actions menu
And "Move right" "link" should be visible
And "Move left" "link" should not be visible
@@ -0,0 +1,35 @@
<?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 core_courseformat;
/**
* Contains the interface that any course format plugin should implement
* when it wants to only display a single activity in the course page.
*
* @package core_courseformat
* @copyright 2025 Sara Arjona <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
interface main_activity_interface {
/**
* Get the main activity of the course.
*
* @return \cm_info|null The main activity object or null if main activity is not set.
*/
public function get_main_activity(): ?\cm_info;
}
-6
View File
@@ -1106,12 +1106,6 @@ class stateactions {
throw new moodle_exception('emptysectionids', 'core', null, $info);
}
// No section actions are allowed if course format does not support sections.
$courseformat = course_get_format($course->id);
if (!$courseformat->uses_sections()) {
throw new moodle_exception('sectionactionnotsupported', 'core', null, $info);
}
list($insql, $inparams) = $DB->get_in_or_equal($sectionids, SQL_PARAMS_NAMED);
// Check if all the given sections exist.
+4
View File
@@ -27,5 +27,9 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
include_course_ajax($course);
$courserenderer = $PAGE->get_renderer('format_singleactivity');
echo $courserenderer->display($course, $section != 0);
@@ -27,10 +27,7 @@ $string['activitytype_help'] = 'Choose the type of activity or resource to use i
$string['defactivitytype'] = 'Default type of activity';
$string['defactivitytypedesc'] = 'Specify the activity type that will be selected by default when creating a new course';
$string['erroractivitytype'] = 'Type of activity is not set up in Course settings';
$string['orphaned'] = 'Orphaned';
$string['orphanedwarning'] = 'These activities are unreachable by users!';
$string['plugin_description'] = 'The course displays only one activity on the course page. You can add more activities in the Additional activities block';
$string['pluginname'] = 'Single activity';
$string['plugin_description'] = 'The course contains only one activity or resource.';
$string['sectionname'] = '';
$string['warningchangeformat'] = 'When changing the existing course format to "Single activity" make sure that you removed all extra activities from the course including "Announcements". Note that sections structure may be modified.';
$string['privacy:metadata'] = 'The Single activity format plugin does not store any personal data.';
+121 -202
View File
@@ -14,14 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This file contains main class for the course format singleactivity
*
* @package format_singleactivity
* @copyright 2012 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_courseformat\sectiondelegate;
defined('MOODLE_INTERNAL') || die();
@@ -34,8 +26,8 @@ require_once($CFG->dirroot. '/course/format/lib.php');
* @copyright 2012 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class format_singleactivity extends core_courseformat\base {
/** @var cm_info the current activity. Use get_activity() to retrieve it. */
class format_singleactivity extends core_courseformat\base implements core_courseformat\main_activity_interface {
/** @var cm_info the current activity. Use get_main_activity() to retrieve it. */
private $activity = false;
/** @var int The category ID guessed from the form data. */
@@ -51,7 +43,7 @@ class format_singleactivity extends core_courseformat\base {
* 'sr' (int) ignored by this format
* @return null|moodle_url
*/
public function get_view_url($section, $options = array()) {
public function get_view_url($section, $options = []) {
return new moodle_url('/course/view.php', ['id' => $this->courseid]);
}
@@ -62,58 +54,7 @@ class format_singleactivity extends core_courseformat\base {
* @param navigation_node $node The course node within the navigation
*/
public function extend_course_navigation($navigation, navigation_node $node) {
// Display orphaned activities for the users who can see them.
$context = context_course::instance($this->courseid);
if (has_capability('moodle/course:viewhiddensections', $context)) {
$modinfo = get_fast_modinfo($this->courseid);
if (!empty($modinfo->sections[1])) {
$section1 = $modinfo->get_section_info(1);
// Show orphaned activities.
$orphanednode = $node->add(get_string('orphaned', 'format_singleactivity'),
$this->get_view_url(1), navigation_node::TYPE_SECTION, null, $section1->id);
$orphanednode->nodetype = navigation_node::NODETYPE_BRANCH;
$orphanednode->add_class('orphaned');
foreach ($modinfo->sections[1] as $cmid) {
if (has_capability('moodle/course:viewhiddenactivities', context_module::instance($cmid))) {
$this->navigation_add_activity($orphanednode, $modinfo->cms[$cmid]);
}
}
}
}
}
/**
* Adds a course module to the navigation node
*
* This is basically copied from function global_navigation::load_section_activities()
* because it is not accessible from outside.
*
* @param navigation_node $node
* @param cm_info $cm
* @return null|navigation_node
*/
protected function navigation_add_activity(navigation_node $node, $cm) {
if (!$cm->uservisible) {
return null;
}
$action = $cm->url;
if (!$action) {
// Do not add to navigation activity without url (i.e. labels).
return null;
}
$activityname = format_string($cm->name, true, array('context' => context_module::instance($cm->id)));
if ($cm->icon) {
$icon = new pix_icon($cm->icon, $cm->modfullname, $cm->iconcomponent);
} else {
$icon = new pix_icon('monologo', $cm->modfullname, $cm->modname);
}
$activitynode = $node->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $cm->id, $icon);
if (global_navigation::module_extends_navigation($cm->modname)) {
$activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
} else {
$activitynode->nodetype = navigation_node::NODETYPE_LEAF;
}
return $activitynode;
// SingleActivity course format does not extend navigation, it uses site_main_menu block instead.
}
/**
@@ -123,11 +64,10 @@ class format_singleactivity extends core_courseformat\base {
* each of values is an array of block names (for left and right side columns)
*/
public function get_default_blocks() {
// No blocks for this format because course view page is not displayed anyway.
return array(
BLOCK_POS_LEFT => array(),
BLOCK_POS_RIGHT => array()
);
return [
BLOCK_POS_LEFT => ['site_main_menu'],
BLOCK_POS_RIGHT => [],
];
}
/**
@@ -176,12 +116,12 @@ class format_singleactivity extends core_courseformat\base {
if ($courseformatoptions === false) {
$config = get_config('format_singleactivity');
$courseformatoptions = array(
'activitytype' => array(
$courseformatoptions = [
'activitytype' => [
'default' => $config->activitytype,
'type' => PARAM_TEXT,
),
);
],
];
if (!empty($availabletypes) && !isset($availabletypes[$config->activitytype])) {
$courseformatoptions['activitytype']['default'] = array_keys($availabletypes)[0];
@@ -189,15 +129,15 @@ class format_singleactivity extends core_courseformat\base {
}
if ($foreditform && !isset($courseformatoptions['activitytype']['label'])) {
$courseformatoptionsedit = array(
'activitytype' => array(
$courseformatoptionsedit = [
'activitytype' => [
'label' => new lang_string('activitytype', 'format_singleactivity'),
'help' => 'activitytype',
'help_component' => 'format_singleactivity',
'element_type' => 'select',
'element_attributes' => array($availabletypes),
),
);
'element_attributes' => [$availabletypes],
],
];
$courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit);
}
return $courseformatoptions;
@@ -215,109 +155,15 @@ class format_singleactivity extends core_courseformat\base {
* @return array array of references to the added form elements
*/
public function create_edit_form_elements(&$mform, $forsection = false) {
global $PAGE;
if (!$this->course && $submitvalues = $mform->getSubmitValues()) {
$this->categoryid = $submitvalues['category'];
}
$elements = parent::create_edit_form_elements($mform, $forsection);
if (!$forsection && ($course = $PAGE->course) && !empty($course->format) &&
$course->format !== 'site' && $course->format !== 'singleactivity') {
// This is the existing course in other format, display a warning.
$element = $mform->addElement('static', '', '',
html_writer::tag('span', get_string('warningchangeformat', 'format_singleactivity'),
array('class' => 'error')));
array_unshift($elements, $element);
}
return $elements;
}
/**
* Make sure that current active activity is in section 0
*
* All other activities are moved to section 1 that will be displayed as 'Orphaned'.
* It may be needed after the course format was changed or activitytype in
* course settings has been changed.
*
* @return null|cm_info current activity
*/
public function reorder_activities() {
course_create_sections_if_missing($this->courseid, array(0, 1));
foreach ($this->get_sections() as $sectionnum => $section) {
if (($sectionnum && $section->visible) ||
(!$sectionnum && !$section->visible)) {
// Make sure that 0 section is visible and all others are hidden.
set_section_visible($this->courseid, $sectionnum, $sectionnum == 0);
}
}
$modinfo = get_fast_modinfo($this->courseid);
// Find the current activity (first activity with the specified type in all course activities).
$activitytype = $this->get_activitytype();
$activity = null;
if (!empty($activitytype)) {
foreach ($modinfo->sections as $sectionnum => $cmlist) {
foreach ($cmlist as $cmid) {
if ($modinfo->cms[$cmid]->modname === $activitytype) {
$activity = $modinfo->cms[$cmid];
break 2;
}
}
}
}
// Make sure the current activity is in the 0-section.
$changed = false;
if ($activity && $activity->sectionnum != 0) {
moveto_module($activity, $modinfo->get_section_info(0));
$changed = true;
}
if ($activity && !$activity->visible) {
set_coursemodule_visible($activity->id, 1);
$changed = true;
}
if ($changed) {
// Cache was reset so get modinfo again.
$modinfo = get_fast_modinfo($this->courseid);
}
// Move all other activities into section 1 (the order must be kept).
$hasvisibleactivities = false;
$firstorphanedcm = null;
foreach ($modinfo->sections as $sectionnum => $cmlist) {
if ($sectionnum && !empty($cmlist) && $firstorphanedcm === null) {
$firstorphanedcm = reset($cmlist);
}
foreach ($cmlist as $cmid) {
if ($sectionnum > 1) {
// These module types cannot be moved from section 0.
if (!$modinfo->cms[$cmid]->is_of_type_that_can_display()) {
continue;
}
moveto_module($modinfo->get_cm($cmid), $modinfo->get_section_info(1));
} else if (!$hasvisibleactivities && $sectionnum == 1 && $modinfo->get_cm($cmid)->visible) {
$hasvisibleactivities = true;
}
}
}
if (!empty($modinfo->sections[0])) {
foreach ($modinfo->sections[0] as $cmid) {
// These module types cannot be moved from section 0.
if (!$modinfo->cms[$cmid]->is_of_type_that_can_display()) {
continue;
}
if (!$activity || $cmid != $activity->id) {
moveto_module($modinfo->get_cm($cmid), $modinfo->get_section_info(1), $firstorphanedcm);
}
}
}
if ($hasvisibleactivities) {
set_section_visible($this->courseid, 1, false);
}
return $activity;
}
/**
* Returns the name of activity type used for this course
*
@@ -334,16 +180,36 @@ class format_singleactivity extends core_courseformat\base {
}
}
/**
* Returns the current activity if exists
*
* @return null|cm_info
*/
protected function get_activity() {
#[\Override]
public function get_main_activity(): ?\cm_info {
if ($this->activity === false) {
$this->activity = $this->reorder_activities();
$modinfo = get_fast_modinfo($this->courseid);
// Find the current activity (first activity with the specified type in all course activities).
$activitytype = $this->get_activitytype();
if (!empty($activitytype) && !empty($modinfo->sections)) {
// Get the first activity of the specified type, but only if it is in the 0-section.
$cmlist = $modinfo->sections[0];
foreach ($cmlist as $cmid) {
if ($modinfo->cms[$cmid]->modname === $activitytype) {
$this->activity = $modinfo->cms[$cmid];
break;
}
}
}
// Make sure the current activity is visible.
if ($this->activity && !$this->activity->visible) {
set_coursemodule_visible($this->activity->id, 1);
}
}
return $this->activity;
if ($this->activity !== false) {
return $this->activity;
}
// No activity found, return null.
return null;
}
/**
@@ -352,7 +218,7 @@ class format_singleactivity extends core_courseformat\base {
* Here we ignore the modules that do not have a page of their own or need sections,
* like the label or subsection.
*
* @return array array($module => $name of the module).
* @return array [$module => $name of the module].
*/
public static function get_supported_activities() {
$availabletypes = get_module_types_names();
@@ -385,7 +251,7 @@ class format_singleactivity extends core_courseformat\base {
}
$libfile = "$CFG->dirroot/mod/$modname/lib.php";
if (!file_exists($libfile)) {
return null;
return false;
}
return true;
}
@@ -434,8 +300,6 @@ class format_singleactivity extends core_courseformat\base {
* If everything is configured correctly, user is redirected from the
* default course view page to the activity view page.
*
* "Section 1" is the administrative page to manage orphaned activities
*
* If user is on course view page and there is no module added to the course
* and the user has 'moodle/course:manageactivities' capability, redirect to create module
* form.
@@ -452,24 +316,18 @@ class format_singleactivity extends core_courseformat\base {
// This is a request to turn editing mode on or off, do not redirect here, /course/view.php will do redirection.
return;
}
$cm = $this->get_activity();
$cursection = optional_param('section', null, PARAM_INT);
if (!empty($cursection) && has_capability('moodle/course:viewhiddensections',
context_course::instance($this->courseid))) {
// Display orphaned activities (course view page, section 1).
return;
}
$cm = $this->get_main_activity();
if (!$this->get_activitytype()) {
if (has_capability('moodle/course:update', context_course::instance($this->courseid))) {
// Teacher is redirected to edit course page.
$url = new moodle_url('/course/edit.php', array('id' => $this->courseid));
$url = new moodle_url('/course/edit.php', ['id' => $this->courseid]);
redirect($url, get_string('erroractivitytype', 'format_singleactivity'));
} else {
// Student sees an empty course page.
return;
}
}
if ($cm === null) {
if ($cm == null) {
if ($this->can_add_activity()) {
// This is a user who has capability to create an activity.
if ($this->activity_has_subtypes()) {
@@ -477,13 +335,20 @@ class format_singleactivity extends core_courseformat\base {
if (optional_param('addactivity', 0, PARAM_INT)) {
return;
} else {
$url = new moodle_url('/course/view.php', array('id' => $this->courseid, 'addactivity' => 1));
$url = new moodle_url('/course/view.php', ['id' => $this->courseid, 'addactivity' => 1]);
redirect($url);
}
}
// Redirect to the add activity form.
$url = new moodle_url('/course/mod.php', array('id' => $this->courseid,
'section' => 0, 'sesskey' => sesskey(), 'add' => $this->get_activitytype()));
$url = new moodle_url(
'/course/mod.php',
[
'id' => $this->courseid,
'section' => 0,
'sesskey' => sesskey(),
'add' => $this->get_activitytype(),
],
);
redirect($url);
} else {
// Student views an empty course page.
@@ -511,7 +376,7 @@ class format_singleactivity extends core_courseformat\base {
public function page_set_cm(moodle_page $page) {
global $PAGE;
parent::page_set_cm($page);
if ($PAGE == $page && ($cm = $this->get_activity()) &&
if ($PAGE == $page && ($cm = $this->get_main_activity()) &&
$cm->uservisible &&
($cm->id === $page->cm->id) &&
($activitynode = $page->navigation->find($cm->id, navigation_node::TYPE_ACTIVITY)) &&
@@ -535,11 +400,7 @@ class format_singleactivity extends core_courseformat\base {
}
}
/**
* Returns true if the course has a front page.
*
* @return boolean false
*/
#[\Override]
public function has_view_page() {
return false;
}
@@ -554,4 +415,62 @@ class format_singleactivity extends core_courseformat\base {
// Return everything (nothing to hide).
return $this->get_format_options();
}
#[\Override]
public function supports_ajax() {
// All home page is rendered in the backend, we only need an ajax editor components in edit mode.
// This will also prevent redirecting to the login page when a guest tries to access the site,
// and will make the home page loading faster.
$ajaxsupport = new stdClass();
$ajaxsupport->capable = $this->show_editor();
return $ajaxsupport;
}
#[\Override]
public function supports_components() {
return true;
}
#[\Override]
public function uses_sections() {
return false;
}
#[\Override]
public function get_section_name($section) {
$section = $this->get_section($section);
if ($section->is_delegated()) {
return $section->name;
}
// The single activity only uses one section inside the additional activities block.
return get_string('pluginname', 'format_singleactivity');
}
#[\Override]
public function get_sectionnum(): int {
// SingleActivity format uses only section 0.
return 0;
}
#[\Override]
public function allow_stealth_module_visibility($cm, $section) {
return true;
}
/**
* Returns if a specific section is visible to the current user.
*
* The single activity format does only have the section zero
* and subsections (delegated sections).
*
* @param section_info $section the section modinfo
* @return bool;
*/
#[\Override]
public function is_section_visible(section_info $section): bool {
$visible = parent::is_section_visible($section);
// Social format does only use section 0 as a normal section.
// Any other included section should be a delegated one (subsections).
return $visible && ($section->sectionnum == 0 || $section->is_delegated());
}
}
-3
View File
@@ -1,3 +0,0 @@
.format-singleactivity .tree_item.orphaned a {
color: red;
}
@@ -19,53 +19,30 @@ Feature: Activity navigation in a single activity course
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| forum | Forum 1 | Test forum description | C1 | forum1 | 0 |
| assign | Assignment 1 | Test assignment description | C1 | assign1 | 0 |
| lesson | Lesson 1 | Test lesson description | C1 | lesson1 | 0 |
| forum | Forum 1 | Test forum description | C1 | forum1 | 0 |
Scenario: Step through hidden activities in the course as a teacher.
Scenario: Step through activities in the course as a teacher
Given I log in as "teacher1"
When I am on "Course 1" course homepage
# The first activity (Forum 1) won't have the previous activity link.
Then "#prev-activity-link" "css_element" should not exist
And I should see "Assignment 1 (hidden)" in the "#next-activity-link" "css_element"
And I follow "Assignment 1 (hidden)"
And I should see "Assignment 1" in the "#next-activity-link" "css_element"
And I follow "Assignment 1"
And I should see "Forum 1" in the "#prev-activity-link" "css_element"
And I should see "Lesson 1 (hidden)" in the "#next-activity-link" "css_element"
And I follow "Lesson 1 (hidden)"
And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element"
And I should see "Lesson 1" in the "#next-activity-link" "css_element"
And I follow "Lesson 1"
And I should see "Assignment 1" in the "#prev-activity-link" "css_element"
And "#next-activity-link" "css_element" should not exist
Scenario: Jump to a hidden activity as a teacher
Given I log in as "teacher1"
When I am on "Course 1" course homepage
Then "Jump to..." "field" should exist
# The current activity (Forum 1) will not be listed.
And the "Jump to..." select box should not contain "Forum 1"
# Check drop down menu contents.
And the "Jump to..." select box should contain "Assignment 1 (hidden)"
And the "Jump to..." select box should contain "Lesson 1 (hidden)"
# Jump to a hidden activity somewhere in the middle.
When I select "Assignment 1 (hidden)" from the "Jump to..." singleselect
Then I should see "Assignment 1"
And I should see "Forum 1" in the "#prev-activity-link" "css_element"
And I should see "Lesson 1 (hidden)" in the "#next-activity-link" "css_element"
# Jump to the first activity.
And I select "Forum 1" from the "Jump to..." singleselect
And I should see "Assignment 1 (hidden)" in the "#next-activity-link" "css_element"
But "#prev-activity-link" "css_element" should not exist
# Jump to the last activity.
And I select "Lesson 1 (hidden)" from the "Jump to..." singleselect
And I should see "Assignment 1 (hidden)" in the "#prev-activity-link" "css_element"
But "#next-activity-link" "css_element" should not exist
Scenario: The activity navigation controls are not available as a student.
Scenario: The activity navigation controls are available as a student
Given I log in as "student1"
And I am on "Course 1" course homepage
# The first activity won't have the previous activity link.
Then "#prev-activity-link" "css_element" should not exist
And "#next-activity-link" "css_element" should not exist
And "Jump to..." "field" should not exist
And "#next-activity-link" "css_element" should exist
And "Jump to..." "field" should exist
Scenario: The activity navigation asks for login to guest user
Given I log in as "guest"
@@ -1,10 +1,10 @@
@format @format_singleactivity
@format @format_singleactivity @block_site_main_menu
Feature: Courses can be created in Single Activity mode
In order to create a single activity course
As a manager
I need to create courses and set default values on them
Scenario: Create a course as a custom course creator
Scenario: Create a course using Single Activity format as a custom course creator
Given the following "users" exist:
| username | firstname | lastname | email |
| kevin | Kevin | the | kevin@example.com |
@@ -26,14 +26,21 @@ Feature: Courses can be created in Single Activity mode
And I press "Add a new course"
And I set the following fields to these values:
| Course full name | My first course |
| Course short name | myfirstcourse |
| Format | Single activity |
| Course short name | C1 |
| Format | Single activity |
And I press "Update format"
Then I should see "Quiz" in the "Type of activity" "field"
And I should see "Forum" in the "Type of activity" "field"
# Check that not all the activity types are in the dropdown.
And I should not see "Text and media" in the "Type of activity" "field"
And I should not see "Subsection" in the "Type of activity" "field"
And I set the field "Type of activity" to "Quiz"
And I set the field "Type of activity" to "Assignment"
And I press "Save and display"
And I should see "New Quiz"
And I should see "New Assignment"
And I set the field "Assignment name" to "My assignment"
And I press "Save and return to course"
And the following "blocks" exist:
| blockname | contextlevel | reference | pagetypepattern | defaultregion |
| site_main_menu | Course | C1 | * | site-pre |
And I should see "My first course" in the "page-header" "region"
And I should see "My assignment" in the "Additional activities" "block"
@@ -1,26 +1,67 @@
@format @format_singleactivity
@format @format_singleactivity @block_site_main_menu
Feature: Edit format course to Single Activity format
In order to set the format course to single activity course
As a teacher
I need to edit the course settings and see the dropdown type activity
Scenario: Edit a format course as a teacher
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | summary | format |
| Course 1 | C1 | <p>Course summary</p> | topics |
| fullname | shortname | format | activitytype |
| Course 1 | C1 | singleactivity | assign |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section |
| assign | Assignment 1 | Test assignment description | C1 | assign1 | 0 |
| forum | Forum 1 | Test forum description | C1 | forum1 | 0 |
@javascript
Scenario: Add subsections and move activities in Single activity course format
Given I log in as "teacher1"
When I am on "Course 1" course homepage with editing mode on
And I click on "Add content" "button" in the ".block_site_main_menu .footer" "css_element"
And I click on "Subsection" "link" in the ".dropdown-menu.show" "css_element"
Then I should see "New subsection" in the "Additional activities" "block"
And I open "Forum 1" actions menu
And I click on "Move" "link" in the "Forum 1" activity
And I click on "New subsection" "link" in the "Move activity" "dialogue"
And I should see "Forum 1" in the "New subsection" "section"
Scenario: Change activity type in Single activity course format
# Navigate to any course page, like groups, to guarantee that Course settings are displayed in boost and classic themes.
Given I am on the "Course 1" "groups" page logged in as "teacher1"
When I navigate to "Settings" in current page administration
# If there is an existing activity, it will be used as main activity.
And I set the following fields to these values:
| Type of activity | Forum |
And I press "Save and display"
Then I should see "Test forum description" in the "page-content" "region"
# However, if there is no existing activity, the new activity form will be displayed.
But I am on the "Course 1" "groups" page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Type of activity | Quiz |
And I press "Save and display"
And I should see "New Quiz"
Scenario: Edit a format course as a teacher
Given the following "courses" exist:
| fullname | shortname | summary | format |
| Course 2 | C2 | Course summary | topics |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C2 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I am on "Course 2" course homepage
When I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Course full name | My first course |
| Course short name | myfirstcourse |
| Format | Single activity |
| Course short name | myfirstcourse |
| Format | Single activity |
And I press "Update format"
Then I should see "Forum" in the "Type of activity" "field"
# Check that not all the activity types are in the dropdown.
@@ -38,7 +38,7 @@ final class format_singleactivity_test extends \advanced_testcase {
// 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]);
course_create_sections_if_missing($course1, [0]);
$data = (object)['id' => $course1->id];
$format = course_get_format($course1);
+2 -2
View File
@@ -110,8 +110,8 @@ final class get_state_test extends \externallib_advanced_testcase {
// User won't see the hidden sections. Remove them from the total.
$visiblesections = $visiblesections - count($hiddensections);
}
if ($format == 'social') {
$visiblesections = 1; // But Social format has one section visible.
if ($format == 'social' || $format == 'singleactivity') {
$visiblesections = 1; // But Social and Single activity formats have only one section visible.
}
// Social course format automatically creates a forum activity.
@@ -64,11 +64,11 @@ final class state_test extends \advanced_testcase {
$this->setAdminUser();
$courseformat = course_get_format($course->id);
$modinfo = $courseformat->get_modinfo();
$issocialformat = $courseformat->get_format() === 'social';
$supportsections = $format !== 'social' && $format !== 'singleactivity';
// Only create activities if the course format is not social.
// There's no course home page (and sections) for social course format.
if (!$issocialformat || $format == 'theunittest') {
if ($supportsections) {
// Add some activities to the course.
$this->getDataGenerator()->create_module('page', ['course' => $course->id], ['section' => 1,
'visible' => 1]);
@@ -111,7 +111,7 @@ final class state_test extends \advanced_testcase {
$sections = $modinfo->get_section_info_all();
foreach ($sections as $key => $section) {
if (!$issocialformat || $format == 'theunittest') {
if ($supportsections) {
$this->assertEquals($section->id, $result->course->sectionlist[$key]);
if (!empty($section->uservisible)) {
$sectionstate = new $sectionclass($courseformat, $section);
+73 -43
View File
@@ -110,7 +110,9 @@ final class stateactions_test extends \advanced_testcase {
private function translate_references(array $references, array $values): array {
$result = [];
foreach ($values as $value) {
$result[] = $references[$value];
if (array_key_exists($value, $references)) {
$result[] = $references[$value];
}
}
return $result;
}
@@ -209,8 +211,13 @@ final class stateactions_test extends \advanced_testcase {
): void {
$this->resetAfterTest();
// Create a course with 3 sections, 1 of them hidden.
$course = $this->create_course($format, 3, [2]);
if ($format === 'singleactivity') {
// Single activity format does not have sections.
$course = $this->create_course($format, 0, []);
} else {
// Create a course with 3 sections, 1 of them hidden.
$course = $this->create_course($format, 3, [2]);
}
$references = $this->course_references($course);
@@ -226,11 +233,19 @@ final class stateactions_test extends \advanced_testcase {
$count++;
}
// Add some activities to the course. One visible and one hidden in both sections 1 and 2.
$references["cm0"] = $this->create_activity($course->id, 'assign', 1, true);
$references["cm1"] = $this->create_activity($course->id, 'book', 1, false);
$references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true);
$references["cm3"] = $this->create_activity($course->id, 'page', 2, false);
if ($format === 'singleactivity') {
// Add some activities to the course. One visible and one hidden in section 0.
$references['cm0'] = $this->create_activity($course->id, 'forum', 0, true);
$references['cm1'] = $this->create_activity($course->id, 'book', 0, false);
} else {
// Add some activities to the course. One visible and one hidden in sections 0, 1 and 2.
$references['cm0'] = $this->create_activity($course->id, 'forum', 0, true);
$references['cm1'] = $this->create_activity($course->id, 'book', 0, false);
$references['cm2'] = $this->create_activity($course->id, 'assign', 1, true);
$references['cm3'] = $this->create_activity($course->id, 'book', 1, false);
$references['cm4'] = $this->create_activity($course->id, 'glossary', 2, true);
$references['cm5'] = $this->create_activity($course->id, 'page', 2, false);
}
if ($expectedexception) {
$this->expectException(moodle_exception::class);
@@ -315,17 +330,23 @@ final class stateactions_test extends \advanced_testcase {
* @return array the testing scenarios
*/
public static function course_state_provider(string $format): array {
$expectedexception = ($format === 'singleactivity');
$expectedexception = false;
$cms = ['cm0', 'cm1', 'cm2', 'cm3'];
$studentcms = ['cm0', 'cm2'];
// All sections and cms that the user can access to.
$usersections = ['section0', 'section1', 'section2', 'section3'];
$studentcms = ['cm0'];
if ($format === 'social') {
$cms = ['initialcm0', 'cm0', 'cm1', 'cm2', 'cm3'];
$studentcms = ['initialcm0', 'cm0'];
$usersections = ['section0']; // Social format only uses section 0 (for all users).
if ($format === 'singleactivity') {
// Single activity format does not have sections.
$cms = ['cm0', 'cm1'];
$usersections = ['section0'];
$studentcms = ['cm0'];
} else if ($format === 'social') {
// Social format only uses section 0 (for all users).
$cms = ['initialcm0', 'cm0', 'cm1', 'cm2', 'cm3', 'cm4', 'cm5'];
$studentcms = ['initialcm0', 'cm0', 'cm2'];
$usersections = ['section0'];
} else {
$cms = ['cm0', 'cm1', 'cm2', 'cm3', 'cm4', 'cm5'];
$usersections = ['section0', 'section1', 'section2', 'section3'];
}
return [
@@ -383,20 +404,23 @@ final class stateactions_test extends \advanced_testcase {
* @return array the testing scenarios
*/
public static function section_state_provider(string $format, string $role): array {
// Social format will raise an exception and debug messages because it does not
// use sections and it does not provide a renderer.
$expectedexception = ($format === 'singleactivity');
// All sections and cms that the user can access to.
$usersections = ['section0', 'section1', 'section2', 'section3'];
$usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
$usercms = ['cm0', 'cm1', 'cm2', 'cm3', 'cm4', 'cm5'];
if ($role == 'student') {
$usersections = ['section0', 'section1', 'section3'];
$usercms = ['cm0'];
$usercms = ['cm0', 'cm2'];
}
if ($format === 'social') {
// Social format only uses section 0 (for all users).
$usersections = ['section0'];
$usercms = ['initialcm0', ...$usercms];
$usersections = ['section0']; // Social format only uses section 0 (for all users).
} else if ($format === 'singleactivity') {
// Single activity format does not have sections.
$usersections = ['section0'];
$usercms = ($role == 'student') ? ['cm0'] : ['cm0', 'cm1'];
}
return [
@@ -420,9 +444,9 @@ final class stateactions_test extends \advanced_testcase {
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section0'], $usersections),
'cm' => ($format == 'social') ? ['initialcm0'] : [],
'cm' => array_intersect(['initialcm0', 'cm0', 'cm1'], $usercms),
],
'expectedexception' => $expectedexception,
'expectedexception' => false,
],
"$role $format section_state visible section" => [
'format' => $format,
@@ -434,7 +458,7 @@ final class stateactions_test extends \advanced_testcase {
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1'], $usersections),
'cm' => array_intersect(['cm0', 'cm1'], $usercms),
'cm' => array_intersect(['cm2', 'cm3'], $usercms),
],
'expectedexception' => $expectedexception,
],
@@ -448,7 +472,7 @@ final class stateactions_test extends \advanced_testcase {
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section2'], $usersections),
'cm' => array_intersect(['cm2', 'cm3'], $usercms),
'cm' => array_intersect(['cm4', 'cm5'], $usercms),
],
'expectedexception' => $expectedexception,
],
@@ -462,7 +486,7 @@ final class stateactions_test extends \advanced_testcase {
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1', 'section3'], $usersections),
'cm' => array_intersect(['cm0', 'cm1'], $usercms),
'cm' => array_intersect(['cm2', 'cm3'], $usercms),
],
'expectedexception' => $expectedexception,
],
@@ -486,7 +510,7 @@ final class stateactions_test extends \advanced_testcase {
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1', 'section3'], $usersections),
'cm' => array_intersect(['cm0', 'cm1'], $usercms),
'cm' => array_intersect(['cm2', 'cm3'], $usercms),
],
'expectedexception' => $expectedexception,
],
@@ -495,12 +519,12 @@ final class stateactions_test extends \advanced_testcase {
'role' => $role,
'method' => 'section_state',
'params' => [
'ids' => ['section3'], 'targetsectionid' => null, 'targetcmid' => 'cm1'
'ids' => ['section3'], 'targetsectionid' => null, 'targetcmid' => 'cm3',
],
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section3'], $usersections),
'cm' => array_intersect(['cm1'], $usercms),
'cm' => array_intersect(['cm3'], $usercms),
],
'expectedexception' => $expectedexception,
],
@@ -515,16 +539,22 @@ final class stateactions_test extends \advanced_testcase {
* @return array the testing scenarios
*/
public static function cm_state_provider(string $format, string $role): array {
$expectedexception = ($format === 'singleactivity');
// All sections and cms that the user can access to.
$usersections = ['section0', 'section1', 'section2', 'section3'];
$usercms = ['cm0', 'cm1', 'cm2', 'cm3'];
$usercms = ['cm0', 'cm1', 'cm2', 'cm3', 'cm4', 'cm5'];
if ($role == 'student') {
$usersections = ['section0', 'section1', 'section3'];
$usercms = ['cm0'];
$usercms = ['cm0', 'cm2'];
}
if ($format === 'social') {
$usercms = ['initialcm0', ...$usercms];
$usersections = ['section0']; // Social format only uses section 0 (for all users).
} else if ($format === 'singleactivity') {
// Single activity format does not have sections.
$usersections = ['section0'];
$usercms = ($role == 'student') ? ['cm0'] : ['cm0', 'cm1'];
}
return [
@@ -547,7 +577,7 @@ final class stateactions_test extends \advanced_testcase {
],
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1'], $usersections),
'section' => array_intersect(['section0'], $usersections),
'cm' => array_intersect(['cm0'], $usercms),
],
'expectedexception' => false,
@@ -561,7 +591,7 @@ final class stateactions_test extends \advanced_testcase {
],
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1'], $usersections),
'section' => array_intersect(['section0'], $usersections),
'cm' => array_intersect(['cm1'], $usercms),
],
'expectedexception' => false,
@@ -571,42 +601,42 @@ final class stateactions_test extends \advanced_testcase {
'role' => $role,
'method' => 'cm_state',
'params' => [
'ids' => ['cm0', 'cm2'], 'targetsectionid' => null, 'targetcmid' => null
'ids' => ['cm2', 'cm4'], 'targetsectionid' => null, 'targetcmid' => null,
],
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1', 'section2'], $usersections),
'cm' => array_intersect(['cm0', 'cm2'], $usercms),
'cm' => array_intersect(['cm2', 'cm4'], $usercms),
],
'expectedexception' => false,
'expectedexception' => $expectedexception,
],
"$role $format cm_state using targetsection" => [
'format' => $format,
'role' => $role,
'method' => 'cm_state',
'params' => [
'ids' => ['cm0'], 'targetsectionid' => 'section2', 'targetcmid' => null
'ids' => ['cm2'], 'targetsectionid' => 'section2', 'targetcmid' => null,
],
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1', 'section2'], $usersections),
'cm' => array_intersect(['cm0'], $usercms),
'cm' => array_intersect(['cm2'], $usercms),
],
'expectedexception' => ($format === 'singleactivity'),
'expectedexception' => $expectedexception,
],
"$role $format cm_state using targetcm" => [
'format' => $format,
'role' => $role,
'method' => 'cm_state',
'params' => [
'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => 'cm3'
'ids' => ['cm2'], 'targetsectionid' => null, 'targetcmid' => 'cm5',
],
'expectedresults' => [
'course' => [],
'section' => array_intersect(['section1', 'section2'], $usersections),
'cm' => array_intersect(['cm0', 'cm3'], $usercms),
'cm' => array_intersect(['cm2', 'cm5'], $usercms),
],
'expectedexception' => false,
'expectedexception' => $expectedexception,
],
"$role $format cm_state using an invalid cm" => [
'format' => $format,
+2 -4
View File
@@ -1009,8 +1009,7 @@ abstract class moodleform_mod extends moodleform {
}
// If the 'show description' feature is enabled, this checkbox appears below the intro.
// We want to hide that when using the singleactivity course format because it is confusing.
if ($this->_features->showdescription && $this->courseformat->has_view_page()) {
if ($this->_features->showdescription) {
$mform->addElement('advcheckbox', 'showdescription', get_string('showdescription'));
$mform->addHelpButton('showdescription', 'showdescription');
}
@@ -1043,8 +1042,7 @@ abstract class moodleform_mod extends moodleform {
$buttonarray = array();
// Label for the submit button to return to the course.
// Ignore this button in single activity format because it is confusing.
if ($submit2label !== false && $this->courseformat->has_view_page()) {
if ($submit2label !== false) {
$buttonarray[] = &$mform->createElement('submit', 'submitbutton2', $submit2label);
}
+10 -1
View File
@@ -2735,16 +2735,25 @@ function blocks_add_default_course_blocks($course) {
}
$showinsubcontexts = false;
if ($course->id == SITEID) {
$pagetypepattern = 'site-index';
} else {
$pagetypepattern = 'course-view-*';
$format = course_get_format($course);
if ($format->has_view_page()) {
$pagetypepattern = 'course-view-*';
} else {
$pagetypepattern = '*';
$showinsubcontexts = true;
}
}
$page = new moodle_page();
$page->set_course($course);
$page->blocks->add_blocks(
$page->blocks->filter_nonexistent_blocks($blocknames),
$pagetypepattern,
null,
$showinsubcontexts,
);
}
+10 -4
View File
@@ -1108,10 +1108,16 @@ class secondary extends view {
// the course in order to obtain the required module settings navigation.
if ($page->context instanceof \context_course) {
$this->page->set_secondary_active_tab($coursesecondarynode->key);
// Get the currently used module in the single activity course.
$module = current(array_filter(get_course_mods($course->id), function ($module) {
return $module->visible == 1;
}));
// Get the currently used module in course.
$format = course_get_format($course);
if ($format instanceof \core_courseformat\main_activity_interface) {
$module = $format->get_main_activity();
} else {
$module = current(array_filter(get_course_mods($course->id), function ($module) {
return $module->visible == 1;
}));
}
// If the default module for the single course format has not been set yet, skip displaying the module
// related navigation in the secondary navigation.
if (!$module) {
+25 -2
View File
@@ -103,12 +103,12 @@ final class permission_test extends advanced_testcase {
'format' => 'topics',
'expected' => false,
],
'Format does not support components' => [
'Single activity supports subsections too' => [
'ismoddisabled' => false,
'missingcapability' => false,
'isdelegated' => false,
'format' => 'singleactivity',
'expected' => false,
'expected' => true,
],
'Plugin enabled, with capability, not inside a delegated section' => [
'ismoddisabled' => false,
@@ -119,4 +119,27 @@ final class permission_test extends advanced_testcase {
],
];
}
/**
* Test that subsection cannot be added when the course format does not support components.
*
* @return void
*/
public function test_can_add_subsection_unsupported_components(
): void {
global $SITE;
$this->resetAfterTest();
course_create_sections_if_missing($SITE, [0, 1]);
$courseformat = course_get_format($SITE);
$targetsection = $courseformat->get_modinfo()->get_section_info(0);
$user = $this->getDataGenerator()->create_and_enrol($SITE, 'editingteacher');
$this->setUser($user);
$this->assertEquals(false, permission::can_add_subsection($targetsection, (int)$user->id));
}
}