MDL-72090 navigation: Create an overflow menu for course admin.

This adds a select menu for navigation on course administration
 pages such as import, export, course copy, and course reset.
This commit is contained in:
abgreeve
2021-12-09 11:13:35 +08:00
parent 5ee0b620ae
commit d02274137a
8 changed files with 213 additions and 38 deletions
+2 -1
View File
@@ -2854,7 +2854,8 @@ function grade_button($type, $courseid, $object) {
function grade_extend_settings($plugininfo, $courseid) {
global $PAGE;
$gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER);
$gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER,
null, 'gradeadmin');
$strings = array_shift($plugininfo);
+2
View File
@@ -46,6 +46,8 @@ $string['aria:favourite'] = 'Course is starred';
$string['aria:favouritestab'] = 'Starred activities';
$string['aria:recommendedtab'] = 'Recommended activities';
$string['aria:modulefavourite'] = 'Star {$a} activity';
$string['browsecourseadminindex'] = 'Browse the course administration with this index.';
$string['browsesettingindex'] = 'Browse settings with this index.';
$string['completion_automatic:done'] = 'Done:';
$string['completion_automatic:failed'] = 'Failed:';
$string['completion_automatic:todo'] = 'To do:';
+188 -30
View File
@@ -17,6 +17,7 @@
namespace core\navigation\views;
use navigation_node;
use url_select;
/**
* Class secondary_navigation_view.
@@ -36,6 +37,9 @@ class secondary extends view {
/** @var int The maximum limit of navigation nodes displayed in the secondary navigation */
const MAX_DISPLAYED_NAV_NODES = 5;
/** @var navigation_node The course overflow node. */
protected $courseoverflownode = null;
/**
* Defines the default structure for the secondary nav in a course context.
*
@@ -252,13 +256,15 @@ class secondary extends view {
if ($child->has_children()) {
$additionalchildren = true;
$urldata[][$child->text] = $additionalnode + $this->get_additional_child_nodes($child);
$text = (is_a($child->text, 'lang_string')) ? $child->text->out() : $child->text;
$urldata[][$text] = $additionalnode + $this->get_additional_child_nodes($child);
} else {
$initialchildren += $additionalnode;
}
}
if ($additionalchildren) {
$urldata[][$node->text] = $initialchildren;
$text = (is_a($node->text, 'lang_string')) ? $node->text->out() : $node->text;
$urldata[][$text] = $initialchildren;
} else {
$urldata = $initialchildren;
}
@@ -326,6 +332,30 @@ class secondary extends view {
return $newnode;
}
/**
* Returns a list of all expected nodes in the course administration.
*
* @return array An array of keys for navigation nodes in the course administration.
*/
protected function get_expected_course_admin_nodes(): array {
$expectednodes = [];
foreach ($this->get_default_course_mapping()['settings'] as $value) {
foreach ($value as $nodekey => $notused) {
$expectednodes[] = $nodekey;
}
}
foreach ($this->get_default_course_mapping()['navigation'] as $value) {
foreach ($value as $nodekey => $notused) {
$expectednodes[] = $nodekey;
}
}
$othernodes = ['users', 'gradeadmin', 'coursereports', 'coursebadges'];
$leftovercourseadminnodes = ['backup', 'restore', 'import', 'copy', 'reset'];
$expectednodes = array_merge($expectednodes, $othernodes);
$expectednodes = array_merge($expectednodes, $leftovercourseadminnodes);
return $expectednodes;
}
/**
* Load the course secondary navigation. Since we are sourcing all the info from existing objects that already do
* the relevant checks, we don't do it again here.
@@ -345,17 +375,9 @@ class secondary extends view {
$nodesordered += $this->get_leaf_nodes($navigation, $nodes['navigation'] ?? []);
$this->add_ordered_nodes($nodesordered);
$coursecontext = \context_course::instance($course->id);
if (has_capability('moodle/course:update', $coursecontext)) {
// All additional nodes will be available under the 'Course admin' page.
$text = get_string('courseadministration');
$url = new \moodle_url('/course/admin.php', array('courseid' => $this->page->course->id));
$this->add($text, $url, null, null, 'courseadmin', new \pix_icon('t/edit', $text));
}
// Try to get any custom nodes defined by a user which may include containers.
$expectedcourseadmin = ['editsettings', 'coursecompletion', 'users', 'coursereports', 'gradebooksetup', 'coursebadges',
'backup', 'restore', 'import', 'copy', 'reset', 'questionbank'];
$expectedcourseadmin = $this->get_expected_course_admin_nodes();
foreach ($settingsnav->children as $value) {
if ($value->key == 'courseadmin') {
foreach ($value->children as $other) {
@@ -363,12 +385,76 @@ class secondary extends view {
$othernode = $this->get_first_action_for_node($other);
// Get the first node and check whether it's been added already.
if ($othernode && !$this->get($othernode->key)) {
$this->add_node(clone $othernode);
$this->add_node($othernode);
} else {
$this->add_node($other);
}
}
}
}
}
$coursecontext = \context_course::instance($course->id);
if (has_capability('moodle/course:update', $coursecontext)) {
$overflownode = $this->get_course_overflow_nodes();
if (is_null($overflownode)) {
return;
}
$actionnode = $this->get_first_action_for_node($overflownode);
// All additional nodes will be available under the 'Course admin' page.
$text = get_string('courseadministration');
$this->add($text, $actionnode->action, null, null, 'courseadmin', new \pix_icon('t/edit', $text));
}
}
/**
* Gets the overflow navigation nodes for the course administration category.
*
* @return navigation_node The course overflow nodes.
*/
protected function get_course_overflow_nodes(): ?navigation_node {
global $SITE;
// This gets called twice on some pages, and so trying to create this navigation node twice results in no children being
// present the second time this is called.
if (isset($this->courseoverflownode)) {
return $this->courseoverflownode;
}
// Start with getting the base node for the front page or the course.
$node = null;
if ($this->page->course == $SITE->id) {
$node = $this->page->settingsnav->find('frontpage', navigation_node::TYPE_SETTING);
} else {
$node = $this->page->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE);
}
$coursesettings = $node->get_children_key_list();
$thissettings = $this->get_children_key_list();
$diff = array_diff($coursesettings, $thissettings);
// Remove our specific created elements (user - participants, badges - coursebadges, grades - gradebooksetup).
$shortdiff = array_filter($diff, function($value) {
return !($value == 'users' || $value == 'coursebadges' || $value == 'gradebooksetup');
});
// Permissions may be in play here that ultimately will show no overflow.
if (empty($shortdiff)) {
return null;
}
$firstitem = array_shift($shortdiff);
$navnode = $node->get($firstitem);
foreach ($shortdiff as $key) {
$courseadminnodes = $node->get($key);
if ($courseadminnodes) {
if ($courseadminnodes->parent->key == $node->key) {
$navnode->add_node($courseadminnodes);
}
}
}
$this->courseoverflownode = $navnode;
return $navnode;
}
/**
@@ -398,28 +484,100 @@ class secondary extends view {
/**
* Returns a url_select object with overflow navigation nodes.
* This looks to see if the current page is within the course administration, or some other page that requires an overflow
* select object.
*
* @return \url_select|null The overflow menu data.
* @return url_select|null The overflow menu data.
*/
public function get_overflow_menu_data(): ?\url_select {
public function get_overflow_menu_data(): ?url_select {
$activenode = $this->find_active_node();
if ($activenode && $activenode->has_action() && $activenode->has_children() && $activenode->key != 'coursehome') {
// This needs to be expanded to does the active node have children and does the page url match any of the children.
$menunode = $this->page->settingsnav->find($activenode->key, null);
if ($menunode instanceof navigation_node) {
// Loop through all children and try and find a match to the current url.
$matchednode = $this->nodes_match_current_url($menunode);
if (is_null($matchednode)) {
return null;
}
if (!isset($menunode) || !$menunode->has_children()) {
return null;
}
$selectdata = $this->get_menu_array($menunode);
return new \url_select($selectdata, $matchednode->action->out(), null);
$incourseadmin = false;
if (!$activenode) {
// Could be in the course admin section.
$courseadmin = $this->page->settingsnav->find('courseadmin', navigation_node::TYPE_COURSE);
if (!$courseadmin) {
return null;
}
$activenode = $courseadmin->find_active_node();
if (!$activenode) {
return null;
}
$incourseadmin = true;
}
return null;
if ($activenode->key == 'courseadmin' || $incourseadmin) {
$courseoverflownode = $this->get_course_overflow_nodes();
if (is_null($courseoverflownode)) {
return null;
}
$menuarray = $this->get_menu_array($courseoverflownode);
if ($activenode->key != 'courseadmin') {
$inmenu = false;
foreach ($menuarray as $key => $value) {
if ($this->page->url->out(false) == $key) {
$inmenu = true;
}
}
if (!$inmenu) {
return null;
}
}
$menuselect = new url_select($menuarray, $this->page->url, null);
$menuselect->set_label(get_string('browsecourseadminindex', 'course'), ['class' => 'sr-only']);
return $menuselect;
} else {
return $this->get_other_overflow_menu_data($activenode);
}
}
/**
* Gets overflow menu data for third party plugin settings.
*
* @param navigation_node $activenode The node to gather the children for to put into the overflow menu.
* @return url_select|null The overflow menu in a url_select object.
*/
protected function get_other_overflow_menu_data(navigation_node $activenode): ?url_select {
if (!$activenode->has_action()) {
return null;
}
if (!$activenode->has_children()) {
return null;
}
// If the setting is extending the course navigation then the page being redirected to should be in the course context.
// It was decided on the issue that put this code here that plugins that extend the course navigation should have the pages
// that are redirected to, be in the course context or module context depending on which callback was used.
// Third part plugins were checked to see if any existing plugins had settings in a system context and none were found.
// The request of third party developers is to keep their settings within the specified context.
if ($this->page->context->contextlevel != CONTEXT_COURSE && $this->page->context->contextlevel != CONTEXT_MODULE) {
return null;
}
// These areas have their own code to retrieve added plugin navigation nodes.
if ($activenode->key == 'coursehome' || $activenode->key == 'questionbank' || $activenode->key == 'coursereports') {
return null;
}
$menunode = $this->page->settingsnav->find($activenode->key, null);
if (!$menunode instanceof navigation_node) {
return null;
}
// Loop through all children and try and find a match to the current url.
$matchednode = $this->nodes_match_current_url($menunode);
if (is_null($matchednode)) {
return null;
}
if (!isset($menunode) || !$menunode->has_children()) {
return null;
}
$selectdata = $this->get_menu_array($menunode);
$urlselect = new url_select($selectdata, $matchednode->action->out(), null);
$urlselect->set_label(get_string('browsesettingindex', 'course'), ['class' => 'sr_only']);
return $urlselect;
}
/**
-1
View File
@@ -131,7 +131,6 @@ abstract class view extends navigation_node {
// If node is one of the new views then set the active node to the child.
if (!$node instanceof view) {
$node->make_active();
$child->make_inactive();
$result = $node;
} else {
$child->make_active();
+6 -6
View File
@@ -4561,6 +4561,12 @@ class settings_navigation extends navigation_node {
badges_add_course_navigation($coursenode, $course);
}
// Import data from other courses.
if ($adminoptions->import) {
$url = new moodle_url('/backup/import.php', array('id' => $course->id));
$coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/import', ''));
}
// Backup this course
if ($adminoptions->backup) {
$url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
@@ -4573,12 +4579,6 @@ class settings_navigation extends navigation_node {
$coursenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, 'restore', new pix_icon('i/restore', ''));
}
// Import data from other courses
if ($adminoptions->import) {
$url = new moodle_url('/backup/import.php', array('id'=>$course->id));
$coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/import', ''));
}
// Copy this course.
if ($adminoptions->copy) {
$url = new moodle_url('/backup/copy.php', array('id' => $course->id));
+6
View File
@@ -54,9 +54,14 @@ $blockshtml = $OUTPUT->blocks('side-pre');
$hasblocks = strpos($blockshtml, 'data-block=') !== false;
$secondarynavigation = false;
$overflow = '';
if ($PAGE->has_secondary_navigation()) {
$moremenu = new \core\navigation\output\more_menu($PAGE->secondarynav, 'nav-tabs');
$secondarynavigation = $moremenu->export_for_template($OUTPUT);
$overflowdata = $PAGE->secondarynav->get_overflow_menu_data();
if (!is_null($overflowdata)) {
$overflow = $overflowdata->export_for_template($OUTPUT);
}
}
$primary = new core\navigation\output\primary($PAGE);
@@ -79,6 +84,7 @@ $templatecontext = [
'langmenu' => $primarymenu['lang'],
'regionmainsettingsmenu' => $regionmainsettingsmenu,
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu),
'overflow' => $overflow
];
$nav = $PAGE->flatnav;
$templatecontext['firstcollectionlabel'] = $nav->get_collectionlabel();
+6
View File
@@ -79,9 +79,14 @@ $bodyattributes = $OUTPUT->body_attributes($extraclasses);
$forceblockdraweropen = $OUTPUT->firstview_fakeblocks();
$secondarynavigation = false;
$overflow = '';
if ($PAGE->has_secondary_navigation()) {
$moremenu = new \core\navigation\output\more_menu($PAGE->secondarynav, 'nav-tabs');
$secondarynavigation = $moremenu->export_for_template($OUTPUT);
$overflowdata = $PAGE->secondarynav->get_overflow_menu_data();
if (!is_null($overflowdata)) {
$overflow = $overflowdata->export_for_template($OUTPUT);
}
}
$primary = new core\navigation\output\primary($PAGE);
@@ -108,6 +113,7 @@ $templatecontext = [
'forceblockdraweropen' => $forceblockdraweropen,
'regionmainsettingsmenu' => $regionmainsettingsmenu,
'hasregionmainsettingsmenu' => !empty($regionmainsettingsmenu),
'overflow' => $overflow
];
$nav = $PAGE->flatnav;
+3
View File
@@ -79,6 +79,9 @@
<div class="region_main_settings_menu_proxy"></div>
{{/hasregionmainsettingsmenu}}
{{{ output.course_content_header }}}
{{#overflow}}
{{> core/url_select}}
{{/overflow}}
{{{ output.main_content }}}
{{{ output.activity_navigation }}}
{{{ output.course_content_footer }}}