From 4053bd2139b0dc118e8b8926ece9a0439f209328 Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Wed, 9 Oct 2024 13:41:15 +0200 Subject: [PATCH 1/5] MDL-82351 course: fix course_ajax_enabled function The course_ajax_enabled was using a course_format_ajax_support which does not initialize the course format with the correct parameters. Having a global function to get something the format base format can provide is unnecessary and redudant, for this reason the function is now deprecated. --- .upgradenotes/MDL-82351-2024102502242849.yml | 7 +++++++ course/lib.php | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .upgradenotes/MDL-82351-2024102502242849.yml diff --git a/.upgradenotes/MDL-82351-2024102502242849.yml b/.upgradenotes/MDL-82351-2024102502242849.yml new file mode 100644 index 00000000000..831be97234f --- /dev/null +++ b/.upgradenotes/MDL-82351-2024102502242849.yml @@ -0,0 +1,7 @@ +issueNumber: MDL-82351 +notes: + core_course: + - message: >- + The course_format_ajax_support function is now deprecated. Use + course_get_format($course)->supports_ajax() instead. + type: deprecated diff --git a/course/lib.php b/course/lib.php index e526417dcb4..6f42157d4da 100644 --- a/course/lib.php +++ b/course/lib.php @@ -1718,10 +1718,14 @@ function course_format_uses_sections($format) { * The returned object's property (boolean)capable indicates that * the course format supports Moodle course ajax features. * + * @deprecated since Moodle 5.0 MDL-82351 + * @todo MDL-83417 Remove this function in Moodle 6.0 * @param string $format * @return stdClass */ +#[\core\attribute\deprecated(since: '5.0', mdl: 'MDL-82351')] function course_format_ajax_support($format) { + \core\deprecation::emit_deprecation_if_present(__FUNCTION__); $course = new stdClass(); $course->format = $format; return course_get_format($course)->supports_ajax(); @@ -2738,7 +2742,7 @@ function course_ajax_enabled($course) { // Check that the course format supports ajax functionality // The site 'format' doesn't have information on course format support if ($SITE->id !== $course->id) { - $courseformatajaxsupport = course_format_ajax_support($course->format); + $courseformatajaxsupport = course_get_format($course)->supports_ajax(); if (!$courseformatajaxsupport->capable) { return false; } From fa1296a7a443c10c51c9ca4c5b0cf0e3d95925b4 Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Wed, 9 Oct 2024 13:42:43 +0200 Subject: [PATCH 2/5] MDL-82351 courseformat: allow blocks to use the course editor Some formats like the social need the blocks and drawers to access the course editor. However, the include_course_ajax was called after the main page structure is created and produces some errors, especially in behat. Now include_course_ajax is called before the page header. --- course/view.php | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/course/view.php b/course/view.php index ecf7fa4ed9b..ef119a32806 100644 --- a/course/view.php +++ b/course/view.php @@ -304,6 +304,23 @@ if (!empty($bulkbutton)) { } $PAGE->set_heading($course->fullname); + +// Make sure that section 0 exists (this function will create one if it is missing). +course_create_sections_if_missing($course, 0); + +// Get information about course modules and existing module types. +// format.php in course formats may rely on presence of these variables. +$modinfo = get_fast_modinfo($course); +$modnames = get_module_types_names(); +$modnamesplural = get_module_types_names(true); +$modnamesused = $modinfo->get_used_module_names(); +$mods = $modinfo->get_cms(); +$sections = $modinfo->get_section_info_all(); + +// Include course AJAX. This should be done before starting the UI +// to allow page header, blocks, or drawers use the course editor. +include_course_ajax($course, $modnamesused); + echo $OUTPUT->header(); // Show communication room status notification. @@ -324,26 +341,11 @@ if ($USER->editing == 1) { // Course wrapper start. echo html_writer::start_tag('div', ['class' => 'course-content']); -// Make sure that section 0 exists (this function will create one if it is missing). -course_create_sections_if_missing($course, 0); - -// Get information about course modules and existing module types. -// format.php in course formats may rely on presence of these variables. -$modinfo = get_fast_modinfo($course); -$modnames = get_module_types_names(); -$modnamesplural = get_module_types_names(true); -$modnamesused = $modinfo->get_used_module_names(); -$mods = $modinfo->get_cms(); -$sections = $modinfo->get_section_info_all(); - // CAUTION, hacky fundamental variable defintion to follow! // Note that because of the way course fromats are constructed though // inclusion we pass parameters around this way. $displaysection = $section; -// Include course AJAX. -include_course_ajax($course, $modnamesused); - // Include the actual course format. require($CFG->dirroot .'/course/format/'. $course->format .'/format.php'); // Content wrapper end. From 8be3db6de225708849fce966166dbd9de742f7aa Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Wed, 9 Oct 2024 13:43:33 +0200 Subject: [PATCH 3/5] MDL-82351 format_social: migrate to reactive course editor The commit adds all the necessary methods to allow social format to use the course editor. The code is similar to the one used in the frontpage format. The commit does not include the migration of the social_activities blocks. --- .../format/social/lang/en/format_social.php | 5 ++- course/format/social/lib.php | 45 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/course/format/social/lang/en/format_social.php b/course/format/social/lang/en/format_social.php index fab01047d0a..28e2ec7eb4e 100644 --- a/course/format/social/lang/en/format_social.php +++ b/course/format/social/lang/en/format_social.php @@ -23,9 +23,12 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['hidefromothers'] = 'Hide'; $string['numberdiscussions'] = 'Number of discussions'; $string['numberdiscussions_help'] = 'This setting specifies how many discussions should be displayed.'; $string['pluginname'] = 'Social'; $string['plugin_description'] = 'The course is centred around a main forum on the course page. Additional activities and resources can be added using the Social activities block.'; -$string['sectionname'] = 'section'; $string['privacy:metadata'] = 'The Social format plugin does not store any personal data.'; +$string['sectionname'] = 'section'; +$string['showfromothers'] = 'Show'; +$string['socialactivities'] = 'Social activities'; diff --git a/course/format/social/lib.php b/course/format/social/lib.php index 79c21bb0a98..c1d239b793a 100644 --- a/course/format/social/lib.php +++ b/course/format/social/lib.php @@ -129,18 +129,45 @@ class format_social extends core_courseformat\base { return $this->get_format_options(); } - /** - * Returns the information about the ajax support in the given source format. - * - * The returned object's property (boolean)capable indicates that - * the course format supports Moodle course ajax features. - * - * @return stdClass - */ + #[\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 redirectng 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 = true; + $ajaxsupport->capable = $this->show_editor(); return $ajaxsupport; } + #[\Override] + public function supports_components() { + return true; + } + + #[\Override] + public function uses_sections() { + return true; + } + + #[\Override] + public function get_section_name($section) { + return get_string('socialactivities', 'format_social'); + } + + /** + * Social format uses only section 0. + * + * @return int + */ + #[\Override] + public function get_sectionnum(): int { + return 0; + } + + + #[\Override] + public function get_max_sections() { + // Social ony uses one section. + return 1; + } } From ab5b390a77f4bb26959a11781670ecc2f56598e9 Mon Sep 17 00:00:00 2001 From: ferranrecio Date: Wed, 9 Oct 2024 13:43:56 +0200 Subject: [PATCH 4/5] MDL-82351 block_social_activities: migrate to reactive course editor This commit integrate and clean the social activities block to use the standard course format outputs and the course editor modules. The code is similar to the one used in the site_main_menu block and cleanup most of the ancient code. --- .../block_social_activities.php | 206 ++--------------- .../classes/output/blocksection.php | 72 ++++++ blocks/social_activities/styles.css | 35 ++- .../templates/blocksection.mustache | 72 ++++++ .../behat/behat_block_social_activities.php | 123 ----------- ...hat_block_social_activities_deprecated.php | 207 ++++++++++++++++++ .../tests/behat/edit_activities.feature | 111 ++++++++-- theme/boost/scss/moodle/blocks.scss | 9 - theme/boost/style/moodle.css | 9 - theme/classic/style/moodle.css | 9 - 10 files changed, 485 insertions(+), 368 deletions(-) create mode 100644 blocks/social_activities/classes/output/blocksection.php create mode 100644 blocks/social_activities/templates/blocksection.mustache create mode 100644 blocks/social_activities/tests/behat/behat_block_social_activities_deprecated.php diff --git a/blocks/social_activities/block_social_activities.php b/blocks/social_activities/block_social_activities.php index c60eee7ac3a..728464aeb27 100644 --- a/blocks/social_activities/block_social_activities.php +++ b/blocks/social_activities/block_social_activities.php @@ -21,8 +21,7 @@ * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ - -class block_social_activities extends block_list { +class block_social_activities extends block_base { function init(){ $this->title = get_string('pluginname', 'block_social_activities'); } @@ -32,15 +31,12 @@ class block_social_activities extends block_list { } function get_content() { - global $USER, $CFG, $DB, $OUTPUT; - if ($this->content !== NULL) { return $this->content; } $this->content = new stdClass(); - $this->content->items = array(); - $this->content->icons = array(); + $this->content->text = ''; $this->content->footer = ''; if (empty($this->instance)) { @@ -48,198 +44,24 @@ class block_social_activities extends block_list { } $course = $this->page->course; + + course_create_sections_if_missing($course, 0); $format = course_get_format($course); - $courserenderer = $format->get_renderer($this->page); - - require_once($CFG->dirroot.'/course/lib.php'); - - $context = context_course::instance($course->id); - $isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context); - $modinfo = get_fast_modinfo($course); - - // Output classes. - $cmnameclass = $format->get_output_classname('content\\cm\\cmname'); - $controlmenuclass = $format->get_output_classname('content\\cm\\controlmenu'); - - $badgeattributes = [ - 'class' => 'badge rounded-pill bg-warning text-dark mt-2', - 'data-region' => 'visibility' - ]; - - // Extra fast view mode. - if (!$isediting) { - if (!empty($modinfo->sections[0])) { - foreach($modinfo->sections[0] as $cmid) { - $cm = $modinfo->cms[$cmid]; - if (!$cm->uservisible || !$cm->is_visible_on_course_page()) { - continue; - } - - $badges = ''; - if (!$cm->visible) { - $badges = html_writer::tag( - 'span', - get_string('hiddenfromstudents'), - $badgeattributes - ); - } - - if ($cm->is_stealth()) { - $badges = html_writer::tag( - 'span', - get_string('hiddenoncoursepage'), - $badgeattributes - ); - } - - if (!$cm->url) { - $activitybasis = html_writer::div( - $cm->get_formatted_content(['overflowdiv' => true, 'noclean' => true]), - 'activity-basis d-flex align-items-center' - ); - $content = html_writer::div( - $activitybasis . $badges, - 'contentwithoutlink activity-item activity', - ['data-activityname' => $cm->name] - ); - $this->content->items[] = $content; - $this->content->icons[] = ''; - } else { - $cmname = new $cmnameclass($format, $cm->get_section_info(), $cm); - $activitybasis = html_writer::div( - $courserenderer->render($cmname), - 'activity-basis d-flex align-items-center'); - $content = html_writer::div( - $activitybasis . $badges, - 'activity-item activity', - ['data-activityname' => $cm->name] - ); - $this->content->items[] = $content; - } - } - } - return $this->content; - } - - // Slow & hacky editing mode. - $ismoving = ismoving($course->id); + $modinfo = $format->get_modinfo(); $section = $modinfo->get_section_info(0); - if ($ismoving) { - $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'")); - $strcancel= get_string('cancel'); - } else { - $strmove = get_string('move'); - } + $courserenderer = $format->get_renderer($this->page); - if ($ismoving) { - $this->content->icons[] = ' ' . $OUTPUT->pix_icon('t/move', get_string('move')); - $cancelurl = new moodle_url('/course/mod.php', array('cancelcopy' => 'true', 'sesskey' => sesskey())); - $this->content->items[] = $USER->activitycopyname . ' (' . $strcancel . ')'; - } + $output = new block_social_activities\output\blocksection($format, $section); - if (!empty($modinfo->sections[0])) { - foreach ($modinfo->sections[0] as $modnumber) { - $mod = $modinfo->cms[$modnumber]; - if (!$mod->uservisible || !$mod->is_visible_on_course_page()) { - continue; - } - if (!$ismoving) { - - $controlmenu = new $controlmenuclass( - $format, - $mod->get_section_info(), - $mod, - ['disableindentation' => true] - ); - - $menu = $controlmenu->get_action_menu($OUTPUT); - - // Add a move primary action. - $moveaction = html_writer::link( - new moodle_url('/course/mod.php', ['sesskey' => sesskey(), 'copy' => $mod->id]), - $OUTPUT->pix_icon('i/dragdrop', $strmove), - ['class' => 'editing_move_activity'] - ); - - $editbuttons = html_writer::tag('div', - $courserenderer->render($controlmenu), - ['class' => 'buttons activity-actions ms-auto'] - ); - } else { - $editbuttons = ''; - $moveaction = ''; - } - if ($mod->visible || has_capability('moodle/course:viewhiddenactivities', $mod->context)) { - if ($ismoving) { - if ($mod->id == $USER->activitycopy) { - continue; - } - $movingurl = new moodle_url('/course/mod.php', array('moveto' => $mod->id, 'sesskey' => sesskey())); - $this->content->items[] = html_writer::link($movingurl, '', array('title' => $strmovefull, - 'class' => 'movehere')); - $this->content->icons[] = ''; - } - - $badges = ''; - if (!$mod->visible) { - $badges = html_writer::tag( - 'span', - get_string('hiddenfromstudents'), - $badgeattributes - ); - } - - if ($mod->is_stealth()) { - $badges = html_writer::tag( - 'span', - get_string('hiddenoncoursepage'), - $badgeattributes - ); - } - - if (!$mod->url) { - $activitybasis = html_writer::div( - $mod->get_formatted_content(['overflowdiv' => true, 'noclean' => true]) . - $editbuttons, - 'activity-basis d-flex align-items-center'); - $content = html_writer::div( - $moveaction . - $activitybasis . - $badges, - 'contentwithoutlink activity-item activity', - ['data-activityname' => $mod->name] - ); - $this->content->items[] = $content; - $this->content->icons[] = ''; - } else { - $cmname = new $cmnameclass($format, $mod->get_section_info(), $mod); - $activitybasis = html_writer::div( - $courserenderer->render($cmname) . - $editbuttons, - 'activity-basis d-flex align-items-center'); - $content = html_writer::div( - $moveaction . - $activitybasis . - $badges, - 'activity-item activity', - ['data-activityname' => $mod->name] - ); - $this->content->items[] = $content; - } - } - } - } - - if ($ismoving) { - $movingurl = new moodle_url('/course/mod.php', array('movetosection' => $section->id, 'sesskey' => sesskey())); - $this->content->items[] = html_writer::link($movingurl, '', array('title' => $strmovefull, 'class' => 'movehere')); - $this->content->icons[] = ''; - } - - $this->content->footer = $courserenderer->course_section_add_cm_control($course, - 0, null, array('inblock' => true)); + $this->content->text = $courserenderer->render($output); + $this->content->footer = $courserenderer->course_section_add_cm_control( + course: $course, + section: 0, + sectionreturn: null, + displayoptions: ['inblock' => true], + ); return $this->content; } } diff --git a/blocks/social_activities/classes/output/blocksection.php b/blocks/social_activities/classes/output/blocksection.php new file mode 100644 index 00000000000..6b12e8262cb --- /dev/null +++ b/blocks/social_activities/classes/output/blocksection.php @@ -0,0 +1,72 @@ +. + +namespace block_social_activities\output; + +use core_courseformat\base as courseformat; +use renderable; +use section_info; +use templatable; + +/** + * Class blocksection + * + * @package block_social_activities + * @copyright 2024 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class blocksection implements renderable, templatable { + + /** + * The class constructor. + * + * @param courseformat $format the course format instance + * @param section_info $section the section to render + */ + public function __construct( + /** @var courseformat $format the course format instance. */ + protected courseformat $format, + /** @var section_info $section the section to render. */ + protected section_info $section, + ) { + } + + /** + * Export for template. + * + * @param \renderer_base $output + * @return array + */ + public function export_for_template(\renderer_base $output) { + $format = $this->format; + $course = $format->get_course(); + $section = $this->section; + + $sectionoutputclass = $format->get_output_classname('content\\section\\cmlist'); + $sectionoutput = new $sectionoutputclass($format, $section); + + $cmlist = $output->render($sectionoutput); + + return [ + 'siteid' => $course->id, + 'cmlist' => $cmlist, + 'sectionid' => $section->id, + 'sectionname' => $format->get_section_name($section), + 'sectionnum' => $section->sectionnum, + 'editing' => $format->show_editor(), + ]; + } +} diff --git a/blocks/social_activities/styles.css b/blocks/social_activities/styles.css index 1de78703fd1..2e6b8b90f3a 100644 --- a/blocks/social_activities/styles.css +++ b/blocks/social_activities/styles.css @@ -1,15 +1,34 @@ -.block_social_activities li { - clear: both; +/* Imitate mobile grid for activity card. */ +.block_social_activities .activity-item .activity-grid { + grid-template-columns: min-content 1fr min-content min-content min-content; + grid-template-rows: 1fr repeat(4, min-content); + grid-template-areas: + "icon name actions" + "visibility visibility visibility" + "dates dates dates" + "completion completion completion" + "altcontent altcontent altcontent" + "afterlink afterlink afterlink" + "availability availability availability"; } -.block_social_activities li .column { - width: 100%; +.block_social_activities .activity-item .activity-grid.noname-grid { + grid-template-columns: 1fr min-content; + grid-template-areas: + "actions" + "visibility" + "altcontent" + "groupmode" + "afterlink" + "completion" + "availability"; } -.block_social_activities li .buttons a img { - vertical-align: text-bottom; +.block_social_activities .activity-item .activity-grid.noname-grid .activity-actions { + justify-self: end; } -.block_social_activities .instancename { - word-break: break-all; +/* Hide extra edit elements in block space. */ +.block_social_activities .activity-groupmode-info { + display: none; } diff --git a/blocks/social_activities/templates/blocksection.mustache b/blocks/social_activities/templates/blocksection.mustache new file mode 100644 index 00000000000..56b995818b7 --- /dev/null +++ b/blocks/social_activities/templates/blocksection.mustache @@ -0,0 +1,72 @@ +{{! + 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 . +}} +{{! + @template block_social_activities/blocksection + + This mustache emulates a course single section structure inside a block. + + It requires to include several divs to emulate the structure of a course format. + + Example context (json): + { + "cmlist": "Sample", + "siteid": 1, + "sectionid": 1, + "sectionname": "Sample", + "sectionnum": 1, + "editing": true + } +}} +
+ {{! The section list is used by the content module to init the sections.}} +
+ {{! The section need some bottom padding and margin for the dropzone. + Otherwise the activities will cover the area. }} +
+ {{#editing}} + {{! The section header is used as a dropzone when the section is empty.}} +
 
+ {{/editing}} + {{{cmlist}}} +
+
+
+{{#js}} +{{! The block should be fast to load, we only load the editor when needed.}} +{{#editing}} +require(['core_courseformat/local/content'], function(component) { + component.init('block_social_activities_section', {}); +}); +{{/editing}} +{{/js}} diff --git a/blocks/social_activities/tests/behat/behat_block_social_activities.php b/blocks/social_activities/tests/behat/behat_block_social_activities.php index ae026737b26..1e64e6e491f 100644 --- a/blocks/social_activities/tests/behat/behat_block_social_activities.php +++ b/blocks/social_activities/tests/behat/behat_block_social_activities.php @@ -40,129 +40,6 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException, * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_block_social_activities extends behat_base { - - /** - * Returns the DOM node of the activity in the social activities block - * - * @throws ElementNotFoundException Thrown by behat_base::find - * @param string $activityname The activity name - * @return NodeElement - */ - protected function get_social_block_activity_node($activityname) { - $activityname = behat_context_helper::escape($activityname); - $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]"; - - return $this->find('xpath', $xpath); - } - - /** - * Checks that the specified activity in the social activities block should have the specified editing icon. - * - * This includes items in the action menu for the item (does not require it to be open) - * - * You should be in the course page with editing mode turned on. - * - * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should have "(?P(?:[^"]|\\")*)" editing icon$/ - * @param string $activityname - * @param string $iconname - */ - public function activity_in_social_activities_block_should_have_editing_icon($activityname, $iconname) { - $activitynode = $this->get_social_block_activity_node($activityname); - - $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' . - $iconname . '" editing icon', $this->getSession()); - $this->find('named_partial', array('link', $iconname), $notfoundexception, $activitynode); - } - - /** - * Checks that the specified activity in the social activities block should not have the specified editing icon. - * - * This includes items in the action menu for the item (does not require it to be open) - * - * You should be in the course page with editing mode turned on. - * - * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should not have "(?P(?:[^"]|\\")*)" editing icon$/ - * @param string $activityname - * @param string $iconname - */ - public function activity_in_social_activities_block_should_not_have_editing_icon($activityname, $iconname) { - $activitynode = $this->get_social_block_activity_node($activityname); - - try { - $this->find('named_partial', array('link', $iconname), false, $activitynode); - throw new ExpectationException('"' . $activityname . '" has a "' . $iconname . - '" editing icon when it should not', $this->getSession()); - } catch (ElementNotFoundException $e) { - // This is good, the menu item should not be there. - } - } - - /** - * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on. - * - * @Given /^I click on "(?P(?:[^"]|\\")*)" "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" activity in social activities block$/ - * @param string $element - * @param string $selectortype - * @param string $activityname - */ - public function i_click_on_in_the_activity_in_social_activities_block($element, $selectortype, $activityname) { - $element = $this->get_social_block_activity_element($element, $selectortype, $activityname); - $element->click(); - } - - /** - * Finds the element containing a specific activity in the social activity block. - * - * @throws ElementNotFoundException - * @param string $element - * @param string $selectortype - * @param string $activityname - * @return NodeElement - */ - protected function get_social_block_activity_element($element, $selectortype, $activityname) { - $activitynode = $this->get_social_block_activity_node($activityname); - - $exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '{$activityname}'"); - return $this->find($selectortype, $element, $exception, $activitynode); - } - - /** - * Checks that the specified activity is hidden in the social activities block. - * - * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should be hidden$/ - * @param string $activityname - */ - public function activity_in_social_activities_block_should_be_hidden($activityname) { - $activitynode = $this->get_social_block_activity_node($activityname); - $exception = new ExpectationException('"' . $activityname . '" is not hidden', $this->getSession()); - $this->find('named_partial', array('badge', get_string('hiddenfromstudents')), $exception, $activitynode); - } - - /** - * Checks that the specified activity is hidden in the social activities block. - * - * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should be available but hidden from course page$/ - * @param string $activityname - */ - public function activity_in_social_activities_block_should_be_available_but_hidden_from_course_page($activityname) { - $activitynode = $this->get_social_block_activity_node($activityname); - $exception = new ExpectationException('"' . $activityname . '" is not hidden but available', $this->getSession()); - $this->find('named_partial', array('badge', get_string('hiddenoncoursepage')), $exception, $activitynode); - } - - /** - * Opens an activity actions menu in the social activities block if it is not already opened. - * - * @Given /^I open "(?P(?:[^"]|\\")*)" actions menu in social activities block$/ - * @throws DriverException The step is not available when Javascript is disabled - * @param string $activityname - */ - public function i_open_actions_menu_in_social_activities_block($activityname) { - $activityname = behat_context_helper::escape($activityname); - $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]"; - $this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']); - } - /** * Return the list of partial named selectors. * diff --git a/blocks/social_activities/tests/behat/behat_block_social_activities_deprecated.php b/blocks/social_activities/tests/behat/behat_block_social_activities_deprecated.php new file mode 100644 index 00000000000..bb314dfd6fa --- /dev/null +++ b/blocks/social_activities/tests/behat/behat_block_social_activities_deprecated.php @@ -0,0 +1,207 @@ +. + +// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. + +require_once(__DIR__ . '/../../../../lib/behat/behat_deprecated_base.php'); + +use Behat\Mink\Element\NodeElement; +use Behat\Mink\Exception\ExpectationException as ExpectationException; +use Behat\Mink\Exception\DriverException as DriverException; +use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; + +/** + * Behat steps in plugin block_social_activities + * + * @package block_social_activities + * @category test + * @copyright 2024 Ferran Recio + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class behat_block_social_activities_deprecated extends behat_deprecated_base { + /** + * Returns the DOM node of the activity in the social activities block + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $activityname The activity name + * @return NodeElement + */ + protected function get_social_block_activity_node($activityname) { + $activityname = behat_context_helper::escape($activityname); + $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]"; + + return $this->find('xpath', $xpath); + } + + /** + * Finds the element containing a specific activity in the social activity block. + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @throws ElementNotFoundException + * @param string $element + * @param string $selectortype + * @param string $activityname + * @return NodeElement + */ + protected function get_social_block_activity_element($element, $selectortype, $activityname) { + $activitynode = $this->get_social_block_activity_node($activityname); + + $exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '{$activityname}'"); + return $this->find($selectortype, $element, $exception, $activitynode); + } + + /** + * Checks that the specified activity in the social activities block should have the specified editing icon. + * + * This includes items in the action menu for the item (does not require it to be open) + * + * You should be in the course page with editing mode turned on. + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @deprecated since 5.0 + * + * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should have "(?P(?:[^"]|\\")*)" editing icon$/ + * @param string $activityname + * @param string $iconname + */ + public function activity_in_social_activities_block_should_have_editing_icon($activityname, $iconname) { + $this->deprecated_message([ + 'behat_block_social_activities::activity_in_social_activities_block_should_have_editing_icon is deprecated', + 'Use: I should see WHATEVER in the ACTIVITYNAME "activity"', + ]); + + $activitynode = $this->get_social_block_activity_node($activityname); + + $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' . + $iconname . '" editing icon', $this->getSession()); + $this->find('named_partial', ['link', $iconname], $notfoundexception, $activitynode); + } + + /** + * Checks that the specified activity in the social activities block should not have the specified editing icon. + * + * This includes items in the action menu for the item (does not require it to be open) + * + * You should be in the course page with editing mode turned on. + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @deprecated since 5.0 + * + * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should not have "(?P(?:[^"]|\\")*)" editing icon$/ + * @param string $activityname + * @param string $iconname + */ + public function activity_in_social_activities_block_should_not_have_editing_icon($activityname, $iconname) { + $this->deprecated_message([ + 'behat_block_social_activities::activity_in_social_activities_block_should_not_have_editing_icon is deprecated', + 'Use: I should not see WHATEVER in the ACTIVITYNAME "activity"', + ]); + + $activitynode = $this->get_social_block_activity_node($activityname); + + try { + $this->find('named_partial', ['link', $iconname], false, $activitynode); + throw new ExpectationException('"' . $activityname . '" has a "' . $iconname . + '" editing icon when it should not', $this->getSession()); + } catch (ElementNotFoundException $e) { + // This is good, the menu item should not be there. + return; + } + } + + /** + * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on. + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @deprecated since 5.0 + * + * @Given /^I click on "(?P(?:[^"]|\\")*)" "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" activity in social activities block$/ + * @param string $element + * @param string $selectortype + * @param string $activityname + */ + public function i_click_on_in_the_activity_in_social_activities_block($element, $selectortype, $activityname) { + $this->deprecated_message([ + 'behat_block_social_activities::i_click_on_in_the_activity_in_social_activities_block is deprecated', + 'Use: I open ACTIVITYNAME actions menu & I choose OPTIONTEXT in the open action menu', + ]); + + $element = $this->get_social_block_activity_element($element, $selectortype, $activityname); + $element->click(); + } + + /** + * Checks that the specified activity is hidden in the social activities block. + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @deprecated since 5.0 + * + * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should be hidden$/ + * @param string $activityname + */ + public function activity_in_social_activities_block_should_be_hidden($activityname) { + $this->deprecated_message([ + 'behat_block_social_activities::activity_in_social_activities_block_should_be_hidden is deprecated', + 'Use: I should see "Hidden from students" in the "ACTIVITYNAME" "core_courseformat > Activity visibility"', + ]); + + $activitynode = $this->get_social_block_activity_node($activityname); + $exception = new ExpectationException('"' . $activityname . '" is not hidden', $this->getSession()); + $this->find('named_partial', ['badge', get_string('hiddenfromstudents')], $exception, $activitynode); + } + + /** + * Checks that the specified activity is hidden in the social activities block. + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @deprecated since 5.0 + * + * @Then /^"(?P(?:[^"]|\\")*)" activity in social activities block should be available but hidden from course page$/ + * @param string $activityname + */ + public function activity_in_social_activities_block_should_be_available_but_hidden_from_course_page($activityname) { + $this->deprecated_message([ + 'behat_block_social_activities::activity_in_social_activities_block_should_be_available_but_hidden_from_course_page is deprecated', + 'Use: I should see "Available but not shown on course page" in the "ACTIVITYNAME" "core_courseformat > Activity visibility"', + ]); + + $activitynode = $this->get_social_block_activity_node($activityname); + $exception = new ExpectationException('"' . $activityname . '" is not hidden but available', $this->getSession()); + $this->find('named_partial', ['badge', get_string('hiddenoncoursepage')], $exception, $activitynode); + } + + /** + * Opens an activity actions menu in the social activities block if it is not already opened. + * + * @todo MDL-78077 This will be deleted in Moodle 6.0. + * @deprecated since 5.0 + * + * @Given /^I open "(?P(?:[^"]|\\")*)" actions menu in social activities block$/ + * @throws DriverException The step is not available when Javascript is disabled + * @param string $activityname + */ + public function i_open_actions_menu_in_social_activities_block($activityname) { + $this->deprecated_message([ + 'behat_block_social_activities::i_open_actions_menu_in_social_activities_block is deprecated', + 'Use: I open "ACTIVITYNAME" actions menu', + ]); + + $activityname = behat_context_helper::escape($activityname); + $xpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_social_activities ')]//li[contains(., $activityname)]"; + $this->execute('behat_action_menu::i_open_the_action_menu_in', [$xpath, 'xpath_element']); + } +} diff --git a/blocks/social_activities/tests/behat/edit_activities.feature b/blocks/social_activities/tests/behat/edit_activities.feature index 9094e19c24e..9e223a17595 100644 --- a/blocks/social_activities/tests/behat/edit_activities.feature +++ b/blocks/social_activities/tests/behat/edit_activities.feature @@ -5,16 +5,18 @@ Feature: Edit activities in social activities block I need to add and edit activities there Background: - Given the following "courses" exist: - | fullname | shortname | format | - | Course 1 | C1 | social | + Given the following "course" exists: + | fullname | Course 1 | + | shortname | C1 | + | format | social | + | numsections | 0 | And the following "users" exist: | username | firstname | lastname | - | user1 | User | One | + | teacher1 | Teacher | One | | student1 | Student | One | And the following "course enrolments" exist: | user | course | role | - | user1 | C1 | editingteacher | + | teacher1 | C1 | editingteacher | | student1 | C1 | student | @javascript @@ -22,10 +24,10 @@ Feature: Edit activities in social activities block Given the following "activities" exist: | activity | course | name | | forum | C1 | My forum name | - And I log in as "user1" + And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on - When I set the field "Edit title" in the "My forum name" "block_social_activities > Activity" to "New forum name" - Then I should not see "My forum name" in the "Social activities" "block" + When I set the field "Edit title" in the "My forum name" "activity" to "New forum name" + Then I should not see "My forum name" And I should see "New forum name" And I follow "New forum name" And I should not see "My forum name" @@ -38,29 +40,102 @@ Feature: Edit activities in social activities block And the following "blocks" exist: | blockname | contextlevel | reference | pagetypepattern | defaultregion | | recent_activity | Course | C1 | course-view-* | side-pre | - And I log in as "user1" + And the following "activity" exists: + | activity | forum | + | course | C1 | + | name | My forum name | + | section | 0 | + And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on - And I press "Add an activity or resource" - And I click on "Add a new Forum" "link" in the "Add an activity or resource" "dialogue" - And I set the field "Forum name" to "My forum name" - And I press "Save and return to course" - When I open "My forum name" actions menu in social activities block + When I open "My forum name" actions menu And I choose "Availability > Make available but don't show on course page" in the open action menu Then I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" # Make sure that "Availability" dropdown in the edit menu has three options. - And I open "My forum name" actions menu in social activities block - And I click on "Edit settings" "link" in the "My forum name" activity in social activities block + And I open "My forum name" actions menu + And I choose "Edit settings" in the open action menu And I expand all fieldsets And the "Availability" select box should contain "Show on course page" And the "Availability" select box should contain "Hide on course page" And the field "Availability" matches value "Make available but don't show on course page" And I press "Save and return to course" - And "My forum name" activity in social activities block should be available but hidden from course page + Then I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" And I turn editing mode off - And "My forum name" activity in social activities block should be available but hidden from course page + And I should see "Available but not shown on course page" in the "My forum name" "core_courseformat > Activity visibility" And I log out # Student will not see the module on the course page but can access it from other reports and blocks: When I am on the "Course 1" course page logged in as student1 Then I should not see "My forum name" in the "Social activities" "block" And I click on "My forum name" "link" in the "Recent activity" "block" And I should see "My forum name" in the ".breadcrumb" "css_element" + + @javascript + Scenario: The move activity modal allow to move activities in the social activities block + And the following "activities" exist: + | activity | course | section | name | + | forum | C1 | 0 | My forum name | + | forum | C1 | 0 | Other forum name | + | forum | C1 | 0 | Yet another forum name | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I should see "My forum name" in the "Social activities" "block" + And I should see "Other forum name" in the "Social activities" "block" + And I should see "Yet another forum name" in the "Social activities" "block" + And "Other forum name" "activity" should appear after "My forum name" "activity" + And "Yet another forum name" "activity" should appear after "Other forum name" "activity" + When I open "My forum name" actions menu + And I click on "Move" "link" in the "My forum name" activity + And I should see "My forum name" in the "Move activity" "dialogue" + And I should see "Other forum name" in the "Move activity" "dialogue" + And I should see "Yet another forum name" in the "Move activity" "dialogue" + And I should see "Social activities" in the "Move activity" "dialogue" + And I click on "Yet another forum name" "link" in the "Move activity" "dialogue" + Then I should see "My forum name" in the "Social activities" "block" + And I should see "Other forum name" in the "Social activities" "block" + And I should see "Yet another forum name" in the "Social activities" "block" + And "Yet another forum name" "activity" should appear after "Other forum name" "activity" + And "My forum name" "activity" should appear after "Yet another forum name" "activity" + + @javascript + Scenario: Teacher can delete an activity in the social activities block + Given the following "activity" exists: + | activity | forum | + | course | C1 | + | name | My forum name | + | section | 0 | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + 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 "Social activities" "block" + + @javascript + Scenario: Teacher can duplicate an activity in the social activities block + Given the following "activity" exists: + | activity | forum | + | course | C1 | + | name | My forum name | + | section | 0 | + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + When I open "My forum name" actions menu + And I choose "Duplicate" in the open action menu + Then I should see "My forum name" in the "Social activities" "block" + And I should see "My forum name (copy)" in the "Social activities" "block" + And "My forum name (copy)" "activity" should appear after "My forum name" "activity" + + @javascript + Scenario: Teacher can move right and left an activity in the social activities block + Given I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + When I open "Social forum" actions menu + And "Move right" "link" should be visible + And "Move left" "link" should not be visible + And I choose "Move right" in the open action menu + Then I open "Social forum" actions menu + And "Move right" "link" should not be visible + And "Move left" "link" should be visible + And I choose "Move left" in the open action menu + And I open "Social forum" actions menu + And "Move right" "link" should be visible + And "Move left" "link" should not be visible diff --git a/theme/boost/scss/moodle/blocks.scss b/theme/boost/scss/moodle/blocks.scss index 5b5657bb6a1..d17e639b285 100644 --- a/theme/boost/scss/moodle/blocks.scss +++ b/theme/boost/scss/moodle/blocks.scss @@ -333,15 +333,6 @@ $blocks-plus-gutter: $blocks-column-width + ( $grid-gutter-width * 0.5 ); color: $text-muted; } -.block_social_activities li a.movehere, -.block_site_main_menu li a.movehere { - display: block; - width: 100%; - height: 2rem; - border: 2px dashed $gray-800; - margin: 4px 0; -} - // // Fake blocks // diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 83e6431bdbe..f1dd9646975 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -27353,15 +27353,6 @@ aside[id^=block-region-side-] .block_recentlyaccesseditems .card:nth-of-type(n+4 color: #6a737b; } -.block_social_activities li a.movehere, -.block_site_main_menu li a.movehere { - display: block; - width: 100%; - height: 2rem; - border: 2px dashed #343a40; - margin: 4px 0; -} - .pagelayout-embedded .has-fake-blocks { padding: 1rem; display: flex; diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index d2453bfdd0a..584c889a74d 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -27353,15 +27353,6 @@ aside[id^=block-region-side-] .block_recentlyaccesseditems .card:nth-of-type(n+4 color: #6a737b; } -.block_social_activities li a.movehere, -.block_site_main_menu li a.movehere { - display: block; - width: 100%; - height: 2rem; - border: 2px dashed #343a40; - margin: 4px 0; -} - .pagelayout-embedded .has-fake-blocks { padding: 1rem; display: flex; From 694edcf76bcfb6ae092b76340eff4b49a788f656 Mon Sep 17 00:00:00 2001 From: ferran Date: Mon, 28 Oct 2024 11:27:31 +0100 Subject: [PATCH 5/5] MDL-82351 core_courseformat: fix social format phpunits --- course/format/tests/stateactions_test.php | 44 +++++++++++++++++++---- course/tests/targets_test.php | 2 +- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/course/format/tests/stateactions_test.php b/course/format/tests/stateactions_test.php index 4c73fa076d4..7e4badc8c67 100644 --- a/course/format/tests/stateactions_test.php +++ b/course/format/tests/stateactions_test.php @@ -217,6 +217,15 @@ final class stateactions_test extends \advanced_testcase { // Create and enrol user using given role. $this->set_test_user_by_role($course, $role); + // Some formats, like social, can create some initial activity. + $modninfo = course_modinfo::instance($course); + $cms = $modninfo->get_cms(); + $count = 0; + foreach ($cms as $cm) { + $references["initialcm{$count}"] = $cm->id; + $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); @@ -271,6 +280,7 @@ final class stateactions_test extends \advanced_testcase { static::course_state_provider('weeks'), static::course_state_provider('topics'), static::course_state_provider('social'), + static::course_state_provider('singleactivity'), static::section_state_provider('weeks', 'admin'), static::section_state_provider('weeks', 'editingteacher'), static::section_state_provider('weeks', 'student'), @@ -280,6 +290,9 @@ final class stateactions_test extends \advanced_testcase { static::section_state_provider('social', 'admin'), static::section_state_provider('social', 'editingteacher'), static::section_state_provider('social', 'student'), + static::section_state_provider('singleactivity', 'admin'), + static::section_state_provider('singleactivity', 'editingteacher'), + static::section_state_provider('singleactivity', 'student'), static::cm_state_provider('weeks', 'admin'), static::cm_state_provider('weeks', 'editingteacher'), static::cm_state_provider('weeks', 'student'), @@ -289,6 +302,9 @@ final class stateactions_test extends \advanced_testcase { static::cm_state_provider('social', 'admin'), static::cm_state_provider('social', 'editingteacher'), static::cm_state_provider('social', 'student'), + static::cm_state_provider('singleactivity', 'admin'), + static::cm_state_provider('singleactivity', 'editingteacher'), + static::cm_state_provider('singleactivity', 'student'), ); } @@ -299,7 +315,15 @@ final class stateactions_test extends \advanced_testcase { * @return array the testing scenarios */ public static function course_state_provider(string $format): array { - $expectedexception = ($format === 'social'); + $expectedexception = ($format === 'singleactivity'); + + $cms = ['cm0', 'cm1', 'cm2', 'cm3']; + $studentcms = ['cm0']; + if ($format === 'social') { + $cms = ['initialcm0', 'cm0', 'cm1', 'cm2', 'cm3']; + $studentcms = ['initialcm0', 'cm0']; + } + return [ // Tests for course_state. "admin $format course_state" => [ @@ -312,7 +336,7 @@ final class stateactions_test extends \advanced_testcase { 'expectedresults' => [ 'course' => ['course'], 'section' => ['section0', 'section1', 'section2', 'section3'], - 'cm' => ['cm0', 'cm1', 'cm2', 'cm3'], + 'cm' => $cms, ], 'expectedexception' => $expectedexception, ], @@ -326,7 +350,7 @@ final class stateactions_test extends \advanced_testcase { 'expectedresults' => [ 'course' => ['course'], 'section' => ['section0', 'section1', 'section2', 'section3'], - 'cm' => ['cm0', 'cm1', 'cm2', 'cm3'], + 'cm' => $cms, ], 'expectedexception' => $expectedexception, ], @@ -340,7 +364,7 @@ final class stateactions_test extends \advanced_testcase { 'expectedresults' => [ 'course' => ['course'], 'section' => ['section0', 'section1', 'section3'], - 'cm' => ['cm0'], + 'cm' => $studentcms, ], 'expectedexception' => $expectedexception, ], @@ -357,7 +381,7 @@ final class stateactions_test extends \advanced_testcase { 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 === 'social'); + $expectedexception = ($format === 'singleactivity'); // All sections and cms that the user can access to. $usersections = ['section0', 'section1', 'section2', 'section3']; @@ -366,6 +390,9 @@ final class stateactions_test extends \advanced_testcase { $usersections = ['section0', 'section1', 'section3']; $usercms = ['cm0']; } + if ($format === 'social') { + $usercms = ['initialcm0', ...$usercms]; + } return [ "$role $format section_state no section" => [ @@ -388,7 +415,7 @@ final class stateactions_test extends \advanced_testcase { 'expectedresults' => [ 'course' => [], 'section' => array_intersect(['section0'], $usersections), - 'cm' => [], + 'cm' => ($format == 'social') ? ['initialcm0'] : [], ], 'expectedexception' => $expectedexception, ], @@ -490,6 +517,9 @@ final class stateactions_test extends \advanced_testcase { $usersections = ['section0', 'section1', 'section3']; $usercms = ['cm0']; } + if ($format === 'social') { + $usercms = ['initialcm0', ...$usercms]; + } return [ "$role $format cm_state no cms" => [ @@ -556,7 +586,7 @@ final class stateactions_test extends \advanced_testcase { 'section' => array_intersect(['section1', 'section2'], $usersections), 'cm' => array_intersect(['cm0'], $usercms), ], - 'expectedexception' => ($format === 'social'), + 'expectedexception' => ($format === 'singleactivity'), ], "$role $format cm_state using targetcm" => [ 'format' => $format, diff --git a/course/tests/targets_test.php b/course/tests/targets_test.php index 2363a83696a..a1239e35645 100644 --- a/course/tests/targets_test.php +++ b/course/tests/targets_test.php @@ -65,7 +65,7 @@ class targets_test extends \advanced_testcase { 'coursenosections' => [ 'params' => [ 'enablecompletion' => 1, - 'format' => 'social', + 'format' => 'singleactivity', 'students' => true ], 'isvalid' => get_string('nocoursesections', 'course')