MDL-78283 core_courseformat: group mode dropdown

This commit is contained in:
Ferran Recio 2023-06-28 16:28:39 +02:00
parent b121e998a9
commit 6e1fff1a57
11 changed files with 326 additions and 40 deletions

View File

@ -1431,6 +1431,21 @@ abstract class base {
return $PAGE->user_is_editing() && has_all_capabilities($capabilities, $coursecontext);
}
/**
* Check if the group mode can be displayed.
* @param cm_info $cm the activity module
* @return bool
*/
public function show_groupmode(cm_info $cm): bool {
if (!plugin_supports('mod', $cm->modname, FEATURE_GROUPS, false)) {
return false;
}
if (!has_capability('moodle/course:manageactivities', $cm->context)) {
return false;
}
return true;
}
/**
* Allows to specify for modinfo that section is not available even when it is visible and conditionally available.
*

View File

@ -69,6 +69,9 @@ class cm implements named_templatable, renderable {
/** @var string the activity availability class name */
protected $availabilityclass;
/** @var string the activity groupmode badge class name */
protected $groupmodeclass;
/**
* Constructor.
*
@ -90,6 +93,7 @@ class cm implements named_templatable, renderable {
$this->cmnameclass = $format->get_output_classname('content\\cm\\cmname');
$this->controlmenuclass = $format->get_output_classname('content\\cm\\controlmenu');
$this->availabilityclass = $format->get_output_classname('content\\cm\\availability');
$this->groupmodeclass = $format->get_output_classname('content\\cm\\groupmode');
}
/**
@ -314,32 +318,9 @@ class cm implements named_templatable, renderable {
* @return bool the module has group mode information
*/
protected function add_groupmode_data(stdClass &$data, renderer_base $output): bool {
if (!plugin_supports('mod', $this->mod->modname, FEATURE_GROUPS, false)) {
return false;
}
if (!has_capability('moodle/course:manageactivities', $this->mod->context)) {
return false;
}
switch ($this->mod->effectivegroupmode) {
case SEPARATEGROUPS:
$groupicon = 'i/groups';
$groupalt = get_string('groupsseparate', 'group');
break;
case VISIBLEGROUPS:
$groupicon = 'i/groupv';
$groupalt = get_string('groupsvisible', 'group');
break;
default:
return false;
}
$data->groupmodeinfo = (object) [
'groupicon' => $groupicon,
'groupalt' => $groupalt,
];
return true;
$groupmode = new $this->groupmodeclass($this->format, $this->section, $this->mod);
$data->groupmodeinfo = $groupmode->export_for_template($output);
return !empty($data->groupmodeinfo);
}
/**

View File

@ -0,0 +1,209 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace core_courseformat\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\named_templatable;
use core\output\choicelist;
use core\output\local\dropdown\status;
use pix_icon;
use renderable;
use section_info;
use stdClass;
/**
* Base class to render an activity group mode badge.
*
* @package core_courseformat
* @copyright 2023 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class groupmode implements named_templatable, renderable {
use courseformat_named_templatable;
/** @var course_format the course format */
protected $format;
/** @var section_info the section object */
private $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->format->show_groupmode($this->mod)) {
return null;
}
if ($this->format->show_editor()) {
return $this->build_editor_data($output);
}
// If the group mode is not editable, the no groups badge is not displayed.
if ($this->mod->effectivegroupmode === NOGROUPS) {
return null;
}
return $this->build_static_data($output);
}
/**
* Build the data for the static badge.
* @param \renderer_base $output
* @return stdClass
*/
protected function build_static_data(\renderer_base $output): stdClass {
switch ($this->mod->effectivegroupmode) {
case SEPARATEGROUPS:
$groupalt = get_string('groupsseparate', 'group');
$groupicon = $this->get_action_icon('cmSeparateGroups', $groupalt);
break;
case VISIBLEGROUPS:
$groupalt = get_string('groupsvisible', 'group');
$groupicon = $this->get_action_icon('cmVisibleGroups', $groupalt);
break;
case NOGROUPS:
default:
$groupalt = get_string('groupsnone', 'group');
$groupicon = $this->get_action_icon('cmNoGroups', $groupalt);
break;
}
$data = (object) [
'groupicon' => $output->render($groupicon),
'groupalt' => $groupalt,
'isInteractive' => false,
];
return $data;
}
/**
* Build the data for the interactive dropdown.
* @param \renderer_base $output
* @return stdClass
*/
protected function build_editor_data(\renderer_base $output): stdClass {
$choice = $this->get_choice_list();
$result = $this->get_dropdown_data($output, $choice);
$result->autohide = ($this->mod->effectivegroupmode === NOGROUPS);
return $result;
}
/**
* 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 {
$buttondata = $this->build_static_data($output);
$dropdown = new status(
$buttondata->groupicon,
$choice,
['dialogwidth' => status::WIDTH['big']],
);
$dropdown->set_dialog_width(status::WIDTH['small']);
$dropdown->set_position(status::POSITION['end']);
return (object) [
'isInteractive' => true,
'groupicon' => $buttondata->groupicon,
'groupalt' => $buttondata->groupalt,
'dropwdown' => $dropdown->export_for_template($output),
];
}
/**
* Create a choice list for the dropdown.
* @return choicelist the choice list
*/
protected function get_choice_list(): choicelist {
$choice = new choicelist();
$choice->add_option(
NOGROUPS,
get_string('groupsnone', 'group'),
$this->get_option_data(null, 'cmNoGroups', $this->mod->id)
);
$choice->add_option(
SEPARATEGROUPS,
get_string('groupsseparate', 'group'),
$this->get_option_data('groupsseparate', 'cmSeparateGroups', $this->mod->id)
);
$choice->add_option(
VISIBLEGROUPS,
get_string('groupsvisible', 'group'),
$this->get_option_data('groupsvisible', 'cmVisibleGroups', $this->mod->id)
);
$choice->set_selected_value($this->mod->effectivegroupmode);
return $choice;
}
/**
* Get the data for the option.
* @param string|null $name the name of the option
* @param string $action the state action of the option
* @param int $id the id of the module
* @return array
*/
private function get_option_data(?string $name, string $action, int $id): array {
return [
'description' => ($name) ? get_string("groupmode_{$name}_help", 'group') : null,
// The dropdown icons are decorative, so we don't need to provide alt text.
'icon' => $this->get_action_icon($action),
'extras' => [
'data-id' => $id,
'data-action' => $action,
]
];
}
/**
* Get the group mode icon.
* @param string $groupmode the group mode
* @param string $groupalt the alt text
* @return pix_icon
*/
protected function get_action_icon(string $groupmode, string $groupalt = ''): pix_icon {
$icons = [
'cmNoGroups' => 'i/groupn',
'cmSeparateGroups' => 'i/groups',
'cmVisibleGroups' => 'i/groupv',
];
return new pix_icon($icons[$groupmode], $groupalt);
}
}

