diff --git a/blocks/site_main_menu/tests/behat/edit_activities.feature b/blocks/site_main_menu/tests/behat/edit_activities.feature index ea61a2f13dd..ff163f5f090 100644 --- a/blocks/site_main_menu/tests/behat/edit_activities.feature +++ b/blocks/site_main_menu/tests/behat/edit_activities.feature @@ -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 diff --git a/course/format/classes/stateactions.php b/course/format/classes/stateactions.php index c2e4ac14c71..5f62719b719 100644 --- a/course/format/classes/stateactions.php +++ b/course/format/classes/stateactions.php @@ -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. diff --git a/course/format/singleactivity/format.php b/course/format/singleactivity/format.php index 5996ef44b2f..908a12f8310 100644 --- a/course/format/singleactivity/format.php +++ b/course/format/singleactivity/format.php @@ -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); diff --git a/course/format/singleactivity/lang/en/format_singleactivity.php b/course/format/singleactivity/lang/en/format_singleactivity.php index ad2ae685b7b..5e9813abd73 100644 --- a/course/format/singleactivity/lang/en/format_singleactivity.php +++ b/course/format/singleactivity/lang/en/format_singleactivity.php @@ -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.'; diff --git a/course/format/singleactivity/lib.php b/course/format/singleactivity/lib.php index 55e0a92a6d6..ae5f1c2f08d 100644 --- a/course/format/singleactivity/lib.php +++ b/course/format/singleactivity/lib.php @@ -14,14 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * 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()); + } } diff --git a/course/format/singleactivity/styles.css b/course/format/singleactivity/styles.css deleted file mode 100644 index d03b9822848..00000000000 --- a/course/format/singleactivity/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -.format-singleactivity .tree_item.orphaned a { - color: red; -} diff --git a/course/format/singleactivity/tests/behat/activity_navigation.feature b/course/format/singleactivity/tests/behat/activity_navigation.feature index 0fae0c0ada0..ed5a1197253 100644 --- a/course/format/singleactivity/tests/behat/activity_navigation.feature +++ b/course/format/singleactivity/tests/behat/activity_navigation.feature @@ -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" diff --git a/course/format/singleactivity/tests/behat/create_course.feature b/course/format/singleactivity/tests/behat/create_course.feature index de1abe860e7..f3d7782d432 100644 --- a/course/format/singleactivity/tests/behat/create_course.feature +++ b/course/format/singleactivity/tests/behat/create_course.feature @@ -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" diff --git a/course/format/singleactivity/tests/behat/edit_format_course.feature b/course/format/singleactivity/tests/behat/edit_format_course.feature index e5df4eb02f5..01680471c6f 100644 --- a/course/format/singleactivity/tests/behat/edit_format_course.feature +++ b/course/format/singleactivity/tests/behat/edit_format_course.feature @@ -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 |

Course summary

| 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. diff --git a/course/format/singleactivity/tests/format_singleactivity_test.php b/course/format/singleactivity/tests/format_singleactivity_test.php index 9e7a7424672..b3414e8fcba 100644 --- a/course/format/singleactivity/tests/format_singleactivity_test.php +++ b/course/format/singleactivity/tests/format_singleactivity_test.php @@ -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); diff --git a/course/format/tests/external/get_state_test.php b/course/format/tests/external/get_state_test.php index 6e8eb93e02e..c5a05469363 100644 --- a/course/format/tests/external/get_state_test.php +++ b/course/format/tests/external/get_state_test.php @@ -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. diff --git a/course/format/tests/output/local/state/state_test.php b/course/format/tests/output/local/state/state_test.php index a40532ee794..6fd4404da33 100644 --- a/course/format/tests/output/local/state/state_test.php +++ b/course/format/tests/output/local/state/state_test.php @@ -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); diff --git a/course/format/tests/stateactions_test.php b/course/format/tests/stateactions_test.php index 0d23d056a59..ceb2a21b086 100644 --- a/course/format/tests/stateactions_test.php +++ b/course/format/tests/stateactions_test.php @@ -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, diff --git a/mod/subsection/tests/permission_test.php b/mod/subsection/tests/permission_test.php index 9171665ede8..dbb70c6cf1c 100644 --- a/mod/subsection/tests/permission_test.php +++ b/mod/subsection/tests/permission_test.php @@ -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)); + } + + }