9f501ab3d2e3659b5e8b857644f5ff88efd5368f
[moodle.git] / blocks / global_navigation_tree / block_global_navigation_tree.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
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.
9 //
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.
14 //
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/>.
18 /**
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
21  *
22  * @since 2.0
23  * @package blocks
24  * @copyright 2009 Sam Hemelryk
25  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26  */
28 /**
29  * The global navigation tree block class
30  *
31  * Used to produce the global navigation block new to Moodle 2.0
32  *
33  * @package blocks
34  * @copyright 2009 Sam Hemelryk
35  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
37 class block_global_navigation_tree extends block_tree {
39     /** @var int */
40     public static $navcount;
41     /** @var string */
42     public $blockname = null;
43     /** @var bool */
44     protected $contentgenerated = false;
45     /** @var bool|null */
46     protected $docked = null;
48     /**
49      * Set the initial properties for the block
50      */
51     function init() {
52         global $CFG;
53         $this->blockname = get_class($this);
54         $this->title = get_string('blockname', $this->blockname);
55         $this->version = 2009082800;
56     }
58     /**
59      * All multiple instances of this block
60      * @return bool Returns true
61      */
62     function instance_allow_multiple() {
63         return false;
64     }
66     /**
67      * Set the applicable formats for this block to all
68      * @return array
69      */
70     function applicable_formats() {
71         return array('all' => true);
72     }
74     /**
75      * Allow the user to configure a block instance
76      * @return bool Returns true
77      */
78     function instance_allow_config() {
79         return true;
80     }
82     function get_required_javascript() {
83         global $CFG;
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);
88     }
90     /**
91      * Gets the content for this block by grabbing it from $this->page
92      */
93     function get_content() {
94         global $CFG, $OUTPUT;
95         // First check if we have already generated, don't waste cycles
96         if ($this->contentgenerated === true) {
97             return $this->content;
98         }
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);
110         }
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'));
117             redirect($url);
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'));
122             redirect($url);
123         }
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;
128         }
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();
137         }
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();
142         }
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();
147         }
149         if (!empty($this->config->showmyhistory) && $this->config->showmyhistory=='yes') {
150             $this->showmyhistory();
151         }
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;
173     }
175     /**
176      * Returns the attributes to set for this block
177      *
178      * This function returns an array of HTML attributes for this block including
179      * the defaults
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
183      *
184      * @return array An array of HTML attributes
185      */
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';
190         }
191         return $attributes;
192     }
194     /**
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
197      *
198      * @return bool
199      */
200     protected function showmyhistory() {
201         global $USER, $PAGE;
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()) {
208             return false;
209         }
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();
215         }
216         $history = $cache->history;
217         $historycount = count($history);
219         // Find the initial active node
220         $child = false;
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();
225         }
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;
238                     } else {
239                         $titlebits[] = $child->text;
240                     }
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
245                             break;
246                         }
247                     }
248                 }
249                 if (!empty($child->shorttext)) {
250                     $titlebits[] = $child->shorttext;
251                 } else {
252                     $titlebits[] = $child->text;
253                 }
254                 $properties['text'] = join(' - ', $titlebits);
255                 $properties['shorttext'] = join(' - ', $titlebits);
256             } else {
257                 $properties['text'] = $child->text;
258                 $properties['shorttext'] = $child->shorttext;
259             }
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
271             // duplicate entries
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]);
280                     }
281                 }
282             }
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);
287             }
288             $child->nodetype = navigation_node::NODETYPE_LEAF;
289             $child->children = array();
290             // Add the child to the history array
291             array_push($history,$child);
292         }
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);
300         }
302         // Cache the history (or update the cached history as it is)
303         $cache->history = $history;
305         return true;
306     }
308     /**
309      * This function loads the users my courses array into the navigation
310      *
311      * @return bool
312      */
313     protected function showmycourses() {
314         global $USER, $PAGE;
316         // Create a navigation cache to point at the same node as the main navigation
317         // cache
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()) {
322             return false;
323         }
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);
329         }
330         $courses = $cache->mycourses;
332         // If no courses to display here, return before adding anything
333         if (!is_array($courses) || count($courses)==0) {
334             return false;
335         }
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;
342         return true;
343     }
345     /**
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
349      */
350     protected function remove_empty_section_branches() {
351         global $PAGE;
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)) {
355             return 0;
356         }
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;
363             }
364         }
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);
369             }
370         }
371     }