View File

@ -62,7 +62,7 @@
{{> core_course/activitychooserbuttonactivity}}
</div>
{{/editing}}
<div class="activity-item {{#modstealth}}hiddenactivity{{/modstealth}}{{!
<div class="activity-item focus-control {{#modstealth}}hiddenactivity{{/modstealth}}{{!
}}{{#modhiddenfromstudents}}hiddenactivity{{/modhiddenfromstudents}}{{!
}}{{#modinline}}activityinline{{/modinline}}" data-activityname="{{activityname}}">
{{$ core_courseformat/local/content/cm/bulkselect }}

View File

@ -105,9 +105,9 @@
{{! Group mode }}
{{#groupmodeinfo}}
<div class="activity-groupmode-info align-self-start mr-sm-2">
{{$ core_courseformat/local/content/cm/groupmode_info}}
{{> core_courseformat/local/content/cm/groupmode_info}}
{{/ core_courseformat/local/content/cm/groupmode_info}}
{{$ core_courseformat/local/content/cm/groupmode}}
{{> core_courseformat/local/content/cm/groupmode}}
{{/ core_courseformat/local/content/cm/groupmode}}
</div>
{{/groupmodeinfo}}

View File

@ -26,14 +26,29 @@
"groupalt": "Visible groups"
}
}}
<div data-region="groupmode-information"
data-activityname="{{activityname}}"
class="groupmode-information d-flex align-items-center"
{{#isInteractive}}
{{#dropwdown}}
{{< core/local/dropdown/status}}
{{$ buttonclasses }}
{{#autohide}} v-parent-focus {{/autohide}}
groupmode-information btn pt-1 icon-no-margin
{{/ buttonclasses }}
{{$ buttoncontent }}
{{{groupicon}}}
<span class="groupmode-icon-info">{{groupalt}}</span>
{{/ buttoncontent }}
{{/ core/local/dropdown/status}}
{{/dropwdown}}
{{/isInteractive}}
{{^isInteractive}}
<div
data-region="groupmode-information"
data-activityname="{{activityname}}"
class="groupmode-information d-flex align-items-center"
>
{{#groupicon}}
{{#pix}}{{groupicon}}, core, {{groupalt}}{{/pix}}
{{/groupicon}}
{{#groupicon}}{{{groupicon}}}{{/groupicon}}
{{#groupalt}}
<div class="groupmode-icon-info">{{groupalt}}</div>
{{/groupalt}}
</div>
{{/isInteractive}}

View File

@ -0,0 +1,65 @@
@core @core_courseformat
Feature: Verify activity group mode interface.
In order to edit the course activity group mode
As a teacher
I need to be able edit the group mode form the page course
Background:
Given the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| numsections | 3 |
And the following "groups" exist:
| name | course | idnumber |
| G1 | C1 | GI1 |
And the following "activities" exist:
| activity | name | intro | course | idnumber | section | groupmode |
| forum | Activity sample 1 | Test forum description | C1 | sample1 | 1 | 0 |
| forum | Activity sample 2 | Test forum description | C1 | sample2 | 1 | 1 |
| forum | Activity sample 3 | Test forum description | C1 | sample3 | 1 | 2 |
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
Given I am on the "C1" "Course" page logged in as "teacher1"
And I turn editing mode on
@javascript
Scenario: Teacher can see the group mode badges in both edit and no edit mode
Given "Visible groups" "icon" should not exist in the "Activity sample 1" "activity"
And "Separate groups" "icon" should not exist in the "Activity sample 1" "activity"
And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity"
And "Separate groups" "icon" should exist in the "Activity sample 2" "activity"
And "Visible groups" "icon" should exist in the "Activity sample 3" "activity"
And "Separate groups" "icon" should not exist in the "Activity sample 3" "activity"
When I turn editing mode off
Then "Visible groups" "icon" should not exist in the "Activity sample 1" "activity"
And "Separate groups" "icon" should not exist in the "Activity sample 1" "activity"
And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity"
And "Separate groups" "icon" should exist in the "Activity sample 2" "activity"
And "Visible groups" "icon" should exist in the "Activity sample 3" "activity"
And "Separate groups" "icon" should not exist in the "Activity sample 3" "activity"
@javascript
Scenario: Teacher can edit the group mode using the activity group mode badge
Given I click on "Separate groups" "icon" in the "Activity sample 2" "activity"
And I click on "Visible groups" "link" in the "Activity sample 2" "activity"
And "Separate groups" "icon" should not exist in the "Activity sample 2" "activity"
And "Visible groups" "icon" should exist in the "Activity sample 2" "activity"
When I click on "Visible groups" "icon" in the "Activity sample 2" "activity"
And I click on "Separate groups" "link" in the "Activity sample 2" "activity"
And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity"
And "Separate groups" "icon" should exist in the "Activity sample 2" "activity"
Then I click on "Separate groups" "icon" in the "Activity sample 2" "activity"
And I click on "No groups" "link" in the "Activity sample 2" "activity"
And "Separate groups" "icon" should not exist in the "Activity sample 2" "activity"
And "Visible groups" "icon" should not exist in the "Activity sample 2" "activity"
And I open "Activity sample 2" actions menu
When I click on "No groups" "icon" in the "Activity sample 2" "activity"
And I click on "Separate groups" "link" in the "Activity sample 2" "activity"
And "Separate groups" "icon" should exist in the "Activity sample 2" "activity"

View File

@ -115,6 +115,8 @@ $string['groupmembers'] = 'Group members';
$string['groupmemberssee'] = 'See group members';
$string['groupmembersselected'] = 'Members of selected group';
$string['groupmode'] = 'Group mode';
$string['groupmode_groupsseparate_help'] = 'Each group member can only see their own group, others are invisible';
$string['groupmode_groupsvisible_help'] = 'Each group member works in their own group, but can also see other groups';
$string['groupmode_help'] = 'This setting has 3 options:
* No groups

View File

@ -253,7 +253,6 @@ class icon_system_fontawesome extends icon_system_font {
'core:i/grading' => 'fa-magic',
'core:i/gradingnotifications' => 'fa-bell-o',
'core:i/groupevent' => 'fa-group',
'core:i/groupn' => 'fa-user',
'core:i/group' => 'fa-users',
'core:i/home' => 'fa-home',
'core:i/hide' => 'fa-eye',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 630 B

View File

@ -1,3 +1,3 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M5.2 10.3L0 13.1V16h16v-3l-5.2-2.7c-.5-.3-.6-.9-.2-1.3 0 0 1.4-1.8 1.4-3.8C12 2.3 10.2 0 8 0S4 2.3 4 5.2C4 7.2 5.4 9 5.4 9c.4.5.3 1-.2 1.3z" fill="#888"/></svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.1875 4.1875L4.40625 5.125C4.84375 4.46875 5.625 4 6.5 4C7.875 4 9 5.125 9 6.5C9 7.15625 8.75 7.75 8.34375 8.1875L9.59375 9.1875C10.1562 8.46875 11 8 12 8C13.6562 8 15 9.34375 15 11C15 11.7188 14.75 12.375 14.3125 12.9062L15.125 13.5C15.6562 12.8125 16 11.9688 16 11C16 10.7812 15.9688 10.5312 15.9375 10.3125C16.3438 10.125 16.8125 10 17.3125 10H18.6562C20.5 10 22 11.5 22 13.3438C22 13.7188 21.6875 14 21.3125 14H15.75L21.6875 18.6875C22.0312 18.9375 22.0938 19.4062 21.8125 19.7188C21.5625 20.0625 21.0938 20.125 20.7812 19.8438L2.28125 5.34375C1.9375 5.09375 1.875 4.625 2.15625 4.3125C2.40625 3.96875 2.875 3.90625 3.1875 4.1875ZM5.3125 10H5.78125L8.0625 11.8125C8.25 12.6875 8.6875 13.4375 9.34375 14H9.3125H2.65625C2.28125 14 2 13.7188 2 13.3438C2 11.5 3.46875 10 5.3125 10ZM10.1562 15H12.125L17.9062 19.5625C17.75 19.8125 17.4688 20 17.1562 20H6.8125C6.34375 20 6 19.625 6 19.1875C6 16.875 7.84375 15 10.1562 15ZM18 9C17.0938 9 16.2812 8.53125 15.8125 7.75C15.375 7 15.375 6.03125 15.8125 5.25C16.2812 4.5 17.0938 4 18 4C18.875 4 19.6875 4.5 20.1562 5.25C20.5938 6.03125 20.5938 7 20.1562 7.75C19.6875 8.53125 18.875 9 18 9Z" fill="#212529"/>
</svg>

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 1.2 KiB