MDL-85433 singleactivity: Support multiple activities

This commit is contained in:
Sara Arjona
2025-06-13 15:26:08 +02:00
parent 6939cebb29
commit c5d66da007
14 changed files with 312 additions and 323 deletions
@@ -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
-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,
+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));
}
}