MDL-78282 core_courseformat: interactive visibility badges

This commit is contained in:
Ferran Recio 2023-06-15 18:00:21 +02:00
parent b2b1915e25
commit 01e0e56665
11 changed files with 465 additions and 24 deletions

View File

@ -67,6 +67,8 @@ class cm implements named_templatable, renderable {
/** @var string the activity completion class name */
protected $completionclass;
/** @var string the activity visibility class name */
protected $visibilityclass;
/**
* Constructor.
@ -90,6 +92,7 @@ class cm implements named_templatable, renderable {
$this->controlmenuclass = $format->get_output_classname('content\\cm\\controlmenu');
$this->availabilityclass = $format->get_output_classname('content\\cm\\availability');
$this->completionclass = $format->get_output_classname('content\\cm\\completion');
$this->visibilityclass = $format->get_output_classname('content\\cm\\visibility');
}
/**
@ -120,13 +123,13 @@ class cm implements named_templatable, renderable {
$haspartials['completion'] = $this->add_completion_data($data, $output);
$haspartials['editor'] = $this->add_editor_data($data, $output);
$haspartials['groupmode'] = $this->add_groupmode_data($data, $output);
$haspartials['visibility'] = $this->add_visibility_data($data, $output);
$this->add_format_data($data, $haspartials, $output);
// Calculated fields.
if (!empty($data->url)) {
$data->hasurl = true;
}
return $data;
}
@ -322,6 +325,23 @@ class cm implements named_templatable, renderable {
return true;
}
/**
* Add visibility information to the data structure.
*
* @param stdClass $data the current cm data reference
* @param renderer_base $output typically, the renderer that's calling this function
* @return bool if the cm has visibility data
*/
protected function add_visibility_data(stdClass &$data, renderer_base $output): bool {
$visibility = new $this->visibilityclass($this->format, $this->section, $this->mod);
$templatedata = $visibility->export_for_template($output);
if ($templatedata) {
$data->visibility = $templatedata;
return true;
}
return false;
}
/**
* Returns the CSS classes for the activity name/content
*

View File

@ -0,0 +1,235 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the default activity availability information.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content\cm;
use cm_info;
use core_courseformat\base as course_format;
use core_courseformat\output\local\courseformat_named_templatable;
use core\output\choicelist;
use core\output\local\dropdown\status;
use core\output\named_templatable;
use pix_icon;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render a course module availability inside a course format.
*
* @package core_courseformat
* @copyright 2020 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class visibility implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
protected $section;
/** @var cm_info the course module instance */
protected $mod;
/**
* Constructor.
* @param course_format $format the course format
* @param section_info $section the section info
* @param cm_info $mod the course module ionfo
*/
public function __construct(course_format $format, section_info $section, cm_info $mod) {
$this->format = $format;
$this->section = $section;
$this->mod = $mod;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
if (!$this->show_visibility()) {
return null;
}
$format = $this->format;
// In rare legacy cases, the section could be stealth (orphaned) but they are not editable.
if (!$format->show_editor()) {
return $this->build_static_data($output);
} else {
return $this->build_editor_data($output);
}
}
/**
* Check if the visibility is displayed.
* @return bool
*/
protected function show_visibility(): bool {
return !$this->mod->visible || $this->mod->is_stealth();
}
/**
* Get the icon for the section visibility.
* @param string $selected the visibility selected value
* @return pix_icon
*/
protected function get_icon(string $selected): pix_icon {
if ($selected === 'hide') {
return new pix_icon('t/show', '');
} else if ($selected === 'stealth') {
return new pix_icon('t/stealth', '');
} else {
return new pix_icon('t/hide', '');
}
}
/**
* Build the data for the editor.
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function build_editor_data(\renderer_base $output): ?stdClass {
$choice = $this->get_choice_list();
return $this->get_dropdown_data($output, $choice);
}
/**
* Build the data for the interactive dropdown.
* @param \renderer_base $output
* @param choicelist $choice the choice list
* @return stdClass
*/
protected function get_dropdown_data(
\renderer_base $output,
choicelist $choice,
): stdClass {
$selected = $choice->get_selected_value();
$icon = $this->get_icon($selected);
$badgetext = $output->sr_text(get_string('availability'));
if ($selected === 'hide') {
$badgetext .= get_string('hiddenfromstudents');
} else if ($selected === 'stealth') {
$badgetext .= get_string('hiddenoncoursepage');
} else {
$badgetext .= get_string("availability_{$selected}", 'core_courseformat');
}
$dropdown = new status(
$output->render($icon) . ' ' . $badgetext,
$choice,
['dialogwidth' => status::WIDTH['big']],
);
return (object) [
'isInteractive' => true,
'dropwdown' => $dropdown->export_for_template($output),
];
}
/**
* Get the availability choice list.
* @return choicelist
*/
protected function get_choice_list(): choicelist {
$choice = $this->create_choice_list();
if (!$this->mod->visible) {
$selected = 'hide';
} else if ($this->mod->is_stealth()) {
$selected = 'stealth';
} else {
$selected = 'show';
}
$choice->set_selected_value($selected);
return $choice;
}
/**
* Create a choice list for the dropdown.
* @return choicelist the choice list
*/
protected function create_choice_list(): choicelist {
global $CFG;
$choice = new choicelist();
$choice->add_option(
'show',
get_string('availability_show', 'core_courseformat'),
$this->get_option_data('show', 'cmShow')
);
$choice->add_option(
'hide',
get_string('availability_hide', 'core_courseformat'),
$this->get_option_data('hide', 'cmHide')
);
if ($CFG->allowstealth && $this->format->allow_stealth_module_visibility($this->mod, $this->section)) {
$choice->add_option(
'stealth',
get_string('availability_stealth', 'core_courseformat'),
$this->get_option_data('stealth', 'cmStealth')
);
}
return $choice;
}
/**
* Get the data for the option.
* @param string $name the name of the option
* @param string $action the state action of the option
* @return array
*/
private function get_option_data(string $name, string $action): array {
return [
'description' => get_string("availability_{$name}_help", 'core_courseformat'),
'icon' => $this->get_icon($name),
// Non-ajax behat is not smart enough to discrimante hidden links
// so we need to keep providing the non-ajax links.
'url' => $this->format->get_non_ajax_cm_action_url($action, $this->mod),
'extras' => [
'data-id' => $this->mod->id,
'data-action' => $action,
]
];
}
/**
* Build the static badges data.
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function build_static_data(\renderer_base $output): ?stdClass {
$data = (object) [
'isInteractive' => false,
];
if (!$this->mod->visible) {
$data->modhiddenfromstudents = true;
} else if ($this->mod->is_stealth()) {
$data->modstealth = true;
}
return $data;
}
}

View File

@ -69,7 +69,10 @@
}
]
},
"modstealth": true
"visibility": {
"isInteractive": false,
"modhiddenfromstudents": true
}
}
}}
{{#moveicon}} {{{moveicon}}} {{/moveicon}}

View File

@ -17,23 +17,19 @@
{{!
@template core_courseformat/local/content/cm/badges
Convenience mustache to displays if an activity is in hidden or stealth mode.
Format plugins are free to implement an alternative inplace editable for activities.
Formats can override this template to display extra badges.
Example context (json):
{
"modhiddenfromstudents" : "1",
"modstealth" : "1"
"visibility": {
"isInteractive": false,
"modhiddenfromstudents" : "1",
"modstealth" : "1"
}
}
}}
{{#modhiddenfromstudents}}
<div class="activity-badges my-1 d-flex align-self-start align-items-center">
<span class="badge badge-pill badge-secondary">{{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}</span>
</div>
{{/modhiddenfromstudents}}
{{#modstealth}}
<div class="activity-badges my-1 d-flex align-self-start align-items-center">
<span class="badge badge-pill badge-secondary">{{#pix}}t/stealth, core{{/pix}}{{#str}}hiddenoncoursepage{{/str}}</span>
</div>
{{/modstealth}}
{{#visibility}}
{{$ core_courseformat/local/content/cm/visibility }}
{{> core_courseformat/local/content/cm/visibility }}
{{/ core_courseformat/local/content/cm/visibility }}
{{/visibility}}

View File

@ -0,0 +1,86 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_courseformat/local/content/cm/visibility
Template to displays if an activity is in hidden or stealth mode.
Example context (json):
{
"modhiddenfromstudents" : "1",
"modstealth" : "1",
"isInteractive": "1",
"dropwdown": {
"buttonid": "dropwdownbutton_648b1549b929e30",
"buttoncontent": "Show on course page",
"choices": {
"description": null,
"options": [
{
"value": "show",
"name": "Show on course page",
"description": "Available to students.",
"url": null,
"icon": null,
"disabled": false,
"hasicon": false,
"hasurl": false,
"selected": true,
"optionnumber": 1,
"first": true,
"optionuniqid": "choice_option_648b1549b929e31"
},
{
"value": "hide",
"name": "Hide on course page",
"description": "Not available to students.",
"url": null,
"icon": null,
"disabled": false,
"hasicon": false,
"hasurl": false,
"optionnumber": 2,
"first": false,
"optionuniqid": "choice_option_648b1549b929e32"
}
],
"hasoptions": true
}
}
}
}}
{{#isInteractive}}
<div class="activity-badges my-1">
{{#dropwdown}}
{{< core/local/dropdown/status}}
{{$ buttonclasses }} badge badge-pill badge-secondary dropdown-toggle border-0 {{/ buttonclasses }}
{{/ core/local/dropdown/status}}
{{/dropwdown}}
</div>
{{/isInteractive}}
{{^isInteractive}}
{{#modhiddenfromstudents}}
<div class="activity-badges my-1 d-flex align-self-start align-items-center">
<span class="badge badge-pill badge-secondary">{{#pix}}i/show, core{{/pix}}{{#str}}hiddenfromstudents{{/str}}</span>
</div>
{{/modhiddenfromstudents}}
{{#modstealth}}
<div class="activity-badges my-1 d-flex align-self-start align-items-center">
<span class="badge badge-pill badge-secondary">{{#pix}}t/stealth, core{{/pix}}{{#str}}hiddenoncoursepage{{/str}}</span>
</div>
{{/modstealth}}
{{/isInteractive}}

View File

@ -0,0 +1,76 @@
@core @core_courseformat
Feature: Verify activity visibility interface.
In order to edit the course activity visibility
As a teacher
I need to be able to see the updateds visibility information
Background:
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | visible |
| assign | Activity sample 1 | Test assignment description | C1 | sample1 | 1 | 1 |
| assign | Activity sample 2 | Test assignment description | C1 | sample2 | 1 | 0 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
Given I am on the "C1" "Course" page logged in as "teacher1"
And I turn editing mode on
@javascript
Scenario: Teacher can hide an activity using the actions menu.
Given I should not see "Hidden from students" in the "Activity sample 1" "activity"
When I open "Activity sample 1" actions menu
And I click on "Hide" "link" in the "Activity sample 1" "activity"
Then I should see "Hidden from students" in the "Activity sample 1" "activity"
@javascript
Scenario: Teacher can show an activity using the actions menu.
Given I should see "Hidden from students" in the "Activity sample 2" "activity"
When I open "Activity sample 2" actions menu
And I click on "Show activity" "link" in the "Activity sample 2" "activity"
Then I should not see "Hidden from students" in the "Activity sample 2" "activity"
@javascript
Scenario: Teacher can make available but not shown an activity using the actions menu.
Given the following config values are set as admin:
| allowstealth | 1 |
And I reload the page
And I should see "Hidden from students" in the "Activity sample 2" "activity"
When I open "Activity sample 2" actions menu
And I click on "Make activity available" "link" in the "Activity sample 2" "activity"
Then I should not see "Hidden from students" in the "Activity sample 2" "activity"
And I should see "Available but not shown on course page" in the "Activity sample 2" "activity"
@javascript
Scenario: Teacher can show an activity using the visibility badge.
Given I should see "Hidden from students" in the "Activity sample 2" "activity"
When I click on "Hidden from students" "button" in the "Activity sample 2" "activity"
And I click on "Show on course page" "link" in the "Activity sample 2" "activity"
Then I should not see "Hidden from students" in the "Activity sample 2" "activity"
@javascript
Scenario: Teacher can make available but not shown an activity using the visibility badge.
Given the following config values are set as admin:
| allowstealth | 1 |
And I reload the page
When I click on "Hidden from students" "button" in the "Activity sample 2" "activity"
And I click on "Make available but don't show on course page" "link" in the "Activity sample 2" "activity"
Then I should not see "Hidden from students" in the "Activity sample 2" "activity"
And I should see "Available but not shown on course page" in the "Activity sample 2" "activity"
@javascript
Scenario: Make available but not shown is available only when stealth activities are enabled.
Given I click on "Hidden from students" "button" in the "Activity sample 2" "activity"
And I should not see "Make available but don't show on course page" in the "Activity sample 2" "activity"
When the following config values are set as admin:
| allowstealth | 1 |
And I reload the page
And I click on "Hidden from students" "button" in the "Activity sample 2" "activity"
Then I should see "Make available but don't show on course page" in the "Activity sample 2" "activity"

View File

@ -1657,8 +1657,13 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
$hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
if (!isset($str)) {
$str = get_strings(array('delete', 'move', 'moveright', 'moveleft',
'editsettings', 'duplicate', 'modhide', 'makeavailable', 'makeunavailable', 'modshow'), 'moodle');
$str = get_strings(
[
'delete', 'move', 'moveright', 'moveleft', 'editsettings', 'duplicate', 'modhide',
'makeavailable', 'makeunavailable', 'modshow', 'modshowcmtitle', 'makeavailablecmtitle',
],
'moodle'
);
$str->assign = get_string('assignroles', 'role');
$str->groupsnone = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
$str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
@ -1785,6 +1790,8 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
'class' => 'editing_show',
'data-action' => ($usecomponents) ? 'cmShow' : 'show',
'data-id' => $mod->id,
// Title is needed mostly for behat tests. Otherwise it will follow any link with "show".
'title' => $str->modshowcmtitle,
]
);
}
@ -1819,6 +1826,8 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
'data-action' => $action,
'data-sectionreturn' => $sr,
'data-id' => $mod->id,
// Title is needed mostly for behat tests. Otherwise it will follow any link with "make available".
'title' => $str->makeavailablecmtitle,
]
);
}

View File

@ -44,7 +44,7 @@ Feature: Toggle activities visibility from the course page
# Stealth behaviour is not available by default:
And "Test forum name" actions menu should not have "Make available" item
And "Test forum name" actions menu should not have "Make unavailable" item
And I click on "Show" "link" in the "Test forum name" activity
And I click on "Show activity" "link" in the "Test forum name" activity
And "Test forum name" activity should be visible
And I open "Test forum name" actions menu
And "Test forum name" actions menu should not have "Show" item
@ -130,7 +130,7 @@ Feature: Toggle activities visibility from the course page
And "Test assignment name" actions menu should have "Show" item
And "Test assignment name" actions menu should not have "Hide" item
And "Test assignment name" actions menu should not have "Make unavailable" item
And I click on "Make available" "link" in the "Test assignment name" activity
And I click on "Make activity available" "link" in the "Test assignment name" activity
And "Test assignment name" activity should be available but hidden from course page
# Make sure that "Availability" dropdown in the edit menu has three options.
And I open "Test assignment name" actions menu

View File

@ -952,7 +952,7 @@ class behat_course extends behat_base {
* @param string $menuitem
*/
public function actions_menu_should_have_item($activityname, $menuitem) {
$activitynode = $this->get_activity_node($activityname);
$activitynode = $this->get_activity_action_menu_node($activityname);
$notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' .
$menuitem . '" item', $this->getSession());
@ -968,7 +968,7 @@ class behat_course extends behat_base {
* @param string $menuitem
*/
public function actions_menu_should_not_have_item($activityname, $menuitem) {
$activitynode = $this->get_activity_node($activityname);
$activitynode = $this->get_activity_action_menu_node($activityname);
try {
$this->find('named_partial', array('link', $menuitem), false, $activitynode);
@ -979,6 +979,20 @@ class behat_course extends behat_base {
}
}
/**
* Returns the DOM node of the activity action menu.
*
* @throws ElementNotFoundException Thrown by behat_base::find
* @param string $activityname The activity name
* @return \Behat\Mink\Element\NodeElement
*/
protected function get_activity_action_menu_node($activityname) {
$activityname = behat_context_helper::escape($activityname);
$xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]" .
"//div[contains(@class, 'action-menu')]";
return $this->find('xpath', $xpath);
}
/**
* Indents to the right the activity or resource specified by it's name. Editing mode should be on.
*

View File

@ -1244,6 +1244,7 @@ $string['mailteachers'] = 'Inform teachers';
$string['maincoursepage'] = 'Main course page';
$string['makeafolder'] = 'Create folder';
$string['makeavailable'] = 'Make available';
$string['makeavailablecmtitle'] = 'Make activity available';
$string['makeeditable'] = 'If you make \'{$a}\' editable by the web server process (eg apache) then you could edit this file directly from this page';
$string['makethismyhome'] = 'Make this my home page';
$string['makeunavailable'] = 'Make unavailable';
@ -1345,6 +1346,7 @@ $string['missingusername'] = 'Missing username';
$string['moddoesnotsupporttype'] = 'Module {$a->modname} does not support uploads of type {$a->type}';
$string['modhide'] = 'Hide';
$string['modshow'] = 'Show';
$string['modshowcmtitle'] = 'Show activity';
$string['modvisible'] = 'Availability';
$string['modvisible_help'] = '* Show on course page: Available to students (subject to any access restrictions which may be set).
* Hide on course page: Not available to students.';

View File

@ -153,7 +153,7 @@ XPATH
]
XPATH
, 'badge' => <<<XPATH
.//span[(contains(@class, 'badge')) and text()[contains(., %locator%)]]
.//*[self::span or self::button][(contains(@class, 'badge')) and text()[contains(., %locator%)]]
XPATH
, 'block' => <<<XPATH
.//*[@data-block][contains(concat(' ', normalize-space(@class), ' '), concat(' ', %locator%, ' ')) or