Merge branch 'MDL-82159-main' of https://github.com/andrewnicols/moodle
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
issueNumber: MDL-82159
|
||||
notes:
|
||||
core:
|
||||
- message: |+
|
||||
The following classes have been renamed and now support autoloading.
|
||||
Existing classes are currently unaffected.
|
||||
|
||||
| Old class name | New class name |
|
||||
| --- | --- |
|
||||
| `\breadcrumb_navigation_node` | `\core\navigation\breadcrumb_navigation_node` |
|
||||
| `\flat_navigation_node` | `\core\navigation\flat_navigation_node` |
|
||||
| `\flat_navigation` | `\core\navigation\flat_navigation_node` |
|
||||
| `\global_navigation_for_ajax` | `\core\navigation\global_navigation_for_ajax` |
|
||||
| `\global_navigation` | `\core\navigation\global_navigation` |
|
||||
| `\navbar` | `\core\navigation\navbar` |
|
||||
| `\navigation_cache` | `\core\navigation\navigation_cache` |
|
||||
| `\navigation_json` | `\core\navigation\navigation_json` |
|
||||
| `\navigation_node_collection` | `\core\navigation\navigation_node_collection` |
|
||||
| `\navigation_node` | `\core\navigation\navigation_node` |
|
||||
| `\settings_navigation_for_ajax` | `\core\navigation\settings_navigation_for_ajax` |
|
||||
| `\settings_navigation` | `\core\navigation\settings_navigation` |
|
||||
|
||||
type: improved
|
||||
@@ -0,0 +1,12 @@
|
||||
issueNumber: MDL-82159
|
||||
notes:
|
||||
core:
|
||||
- message: |
|
||||
The following global constants have been deprecated in favour of class
|
||||
constants:
|
||||
|
||||
| Old constant | New constant |
|
||||
| --- | --- |
|
||||
| `NAVIGATION_CACHE_NAME` | `\core\navigation\navigation_node::CACHE_NAME` |
|
||||
| `NAVIGATION_SITE_ADMIN_CACHE_NAME` | `\core\navigation\navigation_node::SITE_ADMIN_CACHE_NAME` |
|
||||
type: deprecated
|
||||
@@ -548,7 +548,8 @@ class component {
|
||||
// Always keep moodle_exception in place.
|
||||
$keyclasses = [
|
||||
\core\exception\moodle_exception::class,
|
||||
\core_course\section_info::class,
|
||||
\core\navigation\navbar::class,
|
||||
\core\navigation\navigation_node::class,
|
||||
];
|
||||
foreach ($keyclasses as $classname) {
|
||||
if (!array_key_exists($classname, $cache['classmap'])) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\exception\coding_exception;
|
||||
|
||||
/**
|
||||
* Subclass of navigation_node allowing different rendering for the breadcrumbs
|
||||
* in particular adding extra metadata for search engine robots to leverage.
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2015 Brendan Heywood
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class breadcrumb_navigation_node extends navigation_node {
|
||||
/** @var $last boolean A flag indicating this is the last item in the list of breadcrumbs. */
|
||||
private $last = false;
|
||||
|
||||
/**
|
||||
* A proxy constructor
|
||||
*
|
||||
* @param mixed $navnode A navigation_node or an array
|
||||
*/
|
||||
public function __construct($navnode) {
|
||||
if (is_array($navnode)) {
|
||||
parent::__construct($navnode);
|
||||
} else if ($navnode instanceof navigation_node) {
|
||||
// Just clone everything.
|
||||
$objvalues = get_object_vars($navnode);
|
||||
foreach ($objvalues as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
} else {
|
||||
throw new coding_exception('Not a valid breadcrumb_navigation_node');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for "last"
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_last() {
|
||||
return $this->last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for "last"
|
||||
* @param $val boolean
|
||||
*/
|
||||
public function set_last($val) {
|
||||
$this->last = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(breadcrumb_navigation_node::class, \breadcrumb_navigation_node::class);
|
||||
@@ -0,0 +1,177 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\context\course as context_course;
|
||||
use core\context_helper;
|
||||
use core\url;
|
||||
use moodle_page;
|
||||
|
||||
/**
|
||||
* Class used to generate a collection of navigation nodes most closely related
|
||||
* to the current page.
|
||||
*
|
||||
* @deprecated since Moodle 4.0 - do not use any more. Leverage secondary/tertiary navigation concepts
|
||||
* @package core
|
||||
* @copyright 2016 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\core\attribute\deprecated(
|
||||
since: '4.0',
|
||||
reason: 'Do not use this class any more. Leverage secondary/tertiary navigation concepts.'
|
||||
)]
|
||||
class flat_navigation extends navigation_node_collection {
|
||||
/** @var moodle_page the moodle page that the navigation belongs to */
|
||||
protected $page;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param moodle_page $page
|
||||
*/
|
||||
public function __construct(moodle_page &$page) {
|
||||
if (during_initial_install()) {
|
||||
return;
|
||||
}
|
||||
debugging("Flat navigation has been deprecated in favour of primary/secondary navigation concepts");
|
||||
$this->page = $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the list of navigation nodes based on the current navigation and settings trees.
|
||||
*
|
||||
*/
|
||||
public function initialise() {
|
||||
global $PAGE, $USER, $OUTPUT, $CFG;
|
||||
if (during_initial_install()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current = false;
|
||||
|
||||
$course = $PAGE->course;
|
||||
|
||||
$this->page->navigation->initialise();
|
||||
|
||||
// First walk the nav tree looking for "flat_navigation" nodes.
|
||||
if ($course->id > 1) {
|
||||
// It's a real course.
|
||||
$url = new url('/course/view.php', ['id' => $course->id]);
|
||||
|
||||
$coursecontext = context_course::instance($course->id, MUST_EXIST);
|
||||
$displaycontext = context_helper::get_navigation_filter_context($coursecontext);
|
||||
// This is the name that will be shown for the course.
|
||||
$coursename = empty($CFG->navshowfullcoursenames) ?
|
||||
format_string($course->shortname, true, ['context' => $displaycontext]) :
|
||||
format_string($course->fullname, true, ['context' => $displaycontext]);
|
||||
|
||||
$flat = new flat_navigation_node(navigation_node::create($coursename, $url), 0);
|
||||
$flat->set_collectionlabel($coursename);
|
||||
$flat->key = 'coursehome';
|
||||
$flat->icon = new pix_icon('i/course', '');
|
||||
|
||||
$courseformat = course_get_format($course);
|
||||
$coursenode = $PAGE->navigation->find_active_node();
|
||||
$targettype = navigation_node::TYPE_COURSE;
|
||||
|
||||
// Single activity format has no course node - the course node is swapped for the activity node.
|
||||
if (!$courseformat->has_view_page()) {
|
||||
$targettype = navigation_node::TYPE_ACTIVITY;
|
||||
}
|
||||
|
||||
while (!empty($coursenode) && ($coursenode->type != $targettype)) {
|
||||
$coursenode = $coursenode->parent;
|
||||
}
|
||||
// There is one very strange page in mod/feedback/view.php which thinks it is both site and course
|
||||
// context at the same time. That page is broken but we need to handle it (hence the SITEID).
|
||||
if ($coursenode && $coursenode->key != SITEID) {
|
||||
$this->add($flat);
|
||||
foreach ($coursenode->children as $child) {
|
||||
if ($child->action) {
|
||||
$flat = new flat_navigation_node($child, 0);
|
||||
$this->add($flat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->page->navigation->build_flat_navigation_list($this, true, get_string('site'));
|
||||
} else {
|
||||
$this->page->navigation->build_flat_navigation_list($this, false, get_string('site'));
|
||||
}
|
||||
|
||||
$admin = $PAGE->settingsnav->find('siteadministration', navigation_node::TYPE_SITE_ADMIN);
|
||||
if (!$admin) {
|
||||
// Try again - crazy nav tree!
|
||||
$admin = $PAGE->settingsnav->find('root', navigation_node::TYPE_SITE_ADMIN);
|
||||
}
|
||||
if ($admin) {
|
||||
$flat = new flat_navigation_node($admin, 0);
|
||||
$flat->set_showdivider(true, get_string('sitesettings'));
|
||||
$flat->key = 'sitesettings';
|
||||
$flat->icon = new pix_icon('t/preferences', '');
|
||||
$this->add($flat);
|
||||
}
|
||||
|
||||
// Add-a-block in editing mode.
|
||||
if (
|
||||
isset($this->page->theme->addblockposition) &&
|
||||
$this->page->theme->addblockposition == BLOCK_ADDBLOCK_POSITION_FLATNAV &&
|
||||
$PAGE->user_is_editing() && $PAGE->user_can_edit_blocks()
|
||||
) {
|
||||
$url = new url($PAGE->url, ['bui_addblock' => '', 'sesskey' => sesskey()]);
|
||||
$addablock = navigation_node::create(get_string('addblock'), $url);
|
||||
$flat = new flat_navigation_node($addablock, 0);
|
||||
$flat->set_showdivider(true, get_string('blocksaddedit'));
|
||||
$flat->key = 'addblock';
|
||||
$flat->icon = new pix_icon('i/addblock', '');
|
||||
$this->add($flat);
|
||||
|
||||
$addblockurl = "?{$url->get_query_string(false)}";
|
||||
|
||||
$PAGE->requires->js_call_amd(
|
||||
'core_block/add_modal',
|
||||
'init',
|
||||
[$addblockurl, $this->page->get_edited_page_hash()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the parent so we can set a label for this collection if it has not been set yet.
|
||||
*
|
||||
* @param navigation_node $node Node to add
|
||||
* @param string $beforekey If specified, adds before a node with this key,
|
||||
* otherwise adds at end
|
||||
* @return navigation_node Added node
|
||||
*/
|
||||
public function add(navigation_node $node, $beforekey = null) {
|
||||
$result = parent::add($node, $beforekey);
|
||||
// Extend the parent to get a name for the collection of nodes if required.
|
||||
if (empty($this->collectionlabel)) {
|
||||
if ($node instanceof flat_navigation_node) {
|
||||
$this->set_collectionlabel($node->get_collectionlabel());
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(flat_navigation::class, \flat_navigation::class);
|
||||
@@ -0,0 +1,157 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\exception\coding_exception;
|
||||
|
||||
/**
|
||||
* Subclass of navigation_node allowing different rendering for the flat navigation
|
||||
* in particular allowing dividers and indents.
|
||||
*
|
||||
* @deprecated since Moodle 4.0 - do not use any more. Leverage secondary/tertiary navigation concepts
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2016 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class flat_navigation_node extends navigation_node {
|
||||
/** @var $indent integer The indent level */
|
||||
private $indent = 0;
|
||||
|
||||
/** @var $showdivider bool Show a divider before this element */
|
||||
private $showdivider = false;
|
||||
|
||||
/** @var $collectionlabel string Label for a group of nodes */
|
||||
private $collectionlabel = '';
|
||||
|
||||
/**
|
||||
* A proxy constructor
|
||||
*
|
||||
* @param mixed $navnode A navigation_node or an array
|
||||
*/
|
||||
public function __construct($navnode, $indent) {
|
||||
debugging("Flat nav has been deprecated in favour of primary/secondary navigation concepts");
|
||||
if (is_array($navnode)) {
|
||||
parent::__construct($navnode);
|
||||
} else if ($navnode instanceof navigation_node) {
|
||||
// Just clone everything.
|
||||
$objvalues = get_object_vars($navnode);
|
||||
foreach ($objvalues as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
} else {
|
||||
throw new coding_exception('Not a valid flat_navigation_node');
|
||||
}
|
||||
$this->indent = $indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter, a label is required for a flat navigation node that shows a divider.
|
||||
*
|
||||
* @param string $label
|
||||
*/
|
||||
public function set_collectionlabel($label) {
|
||||
$this->collectionlabel = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter, get the label for this flat_navigation node, or it's parent if it doesn't have one.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_collectionlabel() {
|
||||
if (!empty($this->collectionlabel)) {
|
||||
return $this->collectionlabel;
|
||||
}
|
||||
if ($this->parent && ($this->parent instanceof flat_navigation_node || $this->parent instanceof flat_navigation)) {
|
||||
return $this->parent->get_collectionlabel();
|
||||
}
|
||||
debugging('Navigation region requires a label', DEBUG_DEVELOPER);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this node represent a course section link.
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_section() {
|
||||
return $this->type == navigation_node::TYPE_SECTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* In flat navigation - sections are active if we are looking at activities in the section.
|
||||
* @return boolean
|
||||
*/
|
||||
public function isactive() {
|
||||
global $PAGE;
|
||||
|
||||
if ($this->is_section()) {
|
||||
$active = $PAGE->navigation->find_active_node();
|
||||
if ($active) {
|
||||
while ($active = $active->parent) {
|
||||
if ($active->key == $this->key && $active->type == $this->type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->isactive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for "showdivider"
|
||||
* @return boolean
|
||||
*/
|
||||
public function showdivider() {
|
||||
return $this->showdivider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for "showdivider"
|
||||
* @param $val boolean
|
||||
* @param $label string Label for the group of nodes
|
||||
*/
|
||||
public function set_showdivider($val, $label = '') {
|
||||
$this->showdivider = $val;
|
||||
if ($this->showdivider && empty($label)) {
|
||||
debugging('Navigation region requires a label', DEBUG_DEVELOPER);
|
||||
} else {
|
||||
$this->set_collectionlabel($label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for "indent"
|
||||
* @return boolean
|
||||
*/
|
||||
public function get_indent() {
|
||||
return $this->indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for "indent"
|
||||
* @param $val boolean
|
||||
*/
|
||||
public function set_indent($val) {
|
||||
$this->indent = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(flat_navigation_node::class, \flat_navigation_node::class);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,287 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\context\module as context_module;
|
||||
use core\context\course as context_course;
|
||||
use core\context_helper;
|
||||
use core\url;
|
||||
use moodle_page;
|
||||
|
||||
/**
|
||||
* The global navigation class used especially for AJAX requests.
|
||||
*
|
||||
* The primary methods that are used in the global navigation class have been overriden
|
||||
* to ensure that only the relevant branch is generated at the root of the tree.
|
||||
* This can be done because AJAX is only used when the backwards structure for the
|
||||
* requested branch exists.
|
||||
* This has been done only because it shortens the amounts of information that is generated
|
||||
* which of course will speed up the response time.. because no one likes laggy AJAX.
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2009 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class global_navigation_for_ajax extends global_navigation {
|
||||
/** @var int used for determining what type of navigation_node::TYPE_* is being used */
|
||||
protected $branchtype;
|
||||
|
||||
/** @var int the instance id */
|
||||
protected $instanceid;
|
||||
|
||||
/** @var array Holds an array of expandable nodes */
|
||||
protected $expandable = [];
|
||||
|
||||
/**
|
||||
* Constructs the navigation for use in an AJAX request
|
||||
*
|
||||
* @param moodle_page $page moodle_page object
|
||||
* @param int $branchtype
|
||||
* @param int $id
|
||||
*/
|
||||
public function __construct($page, $branchtype, $id) {
|
||||
$this->page = $page;
|
||||
$this->cache = new navigation_cache(self::CACHE_NAME);
|
||||
$this->children = new navigation_node_collection();
|
||||
$this->branchtype = $branchtype;
|
||||
$this->instanceid = $id;
|
||||
$this->initialise();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function initialise() {
|
||||
global $DB, $SITE;
|
||||
|
||||
if ($this->initialised || during_initial_install()) {
|
||||
return $this->expandable;
|
||||
}
|
||||
$this->initialised = true;
|
||||
|
||||
$this->rootnodes = [];
|
||||
$this->rootnodes['site'] = $this->add_course($SITE);
|
||||
$this->rootnodes['mycourses'] = $this->add(
|
||||
get_string('mycourses'),
|
||||
new url('/my/courses.php'),
|
||||
self::TYPE_ROOTNODE,
|
||||
null,
|
||||
'mycourses'
|
||||
);
|
||||
$this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses');
|
||||
// The courses branch is always displayed, and is always expandable (although may be empty).
|
||||
// This mimicks what is done during {@link global_navigation::initialise()}.
|
||||
$this->rootnodes['courses']->isexpandable = true;
|
||||
|
||||
// Branchtype will be one of navigation_node::TYPE_*.
|
||||
switch ($this->branchtype) {
|
||||
case 0:
|
||||
if ($this->instanceid === 'mycourses') {
|
||||
$this->load_courses_enrolled();
|
||||
} else if ($this->instanceid === 'courses') {
|
||||
$this->load_courses_other();
|
||||
}
|
||||
break;
|
||||
case self::TYPE_CATEGORY:
|
||||
$this->load_category($this->instanceid);
|
||||
break;
|
||||
case self::TYPE_MY_CATEGORY:
|
||||
$this->load_category($this->instanceid, self::TYPE_MY_CATEGORY);
|
||||
break;
|
||||
case self::TYPE_COURSE:
|
||||
$course = $DB->get_record('course', ['id' => $this->instanceid], '*', MUST_EXIST);
|
||||
if (!can_access_course($course, null, '', true)) {
|
||||
// Thats OK all courses are expandable by default. We don't need to actually expand it we can just
|
||||
// add the course node and break. This leads to an empty node.
|
||||
$this->add_course($course);
|
||||
break;
|
||||
}
|
||||
require_course_login($course, true, null, false, true);
|
||||
$this->page->set_context(context_course::instance($course->id));
|
||||
$coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
|
||||
$this->add_course_essentials($coursenode, $course);
|
||||
$this->load_course_sections($course, $coursenode);
|
||||
break;
|
||||
case self::TYPE_SECTION:
|
||||
$sql = 'SELECT c.*, cs.section AS sectionnumber
|
||||
FROM {course} c
|
||||
LEFT JOIN {course_sections} cs ON cs.course = c.id
|
||||
WHERE cs.id = ?';
|
||||
$course = $DB->get_record_sql($sql, [$this->instanceid], MUST_EXIST);
|
||||
require_course_login($course, true, null, false, true);
|
||||
$this->page->set_context(context_course::instance($course->id));
|
||||
$coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
|
||||
$this->add_course_essentials($coursenode, $course);
|
||||
$this->load_course_sections($course, $coursenode, $course->sectionnumber);
|
||||
break;
|
||||
case self::TYPE_ACTIVITY:
|
||||
$sql = "SELECT c.*
|
||||
FROM {course} c
|
||||
JOIN {course_modules} cm ON cm.course = c.id
|
||||
WHERE cm.id = :cmid";
|
||||
$params = ['cmid' => $this->instanceid];
|
||||
$course = $DB->get_record_sql($sql, $params, MUST_EXIST);
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$cm = $modinfo->get_cm($this->instanceid);
|
||||
require_course_login($course, true, $cm, false, true);
|
||||
$this->page->set_context(context_module::instance($cm->id));
|
||||
$coursenode = $this->add_course($course, false, self::COURSE_CURRENT);
|
||||
$this->load_course_sections($course, $coursenode, null, $cm);
|
||||
$activitynode = $coursenode->find($cm->id, self::TYPE_ACTIVITY);
|
||||
if ($activitynode) {
|
||||
$modulenode = $this->load_activity($cm, $course, $activitynode);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('Unknown type');
|
||||
return $this->expandable;
|
||||
}
|
||||
|
||||
if ($this->page->context->contextlevel == CONTEXT_COURSE && $this->page->context->instanceid != $SITE->id) {
|
||||
$this->load_for_user(null, true);
|
||||
}
|
||||
|
||||
// Give the local plugins a chance to include some navigation if they want.
|
||||
$this->load_local_plugin_navigation();
|
||||
|
||||
$this->find_expandable($this->expandable);
|
||||
return $this->expandable;
|
||||
}
|
||||
|
||||
/**
|
||||
* They've expanded the general 'courses' branch.
|
||||
*/
|
||||
protected function load_courses_other() {
|
||||
$this->load_all_courses();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a single category into the AJAX navigation.
|
||||
*
|
||||
* This function is special in that it doesn't concern itself with the parent of
|
||||
* the requested category or its siblings.
|
||||
* This is because with the AJAX navigation we know exactly what is wanted and only need to
|
||||
* request that.
|
||||
*
|
||||
* @param int $categoryid id of category to load in navigation.
|
||||
* @param int $nodetype type of node, if category is under MyHome then it's TYPE_MY_CATEGORY
|
||||
*/
|
||||
protected function load_category($categoryid, $nodetype = self::TYPE_CATEGORY) {
|
||||
global $CFG, $DB;
|
||||
|
||||
$limit = 20;
|
||||
if (!empty($CFG->navcourselimit)) {
|
||||
$limit = (int)$CFG->navcourselimit;
|
||||
}
|
||||
|
||||
$catcontextsql = context_helper::get_preload_record_columns_sql('ctx');
|
||||
$sql = "SELECT cc.*, $catcontextsql
|
||||
FROM {course_categories} cc
|
||||
JOIN {context} ctx ON cc.id = ctx.instanceid
|
||||
WHERE ctx.contextlevel = " . CONTEXT_COURSECAT . " AND
|
||||
(cc.id = :categoryid1 OR cc.parent = :categoryid2)
|
||||
ORDER BY cc.depth ASC, cc.sortorder ASC, cc.id ASC";
|
||||
$params = ['categoryid1' => $categoryid, 'categoryid2' => $categoryid];
|
||||
$categories = $DB->get_recordset_sql($sql, $params, 0, $limit);
|
||||
$categorylist = [];
|
||||
$subcategories = [];
|
||||
$basecategory = null;
|
||||
foreach ($categories as $category) {
|
||||
$categorylist[] = $category->id;
|
||||
context_helper::preload_from_record($category);
|
||||
if ($category->id == $categoryid) {
|
||||
$this->add_category($category, $this, $nodetype);
|
||||
$basecategory = $this->addedcategories[$category->id];
|
||||
} else {
|
||||
$subcategories[$category->id] = $category;
|
||||
}
|
||||
}
|
||||
$categories->close();
|
||||
|
||||
// If category is shown in MyHome then only show enrolled courses and hide empty subcategories,
|
||||
// else show all courses.
|
||||
if ($nodetype === self::TYPE_MY_CATEGORY) {
|
||||
$courses = enrol_get_my_courses('*');
|
||||
$categoryids = [];
|
||||
|
||||
// Only search for categories if basecategory was found.
|
||||
if (!is_null($basecategory)) {
|
||||
// Get course parent category ids.
|
||||
foreach ($courses as $course) {
|
||||
$categoryids[] = $course->category;
|
||||
}
|
||||
|
||||
// Get a unique list of category ids which a part of the path
|
||||
// to user's courses.
|
||||
$coursesubcategories = [];
|
||||
$addedsubcategories = [];
|
||||
|
||||
[$sql, $params] = $DB->get_in_or_equal($categoryids);
|
||||
$categories = $DB->get_recordset_select('course_categories', 'id ' . $sql, $params, 'sortorder, id', 'id, path');
|
||||
|
||||
foreach ($categories as $category) {
|
||||
$coursesubcategories = array_merge($coursesubcategories, explode('/', trim($category->path, "/")));
|
||||
}
|
||||
$categories->close();
|
||||
$coursesubcategories = array_unique($coursesubcategories);
|
||||
|
||||
// Only add a subcategory if it is part of the path to user's course and
|
||||
// wasn't already added.
|
||||
foreach ($subcategories as $subid => $subcategory) {
|
||||
if (
|
||||
in_array($subid, $coursesubcategories) &&
|
||||
!in_array($subid, $addedsubcategories)
|
||||
) {
|
||||
$this->add_category($subcategory, $basecategory, $nodetype);
|
||||
$addedsubcategories[] = $subid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($courses as $course) {
|
||||
// Add course if it's in category.
|
||||
if (in_array($course->category, $categorylist)) {
|
||||
$this->add_course($course, true, self::COURSE_MY);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!is_null($basecategory)) {
|
||||
foreach ($subcategories as $key => $category) {
|
||||
$this->add_category($category, $basecategory, $nodetype);
|
||||
}
|
||||
}
|
||||
$courses = $DB->get_recordset('course', ['category' => $categoryid], 'sortorder', '*', 0, $limit);
|
||||
foreach ($courses as $course) {
|
||||
$this->add_course($course);
|
||||
}
|
||||
$courses->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of expandable nodes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_expandable() {
|
||||
return $this->expandable;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(global_navigation_for_ajax::class, \global_navigation_for_ajax::class);
|
||||
@@ -0,0 +1,399 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\context\course as context_course;
|
||||
use core\context_helper;
|
||||
use core_course_category;
|
||||
use core\context\coursecat as context_coursecat;
|
||||
use core\output\action_link;
|
||||
use core\output\pix_icon;
|
||||
use core\url;
|
||||
use moodle_page;
|
||||
|
||||
/**
|
||||
* Navbar class
|
||||
*
|
||||
* This class is used to manage the navbar, which is initialised from the navigation
|
||||
* object held by PAGE
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2009 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class navbar extends navigation_node {
|
||||
/** @var bool A switch for whether the navbar is initialised or not */
|
||||
protected $initialised = false;
|
||||
/** @var mixed keys used to reference the nodes on the navbar */
|
||||
protected $keys = [];
|
||||
/** @var null|string content of the navbar */
|
||||
protected $content = null;
|
||||
/** @var moodle_page object the moodle page that this navbar belongs to */
|
||||
protected $page;
|
||||
/** @var bool A switch for whether to ignore the active navigation information */
|
||||
protected $ignoreactive = false;
|
||||
/** @var bool A switch to let us know if we are in the middle of an install */
|
||||
protected $duringinstall = false;
|
||||
/** @var bool A switch for whether the navbar has items */
|
||||
protected $hasitems = false;
|
||||
/** @var array An array of navigation nodes for the navbar */
|
||||
protected $items;
|
||||
/** @var array An array of child node objects */
|
||||
public $children = [];
|
||||
/** @var bool A switch for whether we want to include the root node in the navbar */
|
||||
public $includesettingsbase = false;
|
||||
/** @var breadcrumb_navigation_node[] $prependchildren */
|
||||
protected $prependchildren = [];
|
||||
|
||||
/**
|
||||
* The almighty constructor
|
||||
*
|
||||
* @param moodle_page $page
|
||||
*/
|
||||
public function __construct(moodle_page $page) {
|
||||
global $CFG;
|
||||
if (during_initial_install()) {
|
||||
$this->duringinstall = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->page = $page;
|
||||
$this->text = get_string('home');
|
||||
$this->shorttext = get_string('home');
|
||||
$this->action = new url($CFG->wwwroot);
|
||||
$this->nodetype = self::NODETYPE_BRANCH;
|
||||
$this->type = self::TYPE_SYSTEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick check to see if the navbar will have items in.
|
||||
*
|
||||
* @return bool Returns true if the navbar will have items, false otherwise
|
||||
*/
|
||||
public function has_items() {
|
||||
if ($this->duringinstall) {
|
||||
return false;
|
||||
} else if ($this->hasitems !== false) {
|
||||
return true;
|
||||
}
|
||||
$outcome = false;
|
||||
if (count($this->children) > 0 || count($this->prependchildren) > 0) {
|
||||
// There have been manually added items - there are definitely items.
|
||||
$outcome = true;
|
||||
} else if (!$this->ignoreactive) {
|
||||
// We will need to initialise the navigation structure to check if there are active items.
|
||||
$this->page->navigation->initialise($this->page);
|
||||
$outcome = ($this->page->navigation->contains_active_node() || $this->page->settingsnav->contains_active_node());
|
||||
}
|
||||
$this->hasitems = $outcome;
|
||||
return $outcome;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on/off ignore active
|
||||
*
|
||||
* @param bool $setting
|
||||
*/
|
||||
public function ignore_active($setting = true) {
|
||||
$this->ignoreactive = ($setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a navigation node
|
||||
*
|
||||
* @param string|int $key for referencing the navbar nodes
|
||||
* @param int $type breadcrumb_navigation_node::TYPE_*
|
||||
* @return breadcrumb_navigation_node|bool
|
||||
*/
|
||||
public function get($key, $type = null) {
|
||||
foreach ($this->children as &$child) {
|
||||
if ($child->key === $key && ($type == null || $type == $child->type)) {
|
||||
return $child;
|
||||
}
|
||||
}
|
||||
foreach ($this->prependchildren as &$child) {
|
||||
if ($child->key === $key && ($type == null || $type == $child->type)) {
|
||||
return $child;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Returns an array of breadcrumb_navigation_nodes that make up the navbar.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_items() {
|
||||
global $CFG;
|
||||
$items = [];
|
||||
// Make sure that navigation is initialised.
|
||||
if (!$this->has_items()) {
|
||||
return $items;
|
||||
}
|
||||
if ($this->items !== null) {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
if (count($this->children) > 0) {
|
||||
// Add the custom children.
|
||||
$items = array_reverse($this->children);
|
||||
}
|
||||
|
||||
// Check if navigation contains the active node.
|
||||
if (!$this->ignoreactive) {
|
||||
// We will need to ensure the navigation has been initialised.
|
||||
$this->page->navigation->initialise($this->page);
|
||||
// Now find the active nodes on both the navigation and settings.
|
||||
$navigationactivenode = $this->page->navigation->find_active_node();
|
||||
$settingsactivenode = $this->page->settingsnav->find_active_node();
|
||||
|
||||
if ($navigationactivenode && $settingsactivenode) {
|
||||
// Parse a combined navigation tree.
|
||||
while ($settingsactivenode && $settingsactivenode->parent !== null) {
|
||||
if (!$settingsactivenode->mainnavonly) {
|
||||
$items[] = new breadcrumb_navigation_node($settingsactivenode);
|
||||
}
|
||||
$settingsactivenode = $settingsactivenode->parent;
|
||||
}
|
||||
if (!$this->includesettingsbase) {
|
||||
// Removes the first node from the settings (root node) from the list.
|
||||
array_pop($items);
|
||||
}
|
||||
while ($navigationactivenode && $navigationactivenode->parent !== null) {
|
||||
if (!$navigationactivenode->mainnavonly) {
|
||||
$items[] = new breadcrumb_navigation_node($navigationactivenode);
|
||||
}
|
||||
if (
|
||||
!empty($CFG->navshowcategories) &&
|
||||
$navigationactivenode->type === self::TYPE_COURSE &&
|
||||
$navigationactivenode->parent->key === 'currentcourse'
|
||||
) {
|
||||
foreach ($this->get_course_categories() as $item) {
|
||||
$items[] = new breadcrumb_navigation_node($item);
|
||||
}
|
||||
}
|
||||
$navigationactivenode = $navigationactivenode->parent;
|
||||
}
|
||||
} else if ($navigationactivenode) {
|
||||
// Parse the navigation tree to get the active node.
|
||||
while ($navigationactivenode && $navigationactivenode->parent !== null) {
|
||||
if (!$navigationactivenode->mainnavonly) {
|
||||
$items[] = new breadcrumb_navigation_node($navigationactivenode);
|
||||
}
|
||||
if (
|
||||
!empty($CFG->navshowcategories) &&
|
||||
$navigationactivenode->type === self::TYPE_COURSE &&
|
||||
$navigationactivenode->parent->key === 'currentcourse'
|
||||
) {
|
||||
foreach ($this->get_course_categories() as $item) {
|
||||
$items[] = new breadcrumb_navigation_node($item);
|
||||
}
|
||||
}
|
||||
$navigationactivenode = $navigationactivenode->parent;
|
||||
}
|
||||
} else if ($settingsactivenode) {
|
||||
// Parse the settings navigation to get the active node.
|
||||
while ($settingsactivenode && $settingsactivenode->parent !== null) {
|
||||
if (!$settingsactivenode->mainnavonly) {
|
||||
$items[] = new breadcrumb_navigation_node($settingsactivenode);
|
||||
}
|
||||
$settingsactivenode = $settingsactivenode->parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$items[] = new breadcrumb_navigation_node([
|
||||
'text' => $this->page->navigation->text,
|
||||
'shorttext' => $this->page->navigation->shorttext,
|
||||
'key' => $this->page->navigation->key,
|
||||
'action' => $this->page->navigation->action,
|
||||
]);
|
||||
|
||||
if (count($this->prependchildren) > 0) {
|
||||
// Add the custom children.
|
||||
$items = array_merge($items, array_reverse($this->prependchildren));
|
||||
}
|
||||
|
||||
$last = reset($items);
|
||||
if ($last) {
|
||||
$last->set_last(true);
|
||||
}
|
||||
$this->items = array_reverse($items);
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of categories leading to this course.
|
||||
*
|
||||
* This function is used by {@link navbar::get_items()} to add back the "courses"
|
||||
* node and category chain leading to the current course. Note that this is only ever
|
||||
* called for the current course, so we don't need to bother taking in any parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_course_categories() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/course/lib.php');
|
||||
|
||||
$categories = [];
|
||||
$cap = 'moodle/category:viewhiddencategories';
|
||||
$showcategories = !core_course_category::is_simple_site();
|
||||
|
||||
if ($showcategories) {
|
||||
foreach ($this->page->categories as $category) {
|
||||
$context = context_coursecat::instance($category->id);
|
||||
if (!core_course_category::can_view_category($category)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$displaycontext = context_helper::get_navigation_filter_context($context);
|
||||
$url = new url('/course/index.php', ['categoryid' => $category->id]);
|
||||
$name = format_string($category->name, true, ['context' => $displaycontext]);
|
||||
$categorynode = breadcrumb_navigation_node::create($name, $url, self::TYPE_CATEGORY, null, $category->id);
|
||||
if (!$category->visible) {
|
||||
$categorynode->hidden = true;
|
||||
}
|
||||
$categories[] = $categorynode;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't show the 'course' node if enrolled in this course.
|
||||
$coursecontext = context_course::instance($this->page->course->id);
|
||||
if (!is_enrolled($coursecontext, null, '', true)) {
|
||||
$courses = $this->page->navigation->get('courses');
|
||||
if (!$courses) {
|
||||
// Courses node may not be present.
|
||||
$courses = breadcrumb_navigation_node::create(
|
||||
get_string('courses'),
|
||||
new url('/course/index.php'),
|
||||
self::TYPE_CONTAINER
|
||||
);
|
||||
}
|
||||
$categories[] = $courses;
|
||||
}
|
||||
|
||||
return $categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new breadcrumb_navigation_node to the navbar, overrides parent::add
|
||||
*
|
||||
* This function overrides {@link breadcrumb_navigation_node::add()} so that we can change
|
||||
* the way nodes get added to allow us to simply call add and have the node added to the
|
||||
* end of the navbar
|
||||
*
|
||||
* @param string $text
|
||||
* @param string|url|action_link $action An action to associate with this node.
|
||||
* @param int $type One of navigation_node::TYPE_*
|
||||
* @param string $shorttext
|
||||
* @param string|int $key A key to identify this node with. Key + type is unique to a parent.
|
||||
* @param pix_icon $icon An optional icon to use for this node.
|
||||
* @return navigation_node
|
||||
*/
|
||||
public function add($text, $action = null, $type = self::TYPE_CUSTOM, $shorttext = null, $key = null, ?pix_icon $icon = null) {
|
||||
if ($this->content !== null) {
|
||||
debugging('Nav bar items must be printed before $OUTPUT->header() has been called', DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
// Properties array used when creating the new navigation node.
|
||||
$itemarray = [
|
||||
'text' => $text,
|
||||
'type' => $type,
|
||||
];
|
||||
// Set the action if one was provided.
|
||||
if ($action !== null) {
|
||||
$itemarray['action'] = $action;
|
||||
}
|
||||
// Set the shorttext if one was provided.
|
||||
if ($shorttext !== null) {
|
||||
$itemarray['shorttext'] = $shorttext;
|
||||
}
|
||||
// Set the icon if one was provided.
|
||||
if ($icon !== null) {
|
||||
$itemarray['icon'] = $icon;
|
||||
}
|
||||
// Default the key to the number of children if not provided.
|
||||
if ($key === null) {
|
||||
$key = count($this->children);
|
||||
}
|
||||
// Set the key.
|
||||
$itemarray['key'] = $key;
|
||||
// Set the parent to this node.
|
||||
$itemarray['parent'] = $this;
|
||||
// Add the child using the navigation_node_collections add method.
|
||||
$this->children[] = new breadcrumb_navigation_node($itemarray);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends a new navigation_node to the start of the navbar
|
||||
*
|
||||
* @param string $text
|
||||
* @param string|url|action_link $action An action to associate with this node.
|
||||
* @param int $type One of navigation_node::TYPE_*
|
||||
* @param string $shorttext
|
||||
* @param string|int $key A key to identify this node with. Key + type is unique to a parent.
|
||||
* @param pix_icon $icon An optional icon to use for this node.
|
||||
* @return navigation_node
|
||||
*/
|
||||
public function prepend(
|
||||
$text,
|
||||
$action = null,
|
||||
$type = self::TYPE_CUSTOM,
|
||||
$shorttext = null,
|
||||
$key = null,
|
||||
?pix_icon $icon = null,
|
||||
) {
|
||||
if ($this->content !== null) {
|
||||
debugging('Nav bar items must be printed before $OUTPUT->header() has been called', DEBUG_DEVELOPER);
|
||||
}
|
||||
// Properties array used when creating the new navigation node.
|
||||
$itemarray = [
|
||||
'text' => $text,
|
||||
'type' => $type,
|
||||
];
|
||||
// Set the action if one was provided.
|
||||
if ($action !== null) {
|
||||
$itemarray['action'] = $action;
|
||||
}
|
||||
// Set the shorttext if one was provided.
|
||||
if ($shorttext !== null) {
|
||||
$itemarray['shorttext'] = $shorttext;
|
||||
}
|
||||
// Set the icon if one was provided.
|
||||
if ($icon !== null) {
|
||||
$itemarray['icon'] = $icon;
|
||||
}
|
||||
// Default the key to the number of children if not provided.
|
||||
if ($key === null) {
|
||||
$key = count($this->children);
|
||||
}
|
||||
// Set the key.
|
||||
$itemarray['key'] = $key;
|
||||
// Set the parent to this node.
|
||||
$itemarray['parent'] = $this;
|
||||
// Add the child node to the prepend list.
|
||||
$this->prependchildren[] = new breadcrumb_navigation_node($itemarray);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(navbar::class, \navbar::class);
|
||||
@@ -0,0 +1,208 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core_cache\cache;
|
||||
use core_cache\session_cache;
|
||||
use core_shutdown_manager;
|
||||
|
||||
/**
|
||||
* The navigation_cache class is used for global and settings navigation data.
|
||||
*
|
||||
* It provides an easy access to the session cache with TTL of 1800 seconds.
|
||||
*
|
||||
* Example use:
|
||||
* <code php>
|
||||
* if (!$cache->viewdiscussion()) {
|
||||
* // Code to do stuff and produce cachable content
|
||||
* $cache->viewdiscussion = has_capability('mod/forum:viewdiscussion', $coursecontext);
|
||||
* }
|
||||
* $content = $cache->viewdiscussion;
|
||||
* </code>
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2009 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class navigation_cache {
|
||||
/** @var session_cache The session cache instance */
|
||||
protected $cache;
|
||||
/** @var array The current cache area data */
|
||||
protected $session = [];
|
||||
|
||||
/**
|
||||
* @var string A unique string to segregate this particular cache.
|
||||
* It can either be unique to start a fresh cache or shared to use an existing cache.
|
||||
*/
|
||||
protected $area;
|
||||
/** @var int cache time information */
|
||||
#[\core\attribute\deprecated(null, since: '4.5', reason: 'This constant is no longer needed.', mdl: 'MDL-79628')]
|
||||
public const CACHETIME = 0;
|
||||
/** @var int cache user id */
|
||||
#[\core\attribute\deprecated(null, since: '4.5', reason: 'This constant is no longer needed.', mdl: 'MDL-79628')]
|
||||
public const CACHEUSERID = 1;
|
||||
/** @var int cache value */
|
||||
public const CACHEVALUE = 2;
|
||||
/** @var null|array An array of cache areas to expire on shutdown */
|
||||
public static $volatilecaches = null;
|
||||
|
||||
/**
|
||||
* Contructor for the cache. Requires a area string be passed in.
|
||||
*
|
||||
* @param string $area The unique string to segregate this particular cache.
|
||||
* @param int $timeout Deprecated since Moodle 4.5. The number of seconds to time the information out after
|
||||
*/
|
||||
public function __construct($area, $timeout = null) {
|
||||
if ($timeout !== null) {
|
||||
debugging(
|
||||
'The timeout argument has been deprecated. Please remove it from your method calls.',
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
}
|
||||
global $USER;
|
||||
$this->area = "user_{$USER->id}_{$area}";
|
||||
$this->cache = cache::make('core', 'navigation_cache');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the navigation cache is initialised
|
||||
*
|
||||
* This is called for each access and ensures that no data is put into the cache before it is required.
|
||||
*/
|
||||
protected function ensure_navigation_cache_initialised() {
|
||||
if (empty($this->session)) {
|
||||
$this->session = $this->cache->get($this->area);
|
||||
if (!is_array($this->session)) {
|
||||
$this->session = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Method to retrieve a cached item by simply calling using = cache->key
|
||||
*
|
||||
* @param mixed $key The identifier for the cached information
|
||||
* @return mixed|void The cached information or void if not found
|
||||
*/
|
||||
public function __get($key) {
|
||||
if (!$this->cached($key)) {
|
||||
return;
|
||||
}
|
||||
return unserialize($this->session[$key][self::CACHEVALUE]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method that simply uses {@see navigation_cache::set()} to store an item in the cache
|
||||
*
|
||||
* @param string|int $key The key to store the information against
|
||||
* @param mixed $information The information to cache
|
||||
*/
|
||||
public function __set($key, $information) {
|
||||
$this->set($key, $information);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets some information in the session cache for later retrieval
|
||||
*
|
||||
* @param string|int $key
|
||||
* @param mixed $information
|
||||
*/
|
||||
public function set($key, $information) {
|
||||
$this->ensure_navigation_cache_initialised();
|
||||
$information = serialize($information);
|
||||
$this->session[$key] = [self::CACHEVALUE => $information];
|
||||
$this->cache->set($this->area, $this->session);
|
||||
}
|
||||
/**
|
||||
* Check the existence of the identifier in the cache
|
||||
*
|
||||
* @param string|int $key The identifier to check
|
||||
* @return bool True if the item exists in the cache, false otherwise
|
||||
*/
|
||||
public function cached($key) {
|
||||
$this->ensure_navigation_cache_initialised();
|
||||
return isset($this->session[$key]) &&
|
||||
is_array($this->session[$key]);
|
||||
}
|
||||
/**
|
||||
* Compare something to it's equivilant in the cache
|
||||
*
|
||||
* @param string $key The key to check
|
||||
* @param mixed $value The value to compare
|
||||
* @param bool $serialise Whether to serialise the value before comparison
|
||||
* this should only be set to false if the value is already
|
||||
* serialised
|
||||
* @return bool True if the value is the same as the cached one, false otherwise
|
||||
*/
|
||||
public function compare($key, $value, $serialise = true) {
|
||||
if ($this->cached($key)) {
|
||||
if ($serialise) {
|
||||
$value = serialize($value);
|
||||
}
|
||||
return $this->session[$key][self::CACHEVALUE] === $value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Deletes the entire cache area, forcing a fresh cache to be created
|
||||
*/
|
||||
public function clear() {
|
||||
$this->cache->delete($this->area);
|
||||
$this->session = [];
|
||||
}
|
||||
/**
|
||||
* Marks the cache as volatile (likely to change)
|
||||
*
|
||||
* Any caches marked as volatile will be destroyed on shutdown by {@see navigation_node::destroy_volatile_caches()}
|
||||
*
|
||||
* @param bool $setting True to mark the cache as volatile, false to remove the volatile flag
|
||||
*/
|
||||
public function volatile($setting = true) {
|
||||
if (self::$volatilecaches === null) {
|
||||
self::$volatilecaches = [];
|
||||
core_shutdown_manager::register_function(['navigation_cache', 'destroy_volatile_caches']);
|
||||
}
|
||||
|
||||
if ($setting) {
|
||||
self::$volatilecaches[$this->area] = $this->area;
|
||||
} else if (array_key_exists($this->area, self::$volatilecaches)) {
|
||||
unset(self::$volatilecaches[$this->area]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys all caches marked as volatile
|
||||
*
|
||||
* This function is static and works with the static volatilecaches property of navigation cache.
|
||||
* It manually resets the cached areas back to an empty array.
|
||||
*/
|
||||
public static function destroy_volatile_caches() {
|
||||
if (is_array(self::$volatilecaches) && count(self::$volatilecaches) > 0) {
|
||||
$cache = cache::make('core', 'navigation_cache');
|
||||
foreach (self::$volatilecaches as $area) {
|
||||
$cache->delete($area);
|
||||
}
|
||||
self::$volatilecaches = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(navigation_cache::class, \navigation_cache::class);
|
||||
@@ -0,0 +1,131 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\output\action_link;
|
||||
use core\output\pix_icon;
|
||||
use core\url;
|
||||
|
||||
/**
|
||||
* Simple class used to output a navigation branch in XML
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2009 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class navigation_json {
|
||||
/** @var array An array of different node types */
|
||||
protected $nodetype = ['node', 'branch'];
|
||||
/** @var array An array of node keys and types */
|
||||
protected $expandable = [];
|
||||
/**
|
||||
* Turns a branch and all of its children into XML
|
||||
*
|
||||
* @param navigation_node $branch
|
||||
* @return string XML string
|
||||
*/
|
||||
public function convert($branch) {
|
||||
$xml = $this->convert_child($branch);
|
||||
return $xml;
|
||||
}
|
||||
/**
|
||||
* Set the expandable items in the array so that we have enough information
|
||||
* to attach AJAX events
|
||||
* @param array $expandable
|
||||
*/
|
||||
public function set_expandable($expandable) {
|
||||
foreach ($expandable as $node) {
|
||||
$this->expandable[$node['key'] . ':' . $node['type']] = $node;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Recusively converts a child node and its children to XML for output
|
||||
*
|
||||
* @param navigation_node $child The child to convert
|
||||
* @param int $depth Pointlessly used to track the depth of the XML structure
|
||||
* @return string JSON
|
||||
*/
|
||||
protected function convert_child($child, $depth = 1) {
|
||||
if (!$child->display) {
|
||||
return '';
|
||||
}
|
||||
$attributes = [];
|
||||
$attributes['id'] = $child->id;
|
||||
$attributes['name'] = (string)$child->text; // This can be lang_string object so typecast it.
|
||||
$attributes['type'] = $child->type;
|
||||
$attributes['key'] = $child->key;
|
||||
$attributes['class'] = $child->get_css_type();
|
||||
$attributes['requiresajaxloading'] = $child->requiresajaxloading;
|
||||
|
||||
if ($child->icon instanceof pix_icon) {
|
||||
$attributes['icon'] = [
|
||||
'component' => $child->icon->component,
|
||||
'pix' => $child->icon->pix,
|
||||
];
|
||||
foreach ($child->icon->attributes as $key => $value) {
|
||||
if ($key == 'class') {
|
||||
$attributes['icon']['classes'] = explode(' ', $value);
|
||||
} else if (!array_key_exists($key, $attributes['icon'])) {
|
||||
$attributes['icon'][$key] = $value;
|
||||
}
|
||||
}
|
||||
} else if (!empty($child->icon)) {
|
||||
$attributes['icon'] = (string)$child->icon;
|
||||
}
|
||||
|
||||
if ($child->forcetitle || $child->title !== $child->text) {
|
||||
$attributes['title'] = htmlentities($child->title ?? '', ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
if (array_key_exists($child->key . ':' . $child->type, $this->expandable)) {
|
||||
$attributes['expandable'] = $child->key;
|
||||
$child->add_class($this->expandable[$child->key . ':' . $child->type]['id']);
|
||||
}
|
||||
|
||||
if (count($child->classes) > 0) {
|
||||
$attributes['class'] .= ' ' . join(' ', $child->classes);
|
||||
}
|
||||
if (is_string($child->action)) {
|
||||
$attributes['link'] = $child->action;
|
||||
} else if ($child->action instanceof url) {
|
||||
$attributes['link'] = $child->action->out();
|
||||
} else if ($child->action instanceof action_link) {
|
||||
$attributes['link'] = $child->action->url->out();
|
||||
}
|
||||
$attributes['hidden'] = ($child->hidden);
|
||||
$attributes['haschildren'] = ($child->children->count() > 0 || $child->type == navigation_node::TYPE_CATEGORY);
|
||||
$attributes['haschildren'] = $attributes['haschildren'] || $child->type == navigation_node::TYPE_MY_CATEGORY;
|
||||
|
||||
if ($child->children->count() > 0) {
|
||||
$attributes['children'] = [];
|
||||
foreach ($child->children as $subchild) {
|
||||
$attributes['children'][] = $this->convert_child($subchild, $depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($depth > 1) {
|
||||
return $attributes;
|
||||
} else {
|
||||
return json_encode($attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(navigation_json::class, \navigation_json::class);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,287 @@
|
||||
<?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\navigation;
|
||||
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Navigation node collection
|
||||
*
|
||||
* This class is responsible for managing a collection of navigation nodes.
|
||||
* It is required because a node's unique identifier is a combination of both its
|
||||
* key and its type.
|
||||
*
|
||||
* Originally an array was used with a string key that was a combination of the two
|
||||
* however it was decided that a better solution would be to use a class that
|
||||
* implements the standard IteratorAggregate interface.
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2010 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class navigation_node_collection implements Countable, IteratorAggregate {
|
||||
/**
|
||||
* A multidimensional array to where the first key is the type and the second
|
||||
* key is the nodes key.
|
||||
* @var array
|
||||
*/
|
||||
protected $collection = [];
|
||||
/**
|
||||
* An array that contains references to nodes in the same order they were added.
|
||||
* This is maintained as a progressive array.
|
||||
* @var array
|
||||
*/
|
||||
protected $orderedcollection = [];
|
||||
/**
|
||||
* A reference to the last node that was added to the collection
|
||||
* @var navigation_node
|
||||
*/
|
||||
protected $last = null;
|
||||
/**
|
||||
* The total number of items added to this array.
|
||||
* @var int
|
||||
*/
|
||||
protected $count = 0;
|
||||
|
||||
/**
|
||||
* Label for collection of nodes.
|
||||
* @var string
|
||||
*/
|
||||
protected $collectionlabel = '';
|
||||
|
||||
/**
|
||||
* Adds a navigation node to the collection.
|
||||
*
|
||||
* @param navigation_node $node Node to add
|
||||
* @param string $beforekey If specified, adds before a node with this key,
|
||||
* otherwise adds at end
|
||||
* @return navigation_node Added node
|
||||
*/
|
||||
public function add(navigation_node $node, $beforekey = null) {
|
||||
global $CFG;
|
||||
$key = $node->key;
|
||||
$type = $node->type;
|
||||
|
||||
// First check we have a 2nd dimension for this type.
|
||||
if (!array_key_exists($type, $this->orderedcollection)) {
|
||||
$this->orderedcollection[$type] = [];
|
||||
}
|
||||
// Check for a collision and report if debugging is turned on.
|
||||
if ($CFG->debug && array_key_exists($key, $this->orderedcollection[$type])) {
|
||||
debugging('Navigation node intersect: Adding a node that already exists ' . $key, DEBUG_DEVELOPER);
|
||||
}
|
||||
|
||||
// Find the key to add before.
|
||||
$newindex = $this->count;
|
||||
$last = true;
|
||||
if ($beforekey !== null) {
|
||||
foreach ($this->collection as $index => $othernode) {
|
||||
if ($othernode->key === $beforekey) {
|
||||
$newindex = $index;
|
||||
$last = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($newindex === $this->count) {
|
||||
debugging('Navigation node add_before: Reference node not found ' . $beforekey .
|
||||
', options: ' . implode(' ', $this->get_key_list()), DEBUG_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the node to the appropriate place in the by-type structure (which
|
||||
// is not ordered, despite the variable name).
|
||||
$this->orderedcollection[$type][$key] = $node;
|
||||
if (!$last) {
|
||||
// Update existing references in the ordered collection (which is the
|
||||
// one that isn't called 'ordered') to shuffle them along if required.
|
||||
for ($oldindex = $this->count; $oldindex > $newindex; $oldindex--) {
|
||||
$this->collection[$oldindex] = $this->collection[$oldindex - 1];
|
||||
}
|
||||
}
|
||||
// Add a reference to the node to the progressive collection.
|
||||
$this->collection[$newindex] = $this->orderedcollection[$type][$key];
|
||||
// Update the last property to a reference to this new node.
|
||||
$this->last = $this->orderedcollection[$type][$key];
|
||||
|
||||
// Reorder the array by index if needed.
|
||||
if (!$last) {
|
||||
ksort($this->collection);
|
||||
}
|
||||
$this->count++;
|
||||
// Return the reference to the now added node.
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all the keys of all the nodes.
|
||||
*
|
||||
* @return array the keys.
|
||||
*/
|
||||
public function get_key_list() {
|
||||
$keys = [];
|
||||
foreach ($this->collection as $node) {
|
||||
$keys[] = $node->key;
|
||||
}
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a label for this collection.
|
||||
*
|
||||
* @param string $label
|
||||
*/
|
||||
public function set_collectionlabel($label) {
|
||||
$this->collectionlabel = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a label for this collection.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_collectionlabel() {
|
||||
return $this->collectionlabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a node from this collection.
|
||||
*
|
||||
* @param string|int $key The key of the node we want to find.
|
||||
* @param int $type One of navigation_node::TYPE_*.
|
||||
* @return navigation_node|null|false
|
||||
*/
|
||||
public function get($key, $type = null) {
|
||||
if ($type !== null) {
|
||||
// If the type is known then we can simply check and fetch.
|
||||
if (!empty($this->orderedcollection[$type][$key])) {
|
||||
return $this->orderedcollection[$type][$key];
|
||||
}
|
||||
} else {
|
||||
// Because we don't know the type we look in the progressive array.
|
||||
foreach ($this->collection as $node) {
|
||||
if ($node->key === $key) {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a node with matching key and type.
|
||||
*
|
||||
* This function searches both the nodes in this collection and all of
|
||||
* the nodes in each collection belonging to the nodes in this collection.
|
||||
*
|
||||
* Recursive.
|
||||
*
|
||||
* @param string|int $key The key of the node we want to find.
|
||||
* @param int $type One of navigation_node::TYPE_*.
|
||||
* @return navigation_node|false
|
||||
*/
|
||||
public function find($key, $type = null) {
|
||||
if (
|
||||
$type !== null
|
||||
&& array_key_exists($type, $this->orderedcollection)
|
||||
&& array_key_exists($key, $this->orderedcollection[$type])
|
||||
) {
|
||||
return $this->orderedcollection[$type][$key];
|
||||
} else {
|
||||
$nodes = $this->getIterator();
|
||||
|
||||
// Search immediate children first.
|
||||
foreach ($nodes as &$node) {
|
||||
if ($node->key === $key && ($type === null || $type === $node->type)) {
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
||||
// Now search each childs children.
|
||||
foreach ($nodes as &$node) {
|
||||
$result = $node->children->find($key, $type);
|
||||
if ($result !== false) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the last node that was added to this collection
|
||||
*
|
||||
* @return navigation_node
|
||||
*/
|
||||
public function last() {
|
||||
return $this->last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all nodes of a given type from this collection
|
||||
*
|
||||
* @param string|int $type node type being searched for.
|
||||
* @return array ordered collection
|
||||
*/
|
||||
public function type($type) {
|
||||
if (!array_key_exists($type, $this->orderedcollection)) {
|
||||
$this->orderedcollection[$type] = [];
|
||||
}
|
||||
return $this->orderedcollection[$type];
|
||||
}
|
||||
/**
|
||||
* Removes the node with the given key and type from the collection
|
||||
*
|
||||
* @param string|int $key The key of the node we want to find.
|
||||
* @param int $type
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($key, $type = null) {
|
||||
$child = $this->get($key, $type);
|
||||
if ($child !== false) {
|
||||
foreach ($this->collection as $colkey => $node) {
|
||||
if ($node->key === $key && (is_null($type) || $node->type == $type)) {
|
||||
unset($this->collection[$colkey]);
|
||||
$this->collection = array_values($this->collection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($this->orderedcollection[$child->type][$child->key]);
|
||||
$this->count--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function count(): int {
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getIterator(): Traversable {
|
||||
return new ArrayIterator($this->collection);
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(navigation_node_collection::class, \navigation_node_collection::class);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
<?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\navigation;
|
||||
|
||||
use moodle_page;
|
||||
|
||||
/**
|
||||
* Class used to populate site admin navigation for ajax.
|
||||
*
|
||||
* @package core
|
||||
* @category navigation
|
||||
* @copyright 2013 Rajesh Taneja <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class settings_navigation_ajax extends settings_navigation {
|
||||
/**
|
||||
* Constructs the navigation for use in an AJAX request
|
||||
*
|
||||
* @param moodle_page $page
|
||||
*/
|
||||
public function __construct(moodle_page &$page) {
|
||||
$this->page = $page;
|
||||
$this->cache = new navigation_cache(self::CACHE_NAME);
|
||||
$this->children = new navigation_node_collection();
|
||||
$this->initialise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the site admin navigation.
|
||||
*/
|
||||
public function initialise() {
|
||||
if ($this->initialised || during_initial_install()) {
|
||||
return false;
|
||||
}
|
||||
$this->context = $this->page->context;
|
||||
$this->load_administration_settings();
|
||||
|
||||
// Check if local plugins is adding node to site admin.
|
||||
$this->load_local_plugin_settings();
|
||||
|
||||
$this->initialised = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias this class to the old name.
|
||||
// This file will be autoloaded by the legacyclasses autoload system.
|
||||
// In future all uses of this class will be corrected and the legacy references will be removed.
|
||||
class_alias(settings_navigation_ajax::class, \settings_navigation_ajax::class);
|
||||
@@ -188,6 +188,20 @@ $legacyclasses = [
|
||||
'form/cachestore_addinstance_form.php',
|
||||
],
|
||||
|
||||
// Navigation API.
|
||||
\breadcrumb_navigation_node::class => 'navigation/breadcrumb_navigation_node.php',
|
||||
\flat_navigation::class => 'navigation/flat_navigation.php',
|
||||
\flat_navigation_node::class => 'navigation/flat_navigation_node.php',
|
||||
\global_navigation::class => 'navigation/global_navigation.php',
|
||||
\global_navigation_for_ajax::class => 'navigation/global_navigation_for_ajax.php',
|
||||
\navbar::class => 'navigation/navbar.php',
|
||||
\navigation_cache::class => 'navigation/navigation_cache.php',
|
||||
\navigation_json::class => 'navigation/navigation_json.php',
|
||||
\navigation_node::class => 'navigation/navigation_node.php',
|
||||
\navigation_node_collection::class => 'navigation/navigation_node_collection.php',
|
||||
\settings_navigation::class => 'navigation/settings_navigation.php',
|
||||
\settings_navigation_ajax::class => 'navigation/settings_navigation_ajax.php',
|
||||
|
||||
// Output API.
|
||||
\theme_config::class => 'output/theme_config.php',
|
||||
\xhtml_container_stack::class => 'output/xhtml_container_stack.php',
|
||||
|
||||
+14
-6321
File diff suppressed because it is too large
Load Diff
@@ -624,7 +624,6 @@ require_once($CFG->libdir .'/setuplib.php'); // Functions that MUST be lo
|
||||
require_once($CFG->libdir .'/filterlib.php'); // Functions for filtering test as it is output.
|
||||
require_once($CFG->libdir .'/weblib.php'); // Functions relating to HTTP and content.
|
||||
require_once($CFG->libdir .'/outputlib.php'); // Functions for generating output.
|
||||
require_once($CFG->libdir .'/navigationlib.php'); // Class for generating Navigation structure.
|
||||
require_once($CFG->libdir .'/dmllib.php'); // Database access.
|
||||
require_once($CFG->libdir .'/datalib.php'); // Legacy lib with a big-mix of functions..
|
||||
require_once($CFG->libdir .'/accesslib.php'); // Access control functions.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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\tests\navigation;
|
||||
|
||||
use core\exception\coding_exception;
|
||||
use core\navigation\global_navigation;
|
||||
|
||||
// phpcs:disable
|
||||
/**
|
||||
* This is a dummy object that allows us to call protected methods within the
|
||||
* global navigation class by prefixing the methods with `exposed_`
|
||||
*/
|
||||
class exposed_global_navigation extends global_navigation {
|
||||
protected $exposedkey = 'exposed_';
|
||||
public function __construct(?\moodle_page $page = null) {
|
||||
global $PAGE;
|
||||
if ($page === null) {
|
||||
$page = $PAGE;
|
||||
}
|
||||
parent::__construct($page);
|
||||
}
|
||||
public function __call($method, $arguments) {
|
||||
if (strpos($method, $this->exposedkey) !== false) {
|
||||
$method = substr($method, strlen($this->exposedkey));
|
||||
}
|
||||
if (method_exists($this, $method)) {
|
||||
return call_user_func_array([$this, $method], $arguments);
|
||||
}
|
||||
throw new coding_exception(
|
||||
'You have attempted to access a method that does not exist for the given object ' . $method,
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
}
|
||||
public function set_initialised() {
|
||||
$this->initialised = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?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\tests\navigation;
|
||||
|
||||
use core\exception\coding_exception;
|
||||
use core\navigation\settings_navigation;
|
||||
|
||||
// phpcs:disable
|
||||
|
||||
/**
|
||||
* This is a dummy object that allows us to call protected methods within the
|
||||
* global navigation class by prefixing the methods with `exposed_`.
|
||||
*
|
||||
* @package core
|
||||
* @copyright Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class exposed_settings_navigation extends settings_navigation {
|
||||
protected string $exposedkey = 'exposed_';
|
||||
|
||||
public function __construct() {
|
||||
global $PAGE;
|
||||
parent::__construct($PAGE);
|
||||
}
|
||||
|
||||
public function __call($method, $arguments) {
|
||||
if (strpos($method, $this->exposedkey) !== false) {
|
||||
$method = substr($method, strlen($this->exposedkey));
|
||||
}
|
||||
if (method_exists($this, $method)) {
|
||||
return call_user_func_array([$this, $method], $arguments);
|
||||
}
|
||||
throw new coding_exception(
|
||||
'You have attempted to access a method that does not exist for the given object ' . $method,
|
||||
DEBUG_DEVELOPER,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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\tests\navigation;
|
||||
|
||||
use core\navigation\navigation_node;
|
||||
use core\output\pix_icon;
|
||||
use core\url;
|
||||
|
||||
/**
|
||||
* TODO describe file navigation_testcase
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2025 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class navigation_testcase extends \advanced_testcase {
|
||||
protected function setup_node(): navigation_node { // phpcs:ignore
|
||||
global $PAGE, $SITE;
|
||||
|
||||
// Perform a reset between tests to reset the PAGE.
|
||||
$this->resetAfterTest();
|
||||
|
||||
$PAGE->set_url('/');
|
||||
$PAGE->set_course($SITE);
|
||||
|
||||
$activeurl = $PAGE->url;
|
||||
$inactiveurl = new url('http://www.moodle.com/');
|
||||
|
||||
navigation_node::override_active_url($PAGE->url);
|
||||
|
||||
$node = new navigation_node('Test Node');
|
||||
$node->type = navigation_node::TYPE_SYSTEM;
|
||||
// We add the first child without key. This way we make sure all keys search by comparison is performed using ===.
|
||||
$node->add('first child without key', null, navigation_node::TYPE_CUSTOM);
|
||||
$demo1 = $node->add('demo1', $inactiveurl, navigation_node::TYPE_COURSE, null, 'demo1', new pix_icon('i/course', ''));
|
||||
$demo2 = $node->add('demo2', $inactiveurl, navigation_node::TYPE_COURSE, null, 'demo2', new pix_icon('i/course', ''));
|
||||
$demo3 = $node->add('demo3', $inactiveurl, navigation_node::TYPE_CATEGORY, null, 'demo3', new pix_icon('i/course', ''));
|
||||
$demo4 = $demo3->add('demo4', $inactiveurl, navigation_node::TYPE_COURSE, null, 'demo4', new pix_icon('i/course', ''));
|
||||
$demo5 = $demo3->add('demo5', $activeurl, navigation_node::TYPE_COURSE, null, 'demo5', new pix_icon('i/course', ''));
|
||||
$demo5->add('activity1', null, navigation_node::TYPE_ACTIVITY, null, 'activity1')->make_active();
|
||||
$demo6 = $demo3->add('demo6', null, navigation_node::TYPE_CONTAINER, 'container node test', 'demo6');
|
||||
$hiddendemo1 = $node->add(
|
||||
'hiddendemo1',
|
||||
$inactiveurl,
|
||||
navigation_node::TYPE_CATEGORY,
|
||||
null,
|
||||
'hiddendemo1',
|
||||
new pix_icon('i/course', '')
|
||||
);
|
||||
$hiddendemo1->hidden = true;
|
||||
$hiddendemo1->add(
|
||||
'hiddendemo2',
|
||||
$inactiveurl,
|
||||
navigation_node::TYPE_COURSE,
|
||||
null,
|
||||
'hiddendemo2',
|
||||
new pix_icon('i/course', '')
|
||||
)->helpbutton = 'Here is a help button';
|
||||
$hiddendemo1->add(
|
||||
'hiddendemo3',
|
||||
$inactiveurl,
|
||||
navigation_node::TYPE_COURSE,
|
||||
null,
|
||||
'hiddendemo3',
|
||||
new pix_icon('i/course', '')
|
||||
)->display = false;
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,6 @@ return new class extends phpunit_coverage_info {
|
||||
'modinfolib.php',
|
||||
'moodlelib.php',
|
||||
'myprofilelib.php',
|
||||
'navigationlib.php',
|
||||
'oauthlib.php',
|
||||
'outputlib.php',
|
||||
'pagelib.php',
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\tests\navigation\exposed_global_navigation;
|
||||
use core\tests\navigation\navigation_testcase;
|
||||
|
||||
/**
|
||||
* Tests for global_navigation.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2025 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(global_navigation::class)]
|
||||
final class global_navigation_test extends navigation_testcase {
|
||||
public function test_module_extends_navigation(): void {
|
||||
$node = new exposed_global_navigation();
|
||||
// Create an initial tree structure to work with.
|
||||
$cat1 = $node->add('category 1', null, navigation_node::TYPE_CATEGORY, null, 'cat1');
|
||||
$cat2 = $node->add('category 2', null, navigation_node::TYPE_CATEGORY, null, 'cat2');
|
||||
$cat3 = $node->add('category 3', null, navigation_node::TYPE_CATEGORY, null, 'cat3');
|
||||
$sub1 = $cat2->add('sub category 1', null, navigation_node::TYPE_CATEGORY, null, 'sub1');
|
||||
$sub2 = $cat2->add('sub category 2', null, navigation_node::TYPE_CATEGORY, null, 'sub2');
|
||||
$sub3 = $cat2->add('sub category 3', null, navigation_node::TYPE_CATEGORY, null, 'sub3');
|
||||
$course1 = $sub2->add('course 1', null, navigation_node::TYPE_COURSE, null, 'course1');
|
||||
$course2 = $sub2->add('course 2', null, navigation_node::TYPE_COURSE, null, 'course2');
|
||||
$course3 = $sub2->add('course 3', null, navigation_node::TYPE_COURSE, null, 'course3');
|
||||
$section1 = $course2->add('section 1', null, navigation_node::TYPE_SECTION, null, 'sec1');
|
||||
$section2 = $course2->add('section 2', null, navigation_node::TYPE_SECTION, null, 'sec2');
|
||||
$section3 = $course2->add('section 3', null, navigation_node::TYPE_SECTION, null, 'sec3');
|
||||
$act1 = $section2->add('activity 1', null, navigation_node::TYPE_ACTIVITY, null, 'act1');
|
||||
$act2 = $section2->add('activity 2', null, navigation_node::TYPE_ACTIVITY, null, 'act2');
|
||||
$act3 = $section2->add('activity 3', null, navigation_node::TYPE_ACTIVITY, null, 'act3');
|
||||
$res1 = $section2->add('resource 1', null, navigation_node::TYPE_RESOURCE, null, 'res1');
|
||||
$res2 = $section2->add('resource 2', null, navigation_node::TYPE_RESOURCE, null, 'res2');
|
||||
$res3 = $section2->add('resource 3', null, navigation_node::TYPE_RESOURCE, null, 'res3');
|
||||
|
||||
$this->assertTrue($node->exposed_module_extends_navigation('data'));
|
||||
$this->assertFalse($node->exposed_module_extends_navigation('test1'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?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\navigation;
|
||||
|
||||
/**
|
||||
* Tests for navbar.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2025 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(navbar::class)]
|
||||
final class navbar_test extends \advanced_testcase {
|
||||
public function test_navbar_prepend_and_add(): \moodle_page {
|
||||
global $PAGE;
|
||||
// Unfortunate hack needed because people use global $PAGE around the place.
|
||||
$PAGE->set_url('/');
|
||||
|
||||
// We need to reset after this test because we using the generator.
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = self::getDataGenerator();
|
||||
$cat1 = $generator->create_category();
|
||||
$cat2 = $generator->create_category(['parent' => $cat1->id]);
|
||||
$course = $generator->create_course(['category' => $cat2->id]);
|
||||
|
||||
$page = new \moodle_page();
|
||||
$page->set_course($course);
|
||||
$page->set_url(new \moodle_url('/course/view.php', ['id' => $course->id]));
|
||||
$page->navbar->prepend('test 1');
|
||||
$page->navbar->prepend('test 2');
|
||||
$page->navbar->add('test 3');
|
||||
$page->navbar->add('test 4');
|
||||
|
||||
$items = $page->navbar->get_items();
|
||||
foreach ($items as $item) {
|
||||
$this->assertInstanceOf(navigation_node::class, $item);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$this->assertSame('test 1', $items[$i++]->text);
|
||||
$this->assertSame('test 2', $items[$i++]->text);
|
||||
$this->assertSame('home', $items[$i++]->key);
|
||||
$this->assertSame('courses', $items[$i++]->key);
|
||||
$this->assertSame($cat1->id, $items[$i++]->key);
|
||||
$this->assertSame($cat2->id, $items[$i++]->key);
|
||||
$this->assertSame($course->id, $items[$i++]->key);
|
||||
$this->assertSame('test 3', $items[$i++]->text);
|
||||
$this->assertSame('test 4', $items[$i++]->text);
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\Depends('test_navbar_prepend_and_add')]
|
||||
public function test_navbar_has_items(\moodle_page $page): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->assertTrue($page->navbar->has_items());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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\navigation;
|
||||
|
||||
/**
|
||||
* Tests for navigation_cache.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2025 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(navigation_cache::class)]
|
||||
final class navigation_cache_test extends \advanced_testcase {
|
||||
public function test_cache__get(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$this->assertTrue($cache->anysetvariable);
|
||||
$this->assertEquals($cache->notasetvariable, null);
|
||||
}
|
||||
|
||||
public function test_cache__set(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$cache->myname = 'Sam Hemelryk';
|
||||
$this->assertTrue($cache->cached('myname'));
|
||||
$this->assertSame('Sam Hemelryk', $cache->myname);
|
||||
}
|
||||
|
||||
public function test_cache_cached(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$this->assertTrue($cache->cached('anysetvariable'));
|
||||
$this->assertFalse($cache->cached('notasetvariable'));
|
||||
}
|
||||
|
||||
public function test_cache_clear(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$cache = clone($cache);
|
||||
$this->assertTrue($cache->cached('anysetvariable'));
|
||||
$cache->clear();
|
||||
$this->assertFalse($cache->cached('anysetvariable'));
|
||||
}
|
||||
|
||||
public function test_cache_set(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$cache->set('software', 'Moodle');
|
||||
$this->assertTrue($cache->cached('software'));
|
||||
$this->assertEquals($cache->software, 'Moodle');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\tests\navigation\navigation_testcase;
|
||||
|
||||
/**
|
||||
* Tests for navigation_node_collection.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2025 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(navigation_node_collection::class)]
|
||||
final class navigation_node_collection_test extends navigation_testcase {
|
||||
public function test_navigation_node_collection_remove_with_no_type(): void {
|
||||
$navigationnodecollection = new navigation_node_collection();
|
||||
$node = $this->setup_node();
|
||||
$node->key = 100;
|
||||
|
||||
// Test it's empty.
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Add a node.
|
||||
$navigationnodecollection->add($node);
|
||||
|
||||
// Test it's not empty.
|
||||
$this->assertEquals(1, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Remove a node - passing key only!
|
||||
$this->assertTrue($navigationnodecollection->remove(100));
|
||||
|
||||
// Test it's empty again!
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
}
|
||||
|
||||
public function test_navigation_node_collection_remove_with_type(): void {
|
||||
$navigationnodecollection = new navigation_node_collection();
|
||||
$node = $this->setup_node();
|
||||
$node->key = 100;
|
||||
|
||||
// Test it's empty.
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Add a node.
|
||||
$navigationnodecollection->add($node);
|
||||
|
||||
// Test it's not empty.
|
||||
$this->assertEquals(1, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Remove a node - passing type.
|
||||
$this->assertTrue($navigationnodecollection->remove(100, 1));
|
||||
|
||||
// Test it's empty again!
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\output\action_link;
|
||||
use core\output\actions\popup_action;
|
||||
use core\output\pix_icon;
|
||||
use core\tests\navigation\navigation_testcase;
|
||||
|
||||
/**
|
||||
* Tests for navigation_node.
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2025 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(navigation_node::class)]
|
||||
final class navigation_node_test extends navigation_testcase {
|
||||
public function test_node__construct(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$fakeproperties = [
|
||||
'text' => 'text',
|
||||
'shorttext' => 'A very silly extra long short text string, more than 25 characters',
|
||||
'key' => 'key',
|
||||
'type' => 'navigation_node::TYPE_COURSE',
|
||||
'action' => new \moodle_url('http://www.moodle.org/')];
|
||||
|
||||
$node = new navigation_node($fakeproperties);
|
||||
$this->assertSame($fakeproperties['text'], $node->text);
|
||||
$this->assertTrue(strpos($fakeproperties['shorttext'], substr($node->shorttext, 0, -3)) === 0);
|
||||
$this->assertSame($fakeproperties['key'], $node->key);
|
||||
$this->assertSame($fakeproperties['type'], $node->type);
|
||||
$this->assertSame($fakeproperties['action'], $node->action);
|
||||
}
|
||||
|
||||
public function test_node_add(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
// Add a node with all args set.
|
||||
$node1 = $node->add(
|
||||
'test_add_1',
|
||||
'http://www.moodle.org/',
|
||||
navigation_node::TYPE_COURSE,
|
||||
'testadd1',
|
||||
'key',
|
||||
new pix_icon('i/course', ''),
|
||||
);
|
||||
// Add a node with the minimum args required.
|
||||
$node2 = $node->add('test_add_2', null, navigation_node::TYPE_CUSTOM, 'testadd2');
|
||||
$node3 = $node->add(str_repeat('moodle ', 15), str_repeat('moodle', 15));
|
||||
|
||||
$this->assertInstanceOf(navigation_node::class, $node1);
|
||||
$this->assertInstanceOf(navigation_node::class, $node2);
|
||||
$this->assertInstanceOf(navigation_node::class, $node3);
|
||||
|
||||
$ref = $node->get('key');
|
||||
$this->assertSame($node1, $ref);
|
||||
|
||||
$ref = $node->get($node2->key);
|
||||
$this->assertSame($node2, $ref);
|
||||
|
||||
$ref = $node->get($node2->key, $node2->type);
|
||||
$this->assertSame($node2, $ref);
|
||||
|
||||
$ref = $node->get($node3->key, $node3->type);
|
||||
$this->assertSame($node3, $ref);
|
||||
}
|
||||
|
||||
public function test_node_add_before(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
// Create 3 nodes.
|
||||
$node1 = navigation_node::create(
|
||||
'test_add_1',
|
||||
null,
|
||||
navigation_node::TYPE_CUSTOM,
|
||||
'test 1',
|
||||
'testadd1'
|
||||
);
|
||||
$node2 = navigation_node::create(
|
||||
'test_add_2',
|
||||
null,
|
||||
navigation_node::TYPE_CUSTOM,
|
||||
'test 2',
|
||||
'testadd2'
|
||||
);
|
||||
$node3 = navigation_node::create(
|
||||
'test_add_3',
|
||||
null,
|
||||
navigation_node::TYPE_CUSTOM,
|
||||
'test 3',
|
||||
'testadd3'
|
||||
);
|
||||
// Add node 2, then node 1 before 2, then node 3 at end.
|
||||
$node->add_node($node2);
|
||||
$node->add_node($node1, 'testadd2');
|
||||
$node->add_node($node3);
|
||||
// Check the last 3 nodes are in 1, 2, 3 order and have those indexes.
|
||||
foreach ($node->children as $child) {
|
||||
$keys[] = $child->key;
|
||||
}
|
||||
$this->assertSame('testadd1', $keys[count($keys) - 3]);
|
||||
$this->assertSame('testadd2', $keys[count($keys) - 2]);
|
||||
$this->assertSame('testadd3', $keys[count($keys) - 1]);
|
||||
}
|
||||
|
||||
public function test_node_add_class(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$node = $node->get('demo1');
|
||||
$this->assertInstanceOf(navigation_node::class, $node);
|
||||
if ($node !== false) {
|
||||
$node->add_class('myclass');
|
||||
$classes = $node->classes;
|
||||
$this->assertContains('myclass', $classes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the add_attribute method.
|
||||
* @covers \navigation_node::add_attribute
|
||||
*/
|
||||
public function test_node_add_attribute(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$node = $node->get('demo1');
|
||||
$this->assertInstanceOf(navigation_node::class, $node);
|
||||
if ($node !== false) {
|
||||
$node->add_attribute('data-foo', 'bar');
|
||||
$attribute = reset($node->attributes);
|
||||
$this->assertEqualsCanonicalizing(['name' => 'data-foo', 'value' => 'bar'], $attribute);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_node_check_if_active(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
// First test the string urls
|
||||
// Demo1 -> action is http://www.moodle.org/, thus should be true.
|
||||
$demo5 = $node->find('demo5', navigation_node::TYPE_COURSE);
|
||||
if ($this->assertInstanceOf(navigation_node::class, $demo5)) {
|
||||
$this->assertTrue($demo5->check_if_active());
|
||||
}
|
||||
|
||||
// Demo2 -> action is http://www.moodle.com/, thus should be false.
|
||||
$demo2 = $node->get('demo2');
|
||||
if ($this->assertInstanceOf(navigation_node::class, $demo2)) {
|
||||
$this->assertFalse($demo2->check_if_active());
|
||||
}
|
||||
}
|
||||
|
||||
public function test_node_contains_active_node(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
// Demo5, and activity1 were set to active during setup.
|
||||
// Should be true as it contains all nodes.
|
||||
$this->assertTrue($node->contains_active_node());
|
||||
// Should be true as demo5 is a child of demo3.
|
||||
$this->assertTrue($node->get('demo3')->contains_active_node());
|
||||
// Obviously duff.
|
||||
$this->assertFalse($node->get('demo1')->contains_active_node());
|
||||
// Should be true as demo5 contains activity1.
|
||||
$this->assertTrue($node->get('demo3')->get('demo5')->contains_active_node());
|
||||
// Should be true activity1 is the active node.
|
||||
$this->assertTrue($node->get('demo3')->get('demo5')->get('activity1')->contains_active_node());
|
||||
// Obviously duff.
|
||||
$this->assertFalse($node->get('demo3')->get('demo4')->contains_active_node());
|
||||
}
|
||||
|
||||
public function test_node_find_active_node(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$activenode1 = $node->find_active_node();
|
||||
$activenode2 = $node->get('demo1')->find_active_node();
|
||||
|
||||
if ($this->assertInstanceOf(navigation_node::class, $activenode1)) {
|
||||
$ref = $node->get('demo3')->get('demo5')->get('activity1');
|
||||
$this->assertSame($activenode1, $ref);
|
||||
}
|
||||
|
||||
$this->assertNotInstanceOf(navigation_node::class, $activenode2);
|
||||
}
|
||||
|
||||
public function test_node_find(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$node1 = $node->find('demo1', navigation_node::TYPE_COURSE);
|
||||
$node2 = $node->find('demo5', navigation_node::TYPE_COURSE);
|
||||
$node3 = $node->find('demo5', navigation_node::TYPE_CATEGORY);
|
||||
$node4 = $node->find('demo0', navigation_node::TYPE_COURSE);
|
||||
$this->assertInstanceOf(navigation_node::class, $node1);
|
||||
$this->assertInstanceOf(navigation_node::class, $node2);
|
||||
$this->assertNotInstanceOf(navigation_node::class, $node3);
|
||||
$this->assertNotInstanceOf(navigation_node::class, $node4);
|
||||
}
|
||||
|
||||
public function test_node_find_expandable(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$expandable = [];
|
||||
$node->find_expandable($expandable);
|
||||
|
||||
$this->assertCount(0, $expandable);
|
||||
if (count($expandable) === 4) {
|
||||
$name = $expandable[0]['key'];
|
||||
$name .= $expandable[1]['key'];
|
||||
$name .= $expandable[2]['key'];
|
||||
$name .= $expandable[3]['key'];
|
||||
$this->assertSame('demo1demo2demo4hiddendemo2', $name);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_node_get(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$node1 = $node->get('demo1'); // Exists.
|
||||
$node2 = $node->get('demo4'); // Doesn't exist for this node.
|
||||
$node3 = $node->get('demo0'); // Doesn't exist at all.
|
||||
$node4 = $node->get(false); // Sometimes occurs in nature code.
|
||||
$this->assertInstanceOf(navigation_node::class, $node1);
|
||||
$this->assertFalse($node2);
|
||||
$this->assertFalse($node3);
|
||||
$this->assertFalse($node4);
|
||||
}
|
||||
|
||||
public function test_node_get_css_type(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$csstype1 = $node->get('demo3')->get_css_type();
|
||||
$csstype2 = $node->get('demo3')->get('demo5')->get_css_type();
|
||||
$node->get('demo3')->get('demo5')->type = 1000;
|
||||
$csstype3 = $node->get('demo3')->get('demo5')->get_css_type();
|
||||
$csstype4 = $node->get('demo3')->get('demo6')->get_css_type();
|
||||
$this->assertSame('type_category', $csstype1);
|
||||
$this->assertSame('type_course', $csstype2);
|
||||
$this->assertSame('type_unknown', $csstype3);
|
||||
$this->assertSame('type_container', $csstype4);
|
||||
}
|
||||
|
||||
public function test_node_make_active(): void {
|
||||
global $CFG;
|
||||
$node = $this->setup_node();
|
||||
|
||||
$node1 = $node->add('active node 1', null, navigation_node::TYPE_CUSTOM, null, 'anode1');
|
||||
$node2 = $node->add('active node 2', new \moodle_url($CFG->wwwroot), navigation_node::TYPE_COURSE, null, 'anode2');
|
||||
$node1->make_active();
|
||||
$node->get('anode2')->make_active();
|
||||
$this->assertTrue($node1->isactive);
|
||||
$this->assertTrue($node->get('anode2')->isactive);
|
||||
}
|
||||
|
||||
public function test_node_remove(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$remove1 = $node->add('child to remove 1', null, navigation_node::TYPE_CUSTOM, null, 'remove1');
|
||||
$remove2 = $node->add('child to remove 2', null, navigation_node::TYPE_CUSTOM, null, 'remove2');
|
||||
$remove3 = $remove2->add('child to remove 3', null, navigation_node::TYPE_CUSTOM, null, 'remove3');
|
||||
|
||||
$this->assertInstanceOf(navigation_node::class, $remove1);
|
||||
$this->assertInstanceOf(navigation_node::class, $remove2);
|
||||
$this->assertInstanceOf(navigation_node::class, $remove3);
|
||||
|
||||
$this->assertInstanceOf(navigation_node::class, $node->get('remove1'));
|
||||
$this->assertInstanceOf(navigation_node::class, $node->get('remove2'));
|
||||
$this->assertInstanceOf(navigation_node::class, $remove2->get('remove3'));
|
||||
|
||||
// Remove element and make sure this is no longer a child.
|
||||
$this->assertTrue($remove1->remove());
|
||||
$this->assertFalse($node->get('remove1'));
|
||||
$this->assertFalse(in_array('remove1', $node->get_children_key_list(), true));
|
||||
|
||||
// Make sure that we can insert element after removal.
|
||||
$insertelement = navigation_node::create('extra element 4', null, navigation_node::TYPE_CUSTOM, null, 'element4');
|
||||
$node->add_node($insertelement, 'remove2');
|
||||
$this->assertNotEmpty($node->get('element4'));
|
||||
|
||||
// Remove more elements.
|
||||
$this->assertTrue($node->get('remove2')->remove());
|
||||
$this->assertFalse($node->get('remove2'));
|
||||
|
||||
// Make sure that we can add element after removal.
|
||||
$node->add('extra element 5', null, navigation_node::TYPE_CUSTOM, null, 'element5');
|
||||
$this->assertNotEmpty($node->get('element5'));
|
||||
|
||||
$this->assertTrue($remove2->get('remove3')->remove());
|
||||
|
||||
$this->assertFalse($node->get('remove1'));
|
||||
$this->assertFalse($node->get('remove2'));
|
||||
}
|
||||
|
||||
public function test_node_remove_class(): void {
|
||||
$node = $this->setup_node();
|
||||
|
||||
$node->add_class('testclass');
|
||||
$this->assertTrue($node->remove_class('testclass'));
|
||||
$this->assertNotContains('testclass', $node->classes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the set_force_into_more_menu method.
|
||||
*
|
||||
* @param bool $haschildren Whether the navigation node has children nodes
|
||||
* @param bool $forceintomoremenu Whether to force the navigation node and its children into the "more" menu
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('set_force_into_more_menu_provider')]
|
||||
public function test_set_force_into_more_menu(bool $haschildren, bool $forceintomoremenu): void {
|
||||
// Create a navigation node.
|
||||
$node = new navigation_node(['text' => 'Navigation node', 'key' => 'navnode']);
|
||||
|
||||
// If required, add some children nodes to the navigation node.
|
||||
if ($haschildren) {
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$node->add("Child navigation node {$i}");
|
||||
}
|
||||
}
|
||||
|
||||
$node->set_force_into_more_menu($forceintomoremenu);
|
||||
// Assert that the expected value has been assigned to the 'forceintomoremenu' property
|
||||
// in the navigation node and its children.
|
||||
$this->assertEquals($forceintomoremenu, $node->forceintomoremenu);
|
||||
foreach ($node->children as $child) {
|
||||
$this->assertEquals($forceintomoremenu, $child->forceintomoremenu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_set_force_into_more_menu function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function set_force_into_more_menu_provider(): array {
|
||||
return [
|
||||
'Navigation node without any children nodes; Force into "more" menu => true.' => [
|
||||
false,
|
||||
true,
|
||||
],
|
||||
'Navigation node with children nodes; Force into "more" menu => true.' => [
|
||||
true,
|
||||
true,
|
||||
],
|
||||
'Navigation node with children nodes; Force into "more" menu => false.' => [
|
||||
true,
|
||||
false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the is_action_link method.
|
||||
*
|
||||
* @param navigation_node $node The sample navigation node
|
||||
* @param bool $expected Whether the navigation node contains an action link
|
||||
* @dataProvider is_action_link_provider
|
||||
* @covers navigation_node::is_action_link
|
||||
*/
|
||||
public function test_is_action_link(navigation_node $node, bool $expected): void {
|
||||
$this->assertEquals($node->is_action_link(), $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_is_action_link function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function is_action_link_provider(): array {
|
||||
return [
|
||||
'The navigation node has an action link.' =>
|
||||
[
|
||||
navigation_node::create('Node', new action_link(
|
||||
new \moodle_url('/'),
|
||||
'',
|
||||
new popup_action('click', new \moodle_url('/'))
|
||||
), navigation_node::TYPE_SETTING),
|
||||
true,
|
||||
],
|
||||
|
||||
'The navigation node does not have an action link.' =>
|
||||
[
|
||||
navigation_node::create('Node', new \moodle_url('/'), navigation_node::TYPE_SETTING),
|
||||
false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the action_link_actions method.
|
||||
*
|
||||
* @param navigation_node $node The sample navigation node
|
||||
* @dataProvider action_link_actions_provider
|
||||
* @covers navigation_node::action_link_actions
|
||||
*/
|
||||
public function test_action_link_actions(navigation_node $node): void {
|
||||
// Get the formatted array of action link actions.
|
||||
$data = $node->action_link_actions();
|
||||
// The navigation node has an action link.
|
||||
if ($node->action instanceof action_link) {
|
||||
if (!empty($node->action->actions)) { // There are actions added to the action link.
|
||||
$this->assertArrayHasKey('actions', $data);
|
||||
$this->assertCount(1, $data['actions']);
|
||||
$expected = (object)[
|
||||
'id' => $node->action->attributes['id'],
|
||||
'event' => $node->action->actions[0]->event,
|
||||
'jsfunction' => $node->action->actions[0]->jsfunction,
|
||||
'jsfunctionargs' => json_encode($node->action->actions[0]->jsfunctionargs),
|
||||
];
|
||||
$this->assertEquals($expected, $data['actions'][0]);
|
||||
} else { // There are no actions added to the action link.
|
||||
$this->assertArrayHasKey('actions', $data);
|
||||
$this->assertEmpty($data['actions']);
|
||||
}
|
||||
} else { // The navigation node does not have an action link.
|
||||
$this->assertEmpty($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_action_link_actions function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function action_link_actions_provider(): array {
|
||||
return [
|
||||
'The navigation node has an action link with an action attached.' =>
|
||||
[
|
||||
navigation_node::create('Node', new action_link(
|
||||
new \moodle_url('/'),
|
||||
'',
|
||||
new popup_action('click', new \moodle_url('/'))
|
||||
), navigation_node::TYPE_SETTING),
|
||||
],
|
||||
'The navigation node has an action link without an action.' =>
|
||||
[
|
||||
navigation_node::create(
|
||||
'Node',
|
||||
new action_link(new \moodle_url('/'), '', null),
|
||||
navigation_node::TYPE_SETTING
|
||||
),
|
||||
],
|
||||
'The navigation node does not have an action link.' =>
|
||||
[
|
||||
navigation_node::create('Node', new \moodle_url('/'), navigation_node::TYPE_SETTING),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -374,7 +374,7 @@ final class primary_test extends \advanced_testcase {
|
||||
}
|
||||
$primary = new primary($PAGE);
|
||||
|
||||
$method = new ReflectionMethod('core\navigation\output\primary', 'get_primary_nav');
|
||||
$method = new ReflectionMethod(primary::class, 'get_primary_nav');
|
||||
$dataprimary = $method->invoke($primary);
|
||||
|
||||
// Take this custom menu that would come from the setting custommenitems.
|
||||
@@ -393,7 +393,7 @@ final class primary_test extends \advanced_testcase {
|
||||
ENDMENU;
|
||||
|
||||
$datacustom = $this->get_custom_menu($custommenuitems);
|
||||
$method = new ReflectionMethod('core\navigation\output\primary', 'merge_primary_and_custom');
|
||||
$method = new ReflectionMethod(primary::class, 'merge_primary_and_custom');
|
||||
$menucomplete = $method->invoke($primary, $dataprimary, $datacustom, $ismobile);
|
||||
return $menucomplete;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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\navigation;
|
||||
|
||||
use core\tests\navigation\exposed_settings_navigation;
|
||||
|
||||
/**
|
||||
* Tests for
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2025 Andrew Lyons <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(settings_navigation::class)]
|
||||
final class settings_navigation_test extends \advanced_testcase {
|
||||
public function test_setting___construct(): settings_navigation {
|
||||
global $PAGE, $SITE;
|
||||
|
||||
$this->resetAfterTest(false);
|
||||
|
||||
$PAGE->set_url('/');
|
||||
$PAGE->set_course($SITE);
|
||||
|
||||
$node = new exposed_settings_navigation();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends test_setting___construct
|
||||
* @param mixed $node
|
||||
* @return mixed
|
||||
*/
|
||||
public function test_setting__initialise($node): settings_navigation {
|
||||
$this->resetAfterTest(false);
|
||||
|
||||
$node->initialise();
|
||||
$this->assertEquals($node->id, 'settingsnav');
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @depends test_setting__initialise
|
||||
* @param mixed $node
|
||||
* @return mixed
|
||||
*/
|
||||
public function test_setting_in_alternative_role($node): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->assertFalse($node->exposed_in_alternative_role());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that users with the correct permissions can view the preferences page.
|
||||
*/
|
||||
public function test_can_view_user_preferences(): void {
|
||||
global $PAGE, $DB, $SITE;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$persontoview = $this->getDataGenerator()->create_user();
|
||||
$persondoingtheviewing = $this->getDataGenerator()->create_user();
|
||||
|
||||
$PAGE->set_url('/');
|
||||
$PAGE->set_course($SITE);
|
||||
|
||||
// Check that a standard user can not view the preferences page.
|
||||
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
|
||||
$this->getDataGenerator()->role_assign($studentrole->id, $persondoingtheviewing->id);
|
||||
$this->setUser($persondoingtheviewing);
|
||||
$settingsnav = new exposed_settings_navigation();
|
||||
$settingsnav->initialise();
|
||||
$settingsnav->extend_for_user($persontoview->id);
|
||||
$this->assertFalse($settingsnav->can_view_user_preferences($persontoview->id));
|
||||
|
||||
// Set persondoingtheviewing as a manager.
|
||||
$managerrole = $DB->get_record('role', ['shortname' => 'manager']);
|
||||
$this->getDataGenerator()->role_assign($managerrole->id, $persondoingtheviewing->id);
|
||||
$settingsnav = new exposed_settings_navigation();
|
||||
$settingsnav->initialise();
|
||||
$settingsnav->extend_for_user($persontoview->id);
|
||||
$this->assertTrue($settingsnav->can_view_user_preferences($persontoview->id));
|
||||
|
||||
// Check that the admin can view the preferences page.
|
||||
$this->setAdminUser();
|
||||
$settingsnav = new exposed_settings_navigation();
|
||||
$settingsnav->initialise();
|
||||
$settingsnav->extend_for_user($persontoview->id);
|
||||
$preferencenode = $settingsnav->find('userviewingsettings' . $persontoview->id, null);
|
||||
$this->assertTrue($settingsnav->can_view_user_preferences($persontoview->id));
|
||||
}
|
||||
}
|
||||
@@ -1,824 +0,0 @@
|
||||
<?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;
|
||||
|
||||
use action_link;
|
||||
use global_navigation;
|
||||
use navbar;
|
||||
use navigation_cache;
|
||||
use navigation_node;
|
||||
use navigation_node_collection;
|
||||
use pix_icon;
|
||||
use popup_action;
|
||||
use settings_navigation;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
require_once($CFG->libdir . '/navigationlib.php');
|
||||
|
||||
/**
|
||||
* Unit tests for lib/navigationlib.php
|
||||
*
|
||||
* @package core
|
||||
* @category test
|
||||
* @copyright 2009 Sam Hemelryk
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later (5)
|
||||
*/
|
||||
final class navigationlib_test extends \advanced_testcase {
|
||||
/**
|
||||
* @var navigation_node
|
||||
*/
|
||||
public $node;
|
||||
|
||||
protected function setup_node() {
|
||||
global $PAGE, $SITE;
|
||||
|
||||
// Perform a reset between tests to reset the PAGE.
|
||||
$this->resetAfterTest();
|
||||
|
||||
$PAGE->set_url('/');
|
||||
$PAGE->set_course($SITE);
|
||||
|
||||
$activeurl = $PAGE->url;
|
||||
$inactiveurl = new \moodle_url('http://www.moodle.com/');
|
||||
|
||||
navigation_node::override_active_url($PAGE->url);
|
||||
|
||||
$this->node = new navigation_node('Test Node');
|
||||
$this->node->type = navigation_node::TYPE_SYSTEM;
|
||||
// We add the first child without key. This way we make sure all keys search by comparison is performed using ===.
|
||||
$this->node->add('first child without key', null, navigation_node::TYPE_CUSTOM);
|
||||
$demo1 = $this->node->add('demo1', $inactiveurl, navigation_node::TYPE_COURSE, null, 'demo1', new pix_icon('i/course', ''));
|
||||
$demo2 = $this->node->add('demo2', $inactiveurl, navigation_node::TYPE_COURSE, null, 'demo2', new pix_icon('i/course', ''));
|
||||
$demo3 = $this->node->add('demo3', $inactiveurl, navigation_node::TYPE_CATEGORY, null, 'demo3', new pix_icon('i/course', ''));
|
||||
$demo4 = $demo3->add('demo4', $inactiveurl, navigation_node::TYPE_COURSE, null, 'demo4', new pix_icon('i/course', ''));
|
||||
$demo5 = $demo3->add('demo5', $activeurl, navigation_node::TYPE_COURSE, null, 'demo5', new pix_icon('i/course', ''));
|
||||
$demo5->add('activity1', null, navigation_node::TYPE_ACTIVITY, null, 'activity1')->make_active();
|
||||
$demo6 = $demo3->add('demo6', null, navigation_node::TYPE_CONTAINER, 'container node test', 'demo6');
|
||||
$hiddendemo1 = $this->node->add('hiddendemo1', $inactiveurl, navigation_node::TYPE_CATEGORY, null, 'hiddendemo1', new pix_icon('i/course', ''));
|
||||
$hiddendemo1->hidden = true;
|
||||
$hiddendemo1->add('hiddendemo2', $inactiveurl, navigation_node::TYPE_COURSE, null, 'hiddendemo2', new pix_icon('i/course', ''))->helpbutton = 'Here is a help button';
|
||||
$hiddendemo1->add('hiddendemo3', $inactiveurl, navigation_node::TYPE_COURSE, null, 'hiddendemo3', new pix_icon('i/course', ''))->display = false;
|
||||
}
|
||||
|
||||
public function test_node__construct(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$fakeproperties = array(
|
||||
'text' => 'text',
|
||||
'shorttext' => 'A very silly extra long short text string, more than 25 characters',
|
||||
'key' => 'key',
|
||||
'type' => 'navigation_node::TYPE_COURSE',
|
||||
'action' => new \moodle_url('http://www.moodle.org/'));
|
||||
|
||||
$node = new navigation_node($fakeproperties);
|
||||
$this->assertSame($fakeproperties['text'], $node->text);
|
||||
$this->assertTrue(strpos($fakeproperties['shorttext'], substr($node->shorttext, 0, -3)) === 0);
|
||||
$this->assertSame($fakeproperties['key'], $node->key);
|
||||
$this->assertSame($fakeproperties['type'], $node->type);
|
||||
$this->assertSame($fakeproperties['action'], $node->action);
|
||||
}
|
||||
|
||||
public function test_node_add(): void {
|
||||
$this->setup_node();
|
||||
|
||||
// Add a node with all args set.
|
||||
$node1 = $this->node->add('test_add_1', 'http://www.moodle.org/', navigation_node::TYPE_COURSE, 'testadd1', 'key', new pix_icon('i/course', ''));
|
||||
// Add a node with the minimum args required.
|
||||
$node2 = $this->node->add('test_add_2', null, navigation_node::TYPE_CUSTOM, 'testadd2');
|
||||
$node3 = $this->node->add(str_repeat('moodle ', 15), str_repeat('moodle', 15));
|
||||
|
||||
$this->assertInstanceOf('navigation_node', $node1);
|
||||
$this->assertInstanceOf('navigation_node', $node2);
|
||||
$this->assertInstanceOf('navigation_node', $node3);
|
||||
|
||||
$ref = $this->node->get('key');
|
||||
$this->assertSame($node1, $ref);
|
||||
|
||||
$ref = $this->node->get($node2->key);
|
||||
$this->assertSame($node2, $ref);
|
||||
|
||||
$ref = $this->node->get($node2->key, $node2->type);
|
||||
$this->assertSame($node2, $ref);
|
||||
|
||||
$ref = $this->node->get($node3->key, $node3->type);
|
||||
$this->assertSame($node3, $ref);
|
||||
}
|
||||
|
||||
public function test_node_add_before(): void {
|
||||
$this->setup_node();
|
||||
|
||||
// Create 3 nodes.
|
||||
$node1 = navigation_node::create('test_add_1', null, navigation_node::TYPE_CUSTOM,
|
||||
'test 1', 'testadd1');
|
||||
$node2 = navigation_node::create('test_add_2', null, navigation_node::TYPE_CUSTOM,
|
||||
'test 2', 'testadd2');
|
||||
$node3 = navigation_node::create('test_add_3', null, navigation_node::TYPE_CUSTOM,
|
||||
'test 3', 'testadd3');
|
||||
// Add node 2, then node 1 before 2, then node 3 at end.
|
||||
$this->node->add_node($node2);
|
||||
$this->node->add_node($node1, 'testadd2');
|
||||
$this->node->add_node($node3);
|
||||
// Check the last 3 nodes are in 1, 2, 3 order and have those indexes.
|
||||
foreach ($this->node->children as $child) {
|
||||
$keys[] = $child->key;
|
||||
}
|
||||
$this->assertSame('testadd1', $keys[count($keys)-3]);
|
||||
$this->assertSame('testadd2', $keys[count($keys)-2]);
|
||||
$this->assertSame('testadd3', $keys[count($keys)-1]);
|
||||
}
|
||||
|
||||
public function test_node_add_class(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$node = $this->node->get('demo1');
|
||||
$this->assertInstanceOf('navigation_node', $node);
|
||||
if ($node !== false) {
|
||||
$node->add_class('myclass');
|
||||
$classes = $node->classes;
|
||||
$this->assertContains('myclass', $classes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the add_attribute method.
|
||||
* @covers \navigation_node::add_attribute
|
||||
*/
|
||||
public function test_node_add_attribute(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$node = $this->node->get('demo1');
|
||||
$this->assertInstanceOf('navigation_node', $node);
|
||||
if ($node !== false) {
|
||||
$node->add_attribute('data-foo', 'bar');
|
||||
$attribute = reset($node->attributes);
|
||||
$this->assertEqualsCanonicalizing(['name' => 'data-foo', 'value' => 'bar'], $attribute);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_node_check_if_active(): void {
|
||||
$this->setup_node();
|
||||
|
||||
// First test the string urls
|
||||
// Demo1 -> action is http://www.moodle.org/, thus should be true.
|
||||
$demo5 = $this->node->find('demo5', navigation_node::TYPE_COURSE);
|
||||
if ($this->assertInstanceOf('navigation_node', $demo5)) {
|
||||
$this->assertTrue($demo5->check_if_active());
|
||||
}
|
||||
|
||||
// Demo2 -> action is http://www.moodle.com/, thus should be false.
|
||||
$demo2 = $this->node->get('demo2');
|
||||
if ($this->assertInstanceOf('navigation_node', $demo2)) {
|
||||
$this->assertFalse($demo2->check_if_active());
|
||||
}
|
||||
}
|
||||
|
||||
public function test_node_contains_active_node(): void {
|
||||
$this->setup_node();
|
||||
|
||||
// Demo5, and activity1 were set to active during setup.
|
||||
// Should be true as it contains all nodes.
|
||||
$this->assertTrue($this->node->contains_active_node());
|
||||
// Should be true as demo5 is a child of demo3.
|
||||
$this->assertTrue($this->node->get('demo3')->contains_active_node());
|
||||
// Obviously duff.
|
||||
$this->assertFalse($this->node->get('demo1')->contains_active_node());
|
||||
// Should be true as demo5 contains activity1.
|
||||
$this->assertTrue($this->node->get('demo3')->get('demo5')->contains_active_node());
|
||||
// Should be true activity1 is the active node.
|
||||
$this->assertTrue($this->node->get('demo3')->get('demo5')->get('activity1')->contains_active_node());
|
||||
// Obviously duff.
|
||||
$this->assertFalse($this->node->get('demo3')->get('demo4')->contains_active_node());
|
||||
}
|
||||
|
||||
public function test_node_find_active_node(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$activenode1 = $this->node->find_active_node();
|
||||
$activenode2 = $this->node->get('demo1')->find_active_node();
|
||||
|
||||
if ($this->assertInstanceOf('navigation_node', $activenode1)) {
|
||||
$ref = $this->node->get('demo3')->get('demo5')->get('activity1');
|
||||
$this->assertSame($activenode1, $ref);
|
||||
}
|
||||
|
||||
$this->assertNotInstanceOf('navigation_node', $activenode2);
|
||||
}
|
||||
|
||||
public function test_node_find(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$node1 = $this->node->find('demo1', navigation_node::TYPE_COURSE);
|
||||
$node2 = $this->node->find('demo5', navigation_node::TYPE_COURSE);
|
||||
$node3 = $this->node->find('demo5', navigation_node::TYPE_CATEGORY);
|
||||
$node4 = $this->node->find('demo0', navigation_node::TYPE_COURSE);
|
||||
$this->assertInstanceOf('navigation_node', $node1);
|
||||
$this->assertInstanceOf('navigation_node', $node2);
|
||||
$this->assertNotInstanceOf('navigation_node', $node3);
|
||||
$this->assertNotInstanceOf('navigation_node', $node4);
|
||||
}
|
||||
|
||||
public function test_node_find_expandable(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$expandable = array();
|
||||
$this->node->find_expandable($expandable);
|
||||
|
||||
$this->assertCount(0, $expandable);
|
||||
if (count($expandable) === 4) {
|
||||
$name = $expandable[0]['key'];
|
||||
$name .= $expandable[1]['key'];
|
||||
$name .= $expandable[2]['key'];
|
||||
$name .= $expandable[3]['key'];
|
||||
$this->assertSame('demo1demo2demo4hiddendemo2', $name);
|
||||
}
|
||||
}
|
||||
|
||||
public function test_node_get(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$node1 = $this->node->get('demo1'); // Exists.
|
||||
$node2 = $this->node->get('demo4'); // Doesn't exist for this node.
|
||||
$node3 = $this->node->get('demo0'); // Doesn't exist at all.
|
||||
$node4 = $this->node->get(false); // Sometimes occurs in nature code.
|
||||
$this->assertInstanceOf('navigation_node', $node1);
|
||||
$this->assertFalse($node2);
|
||||
$this->assertFalse($node3);
|
||||
$this->assertFalse($node4);
|
||||
}
|
||||
|
||||
public function test_node_get_css_type(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$csstype1 = $this->node->get('demo3')->get_css_type();
|
||||
$csstype2 = $this->node->get('demo3')->get('demo5')->get_css_type();
|
||||
$this->node->get('demo3')->get('demo5')->type = 1000;
|
||||
$csstype3 = $this->node->get('demo3')->get('demo5')->get_css_type();
|
||||
$csstype4 = $this->node->get('demo3')->get('demo6')->get_css_type();
|
||||
$this->assertSame('type_category', $csstype1);
|
||||
$this->assertSame('type_course', $csstype2);
|
||||
$this->assertSame('type_unknown', $csstype3);
|
||||
$this->assertSame('type_container', $csstype4);
|
||||
}
|
||||
|
||||
public function test_node_make_active(): void {
|
||||
global $CFG;
|
||||
$this->setup_node();
|
||||
|
||||
$node1 = $this->node->add('active node 1', null, navigation_node::TYPE_CUSTOM, null, 'anode1');
|
||||
$node2 = $this->node->add('active node 2', new \moodle_url($CFG->wwwroot), navigation_node::TYPE_COURSE, null, 'anode2');
|
||||
$node1->make_active();
|
||||
$this->node->get('anode2')->make_active();
|
||||
$this->assertTrue($node1->isactive);
|
||||
$this->assertTrue($this->node->get('anode2')->isactive);
|
||||
}
|
||||
|
||||
public function test_node_remove(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$remove1 = $this->node->add('child to remove 1', null, navigation_node::TYPE_CUSTOM, null, 'remove1');
|
||||
$remove2 = $this->node->add('child to remove 2', null, navigation_node::TYPE_CUSTOM, null, 'remove2');
|
||||
$remove3 = $remove2->add('child to remove 3', null, navigation_node::TYPE_CUSTOM, null, 'remove3');
|
||||
|
||||
$this->assertInstanceOf('navigation_node', $remove1);
|
||||
$this->assertInstanceOf('navigation_node', $remove2);
|
||||
$this->assertInstanceOf('navigation_node', $remove3);
|
||||
|
||||
$this->assertInstanceOf('navigation_node', $this->node->get('remove1'));
|
||||
$this->assertInstanceOf('navigation_node', $this->node->get('remove2'));
|
||||
$this->assertInstanceOf('navigation_node', $remove2->get('remove3'));
|
||||
|
||||
// Remove element and make sure this is no longer a child.
|
||||
$this->assertTrue($remove1->remove());
|
||||
$this->assertFalse($this->node->get('remove1'));
|
||||
$this->assertFalse(in_array('remove1', $this->node->get_children_key_list(), true));
|
||||
|
||||
// Make sure that we can insert element after removal.
|
||||
$insertelement = navigation_node::create('extra element 4', null, navigation_node::TYPE_CUSTOM, null, 'element4');
|
||||
$this->node->add_node($insertelement, 'remove2');
|
||||
$this->assertNotEmpty($this->node->get('element4'));
|
||||
|
||||
// Remove more elements.
|
||||
$this->assertTrue($this->node->get('remove2')->remove());
|
||||
$this->assertFalse($this->node->get('remove2'));
|
||||
|
||||
// Make sure that we can add element after removal.
|
||||
$this->node->add('extra element 5', null, navigation_node::TYPE_CUSTOM, null, 'element5');
|
||||
$this->assertNotEmpty($this->node->get('element5'));
|
||||
|
||||
$this->assertTrue($remove2->get('remove3')->remove());
|
||||
|
||||
$this->assertFalse($this->node->get('remove1'));
|
||||
$this->assertFalse($this->node->get('remove2'));
|
||||
}
|
||||
|
||||
public function test_node_remove_class(): void {
|
||||
$this->setup_node();
|
||||
|
||||
$this->node->add_class('testclass');
|
||||
$this->assertTrue($this->node->remove_class('testclass'));
|
||||
$this->assertNotContains('testclass', $this->node->classes);
|
||||
}
|
||||
|
||||
public function test_module_extends_navigation(): void {
|
||||
$node = new exposed_global_navigation();
|
||||
// Create an initial tree structure to work with.
|
||||
$cat1 = $node->add('category 1', null, navigation_node::TYPE_CATEGORY, null, 'cat1');
|
||||
$cat2 = $node->add('category 2', null, navigation_node::TYPE_CATEGORY, null, 'cat2');
|
||||
$cat3 = $node->add('category 3', null, navigation_node::TYPE_CATEGORY, null, 'cat3');
|
||||
$sub1 = $cat2->add('sub category 1', null, navigation_node::TYPE_CATEGORY, null, 'sub1');
|
||||
$sub2 = $cat2->add('sub category 2', null, navigation_node::TYPE_CATEGORY, null, 'sub2');
|
||||
$sub3 = $cat2->add('sub category 3', null, navigation_node::TYPE_CATEGORY, null, 'sub3');
|
||||
$course1 = $sub2->add('course 1', null, navigation_node::TYPE_COURSE, null, 'course1');
|
||||
$course2 = $sub2->add('course 2', null, navigation_node::TYPE_COURSE, null, 'course2');
|
||||
$course3 = $sub2->add('course 3', null, navigation_node::TYPE_COURSE, null, 'course3');
|
||||
$section1 = $course2->add('section 1', null, navigation_node::TYPE_SECTION, null, 'sec1');
|
||||
$section2 = $course2->add('section 2', null, navigation_node::TYPE_SECTION, null, 'sec2');
|
||||
$section3 = $course2->add('section 3', null, navigation_node::TYPE_SECTION, null, 'sec3');
|
||||
$act1 = $section2->add('activity 1', null, navigation_node::TYPE_ACTIVITY, null, 'act1');
|
||||
$act2 = $section2->add('activity 2', null, navigation_node::TYPE_ACTIVITY, null, 'act2');
|
||||
$act3 = $section2->add('activity 3', null, navigation_node::TYPE_ACTIVITY, null, 'act3');
|
||||
$res1 = $section2->add('resource 1', null, navigation_node::TYPE_RESOURCE, null, 'res1');
|
||||
$res2 = $section2->add('resource 2', null, navigation_node::TYPE_RESOURCE, null, 'res2');
|
||||
$res3 = $section2->add('resource 3', null, navigation_node::TYPE_RESOURCE, null, 'res3');
|
||||
|
||||
$this->assertTrue($node->exposed_module_extends_navigation('data'));
|
||||
$this->assertFalse($node->exposed_module_extends_navigation('test1'));
|
||||
}
|
||||
|
||||
public function test_navbar_prepend_and_add(): \moodle_page {
|
||||
global $PAGE;
|
||||
// Unfortunate hack needed because people use global $PAGE around the place.
|
||||
$PAGE->set_url('/');
|
||||
|
||||
// We need to reset after this test because we using the generator.
|
||||
$this->resetAfterTest();
|
||||
|
||||
$generator = self::getDataGenerator();
|
||||
$cat1 = $generator->create_category();
|
||||
$cat2 = $generator->create_category(array('parent' => $cat1->id));
|
||||
$course = $generator->create_course(array('category' => $cat2->id));
|
||||
|
||||
$page = new \moodle_page();
|
||||
$page->set_course($course);
|
||||
$page->set_url(new \moodle_url('/course/view.php', array('id' => $course->id)));
|
||||
$page->navbar->prepend('test 1');
|
||||
$page->navbar->prepend('test 2');
|
||||
$page->navbar->add('test 3');
|
||||
$page->navbar->add('test 4');
|
||||
|
||||
$items = $page->navbar->get_items();
|
||||
foreach ($items as $item) {
|
||||
$this->assertInstanceOf('navigation_node', $item);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
$this->assertSame('test 1', $items[$i++]->text);
|
||||
$this->assertSame('test 2', $items[$i++]->text);
|
||||
$this->assertSame('home', $items[$i++]->key);
|
||||
$this->assertSame('courses', $items[$i++]->key);
|
||||
$this->assertSame($cat1->id, $items[$i++]->key);
|
||||
$this->assertSame($cat2->id, $items[$i++]->key);
|
||||
$this->assertSame($course->id, $items[$i++]->key);
|
||||
$this->assertSame('test 3', $items[$i++]->text);
|
||||
$this->assertSame('test 4', $items[$i++]->text);
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends test_navbar_prepend_and_add
|
||||
* @param $node
|
||||
*/
|
||||
public function test_navbar_has_items(\moodle_page $page): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->assertTrue($page->navbar->has_items());
|
||||
}
|
||||
|
||||
public function test_cache__get(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$this->assertTrue($cache->anysetvariable);
|
||||
$this->assertEquals($cache->notasetvariable, null);
|
||||
}
|
||||
|
||||
public function test_cache__set(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$cache->myname = 'Sam Hemelryk';
|
||||
$this->assertTrue($cache->cached('myname'));
|
||||
$this->assertSame('Sam Hemelryk', $cache->myname);
|
||||
}
|
||||
|
||||
public function test_cache_cached(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$this->assertTrue($cache->cached('anysetvariable'));
|
||||
$this->assertFalse($cache->cached('notasetvariable'));
|
||||
}
|
||||
|
||||
public function test_cache_clear(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$cache = clone($cache);
|
||||
$this->assertTrue($cache->cached('anysetvariable'));
|
||||
$cache->clear();
|
||||
$this->assertFalse($cache->cached('anysetvariable'));
|
||||
}
|
||||
|
||||
public function test_cache_set(): void {
|
||||
$cache = new navigation_cache('unittest_nav');
|
||||
$cache->anysetvariable = true;
|
||||
|
||||
$cache->set('software', 'Moodle');
|
||||
$this->assertTrue($cache->cached('software'));
|
||||
$this->assertEquals($cache->software, 'Moodle');
|
||||
}
|
||||
|
||||
public function test_setting___construct(): settings_navigation {
|
||||
global $PAGE, $SITE;
|
||||
|
||||
$this->resetAfterTest(false);
|
||||
|
||||
$PAGE->set_url('/');
|
||||
$PAGE->set_course($SITE);
|
||||
|
||||
$node = new exposed_settings_navigation();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends test_setting___construct
|
||||
* @param mixed $node
|
||||
* @return mixed
|
||||
*/
|
||||
public function test_setting__initialise($node): settings_navigation {
|
||||
$this->resetAfterTest(false);
|
||||
|
||||
$node->initialise();
|
||||
$this->assertEquals($node->id, 'settingsnav');
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that users with the correct permissions can view the preferences page.
|
||||
*/
|
||||
public function test_can_view_user_preferences(): void {
|
||||
global $PAGE, $DB, $SITE;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$persontoview = $this->getDataGenerator()->create_user();
|
||||
$persondoingtheviewing = $this->getDataGenerator()->create_user();
|
||||
|
||||
$PAGE->set_url('/');
|
||||
$PAGE->set_course($SITE);
|
||||
|
||||
// Check that a standard user can not view the preferences page.
|
||||
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
||||
$this->getDataGenerator()->role_assign($studentrole->id, $persondoingtheviewing->id);
|
||||
$this->setUser($persondoingtheviewing);
|
||||
$settingsnav = new exposed_settings_navigation();
|
||||
$settingsnav->initialise();
|
||||
$settingsnav->extend_for_user($persontoview->id);
|
||||
$this->assertFalse($settingsnav->can_view_user_preferences($persontoview->id));
|
||||
|
||||
// Set persondoingtheviewing as a manager.
|
||||
$managerrole = $DB->get_record('role', array('shortname' => 'manager'));
|
||||
$this->getDataGenerator()->role_assign($managerrole->id, $persondoingtheviewing->id);
|
||||
$settingsnav = new exposed_settings_navigation();
|
||||
$settingsnav->initialise();
|
||||
$settingsnav->extend_for_user($persontoview->id);
|
||||
$this->assertTrue($settingsnav->can_view_user_preferences($persontoview->id));
|
||||
|
||||
// Check that the admin can view the preferences page.
|
||||
$this->setAdminUser();
|
||||
$settingsnav = new exposed_settings_navigation();
|
||||
$settingsnav->initialise();
|
||||
$settingsnav->extend_for_user($persontoview->id);
|
||||
$preferencenode = $settingsnav->find('userviewingsettings' . $persontoview->id, null);
|
||||
$this->assertTrue($settingsnav->can_view_user_preferences($persontoview->id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends test_setting__initialise
|
||||
* @param mixed $node
|
||||
* @return mixed
|
||||
*/
|
||||
public function test_setting_in_alternative_role($node): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->assertFalse($node->exposed_in_alternative_role());
|
||||
}
|
||||
|
||||
|
||||
public function test_navigation_node_collection_remove_with_no_type(): void {
|
||||
$navigationnodecollection = new navigation_node_collection();
|
||||
$this->setup_node();
|
||||
$this->node->key = 100;
|
||||
|
||||
// Test it's empty
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Add a node
|
||||
$navigationnodecollection->add($this->node);
|
||||
|
||||
// Test it's not empty
|
||||
$this->assertEquals(1, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Remove a node - passing key only!
|
||||
$this->assertTrue($navigationnodecollection->remove(100));
|
||||
|
||||
// Test it's empty again!
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
}
|
||||
|
||||
public function test_navigation_node_collection_remove_with_type(): void {
|
||||
$navigationnodecollection = new navigation_node_collection();
|
||||
$this->setup_node();
|
||||
$this->node->key = 100;
|
||||
|
||||
// Test it's empty
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Add a node
|
||||
$navigationnodecollection->add($this->node);
|
||||
|
||||
// Test it's not empty
|
||||
$this->assertEquals(1, count($navigationnodecollection->get_key_list()));
|
||||
|
||||
// Remove a node - passing type
|
||||
$this->assertTrue($navigationnodecollection->remove(100, 1));
|
||||
|
||||
// Test it's empty again!
|
||||
$this->assertEquals(0, count($navigationnodecollection->get_key_list()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the set_force_into_more_menu method.
|
||||
*
|
||||
* @param bool $haschildren Whether the navigation node has children nodes
|
||||
* @param bool $forceintomoremenu Whether to force the navigation node and its children into the "more" menu
|
||||
* @dataProvider set_force_into_more_menu_provider
|
||||
*/
|
||||
public function test_set_force_into_more_menu(bool $haschildren, bool $forceintomoremenu): void {
|
||||
// Create a navigation node.
|
||||
$node = new navigation_node(['text' => 'Navigation node', 'key' => 'navnode']);
|
||||
|
||||
// If required, add some children nodes to the navigation node.
|
||||
if ($haschildren) {
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$node->add("Child navigation node {$i}");
|
||||
}
|
||||
}
|
||||
|
||||
$node->set_force_into_more_menu($forceintomoremenu);
|
||||
// Assert that the expected value has been assigned to the 'forceintomoremenu' property
|
||||
// in the navigation node and its children.
|
||||
$this->assertEquals($forceintomoremenu, $node->forceintomoremenu);
|
||||
foreach ($node->children as $child) {
|
||||
$this->assertEquals($forceintomoremenu, $child->forceintomoremenu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_set_force_into_more_menu function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function set_force_into_more_menu_provider(): array {
|
||||
return [
|
||||
'Navigation node without any children nodes; Force into "more" menu => true.' =>
|
||||
[
|
||||
false,
|
||||
true,
|
||||
],
|
||||
'Navigation node with children nodes; Force into "more" menu => true.' =>
|
||||
[
|
||||
true,
|
||||
true,
|
||||
],
|
||||
'Navigation node with children nodes; Force into "more" menu => false.' =>
|
||||
[
|
||||
true,
|
||||
false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the is_action_link method.
|
||||
*
|
||||
* @param navigation_node $node The sample navigation node
|
||||
* @param bool $expected Whether the navigation node contains an action link
|
||||
* @dataProvider is_action_link_provider
|
||||
* @covers navigation_node::is_action_link
|
||||
*/
|
||||
public function test_is_action_link(navigation_node $node, bool $expected): void {
|
||||
$this->assertEquals($node->is_action_link(), $expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_is_action_link function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function is_action_link_provider(): array {
|
||||
return [
|
||||
'The navigation node has an action link.' =>
|
||||
[
|
||||
navigation_node::create('Node', new action_link(new \moodle_url('/'), '',
|
||||
new popup_action('click', new \moodle_url('/'))), navigation_node::TYPE_SETTING),
|
||||
true
|
||||
],
|
||||
|
||||
'The navigation node does not have an action link.' =>
|
||||
[
|
||||
navigation_node::create('Node', new \moodle_url('/'), navigation_node::TYPE_SETTING),
|
||||
false
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the action_link_actions method.
|
||||
*
|
||||
* @param navigation_node $node The sample navigation node
|
||||
* @dataProvider action_link_actions_provider
|
||||
* @covers navigation_node::action_link_actions
|
||||
*/
|
||||
public function test_action_link_actions(navigation_node $node): void {
|
||||
// Get the formatted array of action link actions.
|
||||
$data = $node->action_link_actions();
|
||||
// The navigation node has an action link.
|
||||
if ($node->action instanceof action_link) {
|
||||
if (!empty($node->action->actions)) { // There are actions added to the action link.
|
||||
$this->assertArrayHasKey('actions', $data);
|
||||
$this->assertCount(1, $data['actions']);
|
||||
$expected = (object)[
|
||||
'id' => $node->action->attributes['id'],
|
||||
'event' => $node->action->actions[0]->event,
|
||||
'jsfunction' => $node->action->actions[0]->jsfunction,
|
||||
'jsfunctionargs' => json_encode($node->action->actions[0]->jsfunctionargs)
|
||||
];
|
||||
$this->assertEquals($expected, $data['actions'][0]);
|
||||
} else { // There are no actions added to the action link.
|
||||
$this->assertArrayHasKey('actions', $data);
|
||||
$this->assertEmpty($data['actions']);
|
||||
}
|
||||
} else { // The navigation node does not have an action link.
|
||||
$this->assertEmpty($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for the test_action_link_actions function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function action_link_actions_provider(): array {
|
||||
return [
|
||||
'The navigation node has an action link with an action attached.' =>
|
||||
[
|
||||
navigation_node::create('Node', new action_link(new \moodle_url('/'), '',
|
||||
new popup_action('click', new \moodle_url('/'))), navigation_node::TYPE_SETTING),
|
||||
],
|
||||
'The navigation node has an action link without an action.' =>
|
||||
[
|
||||
navigation_node::create('Node', new action_link(new \moodle_url('/'), '', null),
|
||||
navigation_node::TYPE_SETTING),
|
||||
],
|
||||
'The navigation node does not have an action link.' =>
|
||||
[
|
||||
navigation_node::create('Node', new \moodle_url('/'), navigation_node::TYPE_SETTING),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a dummy object that allows us to call protected methods within the
|
||||
* global navigation class by prefixing the methods with `exposed_`
|
||||
*/
|
||||
class exposed_global_navigation extends global_navigation {
|
||||
protected $exposedkey = 'exposed_';
|
||||
public function __construct(?\moodle_page $page=null) {
|
||||
global $PAGE;
|
||||
if ($page === null) {
|
||||
$page = $PAGE;
|
||||
}
|
||||
parent::__construct($page);
|
||||
}
|
||||
public function __call($method, $arguments) {
|
||||
if (strpos($method, $this->exposedkey) !== false) {
|
||||
$method = substr($method, strlen($this->exposedkey));
|
||||
}
|
||||
if (method_exists($this, $method)) {
|
||||
return call_user_func_array(array($this, $method), $arguments);
|
||||
}
|
||||
throw new \coding_exception('You have attempted to access a method that does not exist for the given object '.$method, DEBUG_DEVELOPER);
|
||||
}
|
||||
public function set_initialised() {
|
||||
$this->initialised = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class mock_initialise_global_navigation extends global_navigation {
|
||||
|
||||
protected static $count = 1;
|
||||
|
||||
public function load_for_category() {
|
||||
$this->add('load_for_category', null, null, null, 'initcall'.self::$count);
|
||||
self::$count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function load_for_course() {
|
||||
$this->add('load_for_course', null, null, null, 'initcall'.self::$count);
|
||||
self::$count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function load_for_activity() {
|
||||
$this->add('load_for_activity', null, null, null, 'initcall'.self::$count);
|
||||
self::$count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function load_for_user($user=null, $forceforcontext=false) {
|
||||
$this->add('load_for_user', null, null, null, 'initcall'.self::$count);
|
||||
self::$count++;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a dummy object that allows us to call protected methods within the
|
||||
* global navigation class by prefixing the methods with `exposed_`.
|
||||
*/
|
||||
class exposed_navbar extends navbar {
|
||||
protected $exposedkey = 'exposed_';
|
||||
|
||||
public function __construct(\moodle_page $page) {
|
||||
parent::__construct($page);
|
||||
}
|
||||
public function __call($method, $arguments) {
|
||||
if (strpos($method, $this->exposedkey) !== false) {
|
||||
$method = substr($method, strlen($this->exposedkey));
|
||||
}
|
||||
if (method_exists($this, $method)) {
|
||||
return call_user_func_array(array($this, $method), $arguments);
|
||||
}
|
||||
throw new \coding_exception('You have attempted to access a method that does not exist for the given object '.$method, DEBUG_DEVELOPER);
|
||||
}
|
||||
}
|
||||
|
||||
class navigation_exposed_moodle_page extends \moodle_page {
|
||||
public function set_navigation(navigation_node $node) {
|
||||
$this->_navigation = $node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a dummy object that allows us to call protected methods within the
|
||||
* global navigation class by prefixing the methods with `exposed_`.
|
||||
*/
|
||||
class exposed_settings_navigation extends settings_navigation {
|
||||
protected $exposedkey = 'exposed_';
|
||||
public function __construct() {
|
||||
global $PAGE;
|
||||
parent::__construct($PAGE);
|
||||
}
|
||||
public function __call($method, $arguments) {
|
||||
if (strpos($method, $this->exposedkey) !== false) {
|
||||
$method = substr($method, strlen($this->exposedkey));
|
||||
}
|
||||
if (method_exists($this, $method)) {
|
||||
return call_user_func_array(array($this, $method), $arguments);
|
||||
}
|
||||
throw new \coding_exception('You have attempted to access a method that does not exist for the given object '.$method, DEBUG_DEVELOPER);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ require_once("{$CFG->libdir}/setuplib.php"); // Functions that MUST be lo
|
||||
require_once("{$CFG->libdir}/filterlib.php"); // Functions for filtering test as it is output.
|
||||
require_once("{$CFG->libdir}/weblib.php"); // Functions relating to HTTP and content.
|
||||
require_once("{$CFG->libdir}/outputlib.php"); // Functions for generating output.
|
||||
require_once("{$CFG->libdir}/navigationlib.php"); // Class for generating Navigation structure.
|
||||
require_once("{$CFG->libdir}/dmllib.php"); // Database access.
|
||||
require_once("{$CFG->libdir}/datalib.php"); // Legacy lib with a big-mix of functions.
|
||||
require_once("{$CFG->libdir}/accesslib.php"); // Access control functions.
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
require_once($CFG->libdir . '/navigationlib.php');
|
||||
|
||||
require_login(null, false);
|
||||
if (isguestuser()) {
|
||||
|
||||
Reference in New Issue
Block a user