. /** * This file contains classes used to manage the navigation structures in Moodle * and was introduced as part of the changes occuring in Moodle 2.0 * * @since 2.0 * @package moodlecore * @subpackage navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ if (!function_exists('get_all_sections')) { /** Include course lib for its functions */ require_once($CFG->dirroot.'/course/lib.php'); } /** * The name that will be used to separate the navigation cache within SESSION */ define('NAVIGATION_CACHE_NAME', 'navigation'); /** * This class is used to represent a node in a navigation tree * * This class is used to represent a node in a navigation tree within Moodle, * the tree could be one of global navigation, settings navigation, or the navbar. * Each node can be one of two types either a Leaf (default) or a branch. * When a node is first created it is created as a leaf, when/if children are added * the node then becomes a branch. * * @package moodlecore * @subpackage navigation * @copyright 2009 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class navigation_node implements renderable { /** @var int Used to identify this node a leaf (default) 0 */ const NODETYPE_LEAF = 0; /** @var int Used to identify this node a branch, happens with children 1 */ const NODETYPE_BRANCH = 1; /** @var null Unknown node type null */ const TYPE_UNKNOWN = null; /** @var int System node type 0 */ const TYPE_ROOTNODE = 0; /** @var int System node type 1 */ const TYPE_SYSTEM = 1; /** @var int Category node type 10 */ const TYPE_CATEGORY = 10; /** @var int Course node type 20 */ const TYPE_COURSE = 20; /** @var int Course Structure node type 30 */ const TYPE_SECTION = 30; /** @var int Activity node type, e.g. Forum, Quiz 40 */ const TYPE_ACTIVITY = 40; /** @var int Resource node type, e.g. Link to a file, or label 50 */ const TYPE_RESOURCE = 50; /** @var int A custom node type, default when adding without specifing type 60 */ const TYPE_CUSTOM = 60; /** @var int Setting node type, used only within settings nav 70 */ const TYPE_SETTING = 70; /** @var int Setting node type, used only within settings nav 80 */ const TYPE_USER = 80; /** @var int Setting node type, used for containers of no importance 90 */ const TYPE_CONTAINER = 90; /** @var int Parameter to aid the coder in tracking [optional] */ public $id = null; /** @var string|int The identifier for the node, used to retrieve the node */ public $key = null; /** @var string The text to use for the node */ public $text = null; /** @var string Short text to use if requested [optional] */ public $shorttext = null; /** @var string The title attribute for an action if one is defined */ public $title = null; /** @var string A string that can be used to build a help button */ public $helpbutton = null; /** @var moodle_url|action_link|null An action for the node (link) */ public $action = null; /** @var pix_icon The path to an icon to use for this node */ public $icon = null; /** @var int See TYPE_* constants defined for this class */ public $type = self::TYPE_UNKNOWN; /** @var int See NODETYPE_* constants defined for this class */ public $nodetype = self::NODETYPE_LEAF; /** @var bool If set to true the node will be collapsed by default */ public $collapse = false; /** @var bool If set to true the node will be expanded by default */ public $forceopen = false; /** @var array An array of CSS classes for the node */ public $classes = array(); /** @var navigation_node_collection An array of child nodes */ public $children = array(); /** @var bool If set to true the node will be recognised as active */ public $isactive = false; /** @var bool If set to true the node will be dimmed */ public $hidden = false; /** @var bool If set to false the node will not be displayed */ public $display = true; /** @var bool If set to true then an HR will be printed before the node */ public $preceedwithhr = false; /** @var bool If set to true the the navigation bar should ignore this node */ public $mainnavonly = false; /** @var bool If set to true a title will be added to the action no matter what */ public $forcetitle = false; /** @var navigation_node A reference to the node parent */ public $parent = null; /** @var bool Override to not display the icon even if one is provided **/ public $hideicon = false; /** @var array */ protected $namedtypes = array(0=>'system',10=>'category',20=>'course',30=>'structure',40=>'activity',50=>'resource',60=>'custom',70=>'setting', 80=>'user'); /** @var moodle_url */ protected static $fullmeurl = null; /** @var bool toogles auto matching of active node */ public static $autofindactive = true; /** * Constructs a new navigation_node * * @param array|string $properties Either an array of properties or a string to use * as the text for the node */ public function __construct($properties) { if (is_array($properties)) { // Check the array for each property that we allow to set at construction. // text - The main content for the node // shorttext - A short text if required for the node // icon - The icon to display for the node // type - The type of the node // key - The key to use to identify the node // parent - A reference to the nodes parent // action - The action to attribute to this node, usually a URL to link to if (array_key_exists('text', $properties)) { $this->text = $properties['text']; } if (array_key_exists('shorttext', $properties)) { $this->shorttext = $properties['shorttext']; } if (!array_key_exists('icon', $properties)) { $properties['icon'] = new pix_icon('i/navigationitem', 'moodle'); } $this->icon = $properties['icon']; if ($this->icon instanceof pix_icon) { if (empty($this->icon->attributes['class'])) { $this->icon->attributes['class'] = 'navicon'; } else { $this->icon->attributes['class'] .= ' navicon'; } } if (array_key_exists('type', $properties)) { $this->type = $properties['type']; } else { $this->type = self::TYPE_CUSTOM; } if (array_key_exists('key', $properties)) { $this->key = $properties['key']; } if (array_key_exists('parent', $properties)) { $this->parent = $properties['parent']; } // This needs to happen last because of the check_if_active call that occurs if (array_key_exists('action', $properties)) { $this->action = $properties['action']; if (is_string($this->action)) { $this->action = new moodle_url($this->action); } if (self::$autofindactive) { $this->check_if_active(); } } } else if (is_string($properties)) { $this->text = $properties; } if ($this->text === null) { throw new coding_exception('You must set the text for the node when you create it.'); } // Default the title to the text $this->title = $this->text; // Instantiate a new navigation node collection for this nodes children $this->children = new navigation_node_collection(); } /** * Checks if this node is the active node. * * This is determined by comparing the action for the node against the * defined URL for the page. A match will see this node marked as active. * * @param int $strength One of URL_MATCH_EXACT, URL_MATCH_PARAMS, or URL_MATCH_BASE * @return bool */ public function check_if_active($strength=URL_MATCH_EXACT) { global $FULLME, $PAGE; // Set fullmeurl if it hasn't already been set if (self::$fullmeurl == null) { if ($PAGE->has_set_url()) { self::override_active_url(new moodle_url($PAGE->url)); } else { self::override_active_url(new moodle_url($FULLME)); } } // Compare the action of this node against the fullmeurl if ($this->action instanceof moodle_url && $this->action->compare(self::$fullmeurl, $strength)) { $this->make_active(); return true; } return false; } /** * Overrides the fullmeurl variable providing * * @param moodle_url $url The url to use for the fullmeurl. */ public static function override_active_url(moodle_url $url) { self::$fullmeurl = $url; } /** * Adds a navigation node as a child of this node. * * @param string $text * @param moodle_url|action_link $action * @param int $type * @param string $shorttext * @param string|int $key * @param pix_icon $icon * @return navigation_node */ public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) { // First convert the nodetype for this node to a branch as it will now have children if ($this->nodetype !== self::NODETYPE_BRANCH) { $this->nodetype = self::NODETYPE_BRANCH; } // Properties array used when creating the new navigation node $itemarray = array( '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 = $this->children->count(); } // 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 $node = $this->children->add(new navigation_node($itemarray)); // If the node is a category node or the user is logged in and its a course // then mark this node as a branch (makes it expandable by AJAX) if (($type==self::TYPE_CATEGORY) || (isloggedin() && $type==self::TYPE_COURSE)) { $node->nodetype = self::NODETYPE_BRANCH; } // If this node is hidden mark it's children as hidden also if ($this->hidden) { $node->hidden = true; } // Return the node (reference returned by $this->children->add() return $node; } /** * Searches for a node of the given type with the given key. * * This searches this node plus all of its children, and their children.... * If you know the node you are looking for is a child of this node then please * use the get method instead. * * @param int|string $key The key of the node we are looking for * @param int $type One of navigation_node::TYPE_* * @return navigation_node|false */ public function find($key, $type) { return $this->children->find($key, $type); } /** * Get ths child of this node that has the given key + (optional) type. * * If you are looking for a node and want to search all children + thier children * then please use the find method instead. * * @param int|string $key The key of the node we are looking for * @param int $type One of navigation_node::TYPE_* * @return navigation_node|false */ public function get($key, $type=null) { return $this->children->get($key, $type); } /** * Removes this node. * * @return bool */ public function remove() { return $this->parent->children->remove($this->key, $this->type); } /** * Checks if this node has or could have any children * * @return bool Returns true if it has children or could have (by AJAX expansion) */ public function has_children() { return ($this->nodetype === navigation_node::NODETYPE_BRANCH || $this->children->count()>0); } /** * Marks this node as active and forces it open. */ public function make_active() { $this->isactive = true; $this->add_class('active_tree_node'); $this->force_open(); if ($this->parent !== null) { $this->parent->make_inactive(); } } /** * Marks a node as inactive and recusised back to the base of the tree * doing the same to all parents. */ public function make_inactive() { $this->isactive = false; $this->remove_class('active_tree_node'); if ($this->parent !== null) { $this->parent->make_inactive(); } } /** * Forces this node to be open and at the same time forces open all * parents until the root node. * * Recursive. */ public function force_open() { $this->forceopen = true; if ($this->parent !== null) { $this->parent->force_open(); } } /** * Adds a CSS class to this node. * * @param string $class * @return bool */ public function add_class($class) { if (!in_array($class, $this->classes)) { $this->classes[] = $class; } return true; } /** * Removes a CSS class from this node. * * @param string $class * @return bool True if the class was successfully removed. */ public function remove_class($class) { if (in_array($class, $this->classes)) { $key = array_search($class,$this->classes); if ($key!==false) { unset($this->classes[$key]); return true; } } return false; } /** * Sets the title for this node and forces Moodle to utilise it. * @param string $title */ public function title($title) { $this->title = $title; $this->forcetitle = true; } /** * Resets the page specific information on this node if it is being unserialised. */ public function __wakeup(){ $this->forceopen = false; $this->isactive = false; $this->remove_class('active_tree_node'); } /** * Checks if this node or any of its children contain the active node. * * Recursive. * * @return bool */ public function contains_active_node() { if ($this->isactive) { return true; } else { foreach ($this->children as $child) { if ($child->isactive || $child->contains_active_node()) { return true; } } } return false; } /** * Finds the active node. * * Searches this nodes children plus all of the children for the active node * and returns it if found. * * Recursive. * * @return navigation_node|false */ public function find_active_node() { if ($this->isactive) { return $this; } else { foreach ($this->children as &$child) { $outcome = $child->find_active_node(); if ($outcome !== false) { return $outcome; } } } return false; } /** * Gets the content for this node. * * @param bool $shorttext If true shorttext is used rather than the normal text * @return string */ public function get_content($shorttext=false) { if ($shorttext && $this->shorttext!==null) { return format_string($this->shorttext); } else { return format_string($this->text); } } /** * Gets the title to use for this node. * * @return string */ public function get_title() { if ($this->forcetitle || $this->action != null){ return $this->title; } else { return ''; } } /** * Gets the CSS class to add to this node to describe its type * * @return string */ public function get_css_type() { if (array_key_exists($this->type, $this->namedtypes)) { return 'type_'.$this->namedtypes[$this->type]; } return 'type_unknown'; } /** * Finds all nodes that are expandable by AJAX * * @param array $expandable An array by reference to populate with expandable nodes. */ public function find_expandable(array &$expandable) { if (!isloggedin()) { return; } foreach ($this->children as &$child) { if ($child->nodetype == self::NODETYPE_BRANCH && $child->children->count()==0 && $child->display) { $child->id = 'expandable_branch_'.(count($expandable)+1); $this->add_class('canexpand'); $expandable[] = array('id'=>$child->id,'branchid'=>$child->key,'type'=>$child->type); } $child->find_expandable($expandable); } } public function find_all_of_type($type) { $nodes = $this->children->type($type); foreach ($this->children as &$node) { $childnodes = $node->find_all_of_type($type); $nodes = array_merge($nodes, $childnodes); } return $nodes; } } /** * 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 moodlecore * @subpackage navigation * @copyright 2010 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class navigation_node_collection implements IteratorAggregate { /** * A multidimensional array to where the first key is the type and the second * key is the nodes key. * @var array */ protected $collection = array(); /** * 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 = array(); /** * 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; /** * Adds a navigation node to the collection * * @param navigation_node $node * @return navigation_node */ public function add(navigation_node $node) { 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] = array(); } // 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); } // Add the node to the appropriate place in the ordered structure. $this->orderedcollection[$type][$key] = $node; // Add a reference to the node to the progressive collection. $this->collection[$this->count] = &$this->orderedcollection[$type][$key]; // Update the last property to a reference to this new node. $this->last = &$this->orderedcollection[$type][$key]; $this->count++; // Return the reference to the now added node return $this->last; } /** * 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 */ 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|null */ 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 */ public function type($type) { if (!array_key_exists($type, $this->orderedcollection)) { $this->orderedcollection[$type] = array(); } return $this->orderedcollection[$type]; } /** * Removes the node with the given key and type from the collection * * @param string|int $key * @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 && $node->type == $type) { unset($this->collection[$colkey]); break; } } unset($this->orderedcollection[$child->type][$child->key]); $this->count--; return true; } return false; } /** * Gets the number of nodes in this collection * @return int */ public function count() { return count($this->collection); } /** * Gets an array iterator for the collection. * * This is required by the IteratorAggregator interface and is used by routines * such as the foreach loop. * * @return ArrayIterator */ public function getIterator() { return new ArrayIterator($this->collection); } } /** * The global navigation class used for... the global navigation * * This class is used by PAGE to store the global navigation for the site * and is then used by the settings nav and navbar to save on processing and DB calls * * See *
* // To get resources:
* $this->cache->{'course'.$courseid.'resources'}
* // To get activities:
* $this->cache->{'course'.$courseid.'activities'}
*
*
* @param stdClass $course The course to get modules for
*/
protected function get_course_modules($course) {
global $CFG;
$mods = $modnames = $modnamesplural = $modnamesused = array();
// This function is included when we include course/lib.php at the top
// of this file
get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
$resources = array();
$activities = array();
foreach($modnames as $modname=>$modnamestr) {
if (!course_allowed_module($course, $modname)) {
continue;
}
$libfile = "$CFG->dirroot/mod/$modname/lib.php";
if (!file_exists($libfile)) {
continue;
}
include_once($libfile);
$gettypesfunc = $modname.'_get_types';
if (function_exists($gettypesfunc)) {
$types = $gettypesfunc();
foreach($types as $type) {
if (!isset($type->modclass) || !isset($type->typestr)) {
debugging('Incorrect activity type in '.$modname);
continue;
}
if ($type->modclass == MOD_CLASS_RESOURCE) {
$resources[html_entity_decode($type->type)] = $type->typestr;
} else {
$activities[html_entity_decode($type->type)] = $type->typestr;
}
}
} else {
$archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
if ($archetype == MOD_ARCHETYPE_RESOURCE) {
$resources[$modname] = $modnamestr;
} else {
// all other archetypes are considered activity
$activities[$modname] = $modnamestr;
}
}
}
$this->cache->{'course'.$course->id.'resources'} = $resources;
$this->cache->{'course'.$course->id.'activities'} = $activities;
}
/**
* This function loads the course settings that are available for the user
*
* @param bool $forceopen If set to true the course node will be forced open
* @return navigation_node|false
*/
protected function load_course_settings($forceopen = false) {
global $CFG, $USER, $SESSION, $OUTPUT;
$course = $this->page->course;
$coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
if (!$this->cache->cached('canviewcourse'.$course->id)) {
$this->cache->{'canviewcourse'.$course->id} = has_capability('moodle/course:participate', $coursecontext);
}
if ($course->id === SITEID || !$this->cache->{'canviewcourse'.$course->id}) {
return false;
}
$coursenode = $this->add(get_string('courseadministration'), null, self::TYPE_COURSE, null, 'courseadmin');
if ($forceopen) {
$coursenode->force_open();
}
if (has_capability('moodle/course:update', $coursecontext)) {
// Add the turn on/off settings
$url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
if ($this->page->user_is_editing()) {
$url->param('edit', 'off');
$editstring = get_string('turneditingoff');
} else {
$url->param('edit', 'on');
$editstring = get_string('turneditingon');
}
$coursenode->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
if ($this->page->user_is_editing()) {
// Add `add` resources|activities branches
$structurefile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
if (file_exists($structurefile)) {
require_once($structurefile);
$formatstring = call_user_func('callback_'.$course->format.'_definition');
$formatidentifier = optional_param(call_user_func('callback_'.$course->format.'_request_key'), 0, PARAM_INT);
} else {
$formatstring = get_string('topic');
$formatidentifier = optional_param('topic', 0, PARAM_INT);
}
if (!$this->cache->cached('coursesections'.$course->id)) {
$this->cache->{'coursesections'.$course->id} = get_all_sections($course->id);
}
$sections = $this->cache->{'coursesections'.$course->id};
$addresource = $this->add(get_string('addresource'));
$addactivity = $this->add(get_string('addactivity'));
if ($formatidentifier!==0) {
$addresource->force_open();
$addactivity->force_open();
}
if (!$this->cache->cached('course'.$course->id.'resources')) {
$this->get_course_modules($course);
}
$resources = $this->cache->{'course'.$course->id.'resources'};
$activities = $this->cache->{'course'.$course->id.'activities'};
$textlib = textlib_get_instance();
foreach ($sections as $section) {
if ($formatidentifier !== 0 && $section->section != $formatidentifier) {
continue;
}
$sectionurl = new moodle_url('/course/view.php', array('id'=>$course->id, $formatstring=>$section->section));
if ($section->section == 0) {
$sectionresources = $addresource->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
$sectionactivities = $addactivity->add(get_string('course'), $sectionurl, self::TYPE_SETTING);
} else {
$sectionresources = $addresource->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
$sectionactivities = $addactivity->add($formatstring.' '.$section->section, $sectionurl, self::TYPE_SETTING);
}
foreach ($resources as $value=>$resource) {
$url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
$pos = strpos($value, '&type=');
if ($pos!==false) {
$url->param('add', $textlib->substr($value, 0,$pos));
$url->param('type', $textlib->substr($value, $pos+6));
} else {
$url->param('add', $value);
}
$sectionresources->add($resource, $url, self::TYPE_SETTING);
}
$subbranch = false;
foreach ($activities as $activityname=>$activity) {
if ($activity==='--') {
$subbranch = false;
continue;
}
if (strpos($activity, '--')===0) {
$subbranch = $sectionresources->add(trim($activity, '-'));
continue;
}
$url = new moodle_url('/course/mod.php', array('id'=>$course->id, 'sesskey'=>sesskey(), 'section'=>$section->section));
$pos = strpos($activityname, '&type=');
if ($pos!==false) {
$url->param('add', $textlib->substr($activityname, 0,$pos));
$url->param('type', $textlib->substr($activityname, $pos+6));
} else {
$url->param('add', $activityname);
}
if ($subbranch !== false) {
$subbranch->add($activity, $url, self::TYPE_SETTING);
} else {
$sectionresources->add($activity, $url, self::TYPE_SETTING);
}
}
}
}
// Add the course settings link
$url = new moodle_url('/course/edit.php', array('id'=>$course->id));
$coursenode->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
// Add the course completion settings link
if ($CFG->enablecompletion && $course->enablecompletion) {
$url = new moodle_url('/course/completion.php', array('id'=>$course->id));
$coursenode->add(get_string('completion', 'completion'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
}
}
if (has_capability('moodle/role:assign', $coursecontext)) {
// Add assign or override roles if allowed
$url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$coursecontext->id));
$coursenode->add(get_string('assignroles', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
// Override roles
if (has_capability('moodle/role:review', $coursecontext) or count(get_overridable_roles($coursecontext))>0) {
$url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$coursecontext->id));
$coursenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/permissions', ''));
}
// Check role permissions
if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
$url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$coursecontext->id));
$coursenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/checkpermissions', ''));
}
// Manage filters
if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) {
$url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
$coursenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', ''));
}
}
// Add view grade report is permitted
$reportavailable = false;
if (has_capability('moodle/grade:viewall', $coursecontext)) {
$reportavailable = true;
} else if (!empty($course->showgrades)) {
$reports = get_plugin_list('gradereport');
if (is_array($reports) && count($reports)>0) { // Get all installed reports
arsort($reports); // user is last, we want to test it first
foreach ($reports as $plugin => $plugindir) {
if (has_capability('gradereport/'.$plugin.':view', $coursecontext)) {
//stop when the first visible plugin is found
$reportavailable = true;
break;
}
}
}
}
if ($reportavailable) {
$url = new moodle_url('/grade/report/index.php', array('id'=>$course->id));
$gradenode = $coursenode->add(get_string('grades'), $url, self::TYPE_SETTING, null, 'grades', new pix_icon('i/grades', ''));
}
// Add outcome if permitted
if (!empty($CFG->enableoutcomes) && has_capability('moodle/course:update', $coursecontext)) {
$url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$course->id));
$coursenode->add(get_string('outcomes', 'grades'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/outcomes', ''));
}
// Add meta course links
if ($course->metacourse) {
if (has_capability('moodle/course:managemetacourse', $coursecontext)) {
$url = new moodle_url('/course/importstudents.php', array('id'=>$course->id));
$coursenode->add(get_string('childcourses'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
} else if (has_capability('moodle/role:assign', $coursecontext)) {
$roleassign = $coursenode->add(get_string('childcourses'), null, self::TYPE_SETTING, null, null, new pix_icon('i/course', ''));
$roleassign->hidden = true;
}
}
// Manage groups in this course
if (($course->groupmode || !$course->groupmodeforce) && has_capability('moodle/course:managegroups', $coursecontext)) {
$url = new moodle_url('/group/index.php', array('id'=>$course->id));
$coursenode->add(get_string('groups'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/group', ''));
}
// Backup this course
if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
$url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
$coursenode->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', ''));
}
// Restore to this course
if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
$url = new moodle_url('/files/index.php', array('id'=>$course->id, 'wdir'=>'/backupdata'));
$url = null; // Disabled until restore is implemented. MDL-21432
$coursenode->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
}
// Import data from other courses
if (has_capability('moodle/restore:restoretargetimport', $coursecontext)) {
$url = new moodle_url('/course/import.php', array('id'=>$course->id));
$url = null; // Disabled until restore is implemented. MDL-21432
$coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
}
// Publish course on a hub
if (has_capability('moodle/course:publish', $coursecontext)) {
$url = new moodle_url('/course/publish/index.php', array('id'=>$course->id));
$coursenode->add(get_string('publish'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/publish', ''));
}
// Reset this course
if (has_capability('moodle/course:reset', $coursecontext)) {
$url = new moodle_url('/course/reset.php', array('id'=>$course->id));
$coursenode->add(get_string('reset'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/return', ''));
}
// Manage questions
$questioncaps = array('moodle/question:add',
'moodle/question:editmine',
'moodle/question:editall',
'moodle/question:viewmine',
'moodle/question:viewall',
'moodle/question:movemine',
'moodle/question:moveall');
if (has_any_capability($questioncaps, $coursecontext)) {
$questionlink = $CFG->wwwroot.'/question/edit.php';
} else if (has_capability('moodle/question:managecategory', $coursecontext)) {
$questionlink = $CFG->wwwroot.'/question/category.php';
}
if (isset($questionlink)) {
$url = new moodle_url($questionlink, array('courseid'=>$course->id));
$coursenode->add(get_string('questions','quiz'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/questions', ''));
}
// Repository Instances
require_once($CFG->dirroot.'/repository/lib.php');
$editabletypes = repository::get_editable_types($coursecontext);
if (has_capability('moodle/course:update', $coursecontext) && !empty($editabletypes)) {
$url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$coursecontext->id));
$coursenode->add(get_string('repositories'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/repository', ''));
}
// Manage files
if (has_capability('moodle/course:managefiles', $coursecontext)) {
$url = new moodle_url('/files/index.php', array('contextid'=>$coursecontext->id, 'itemid'=>0, 'filearea'=>'course_content'));
$coursenode->add(get_string('files'), $url, self::TYPE_SETTING, null, 'coursefiles', new pix_icon('i/files', ''));
}
// Authorize hooks
if ($course->enrol == 'authorize' || (empty($course->enrol) && $CFG->enrol == 'authorize')) {
require_once($CFG->dirroot.'/enrol/authorize/const.php');
$url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id));
$coursenode->add(get_string('payments'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
if (has_capability('enrol/authorize:managepayments', $this->page->context)) {
$cnt = $DB->count_records('enrol_authorize', array('status'=>AN_STATUS_AUTH, 'courseid'=>$course->id));
if ($cnt) {
$url = new moodle_url('/enrol/authorize/index.php', array('course'=>$course->id,'status'=>AN_STATUS_AUTH));
$coursenode->add(get_string('paymentpending', 'moodle', $cnt), $url, self::TYPE_SETTING, null, null, new pix_icon('i/payment', ''));
}
}
}
// Unenrol link
if (empty($course->metacourse) && ($course->id!==SITEID)) {
if (is_enrolled(get_context_instance(CONTEXT_COURSE, $course->id))) {
if (has_capability('moodle/role:unassignself', $this->page->context, NULL, false) and get_user_roles($this->page->context, $USER->id, false)) { // Have some role
$this->content->items[]=''.get_string('unenrolme', '', format_string($course->shortname)).'';
$this->content->icons[]='';
}
} else if (is_viewing(get_context_instance(CONTEXT_COURSE, $course->id))) {
// inspector, manager, etc. - do not show anything
} else {
// access because otherwise they would not get into this course at all
$this->content->items[]=''.get_string('enrolme', '', format_string($course->shortname)).'';
$this->content->icons[]='';
}
}
// Switch roles
$roles = array();
$assumedrole = $this->in_alternative_role();
if ($assumedrole!==false) {
$roles[0] = get_string('switchrolereturn');
}
if (has_capability('moodle/role:switchroles', $coursecontext)) {
$availableroles = get_switchable_roles($coursecontext);
if (is_array($availableroles)) {
foreach ($availableroles as $key=>$role) {
if ($key == $CFG->guestroleid || $assumedrole===(int)$key) {
continue;
}
$roles[$key] = $role;
}
}
}
if (is_array($roles) && count($roles)>0) {
$switchroles = $this->add(get_string('switchroleto'));
if ((count($roles)==1 && array_key_exists(0, $roles))|| $assumedrole!==false) {
$switchroles->force_open();
}
$returnurl = $this->page->url;
$returnurl->param('sesskey', sesskey());
$SESSION->returnurl = serialize($returnurl);
foreach ($roles as $key=>$name) {
$url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>'1'));
$switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/roles', ''));
}
}
// Return we are done
return $coursenode;
}
/**
* This function calls the module function to inject module settings into the
* settings navigation tree.
*
* This only gets called if there is a corrosponding function in the modules
* lib file.
*
* For examples mod/forum/lib.php ::: forum_extend_settings_navigation()
*
* @return navigation_node|false
*/
protected function load_module_settings() {
global $CFG;
if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
$cm = get_coursemodule_from_id(false, $this->context->instanceid, 0, false, MUST_EXIST);
$this->page->set_cm($cm, $this->page->course);
}
$modulenode = $this->add(get_string($this->page->activityname.'administration', $this->page->activityname));
$modulenode->force_open();
// Settings for the module
if (has_capability('moodle/course:manageactivities', $this->page->cm->context)) {
$url = new moodle_url('/course/modedit.php', array('update' => $this->page->cm->id, 'return' => true, 'sesskey' => sesskey()));
$modulenode->add(get_string('settings'), $url, navigation_node::TYPE_SETTING);
}
// Assign local roles
if (count(get_assignable_roles($this->page->cm->context))>0) {
$url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->page->cm->context->id));
$modulenode->add(get_string('localroles', 'role'), $url, self::TYPE_SETTING);
}
// Override roles
if (has_capability('moodle/role:review', $this->page->cm->context) or count(get_overridable_roles($this->page->cm->context))>0) {
$url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->page->cm->context->id));
$modulenode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
}
// Check role permissions
if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->page->cm->context)) {
$url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->page->cm->context->id));
$modulenode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
}
// Manage filters
if (has_capability('moodle/filter:manage', $this->page->cm->context) && count(filter_get_available_in_context($this->page->cm->context))>0) {
$url = new moodle_url('/filter/manage.php', array('contextid'=>$this->page->cm->context->id));
$modulenode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING);
}
if (has_capability('coursereport/log:view', get_context_instance(CONTEXT_COURSE, $this->page->cm->course))) {
$url = new moodle_url('/course/report/log/index.php', array('chooselog'=>'1','id'=>$this->page->cm->course,'modid'=>$this->page->cm->id));
$modulenode->add(get_string('logs'), $url, self::TYPE_SETTING);
}
// Add a backup link
$featuresfunc = $this->page->activityname.'_supports';
if ($featuresfunc(FEATURE_BACKUP_MOODLE2) && has_capability('moodle/backup:backupactivity', $this->page->cm->context)) {
$url = new moodle_url('/backup/backup.php', array('id'=>$this->page->cm->course, 'cm'=>$this->page->cm->id));
$modulenode->add(get_string('backup'), $url, self::TYPE_SETTING);
}
$file = $CFG->dirroot.'/mod/'.$this->page->activityname.'/lib.php';
$function = $this->page->activityname.'_extend_settings_navigation';
if (file_exists($file)) {
require_once($file);
}
if (!function_exists($function)) {
return $modulenode;
}
$function($this, $modulenode);
// Remove the module node if there are no children
if (empty($modulenode->children)) {
$modulenode->remove();
}
return $modulenode;
}
/**
* Loads the user settings block of the settings nav
*
* This function is simply works out the userid and whether we need to load
* just the current users profile settings, or the current user and the user the
* current user is viewing.
*
* This function has some very ugly code to work out the user, if anyone has
* any bright ideas please feel free to intervene.
*
* @param int $courseid The course id of the current course
* @return navigation_node|false
*/
protected function load_user_settings($courseid=SITEID) {
global $USER, $FULLME, $CFG;
if (isguestuser() || !isloggedin()) {
return false;
}
// This is terribly ugly code, but I couldn't see a better way around it
// we need to pick up the user id, it can be the current user or someone else
// and the key depends on the current location
// Default to look at id
$userkey='id';
if (strpos($FULLME,'/blog/') || strpos($FULLME, $CFG->admin.'/roles/')) {
// And blog and roles just do thier own thing using `userid`
$userkey = 'userid';
} else if ($this->context->contextlevel >= CONTEXT_COURSECAT && strpos($FULLME, '/message/')===false && strpos($FULLME, '/mod/forum/user')===false && strpos($FULLME, '/user/editadvanced')===false) {
// If we have a course context and we are not in message or forum
// Message and forum both pick the user up from `id`
$userkey = 'user';
}
$userid = optional_param($userkey, $USER->id, PARAM_INT);
if ($userid!=$USER->id) {
$usernode = $this->generate_user_settings($courseid, $userid, 'userviewingsettings');
$this->generate_user_settings($courseid, $USER->id);
} else {
$usernode = $this->generate_user_settings($courseid, $USER->id);
}
return $usernode;
}
/**
* This function gets called by {@link load_user_settings()} and actually works out
* what can be shown/done
*
* @param int $courseid The current course' id
* @param int $userid The user id to load for
* @param string $gstitle The string to pass to get_string for the branch title
* @return navigation_node|false
*/
protected function generate_user_settings($courseid, $userid, $gstitle='usercurrentsettings') {
global $DB, $CFG, $USER, $SITE;
if ($courseid != SITEID) {
if (!empty($this->page->course->id) && $this->page->course->id == $courseid) {
$course = $this->page->course;
} else {
$course = $DB->get_record("course", array("id"=>$courseid), '*', MUST_EXIST);
}
} else {
$course = $SITE;
}
$coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); // Course context
$systemcontext = get_system_context();
$currentuser = ($USER->id == $userid);
if ($currentuser) {
$user = $USER;
$usercontext = get_context_instance(CONTEXT_USER, $user->id); // User context
} else {
if (!$user = $DB->get_record('user', array('id'=>$userid))) {
return false;
}
// Check that the user can view the profile
$usercontext = get_context_instance(CONTEXT_USER, $user->id); // User context
if ($course->id==SITEID) {
if ($CFG->forceloginforprofiles && !!has_coursemanager_role($user->id) && !has_capability('moodle/user:viewdetails', $usercontext)) { // Reduce possibility of "browsing" userbase at site level
// Teachers can browse and be browsed at site level. If not forceloginforprofiles, allow access (bug #4366)
return false;
}
} else {
if ((!has_capability('moodle/user:viewdetails', $coursecontext) && !has_capability('moodle/user:viewdetails', $usercontext)) || !has_capability('moodle/course:participate', $coursecontext, $user->id, false)) {
return false;
}
if (groups_get_course_groupmode($course) == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) {
// If groups are in use, make sure we can see that group
return false;
}
}
}
$fullname = fullname($user, has_capability('moodle/site:viewfullnames', $this->page->context));
// Add a user setting branch
$usersetting = $this->add(get_string($gstitle, 'moodle', $fullname), null, self::TYPE_CONTAINER, null, $gstitle);
$usersetting->id = 'usersettings';
// Check if the user has been deleted
if ($user->deleted) {
if (!has_capability('moodle/user:update', $coursecontext)) {
// We can't edit the user so just show the user deleted message
$usersetting->add(get_string('userdeleted'), null, self::TYPE_SETTING);
} else {
// We can edit the user so show the user deleted message and link it to the profile
if ($course->id == SITEID) {
$profileurl = new moodle_url('/user/profile.php', array('id'=>$user->id));
} else {
$profileurl = new moodle_url('/user/view.php', array('id'=>$user->id, 'course'=>$course->id));
}
$usersetting->add(get_string('userdeleted'), $profileurl, self::TYPE_SETTING);
}
return true;
}
// Add the profile edit link
if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) {
if (($currentuser || !is_primary_admin($user->id)) && has_capability('moodle/user:update', $systemcontext)) {
$url = new moodle_url('/user/editadvanced.php', array('id'=>$user->id, 'course'=>$course->id));
$usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
} else if ((has_capability('moodle/user:editprofile', $usercontext) && !is_primary_admin($user->id)) || ($currentuser && has_capability('moodle/user:editownprofile', $systemcontext))) {
$url = new moodle_url('/user/edit.php', array('id'=>$user->id, 'course'=>$course->id));
$usersetting->add(get_string('editmyprofile'), $url, self::TYPE_SETTING);
}
}
// Change password link
if (!empty($user->auth)) {
$userauth = get_auth_plugin($user->auth);
if ($currentuser && !session_is_loggedinas() && $userauth->can_change_password() && !isguestuser() && has_capability('moodle/user:changeownpassword', $systemcontext)) {
$passwordchangeurl = $userauth->change_password_url();
if (!$passwordchangeurl) {
if (empty($CFG->loginhttps)) {
$wwwroot = $CFG->wwwroot;
} else {
$wwwroot = str_replace('http:', 'https:', $CFG->wwwroot);
}
$passwordchangeurl = new moodle_url('/login/change_password.php');
} else {
$urlbits = explode($passwordchangeurl. '?', 1);
$passwordchangeurl = new moodle_url($urlbits[0]);
if (count($urlbits)==2 && preg_match_all('#\&([^\=]*?)\=([^\&]*)#si', '&'.$urlbits[1], $matches)) {
foreach ($matches as $pair) {
$fullmeurl->param($pair[1],$pair[2]);
}
}
}
$passwordchangeurl->param('id', $course->id);
$usersetting->add(get_string("changepassword"), $passwordchangeurl, self::TYPE_SETTING);
}
}
// View the roles settings
if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:manage'), $usercontext)) {
$roles = $usersetting->add(get_string('roles'), null, self::TYPE_SETTING);
$url = new moodle_url('/admin/roles/usersroles.php', array('userid'=>$user->id, 'courseid'=>$course->id));
$roles->add(get_string('thisusersroles', 'role'), $url, self::TYPE_SETTING);
$assignableroles = get_assignable_roles($usercontext, ROLENAME_BOTH);
if (!empty($assignableroles)) {
$url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
$roles->add(get_string('assignrolesrelativetothisuser', 'role'), $url, self::TYPE_SETTING);
}
if (has_capability('moodle/role:review', $usercontext) || count(get_overridable_roles($usercontext, ROLENAME_BOTH))>0) {
$url = new moodle_url('/admin/roles/permissions.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
$roles->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
}
$url = new moodle_url('/admin/roles/check.php', array('contextid'=>$usercontext->id,'userid'=>$user->id, 'courseid'=>$course->id));
$roles->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
}
// Portfolio
if ($currentuser && !empty($CFG->enableportfolios) && has_capability('moodle/portfolio:export', $systemcontext)) {
require_once($CFG->libdir . '/portfoliolib.php');
if (portfolio_instances(true, false)) {
$portfolio = $usersetting->add(get_string('portfolios', 'portfolio'), null, self::TYPE_SETTING);
$portfolio->add(get_string('configure', 'portfolio'), new moodle_url('/user/portfolio.php'), self::TYPE_SETTING);
$portfolio->add(get_string('logs', 'portfolio'), new moodle_url('/user/portfoliologs.php'), self::TYPE_SETTING);
}
}
$enablemanagetokens = false;
if (!empty($CFG->enablerssfeeds)) {
$enablemanagetokens = true;
} else if (!is_siteadmin($USER->id)
&& !empty($CFG->enablewebservices)
&& has_capability('moodle/webservice:createtoken', get_system_context()) ) {
$enablemanagetokens = true;
}
// Security keys
if ($currentuser && $enablemanagetokens) {
$url = new moodle_url('/user/managetoken.php', array('sesskey'=>sesskey()));
$usersetting->add(get_string('securitykeys', 'webservice'), $url, self::TYPE_SETTING);
}
// Repository
if (!$currentuser) {
require_once($CFG->dirroot . '/repository/lib.php');
$editabletypes = repository::get_editable_types($usercontext);
if ($usercontext->contextlevel == CONTEXT_USER && !empty($editabletypes)) {
$url = new moodle_url('/repository/manage_instances.php', array('contextid'=>$usercontext->id));
$usersetting->add(get_string('repositories', 'repository'), $url, self::TYPE_SETTING);
}
}
// Messaging
// TODO this is adding itself to the messaging settings for other people based on one's own setting
if (has_capability('moodle/user:editownmessageprofile', $systemcontext)) {
$url = new moodle_url('/message/edit.php', array('id'=>$user->id, 'course'=>$course->id));
$usersetting->add(get_string('editmymessage', 'message'), $url, self::TYPE_SETTING);
}
// Login as ...
if (!$user->deleted and !$currentuser && !session_is_loggedinas() && has_capability('moodle/user:loginas', $coursecontext) && !is_siteadmin($user->id)) {
$url = new moodle_url('/course/loginas.php', array('id'=>$course->id, 'user'=>$user->id, 'sesskey'=>sesskey()));
$usersetting->add(get_string('loginas'), $url, self::TYPE_SETTING);
}
return $usersetting;
}
/**
* Loads block specific settings in the navigation
*
* @return navigation_node
*/
protected function load_block_settings() {
global $CFG;
$blocknode = $this->add(print_context_name($this->context));
$blocknode->force_open();
// Assign local roles
$assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
$blocknode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING);
// Override roles
if (has_capability('moodle/role:review', $this->context) or count(get_overridable_roles($this->context))>0) {
$url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id));
$blocknode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
}
// Check role permissions
if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) {
$url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id));
$blocknode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
}
return $blocknode;
}
/**
* Loads category specific settings in the navigation
*
* @return navigation_node
*/
protected function load_category_settings() {
global $CFG;
$categorynode = $this->add(print_context_name($this->context));
$categorynode->force_open();
if ($this->page->user_is_editing() && has_capability('moodle/category:manage', $this->context)) {
$categorynode->add(get_string('editcategorythis'), new moodle_url('/course/editcategory.php', array('id' => $this->context->instanceid)));
$categorynode->add(get_string('addsubcategory'), new moodle_url('/course/editcategory.php', array('parent' => $this->context->instanceid)));
}
// Assign local roles
$assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
$categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING);
// Override roles
if (has_capability('moodle/role:review', $this->context) or count(get_overridable_roles($this->context))>0) {
$url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$this->context->id));
$categorynode->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING);
}
// Check role permissions
if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $this->context)) {
$url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$this->context->id));
$categorynode->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING);
}
// Manage filters
if (has_capability('moodle/filter:manage', $this->context) && count(filter_get_available_in_context($this->context))>0) {
$url = new moodle_url('/filter/manage.php', array('contextid'=>$this->context->id));
$categorynode->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING);
}
return $categorynode;
}
/**
* Determine whether the user is assuming another role
*
* This function checks to see if the user is assuming another role by means of
* role switching. In doing this we compare each RSW key (context path) against
* the current context path. This ensures that we can provide the switching
* options against both the course and any page shown under the course.
*
* @return bool|int The role(int) if the user is in another role, false otherwise
*/
protected function in_alternative_role() {
global $USER;
if (!empty($USER->access['rsw']) && is_array($USER->access['rsw'])) {
if (!empty($this->page->context) && !empty($USER->access['rsw'][$this->page->context->path])) {
return $USER->access['rsw'][$this->page->context->path];
}
foreach ($USER->access['rsw'] as $key=>$role) {
if (strpos($this->context->path,$key)===0) {
return $role;
}
}
}
return false;
}
/**
* This function loads all of the front page settings into the settings navigation.
* This function is called when the user is on the front page, or $COURSE==$SITE
* @return navigation_node
*/
protected function load_front_page_settings($forceopen = false) {
global $SITE, $CFG;
$course = clone($SITE);
$coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); // Course context
$frontpage = $this->add(get_string('frontpagesettings'), null, self::TYPE_SETTING, null, 'frontpage');
if ($forceopen) {
$frontpage->force_open();
}
$frontpage->id = 'frontpagesettings';
if (has_capability('moodle/course:update', $coursecontext)) {
// Add the turn on/off settings
$url = new moodle_url('/course/view.php', array('id'=>$course->id, 'sesskey'=>sesskey()));
if ($this->page->user_is_editing()) {
$url->param('edit', 'off');
$editstring = get_string('turneditingoff');
} else {
$url->param('edit', 'on');
$editstring = get_string('turneditingon');
}
$frontpage->add($editstring, $url, self::TYPE_SETTING, null, null, new pix_icon('i/edit', ''));
// Add the course settings link
$url = new moodle_url('/admin/settings.php', array('section'=>'frontpagesettings'));
$frontpage->add(get_string('settings'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
}
//Participants
if (has_capability('moodle/site:viewparticipants', $coursecontext)) {
$url = new moodle_url('/user/index.php', array('contextid'=>$coursecontext->id));
$frontpage->add(get_string('participants'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/users', ''));
}
// Roles
if (has_capability('moodle/role:assign', $coursecontext)) {
// Add assign or override roles if allowed
$url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$coursecontext->id));
$frontpage->add(get_string('assignroles', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/roles', ''));
// Override roles
if (has_capability('moodle/role:review', $coursecontext) or count(get_overridable_roles($coursecontext))>0) {
$url = new moodle_url('/'.$CFG->admin.'/roles/permissions.php', array('contextid'=>$coursecontext->id));
$frontpage->add(get_string('permissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/permissions', ''));
}
// Check role permissions
if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride','moodle/role:override', 'moodle/role:assign'), $coursecontext)) {
$url = new moodle_url('/'.$CFG->admin.'/roles/check.php', array('contextid'=>$coursecontext->id));
$frontpage->add(get_string('checkpermissions', 'role'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/checkpermissions', ''));
}
// Manage filters
if (has_capability('moodle/filter:manage', $coursecontext) && count(filter_get_available_in_context($coursecontext))>0) {
$url = new moodle_url('/filter/manage.php', array('contextid'=>$coursecontext->id));
$frontpage->add(get_string('filters', 'admin'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/filter', ''));
}
}
// Backup this course
if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
$url = new moodle_url('/backup/backup.php', array('id'=>$course->id));
$frontpage->add(get_string('backup'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/backup', ''));
}
// Restore to this course
if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
$url = new moodle_url('/files/index.php', array('id'=>$course->id, 'wdir'=>'/backupdata'));
$url = null; // Disabled until restore is implemented. MDL-21432
$frontpage->add(get_string('restore'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/restore', ''));
}
// Manage questions
$questioncaps = array('moodle/question:add',
'moodle/question:editmine',
'moodle/question:editall',
'moodle/question:viewmine',
'moodle/question:viewall',
'moodle/question:movemine',
'moodle/question:moveall');
if (has_any_capability($questioncaps, $this->context)) {
$questionlink = $CFG->wwwroot.'/question/edit.php';
} else if (has_capability('moodle/question:managecategory', $this->context)) {
$questionlink = $CFG->wwwroot.'/question/category.php';
}
if (isset($questionlink)) {
$url = new moodle_url($questionlink, array('courseid'=>$course->id));
$frontpage->add(get_string('questions','quiz'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/questions', ''));
}
// Manage files
if (has_capability('moodle/course:managefiles', $this->context)) {
$url = new moodle_url('/files/index.php', array('id'=>$course->id));
$frontpage->add(get_string('files'), $url, self::TYPE_SETTING, null, null, new pix_icon('i/files', ''));
}
return $frontpage;
}
/**
* This function marks the cache as volatile so it is cleared during shutdown
*/
public function clear_cache() {
$this->cache->volatile();
}
}
/**
* Simple class used to output a navigation branch in XML
*
* @package moodlecore
* @subpackage navigation
* @copyright 2009 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class navigation_json {
/** @var array */
protected $nodetype = array('node','branch');
/** @var array */
protected $expandable = array();
/**
* 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[(string)$node['branchid']] = $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) {
global $OUTPUT;
if (!$child->display) {
return '';
}
$attributes = array();
$attributes['id'] = $child->id;
$attributes['name'] = $child->text;
$attributes['type'] = $child->type;
$attributes['key'] = $child->key;
$attributes['class'] = $child->get_css_type();
if ($child->icon instanceof pix_icon) {
$attributes['icon'] = array(
'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);
}
if (array_key_exists((string)$child->key, $this->expandable)) {
$attributes['expandable'] = $child->key;
$child->add_class($this->expandable[$child->key]['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 moodle_url) {
$attributes['link'] = $child->action->out();
}
$attributes['hidden'] = ($child->hidden);
$attributes['haschildren'] = ($child->children->count()>0 || $child->type == navigation_node::TYPE_CATEGORY);
if (count($child->children)>0) {
$attributes['children'] = array();
foreach ($child->children as $subchild) {
$attributes['children'][] = $this->convert_child($subchild, $depth+1);
}
}
if ($depth > 1) {
return $attributes;
} else {
return json_encode($attributes);
}
}
}
/**
* The cache class used by global navigation and settings navigation to cache bits
* and bobs that are used during their generation.
*
* It is basically an easy access point to session with a bit of smarts to make
* sure that the information that is cached is valid still.
*
* Example use:
*
* if (!$cache->viewdiscussion()) {
* // Code to do stuff and produce cachable content
* $cache->viewdiscussion = has_capability('mod/forum:viewdiscussion', $coursecontext);
* }
* $content = $cache->viewdiscussion;
*
*
* @package moodlecore
* @subpackage navigation
* @copyright 2009 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class navigation_cache {
/** @var int */
protected $creation;
/** @var array */
protected $session;
/** @var string */
protected $area;
/** @var int */
protected $timeout;
/** @var stdClass */
protected $currentcontext;
/** @var int */
const CACHETIME = 0;
/** @var int */
const CACHEUSERID = 1;
/** @var int */
const CACHEVALUE = 2;
/** @var null|array An array of navigation cache areas to expire on shutdown */
public static $volatilecaches;
/**
* Contructor for the cache. Requires two arguments
*
* @param string $area The string to use to segregate this particular cache
* it can either be unique to start a fresh cache or if you want
* to share a cache then make it the string used in the original
* cache
* @param int $timeout The number of seconds to time the information out after
*/
public function __construct($area, $timeout=60) {
global $SESSION;
$this->creation = time();
$this->area = $area;
if (!isset($SESSION->navcache)) {
$SESSION->navcache = new stdClass;
}
if (!isset($SESSION->navcache->{$area})) {
$SESSION->navcache->{$area} = array();
}
$this->session = &$SESSION->navcache->{$area};
$this->timeout = time()-$timeout;
if (rand(0,10)===0) {
$this->garbage_collection();
}
}
/**
* Magic Method to retrieve something by simply calling using = cache->key
*
* @param mixed $key The identifier for the information you want out again
* @return void|mixed Either void or what ever was put in
*/
public function __get($key) {
if (!$this->cached($key)) {
return;
}
$information = $this->session[$key][self::CACHEVALUE];
return unserialize($information);
}
/**
* Magic method that simply uses {@link set();} to store something in the cache
*
* @param string|int $key
* @param mixed $information
*/
public function __set($key, $information) {
$this->set($key, $information);
}
/**
* Sets some information against the cache (session) for later retrieval
*
* @param string|int $key
* @param mixed $information
*/
public function set($key, $information) {
global $USER;
$information = serialize($information);
$this->session[$key]= array(self::CACHETIME=>time(), self::CACHEUSERID=>$USER->id, self::CACHEVALUE=>$information);
}
/**
* Check the existence of the identifier in the cache
*
* @param string|int $key
* @return bool
*/
public function cached($key) {
global $USER;
if (!array_key_exists($key, $this->session) || !is_array($this->session[$key]) || $this->session[$key][self::CACHEUSERID]!=$USER->id || $this->session[$key][self::CACHETIME] < $this->timeout) {
return false;
}
return true;
}
/**
* Compare something to it's equivilant in the cache
*
* @param string $key
* @param mixed $value
* @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 If the value is the same false if it is not set or doesn't match
*/
public function compare($key, $value, $serialise=true) {
if ($this->cached($key)) {
if ($serialise) {
$value = serialize($value);
}
if ($this->session[$key][self::CACHEVALUE] === $value) {
return true;
}
}
return false;
}
/**
* Wipes the entire cache, good to force regeneration
*/
public function clear() {
$this->session = array();
}
/**
* Checks all cache entries and removes any that have expired, good ole cleanup
*/
protected function garbage_collection() {
foreach ($this->session as $key=>$cachedinfo) {
if (is_array($cachedinfo) && $cachedinfo[self::CACHETIME]<$this->timeout) {
unset($this->session[$key]);
}
}
}
/**
* Marks the cache as being volatile (likely to change)
*
* Any caches marked as volatile will be destroyed at the on shutdown by
* {@link navigation_node::destroy_volatile_caches()} which is registered
* as a shutdown function if any caches are marked as volatile.
*
* @param bool $setting True to destroy the cache false not too
*/
public function volatile($setting = true) {
if (self::$volatilecaches===null) {
self::$volatilecaches = array();
register_shutdown_function(array('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 in conjunction with the static volatilecaches
* property of navigation cache.
* Because this function is static it manually resets the cached areas back to an
* empty array.
*/
public static function destroy_volatile_caches() {
global $SESSION;
if (is_array(self::$volatilecaches) && count(self::$volatilecaches)>0) {
foreach (self::$volatilecaches as $area) {
$SESSION->navcache->{$area} = array();
}
}
}
}