From e0d7dbd022124ad5da21290e3ce3f914d3be4917 Mon Sep 17 00:00:00 2001 From: Peter Dias Date: Mon, 1 Nov 2021 14:33:59 +0800 Subject: [PATCH] MDL-72875 course: Introduce tertiary nav in participants - Introduce tertiary nav in the participants page --- admin/roles/assign.php | 34 ++-- admin/roles/check.php | 6 +- admin/roles/permissions.php | 3 + .../templates/participants_actionbar.mustache | 115 ++++++++++++ enrol/instances.php | 1 + enrol/manual/lib.php | 1 + enrol/otherusers.php | 3 + enrol/renderer.php | 10 +- group/groupings.php | 5 +- group/index.php | 5 +- group/overview.php | 5 +- lang/en/course.php | 1 + lib/classes/navigation/views/secondary.php | 7 + .../output/participants_action_bar.php | 167 ++++++++++++++++++ lib/enrollib.php | 14 +- lib/outputrenderers.php | 13 ++ user/index.php | 33 ++-- version.php | 2 +- 18 files changed, 370 insertions(+), 55 deletions(-) create mode 100644 course/templates/participants_actionbar.mustache create mode 100644 lib/classes/output/participants_action_bar.php diff --git a/admin/roles/assign.php b/admin/roles/assign.php index 50557d69f4c..c542943da30 100644 --- a/admin/roles/assign.php +++ b/admin/roles/assign.php @@ -184,6 +184,25 @@ switch ($context->contextlevel) { echo $OUTPUT->header(); +$backurl = null; +// We are looking at a particular role. The page URL has been set correctly. +if ($roleid) { + $backurl = $pageurl; +} else if ($context->contextlevel == CONTEXT_COURSE && !$isfrontpage) { + // Return to the intermediary page when within the course context. + $backurl = new moodle_url('/enrol/otherusers.php', ['id' => $course->id]); +} else if ($returnurl) { + // Factor in for $returnurl being passed. + $backurl = new moodle_url($returnurl); +} + +if ($backurl) { + echo $OUTPUT->render(new single_button($backurl, get_string('back'), 'get')); +} else if ($isfrontpage) { + // The front page doesn't have an intermediate page 'other users' but needs similar tertiary nav like a standard course. + echo $OUTPUT->render_participants_tertiary_nav($course); +} + // Print heading. echo $OUTPUT->heading_with_help($title, 'assignroles', 'core_role'); @@ -245,7 +264,6 @@ if ($roleid) { $select = new single_select($PAGE->url, 'roleid', $nameswithcounts, $roleid, null); $select->label = get_string('assignanotherrole', 'core_role'); echo $OUTPUT->render($select); - echo '

' . get_string('backtoallroles', 'core_role') . '

'; echo ''; } else if (empty($assignableroles)) { @@ -314,17 +332,13 @@ if ($roleid) { echo html_writer::table($table); - if ($context->contextlevel > CONTEXT_USER) { - - if ($returnurl) { - $url = new moodle_url($returnurl); - } else { + if (!$PAGE->has_secondary_navigation() && $context->contextlevel > CONTEXT_USER) { + if (!$returnurl) { $url = $context->get_url(); + echo html_writer::start_tag('div', array('class' => 'backlink')); + echo html_writer::tag('a', get_string('backto', '', $contextname), array('href' => $url)); + echo html_writer::end_tag('div'); } - - echo html_writer::start_tag('div', array('class'=>'backlink')); - echo html_writer::tag('a', get_string('backto', '', $contextname), array('href' => $url)); - echo html_writer::end_tag('div'); } } diff --git a/admin/roles/check.php b/admin/roles/check.php index 10e173ef13e..631085ab7a7 100644 --- a/admin/roles/check.php +++ b/admin/roles/check.php @@ -126,6 +126,10 @@ if (!is_null($reportuser)) { } echo $OUTPUT->header(); +// Display the participants tertiary action bar if within a course context. +if ($course && $context->contextlevel == CONTEXT_COURSE) { + echo $OUTPUT->render_participants_tertiary_nav($course); +} // Print heading. echo $OUTPUT->heading($title); @@ -180,7 +184,7 @@ echo ''; echo $OUTPUT->box_end(); // Appropriate back link. -if ($context->contextlevel > CONTEXT_USER) { +if (!$PAGE->has_secondary_navigation() && $context->contextlevel > CONTEXT_USER) { echo html_writer::start_tag('div', array('class'=>'backlink')); if ($returnurl) { $backurl = new moodle_url($returnurl); diff --git a/admin/roles/permissions.php b/admin/roles/permissions.php index bc549484400..dfd6547a00b 100644 --- a/admin/roles/permissions.php +++ b/admin/roles/permissions.php @@ -204,6 +204,9 @@ if ($capability && ($allowoverrides || ($allowsafeoverrides && is_safe_capabilit } echo $OUTPUT->header(); +if ($context->contextlevel == CONTEXT_COURSE && $course) { + echo $OUTPUT->render_participants_tertiary_nav($course); +} echo $OUTPUT->heading($title); $adminurl = new moodle_url('/admin/'); diff --git a/course/templates/participants_actionbar.mustache b/course/templates/participants_actionbar.mustache new file mode 100644 index 00000000000..efff47108f8 --- /dev/null +++ b/course/templates/participants_actionbar.mustache @@ -0,0 +1,115 @@ +{{! + 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 core_course/participants_actionbar + + Displays the tertiary nav for the participants page + + Context variables required for this template: + * urlselect Object - The content to be rendered within the url_select + * renderedcontent String - Any additional content/actions to be displayed inline with the select box + + Example context (json): + { + "urlselect" : { + "formid": "url_select_f61834256d7754158", + "classes": "urlselect", + "label": "", + "disabled": false, + "title":null, + "id":"url_select61834256d7754159", + "sesskey":"LWILINyZMH", + "action":"http:\/\/localhost\/stable_master\/course\/jumpto.php", + "showbutton":null, + "options": [ + { + "name":"Enrolments", + "isgroup":true, + "options": [ + { + "name":"Enrolled users", + "value":"\/user\/index.php?id=4", + "selected":true + }, + { + "name":"Enrolment methods", + "value":"\/enrol\/instances.php?id=4", + "selected":false + } + ] + }, + { + "name":"Groups", + "isgroup":true, + "options": [ + { + "name":"Groups", + "value":"\/group\/index.php?id=4", + "selected":false + }, + { + "name":"Groupings", + "value":"\/group\/groupings.php?id=4", + "selected":false + }, + { + "name":"Overview", + "value":"\/group\/overview.php?id=4", + "selected":false + } + ] + }, + { + "name":"Permissions", + "isgroup":true, + "options": [ + { + "name":"Permissions", + "value":"\/admin\/roles\/permissions.php?contextid=279", + "selected":false + }, + { + "name":"Other users", + "value":"\/enrol\/otherusers.php?id=4", + "selected":false + }, + { + "name":"Check permissions", + "value":"\/admin\/roles\/check.php?contextid=279", + "selected":false + } + ] + } + ], + "labelattributes":[], + "helpicon":false, + "attributes":[] + }, + "renderedcontent":"
<\/div>" + } +}} +
+
+ {{#urlselect}} +
+ {{> core/url_select }} +
+ {{/urlselect}} + {{#renderedcontent}} +
+ {{{renderedcontent}}} +
+ {{/renderedcontent}} +
+
diff --git a/enrol/instances.php b/enrol/instances.php index 2c11f49376c..eb9b5c5b0af 100644 --- a/enrol/instances.php +++ b/enrol/instances.php @@ -186,6 +186,7 @@ if ($canconfig and $action and confirm_sesskey()) { echo $OUTPUT->header(); +echo $OUTPUT->render_participants_tertiary_nav($course); echo $OUTPUT->heading(get_string('enrolmentinstances', 'enrol')); echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthnormal'); diff --git a/enrol/manual/lib.php b/enrol/manual/lib.php index 2e052760c56..b4221d005f9 100644 --- a/enrol/manual/lib.php +++ b/enrol/manual/lib.php @@ -218,6 +218,7 @@ class enrol_manual_plugin extends enrol_plugin { $button = new enrol_user_button($link, get_string('enrolusers', 'enrol_manual'), 'get'); $button->class .= ' enrol_manual_plugin'; + $button->primary = true; $context = context_course::instance($instance->courseid); $arguments = array('contextid' => $context->id); diff --git a/enrol/otherusers.php b/enrol/otherusers.php index 826ebdc43a9..dfb5b2e1526 100644 --- a/enrol/otherusers.php +++ b/enrol/otherusers.php @@ -94,5 +94,8 @@ $PAGE->set_title($course->fullname.': '.get_string('totalotherusers', 'enrol', $ $PAGE->set_heading($PAGE->title); echo $OUTPUT->header(); +$searchbutton = $table->get_user_search_button(); +$searchbutton->primary = true; +echo $OUTPUT->render_participants_tertiary_nav($course, $OUTPUT->render($searchbutton)); echo $renderer->render($table); echo $OUTPUT->footer(); diff --git a/enrol/renderer.php b/enrol/renderer.php index 8c572d1ae30..c2458edec22 100644 --- a/enrol/renderer.php +++ b/enrol/renderer.php @@ -41,7 +41,7 @@ class core_enrol_renderer extends plugin_renderer_base { 'value' => $button->label, 'disabled' => $button->disabled ? 'disabled' : null, 'title' => $button->tooltip, - 'class' => 'btn btn-secondary my-1'); + 'class' => 'btn ' . ($button->primary ? 'btn-primary' : 'btn-secondary')); if ($button->actions) { $id = html_writer::random_id('single_button'); @@ -96,18 +96,10 @@ class core_enrol_renderer extends plugin_renderer_base { $table->initialise_javascript(); $content = ''; - $searchbutton = $table->get_user_search_button(); - if ($searchbutton) { - $content .= $this->output->render($searchbutton); - } $content .= html_writer::tag('div', get_string('otheruserdesc', 'enrol'), array('class'=>'otherusersdesc')); $content .= $this->output->render($table->get_paging_bar()); $content .= html_writer::table($table); $content .= $this->output->render($table->get_paging_bar()); - $searchbutton = $table->get_user_search_button(); - if ($searchbutton) { - $content .= $this->output->render($searchbutton); - } return $content; } diff --git a/group/groupings.php b/group/groupings.php index aab6a379bb8..7ff87f7626d 100644 --- a/group/groupings.php +++ b/group/groupings.php @@ -59,10 +59,7 @@ $PAGE->set_heading($course->fullname); $PAGE->set_pagelayout('standard'); echo $OUTPUT->header(); -// Add tabs -$currenttab = 'groupings'; -require('tabs.php'); - +echo $OUTPUT->render_participants_tertiary_nav($course); echo $OUTPUT->heading($strgroupings); $data = array(); diff --git a/group/index.php b/group/index.php index f2296fcf844..3f79cb22927 100644 --- a/group/index.php +++ b/group/index.php @@ -170,10 +170,7 @@ $PAGE->set_heading($course->fullname); $PAGE->set_pagelayout('standard'); echo $OUTPUT->header(); -// Add tabs -$currenttab = 'groups'; -require('tabs.php'); - +echo $OUTPUT->render_participants_tertiary_nav($course); echo $OUTPUT->heading(format_string($course->shortname, true, array('context' => $context)) .' '.$strgroups, 3); $groups = groups_get_all_groups($courseid); diff --git a/group/overview.php b/group/overview.php index 85477aa4c68..8a99ba3a890 100644 --- a/group/overview.php +++ b/group/overview.php @@ -195,10 +195,7 @@ $PAGE->set_heading($course->fullname); $PAGE->set_pagelayout('standard'); echo $OUTPUT->header(); -// Add tabs -$currenttab = 'overview'; -require('tabs.php'); - +echo $OUTPUT->render_participants_tertiary_nav($course); /// Print overview echo $OUTPUT->heading(format_string($course->shortname, true, array('context' => $context)) .' '.$stroverview, 3); diff --git a/lang/en/course.php b/lang/en/course.php index a5a83064a8a..6ee211138a9 100644 --- a/lang/en/course.php +++ b/lang/en/course.php @@ -97,6 +97,7 @@ $string['norecentaccessesinfomessage'] = 'Hi {$a->userfirstname},

A number of students in {$a->coursename} have not accessed the course recently.

'; $string['noteachinginfomessage'] = 'Hi {$a->userfirstname},

Courses with start dates in the next week have been identified as having no teacher or student enrolments.

'; +$string['participantsnavigation'] = 'Participants tertiary navigation.'; $string['privacy:perpage'] = 'The number of courses to show per page.'; $string['privacy:completionpath'] = 'Course completion'; $string['privacy:favouritespath'] = 'Course starred information'; diff --git a/lib/classes/navigation/views/secondary.php b/lib/classes/navigation/views/secondary.php index eeec8eaecbe..48f885d7f13 100644 --- a/lib/classes/navigation/views/secondary.php +++ b/lib/classes/navigation/views/secondary.php @@ -54,6 +54,13 @@ class secondary extends view { ], self::TYPE_SETTING => [ 'editsettings' => 0, + 'review' => 1.1, + 'manageinstances' => 1.2, + 'groups' => 1.3, + 'override' => 1.4, + 'roles' => 1.5, + 'permissions' => 1.6, + 'otherusers' => 1.7, 'gradebooksetup' => 2.1, 'outcomes' => 2.2, 'coursecompletion' => 6, diff --git a/lib/classes/output/participants_action_bar.php b/lib/classes/output/participants_action_bar.php new file mode 100644 index 00000000000..045c4eb93ff --- /dev/null +++ b/lib/classes/output/participants_action_bar.php @@ -0,0 +1,167 @@ +. + +namespace core\output; + +use moodle_page; +use navigation_node; +use moodle_url; + +/** + * Class responsible for generating the action bar (tertiary nav) elements in the participants page and related pages. + * + * @package core + * @copyright 2021 Peter Dias + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class participants_action_bar implements \renderable { + /** @var object $course The course we are dealing with. */ + private $course; + /** @var moodle_page $page The current page. */ + private $page; + /** @var navigation_node $node The settings node for the participants page. */ + private $node; + /** @var string|null $renderedcontent Rendered buttons to be displayed in-line with the select box */ + private $renderedcontent; + + /** + * Constructor participants_action_bar + * @param object $course The course that we are generating the nav for + * @param moodle_page $page The page object + * @param string|null $renderedcontent Any additional rendered content/actions to be displayed in line with the nav + */ + public function __construct(object $course, moodle_page $page, ?string $renderedcontent) { + $this->course = $course; + $this->page = $page; + $this->node = $this->page->settingsnav->find('users', navigation_node::TYPE_CONTAINER); + $this->renderedcontent = $renderedcontent; + } + + /** + * Return the nodes required to be displayed in the url_select box. + * The nodes are divided into 3 sections each with the heading as the key. + * + * @return array + */ + protected function get_ordered_nodes(): array { + return [ + 'enrolments:enrol' => [ + 'review', + 'manageinstances' + ], + 'groups:group' => [ + 'groups' + ], + 'permissions:role' => [ + 'override', + 'roles', + 'otherusers', + 'permissions' + ] + ]; + } + + /** + * Get the content for the url_select select box. + * + * @return array + */ + protected function get_content_for_select(): array { + if (!$this->node) { + return []; + } + + $nodes = $this->get_ordered_nodes(); + $formattedcontent = []; + foreach ($nodes as $description => $content) { + list($stringid, $location) = explode(':', $description); + $heading = get_string($stringid, $location); + $items = []; + foreach ($content as $key) { + if ($node = $this->node->find($key, null)) { + $urldescription = $node->text; + $items[$node->action()->out()] = $urldescription; + + // Additional items to be added. + if ($key === 'groups') { + $params = ['id' => $this->course->id]; + $items += [ + (new moodle_url('/group/groupings.php', $params))->out() => get_string('groupings', 'group'), + (new moodle_url('/group/overview.php', $params))->out() => get_string('overview', 'group') + ]; + } + } + } + if ($items) { + $formattedcontent[][$heading] = $items; + } + } + + // TODO: Implement MDL-72930. + return $formattedcontent; + } + + /** + * Recursively tries to find a matching url + * @param array $urlcontent The content for the url_select + * @param int $strictness Strictness for the compare criteria + * @return string The matching active url + */ + protected function find_active_page(array $urlcontent, int $strictness = URL_MATCH_EXACT): string { + foreach ($urlcontent as $key => $value) { + if (is_array($value) && $activeitem = $this->find_active_page($value, $strictness)) { + return $activeitem; + } else if ($this->page->url->compare(new moodle_url($key), $strictness)) { + return $key; + } + } + + return ""; + } + + /** + * Gets the url_select to be displayed in the participants page if available. + * + * @param \renderer_base $output + * @return object|null The content required to render the url_select + */ + public function get_dropdown(\renderer_base $output): ?object { + if ($urlselectcontent = $this->get_content_for_select()) { + $activeurl = $this->find_active_page($urlselectcontent); + $activeurl = $activeurl ?: $this->find_active_page($urlselectcontent, URL_MATCH_BASE); + $urlselect = new \url_select($urlselectcontent, $activeurl, null); + $urlselect->set_label(get_string('participantsnavigation', 'course'), ['class' => 'sr-only']); + return $urlselect->export_for_template($output); + } + + return null; + } + + /** + * Export the content to be displayed on the participants page. + * + * @param \renderer_base $output + * @return array Consists of the following: + * - urlselect A stdclass representing the standard navigation options to be fed into a urlselect + * - renderedcontent Rendered content to be displayed in line with the tertiary nav + */ + public function export_for_template(\renderer_base $output) { + return [ + 'urlselect' => $this->get_dropdown($output), + 'renderedcontent' => $this->renderedcontent, + ]; + } +} diff --git a/lib/enrollib.php b/lib/enrollib.php index 9e6e8a8cc5a..3f6d2ee3cb1 100644 --- a/lib/enrollib.php +++ b/lib/enrollib.php @@ -433,13 +433,15 @@ function enrol_add_course_navigation(navigation_node $coursenode, $course) { $usersnode = $coursenode->add(get_string('users'), null, navigation_node::TYPE_CONTAINER, null, 'users'); - if ($course->id != SITEID) { - // list all participants - allows assigning roles, groups, etc. - if (has_capability('moodle/course:enrolreview', $coursecontext)) { - $url = new moodle_url('/user/index.php', array('id'=>$course->id)); - $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'review', new pix_icon('i/enrolusers', '')); - } + // List all participants - allows assigning roles, groups, etc. + // Have this available even in the site context as the page is still accessible from the frontpage. + if (has_capability('moodle/course:enrolreview', $coursecontext)) { + $url = new moodle_url('/user/index.php', array('id' => $course->id)); + $usersnode->add(get_string('enrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, + null, 'review', new pix_icon('i/enrolusers', '')); + } + if ($course->id != SITEID) { // manage enrol plugin instances if (has_capability('moodle/course:enrolconfig', $coursecontext) or has_capability('moodle/course:enrolreview', $coursecontext)) { $url = new moodle_url('/enrol/instances.php', array('id'=>$course->id)); diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 5ec4c06913b..87f88f9d8ce 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -4882,6 +4882,19 @@ EOD; return $this->render_from_template($element->get_template(), $element->export_for_template($this)); } + /** + * Renders the tertiary nav for the participants page + * + * @param object $course The course we are operating within + * @param string|null $renderedbuttons Any additional buttons/content to be displayed in line with the nav + * @return string + */ + public function render_participants_tertiary_nav(object $course, ?string $renderedbuttons = null) { + $actionbar = new \core\output\participants_action_bar($course, $this->page, $renderedbuttons); + $content = $this->render_from_template('core_course/participants_actionbar', $actionbar->export_for_template($this)); + return $content ?: ""; + } + /** * Renders release information in the footer popup * @return string Moodle release info. diff --git a/user/index.php b/user/index.php index 8896dacbafc..190dd3fc28f 100644 --- a/user/index.php +++ b/user/index.php @@ -100,13 +100,28 @@ if ($node) { } echo $OUTPUT->header(); + +$participanttable = new \core_user\table\participants("user-index-participants-{$course->id}"); + +// Manage enrolments. +$manager = new course_enrolment_manager($PAGE, $course); +$enrolbuttons = $manager->get_manual_enrol_buttons(); +$enrolrenderer = $PAGE->get_renderer('core_enrol'); +$enrolbuttonsout = ''; +foreach ($enrolbuttons as $enrolbutton) { + $enrolbuttonsout .= $enrolrenderer->render($enrolbutton); +} + +echo $OUTPUT->render_participants_tertiary_nav($course, html_writer::div($enrolbuttonsout, '', [ + 'data-region' => 'wrapper', + 'data-table-uniqueid' => $participanttable->uniqueid, +])); + echo $OUTPUT->heading(get_string('participants')); $filterset = new \core_user\table\participants_filterset(); $filterset->add_filter(new integer_filter('courseid', filter::JOINTYPE_DEFAULT, [(int)$course->id])); -$participanttable = new \core_user\table\participants("user-index-participants-{$course->id}"); - $canaccessallgroups = has_capability('moodle/site:accessallgroups', $context); $filtergroupids = $urlgroupid ? [$urlgroupid] : []; @@ -155,20 +170,6 @@ if ($roleid) { } } -// Manage enrolments. -$manager = new course_enrolment_manager($PAGE, $course); -$enrolbuttons = $manager->get_manual_enrol_buttons(); -$enrolrenderer = $PAGE->get_renderer('core_enrol'); -$enrolbuttonsout = ''; -foreach ($enrolbuttons as $enrolbutton) { - $enrolbuttonsout .= $enrolrenderer->render($enrolbutton); -} - -echo html_writer::div($enrolbuttonsout, 'd-flex justify-content-end', [ - 'data-region' => 'wrapper', - 'data-table-uniqueid' => $participanttable->uniqueid, -]); - // Render the user filters. $userrenderer = $PAGE->get_renderer('core_user'); echo $userrenderer->participants_filter($context, $participanttable->uniqueid); diff --git a/version.php b/version.php index 7cf5275b67d..94a85ba9b44 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2021120100.00; // YYYYMMDD = weekly release date of this DEV branch. +$version = 2021120100.01; // YYYYMMDD = weekly release date of this DEV branch. // RR = release increments - 00 in DEV branches. // .XX = incremental changes. $release = '4.0dev+ (Build: 20211201)'; // Human-friendly version name