3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * This file contains classes used to manage the navigation structures in Moodle
20 * and was introduced as part of the changes occuring in Moodle 2.0
24 * @copyright 2009 Sam Hemelryk
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29 * The global navigation tree block class
31 * Used to produce the global navigation block new to Moodle 2.0
34 * @copyright 2009 Sam Hemelryk
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class block_global_navigation_tree extends block_tree {
40 public static $navcount;
42 public $blockname = null;
44 protected $contentgenerated = false;
46 protected $docked = null;
49 * Set the initial properties for the block
53 $this->blockname = get_class($this);
54 $this->title = get_string('pluginname', $this->blockname);
55 $this->version = 2009082800;
59 * All multiple instances of this block
60 * @return bool Returns true
62 function instance_allow_multiple() {
67 * Set the applicable formats for this block to all
70 function applicable_formats() {
71 return array('all' => true);
75 * Allow the user to configure a block instance
76 * @return bool Returns true
78 function instance_allow_config() {
82 function get_required_javascript() {
84 $this->_initialise_dock();
85 $this->page->requires->js_module(array('name'=>'core_dock', 'fullpath'=>'/blocks/dock.js', 'requires'=>array('base', 'cookie', 'dom', 'io', 'node', 'event-custom', 'event-mouseenter', 'yui2-container')));
86 $this->page->requires->js_module(array('name'=>'block_navigation', 'fullpath'=>'/blocks/global_navigation_tree/navigation.js', 'requires'=>array('core_dock', 'io', 'node', 'dom', 'event-custom', 'json-parse')));
87 user_preference_allow_ajax_update('docked_block_instance_'.$this->instance->id, PARAM_INT);
91 * Gets the content for this block by grabbing it from $this->page
93 function get_content() {
95 // First check if we have already generated, don't waste cycles
96 if ($this->contentgenerated === true) {
97 return $this->content;
99 $this->page->requires->yui2_lib('dom');
100 // JS for navigation moved to the standard theme, the code will probably have to depend on the actual page structure
101 // $this->page->requires->js('/lib/javascript-navigation.js');
102 // Navcount is used to allow us to have multiple trees although I dont' know why
103 // you would want to trees the same
105 block_global_navigation_tree::$navcount++;
107 // Check if this block has been docked
108 if ($this->docked === null) {
109 $this->docked = get_user_preferences('nav_in_tab_panel_globalnav'.block_global_navigation_tree::$navcount, 0);
112 // Check if there is a param to change the docked state
113 if ($this->docked && optional_param('undock', null, PARAM_INT)==$this->instance->id) {
114 unset_user_preference('nav_in_tab_panel_globalnav'.block_global_navigation_tree::$navcount);
115 $url = $this->page->url;
116 $url->remove_params(array('undock'));
118 } else if (!$this->docked && optional_param('dock', null, PARAM_INT)==$this->instance->id) {
119 set_user_preferences(array('nav_in_tab_panel_globalnav'.block_global_navigation_tree::$navcount=>1));
120 $url = $this->page->url;
121 $url->remove_params(array('dock'));
125 // Set the expansionlimit if one has been set in block config
126 if (!empty($this->config->expansionlimit) && $this->config->expansionlimit!='0') {
127 $this->page->navigation->expansionlimit = $this->config->expansionlimit;
130 // Initialise (only actually happens if it hasn't already been done yet
131 $this->page->navigation->initialise();
133 // Remove empty branches if the user has selected to
135 if (empty($this->config->showemptybranches) || $this->config->showemptybranches=='no') {
136 $this->remove_empty_section_branches();
139 // Load the my courses branch if the user has selected to
140 if (isset($CFG->navshowcategories) && empty($CFG->navshowcategories)) {
141 $this->page->navigation->collapse_course_categories();
144 // Load the my courses branch if the user has selected to
145 if (!empty($this->config->showmycourses) && $this->config->showmycourses=='yes') {
146 $this->showmycourses();
149 if (!empty($this->config->showmyhistory) && $this->config->showmyhistory=='yes') {
150 $this->showmyhistory();
153 // Get the expandable items so we can pass them to JS
154 $expandable = array();
155 $this->page->navigation->find_expandable($expandable);
157 // Initialise the JS tree object
158 $module = array('name'=>'block_navigation', 'fullpath'=>'/blocks/global_navigation_tree/navigation.js', 'requires'=>array('core_dock', 'io', 'node', 'dom', 'event-custom', 'json-parse'));
159 $arguments = array($this->instance->id, array('expansions'=>$expandable, 'instance'=>$this->instance->id, 'candock'=>$this->instance_can_be_docked()));
160 $this->page->requires->js_init_call('M.block_navigation.init_add_tree', $arguments, false, $module);
162 // Grab the items to display
163 $this->content->items = array($this->page->navigation);
165 $reloadlink = new moodle_url($this->page->url, array('regenerate'=>'navigation'));
167 $this->content->footer .= $OUTPUT->action_icon($reloadlink, new pix_icon('t/reload', get_string('reload')), null, array('class'=>'customcommand'));
169 // Set content generated to true so that we know it has been done
170 $this->contentgenerated = true;
172 return $this->content;
176 * Returns the attributes to set for this block
178 * This function returns an array of HTML attributes for this block including
180 * {@link block_tree->html_attributes()} is used to get the default arguments
181 * and then we check whether the user has enabled hover expansion and add the
182 * appropriate hover class if it has
184 * @return array An array of HTML attributes
186 public function html_attributes() {
187 $attributes = parent::html_attributes();
188 if (!empty($this->config->enablehoverexpansion) && $this->config->enablehoverexpansion == 'yes') {
189 $attributes['class'] .= ' block_js_expansion';
195 * This function maintains a history of the active pages that a user has visited
196 * and displays it back to the user as part of the navigation structure
200 protected function showmyhistory() {
203 // Create a navigation cache so that we can store the history
204 $cache = new navigation_cache('navigationhistory', 60*60);
206 // If the user isn't logged in or is a guest we don't want to display anything
207 if (!isloggedin() || isguestuser()) {
211 // Check the cache to see if we have loaded my courses already
212 // there is a very good chance that we have
213 if (!$cache->cached('history')) {
214 $cache->history = array();
216 $history = $cache->history;
217 $historycount = count($history);
219 // Find the initial active node
221 if ($PAGE->navigation->contains_active_node()) {
222 $child = $PAGE->navigation->find_active_node();
223 } else if ($PAGE->settingsnav->contains_active_node()) {
224 $child = $PAGE->settingsnav->find_active_node();
226 // Check that we found an active child node
227 if ($child!==false) {
228 $properties = array();
229 // Check whether this child contains another active child node
230 // this can happen if we are looking at a module
231 if ($child->contains_active_node()) {
232 $titlebits = array();
233 // Loop while the child contains active nodes and in each iteration
234 // find the next node in the correct direction
235 while ($child!==null && $child->contains_active_node()) {
236 if (!empty($child->shorttext)) {
237 $titlebits[] = $child->shorttext;
239 $titlebits[] = $child->text;
241 foreach ($child->children as $child) {
242 if ($child->contains_active_node() || $child->isactive) {
243 // We have found the active child or one of its parents
244 // so break the foreach so we can proceed in the while
249 if (!empty($child->shorttext)) {
250 $titlebits[] = $child->shorttext;
252 $titlebits[] = $child->text;
254 $properties['text'] = join(' - ', $titlebits);
255 $properties['shorttext'] = join(' - ', $titlebits);
257 $properties['text'] = $child->text;
258 $properties['shorttext'] = $child->shorttext;
260 $properties['action'] = $child->action;
261 $properties['key'] = $child->key;
262 $properties['type'] = $child->type;
263 $properties['icon'] = $child->icon;
265 // Create a new navigation node object free of the main structure
266 // so that it is easily storeable and customised
267 $child = new navigation_node($properties);
269 // Check that this page isn't already in the history array. If it is
270 // we will remove it so that it gets added at the top and we dont get
272 foreach ($history as $key=>$node) {
273 if ($node->key == $child->key && $node->type == $child->type) {
274 if ($node->action instanceof moodle_url && $child->action instanceof moodle_url && $node->action->compare($child->action)) {
275 unset($history[$key]);
276 } else if ($child->action instanceof moodle_url && $child->action->out_omit_querystring() == $node->action) {
277 unset($history[$key]);
278 } else if ($child->action == $node->action) {
279 unset($history[$key]);
283 // If there is more than 5 elements in the array remove the first one
284 // We want a fifo array
285 if (count($history) > 5) {
286 array_shift($history);
288 $child->nodetype = navigation_node::NODETYPE_LEAF;
289 $child->children = array();
290 // Add the child to the history array
291 array_push($history,$child);
294 // If we have `more than nothing` in the history display it :D
295 if ($historycount > 0) {
296 // Add a branch to hold the users history
297 $mymoodle = $PAGE->navigation->get('mymoodle', navigation_node::TYPE_CUSTOM);
298 $myhistorybranch = $mymoodle->add(get_string('showmyhistorytitle', $this->blockname), null, navigation_node::TYPE_CUSTOM, null, 'myhistory');
299 $mymoodle->get($myhistorybranch)->children = array_reverse($history);
302 // Cache the history (or update the cached history as it is)
303 $cache->history = $history;
309 * This function loads the users my courses array into the navigation
313 protected function showmycourses() {
316 // Create a navigation cache to point at the same node as the main navigation
318 $cache = new navigation_cache('navigation');
320 // If the user isn't logged in or is a guest we don't want to display anything
321 if (!isloggedin() || isguestuser()) {
325 // Check the cache to see if we have loaded my courses already
326 // there is a very good chance that we have
327 if (!$cache->cached('mycourses')) {
328 $cache->mycourses = get_my_courses($USER->id);
330 $courses = $cache->mycourses;
332 // If no courses to display here, return before adding anything
333 if (!is_array($courses) || count($courses)==0) {
337 // Add a branch labelled something like My Courses
338 $mymoodle = $PAGE->navigation->get('mymoodle', navigation_node::TYPE_CUSTOM);
339 $mycoursesbranch = $mymoodle->add(get_string('mycourses'), null,navigation_node::TYPE_CATEGORY, null, 'mycourses');
340 $PAGE->navigation->add_courses($courses, 'mycourses');
341 $mymoodle->get($mycoursesbranch)->type = navigation_node::TYPE_CUSTOM;
346 * This function searches all branches and removes any empty section branches
347 * for the global navigation structure. This is usually called by the global
348 * navigation block based on a block setting
350 protected function remove_empty_section_branches() {
352 $cache = new navigation_cache('navigation');
353 $course = &$PAGE->navigation->find_active_node(navigation_node::TYPE_COURSE);
354 if ($course===false || !$cache->cached('modinfo'.$course->key) || !$cache->cached('coursesections'.$course->key)) {
357 $sectionstoremove = array();
358 $coursesections = $cache->{'coursesections'.$course->key};
359 $modinfosections = $cache->{'modinfo'.$course->key}->sections;
360 foreach ($coursesections as $id=>$section) {
361 if (!array_key_exists($id, $modinfosections)) {
362 $sectionstoremove[] = $section->id;
366 foreach ($course->children as $key=>$node) {
367 if ($node->type == navigation_node::TYPE_SECTION && in_array($node->key, $sectionstoremove)) {
368 $course->remove_child($key);