MDL-25561 Gradebook : fixed whitespaces
[moodle.git] / grade / lib.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  * Functions used by gradebook plugins and reports.
20  *
21  * @package   moodlecore
22  * @copyright 2009 Petr Skoda and Nicolas Connault
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 require_once $CFG->libdir.'/gradelib.php';
28 /**
29  * This class iterates over all users that are graded in a course.
30  * Returns detailed info about users and their grades.
31  *
32  * @author Petr Skoda <skodak@moodle.org>
33  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class graded_users_iterator {
36     public $course;
37     public $grade_items;
38     public $groupid;
39     public $users_rs;
40     public $grades_rs;
41     public $gradestack;
42     public $sortfield1;
43     public $sortorder1;
44     public $sortfield2;
45     public $sortorder2;
47     /**
48      * Constructor
49      *
50      * @param object $course A course object
51      * @param array  $grade_items array of grade items, if not specified only user info returned
52      * @param int    $groupid iterate only group users if present
53      * @param string $sortfield1 The first field of the users table by which the array of users will be sorted
54      * @param string $sortorder1 The order in which the first sorting field will be sorted (ASC or DESC)
55      * @param string $sortfield2 The second field of the users table by which the array of users will be sorted
56      * @param string $sortorder2 The order in which the second sorting field will be sorted (ASC or DESC)
57      */
58     public function graded_users_iterator($course, $grade_items=null, $groupid=0,
59                                           $sortfield1='lastname', $sortorder1='ASC',
60                                           $sortfield2='firstname', $sortorder2='ASC') {
61         $this->course      = $course;
62         $this->grade_items = $grade_items;
63         $this->groupid     = $groupid;
64         $this->sortfield1  = $sortfield1;
65         $this->sortorder1  = $sortorder1;
66         $this->sortfield2  = $sortfield2;
67         $this->sortorder2  = $sortorder2;
69         $this->gradestack  = array();
70     }
72     /**
73      * Initialise the iterator
74      * @return boolean success
75      */
76     public function init() {
77         global $CFG, $DB;
79         $this->close();
81         grade_regrade_final_grades($this->course->id);
82         $course_item = grade_item::fetch_course_item($this->course->id);
83         if ($course_item->needsupdate) {
84             // can not calculate all final grades - sorry
85             return false;
86         }
88         $coursecontext = get_context_instance(CONTEXT_COURSE, $this->course->id);
89         $relatedcontexts = get_related_contexts_string($coursecontext);
91         list($gradebookroles_sql, $params) =
92             $DB->get_in_or_equal(explode(',', $CFG->gradebookroles), SQL_PARAMS_NAMED, 'grbr');
94         //limit to users with an active enrolment
95         list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext);
97         $params = array_merge($params, $enrolledparams);
99         if ($this->groupid) {
100             $groupsql = "INNER JOIN {groups_members} gm ON gm.userid = u.id";
101             $groupwheresql = "AND gm.groupid = :groupid";
102             // $params contents: gradebookroles
103             $params['groupid'] = $this->groupid;
104         } else {
105             $groupsql = "";
106             $groupwheresql = "";
107         }
109         if (empty($this->sortfield1)) {
110             // we must do some sorting even if not specified
111             $ofields = ", u.id AS usrt";
112             $order   = "usrt ASC";
114         } else {
115             $ofields = ", u.$this->sortfield1 AS usrt1";
116             $order   = "usrt1 $this->sortorder1";
117             if (!empty($this->sortfield2)) {
118                 $ofields .= ", u.$this->sortfield2 AS usrt2";
119                 $order   .= ", usrt2 $this->sortorder2";
120             }
121             if ($this->sortfield1 != 'id' and $this->sortfield2 != 'id') {
122                 // user order MUST be the same in both queries,
123                 // must include the only unique user->id if not already present
124                 $ofields .= ", u.id AS usrt";
125                 $order   .= ", usrt ASC";
126             }
127         }
129         // $params contents: gradebookroles and groupid (for $groupwheresql)
130         $users_sql = "SELECT u.* $ofields
131                         FROM {user} u
132                         JOIN ($enrolledsql) je ON je.id = u.id
133                              $groupsql
134                         JOIN (
135                                   SELECT DISTINCT ra.userid
136                                     FROM {role_assignments} ra
137                                    WHERE ra.roleid $gradebookroles_sql
138                                      AND ra.contextid $relatedcontexts
139                              ) rainner ON rainner.userid = u.id
140                          WHERE u.deleted = 0
141                              $groupwheresql
142                     ORDER BY $order";
143         $this->users_rs = $DB->get_recordset_sql($users_sql, $params);
145         if (!empty($this->grade_items)) {
146             $itemids = array_keys($this->grade_items);
147             list($itemidsql, $grades_params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED, 'items');
148             $params = array_merge($params, $grades_params);
149             // $params contents: gradebookroles, enrolledparams, groupid (for $groupwheresql) and itemids
151             $grades_sql = "SELECT g.* $ofields
152                              FROM {grade_grades} g
153                              JOIN {user} u ON g.userid = u.id
154                              JOIN ($enrolledsql) je ON je.id = u.id
155                                   $groupsql
156                              JOIN (
157                                       SELECT DISTINCT ra.userid
158                                         FROM {role_assignments} ra
159                                        WHERE ra.roleid $gradebookroles_sql
160                                          AND ra.contextid $relatedcontexts
161                                   ) rainner ON rainner.userid = u.id
162                               WHERE u.deleted = 0
163                               AND g.itemid $itemidsql
164                               $groupwheresql
165                          ORDER BY $order, g.itemid ASC";
166             $this->grades_rs = $DB->get_recordset_sql($grades_sql, $params);
167         } else {
168             $this->grades_rs = false;
169         }
171         return true;
172     }
174     /**
175      * Returns information about the next user
176      * @return mixed array of user info, all grades and feedback or null when no more users found
177      */
178     function next_user() {
179         if (!$this->users_rs) {
180             return false; // no users present
181         }
183         if (!$this->users_rs->valid()) {
184             if ($current = $this->_pop()) {
185                 // this is not good - user or grades updated between the two reads above :-(
186             }
188             return false; // no more users
189         } else {
190             $user = $this->users_rs->current();
191             $this->users_rs->next();
192         }
194         // find grades of this user
195         $grade_records = array();
196         while (true) {
197             if (!$current = $this->_pop()) {
198                 break; // no more grades
199             }
201             if (empty($current->userid)) {
202                 break;
203             }
205             if ($current->userid != $user->id) {
206                 // grade of the next user, we have all for this user
207                 $this->_push($current);
208                 break;
209             }
211             $grade_records[$current->itemid] = $current;
212         }
214         $grades = array();
215         $feedbacks = array();
217         if (!empty($this->grade_items)) {
218             foreach ($this->grade_items as $grade_item) {
219                 if (array_key_exists($grade_item->id, $grade_records)) {
220                     $feedbacks[$grade_item->id]->feedback       = $grade_records[$grade_item->id]->feedback;
221                     $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat;
222                     unset($grade_records[$grade_item->id]->feedback);
223                     unset($grade_records[$grade_item->id]->feedbackformat);
224                     $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false);
225                 } else {
226                     $feedbacks[$grade_item->id]->feedback       = '';
227                     $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE;
228                     $grades[$grade_item->id] =
229                         new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false);
230                 }
231             }
232         }
234         $result = new stdClass();
235         $result->user      = $user;
236         $result->grades    = $grades;
237         $result->feedbacks = $feedbacks;
238         return $result;
239     }
241     /**
242      * Close the iterator, do not forget to call this function.
243      * @return void
244      */
245     function close() {
246         if ($this->users_rs) {
247             $this->users_rs->close();
248             $this->users_rs = null;
249         }
250         if ($this->grades_rs) {
251             $this->grades_rs->close();
252             $this->grades_rs = null;
253         }
254         $this->gradestack = array();
255     }
258     /**
259      * _push
260      *
261      * @param grade_grade $grade Grade object
262      *
263      * @return void
264      */
265     function _push($grade) {
266         array_push($this->gradestack, $grade);
267     }
270     /**
271      * _pop
272      *
273      * @return object current grade object
274      */
275     function _pop() {
276         global $DB;
277         if (empty($this->gradestack)) {
278             if (empty($this->grades_rs) || !$this->grades_rs->valid()) {
279                 return null; // no grades present
280             }
282             $current = $this->grades_rs->current();
284             $this->grades_rs->next();
286             return $current;
287         } else {
288             return array_pop($this->gradestack);
289         }
290     }
293 /**
294  * Print a selection popup form of the graded users in a course.
295  *
296  * @deprecated since 2.0
297  *
298  * @param int    $course id of the course
299  * @param string $actionpage The page receiving the data from the popoup form
300  * @param int    $userid   id of the currently selected user (or 'all' if they are all selected)
301  * @param int    $groupid id of requested group, 0 means all
302  * @param int    $includeall bool include all option
303  * @param bool   $return If true, will return the HTML, otherwise, will print directly
304  * @return null
305  */
306 function print_graded_users_selector($course, $actionpage, $userid=0, $groupid=0, $includeall=true, $return=false) {
307     global $CFG, $USER, $OUTPUT;
308     return $OUTPUT->render(grade_get_graded_users_select(substr($actionpage, 0, strpos($actionpage, '/')), $course, $userid, $groupid, $includeall));
311 function grade_get_graded_users_select($report, $course, $userid, $groupid, $includeall) {
312     global $USER;
314     if (is_null($userid)) {
315         $userid = $USER->id;
316     }
318     $menu = array(); // Will be a list of userid => user name
319     $gui = new graded_users_iterator($course, null, $groupid);
320     $gui->init();
321     $label = get_string('selectauser', 'grades');
322     if ($includeall) {
323         $menu[0] = get_string('allusers', 'grades');
324         $label = get_string('selectalloroneuser', 'grades');
325     }
326     while ($userdata = $gui->next_user()) {
327         $user = $userdata->user;
328         $menu[$user->id] = fullname($user);
329     }
330     $gui->close();
332     if ($includeall) {
333         $menu[0] .= " (" . (count($menu) - 1) . ")";
334     }
335     $select = new single_select(new moodle_url('/grade/report/'.$report.'/index.php', array('id'=>$course->id)), 'userid', $menu, $userid);
336     $select->label = $label;
337     $select->formid = 'choosegradeuser';
338     return $select;
341 /**
342  * Print grading plugin selection popup form.
343  *
344  * @param array   $plugin_info An array of plugins containing information for the selector
345  * @param boolean $return return as string
346  *
347  * @return nothing or string if $return true
348  */
349 function print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return=false) {
350     global $CFG, $OUTPUT, $PAGE;
352     $menu = array();
353     $count = 0;
354     $active = '';
356     foreach ($plugin_info as $plugin_type => $plugins) {
357         if ($plugin_type == 'strings') {
358             continue;
359         }
361         $first_plugin = reset($plugins);
363         $sectionname = $plugin_info['strings'][$plugin_type];
364         $section = array();
366         foreach ($plugins as $plugin) {
367             $link = $plugin->link->out(false);
368             $section[$link] = $plugin->string;
369             $count++;
370             if ($plugin_type === $active_type and $plugin->id === $active_plugin) {
371                 $active = $link;
372             }
373         }
375         if ($section) {
376             $menu[] = array($sectionname=>$section);
377         }
378     }
380     // finally print/return the popup form
381     if ($count > 1) {
382         $select = new url_select($menu, $active, null, 'choosepluginreport');
384         if ($return) {
385             return $OUTPUT->render($select);
386         } else {
387             echo $OUTPUT->render($select);
388         }
389     } else {
390         // only one option - no plugin selector needed
391         return '';
392     }
395 /**
396  * Print grading plugin selection tab-based navigation.
397  *
398  * @param string  $active_type type of plugin on current page - import, export, report or edit
399  * @param string  $active_plugin active plugin type - grader, user, cvs, ...
400  * @param array   $plugin_info Array of plugins
401  * @param boolean $return return as string
402  *
403  * @return nothing or string if $return true
404  */
405 function grade_print_tabs($active_type, $active_plugin, $plugin_info, $return=false) {
406     global $CFG, $COURSE;
408     if (!isset($currenttab)) { //TODO: this is weird
409         $currenttab = '';
410     }
412     $tabs = array();
413     $top_row  = array();
414     $bottom_row = array();
415     $inactive = array($active_plugin);
416     $activated = array();
418     $count = 0;
419     $active = '';
421     foreach ($plugin_info as $plugin_type => $plugins) {
422         if ($plugin_type == 'strings') {
423             continue;
424         }
426         // If $plugins is actually the definition of a child-less parent link:
427         if (!empty($plugins->id)) {
428             $string = $plugins->string;
429             if (!empty($plugin_info[$active_type]->parent)) {
430                 $string = $plugin_info[$active_type]->parent->string;
431             }
433             $top_row[] = new tabobject($plugin_type, $plugins->link, $string);
434             continue;
435         }
437         $first_plugin = reset($plugins);
438         $url = $first_plugin->link;
440         if ($plugin_type == 'report') {
441             $url = $CFG->wwwroot.'/grade/report/index.php?id='.$COURSE->id;
442         }
444         $top_row[] = new tabobject($plugin_type, $url, $plugin_info['strings'][$plugin_type]);
446         if ($active_type == $plugin_type) {
447             foreach ($plugins as $plugin) {
448                 $bottom_row[] = new tabobject($plugin->id, $plugin->link, $plugin->string);
449                 if ($plugin->id == $active_plugin) {
450                     $inactive = array($plugin->id);
451                 }
452             }
453         }
454     }
456     $tabs[] = $top_row;
457     $tabs[] = $bottom_row;
459     if ($return) {
460         return print_tabs($tabs, $active_type, $inactive, $activated, true);
461     } else {
462         print_tabs($tabs, $active_type, $inactive, $activated);
463     }
466 /**
467  * grade_get_plugin_info
468  *
469  * @param int    $courseid The course id
470  * @param string $active_type type of plugin on current page - import, export, report or edit
471  * @param string $active_plugin active plugin type - grader, user, cvs, ...
472  *
473  * @return array
474  */
475 function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
476     global $CFG, $SITE;
478     $context = get_context_instance(CONTEXT_COURSE, $courseid);
480     $plugin_info = array();
481     $count = 0;
482     $active = '';
483     $url_prefix = $CFG->wwwroot . '/grade/';
485     // Language strings
486     $plugin_info['strings'] = grade_helper::get_plugin_strings();
488     if ($reports = grade_helper::get_plugins_reports($courseid)) {
489         $plugin_info['report'] = $reports;
490     }
492     //showing grade categories and items make no sense if we're not within a course
493     if ($courseid!=$SITE->id) {
494         if ($edittree = grade_helper::get_info_edit_structure($courseid)) {
495             $plugin_info['edittree'] = $edittree;
496         }
497     }
499     if ($scale = grade_helper::get_info_scales($courseid)) {
500         $plugin_info['scale'] = array('view'=>$scale);
501     }
503     if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
504         $plugin_info['outcome'] = $outcomes;
505     }
507     if ($letters = grade_helper::get_info_letters($courseid)) {
508         $plugin_info['letter'] = $letters;
509     }
511     if ($imports = grade_helper::get_plugins_import($courseid)) {
512         $plugin_info['import'] = $imports;
513     }
515     if ($exports = grade_helper::get_plugins_export($courseid)) {
516         $plugin_info['export'] = $exports;
517     }
519     foreach ($plugin_info as $plugin_type => $plugins) {
520         if (!empty($plugins->id) && $active_plugin == $plugins->id) {
521             $plugin_info['strings']['active_plugin_str'] = $plugins->string;
522             break;
523         }
524         foreach ($plugins as $plugin) {
525             if (is_a($plugin, 'grade_plugin_info')) {
526                 if ($active_plugin == $plugin->id) {
527                     $plugin_info['strings']['active_plugin_str'] = $plugin->string;
528                 }
529             }
530         }
531     }
533     //hide course settings if we're not in a course
534     if ($courseid!=$SITE->id) {
535         if ($setting = grade_helper::get_info_manage_settings($courseid)) {
536             $plugin_info['settings'] = array('course'=>$setting);
537         }
538     }
540     // Put preferences last
541     if ($preferences = grade_helper::get_plugins_report_preferences($courseid)) {
542         $plugin_info['preferences'] = $preferences;
543     }
545     foreach ($plugin_info as $plugin_type => $plugins) {
546         if (!empty($plugins->id) && $active_plugin == $plugins->id) {
547             $plugin_info['strings']['active_plugin_str'] = $plugins->string;
548             break;
549         }
550         foreach ($plugins as $plugin) {
551             if (is_a($plugin, 'grade_plugin_info')) {
552                 if ($active_plugin == $plugin->id) {
553                     $plugin_info['strings']['active_plugin_str'] = $plugin->string;
554                 }
555             }
556         }
557     }
559     return $plugin_info;
562 /**
563  * A simple class containing info about grade plugins.
564  * Can be subclassed for special rules
565  *
566  * @package moodlecore
567  * @copyright 2009 Nicolas Connault
568  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
569  */
570 class grade_plugin_info {
571     /**
572      * A unique id for this plugin
573      *
574      * @var mixed
575      */
576     public $id;
577     /**
578      * A URL to access this plugin
579      *
580      * @var mixed
581      */
582     public $link;
583     /**
584      * The name of this plugin
585      *
586      * @var mixed
587      */
588     public $string;
589     /**
590      * Another grade_plugin_info object, parent of the current one
591      *
592      * @var mixed
593      */
594     public $parent;
596     /**
597      * Constructor
598      *
599      * @param int $id A unique id for this plugin
600      * @param string $link A URL to access this plugin
601      * @param string $string The name of this plugin
602      * @param object $parent Another grade_plugin_info object, parent of the current one
603      *
604      * @return void
605      */
606     public function __construct($id, $link, $string, $parent=null) {
607         $this->id = $id;
608         $this->link = $link;
609         $this->string = $string;
610         $this->parent = $parent;
611     }
614 /**
615  * Prints the page headers, breadcrumb trail, page heading, (optional) dropdown navigation menu and
616  * (optional) navigation tabs for any gradebook page. All gradebook pages MUST use these functions
617  * in favour of the usual print_header(), print_header_simple(), print_heading() etc.
618  * !IMPORTANT! Use of tabs.php file in gradebook pages is forbidden unless tabs are switched off at
619  * the site level for the gradebook ($CFG->grade_navmethod = GRADE_NAVMETHOD_DROPDOWN).
620  *
621  * @param int     $courseid Course id
622  * @param string  $active_type The type of the current page (report, settings,
623  *                             import, export, scales, outcomes, letters)
624  * @param string  $active_plugin The plugin of the current page (grader, fullview etc...)
625  * @param string  $heading The heading of the page. Tries to guess if none is given
626  * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function
627  * @param string  $bodytags Additional attributes that will be added to the <body> tag
628  * @param string  $buttons Additional buttons to display on the page
629  * @param boolean $shownavigation should the gradebook navigation drop down (or tabs) be shown?
630  *
631  * @return string HTML code or nothing if $return == false
632  */
633 function print_grade_page_head($courseid, $active_type, $active_plugin=null,
634                                $heading = false, $return=false,
635                                $buttons=false, $shownavigation=true) {
636     global $CFG, $OUTPUT, $PAGE;
638     $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin);
640     // Determine the string of the active plugin
641     $stractive_plugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading;
642     $stractive_type = $plugin_info['strings'][$active_type];
644     if (empty($plugin_info[$active_type]->id) || !empty($plugin_info[$active_type]->parent)) {
645         $title = $PAGE->course->fullname.': ' . $stractive_type . ': ' . $stractive_plugin;
646     } else {
647         $title = $PAGE->course->fullname.': ' . $stractive_plugin;
648     }
650     if ($active_type == 'report') {
651         $PAGE->set_pagelayout('report');
652     } else {
653         $PAGE->set_pagelayout('admin');
654     }
655     $PAGE->set_title(get_string('grades') . ': ' . $stractive_type);
656     $PAGE->set_heading($title);
657     if ($buttons instanceof single_button) {
658         $buttons = $OUTPUT->render($buttons);
659     }
660     $PAGE->set_button($buttons);
661     grade_extend_settings($plugin_info, $courseid);
663     $returnval = $OUTPUT->header();
664     if (!$return) {
665         echo $returnval;
666     }
668     // Guess heading if not given explicitly
669     if (!$heading) {
670         $heading = $stractive_plugin;
671     }
673     if ($shownavigation) {
674         if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN) {
675             $returnval .= print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return);
676         }
678         if ($return) {
679             $returnval .= $OUTPUT->heading($heading);
680         } else {
681             echo $OUTPUT->heading($heading);
682         }
684         if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS) {
685             $returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
686         }
687     }
689     if ($return) {
690         return $returnval;
691     }
694 /**
695  * Utility class used for return tracking when using edit and other forms in grade plugins
696  *
697  * @package moodlecore
698  * @copyright 2009 Nicolas Connault
699  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
700  */
701 class grade_plugin_return {
702     public $type;
703     public $plugin;
704     public $courseid;
705     public $userid;
706     public $page;
708     /**
709      * Constructor
710      *
711      * @param array $params - associative array with return parameters, if null parameter are taken from _GET or _POST
712      */
713     public function grade_plugin_return($params = null) {
714         if (empty($params)) {
715             $this->type     = optional_param('gpr_type', null, PARAM_SAFEDIR);
716             $this->plugin   = optional_param('gpr_plugin', null, PARAM_PLUGIN);
717             $this->courseid = optional_param('gpr_courseid', null, PARAM_INT);
718             $this->userid   = optional_param('gpr_userid', null, PARAM_INT);
719             $this->page     = optional_param('gpr_page', null, PARAM_INT);
721         } else {
722             foreach ($params as $key=>$value) {
723                 if (property_exists($this, $key)) {
724                     $this->$key = $value;
725                 }
726             }
727         }
728     }
730     /**
731      * Returns return parameters as options array suitable for buttons.
732      * @return array options
733      */
734     public function get_options() {
735         if (empty($this->type)) {
736             return array();
737         }
739         $params = array();
741         if (!empty($this->plugin)) {
742             $params['plugin'] = $this->plugin;
743         }
745         if (!empty($this->courseid)) {
746             $params['id'] = $this->courseid;
747         }
749         if (!empty($this->userid)) {
750             $params['userid'] = $this->userid;
751         }
753         if (!empty($this->page)) {
754             $params['page'] = $this->page;
755         }
757         return $params;
758     }
760     /**
761      * Returns return url
762      *
763      * @param string $default default url when params not set
764      * @param array  $extras Extra URL parameters
765      *
766      * @return string url
767      */
768     public function get_return_url($default, $extras=null) {
769         global $CFG;
771         if (empty($this->type) or empty($this->plugin)) {
772             return $default;
773         }
775         $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php';
776         $glue = '?';
778         if (!empty($this->courseid)) {
779             $url .= $glue.'id='.$this->courseid;
780             $glue = '&amp;';
781         }
783         if (!empty($this->userid)) {
784             $url .= $glue.'userid='.$this->userid;
785             $glue = '&amp;';
786         }
788         if (!empty($this->page)) {
789             $url .= $glue.'page='.$this->page;
790             $glue = '&amp;';
791         }
793         if (!empty($extras)) {
794             foreach ($extras as $key=>$value) {
795                 $url .= $glue.$key.'='.$value;
796                 $glue = '&amp;';
797             }
798         }
800         return $url;
801     }
803     /**
804      * Returns string with hidden return tracking form elements.
805      * @return string
806      */
807     public function get_form_fields() {
808         if (empty($this->type)) {
809             return '';
810         }
812         $result  = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />';
814         if (!empty($this->plugin)) {
815             $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />';
816         }
818         if (!empty($this->courseid)) {
819             $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />';
820         }
822         if (!empty($this->userid)) {
823             $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />';
824         }
826         if (!empty($this->page)) {
827             $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />';
828         }
829     }
831     /**
832      * Add hidden elements into mform
833      *
834      * @param object &$mform moodle form object
835      *
836      * @return void
837      */
838     public function add_mform_elements(&$mform) {
839         if (empty($this->type)) {
840             return;
841         }
843         $mform->addElement('hidden', 'gpr_type', $this->type);
844         $mform->setType('gpr_type', PARAM_SAFEDIR);
846         if (!empty($this->plugin)) {
847             $mform->addElement('hidden', 'gpr_plugin', $this->plugin);
848             $mform->setType('gpr_plugin', PARAM_PLUGIN);
849         }
851         if (!empty($this->courseid)) {
852             $mform->addElement('hidden', 'gpr_courseid', $this->courseid);
853             $mform->setType('gpr_courseid', PARAM_INT);
854         }
856         if (!empty($this->userid)) {
857             $mform->addElement('hidden', 'gpr_userid', $this->userid);
858             $mform->setType('gpr_userid', PARAM_INT);
859         }
861         if (!empty($this->page)) {
862             $mform->addElement('hidden', 'gpr_page', $this->page);
863             $mform->setType('gpr_page', PARAM_INT);
864         }
865     }
867     /**
868      * Add return tracking params into url
869      *
870      * @param moodle_url $url A URL
871      *
872      * @return string $url with return tracking params
873      */
874     public function add_url_params(moodle_url $url) {
875         if (empty($this->type)) {
876             return $url;
877         }
879         $url->param('gpr_type', $this->type);
881         if (!empty($this->plugin)) {
882             $url->param('gpr_plugin', $this->plugin);
883         }
885         if (!empty($this->courseid)) {
886             $url->param('gpr_courseid' ,$this->courseid);
887         }
889         if (!empty($this->userid)) {
890             $url->param('gpr_userid', $this->userid);
891         }
893         if (!empty($this->page)) {
894             $url->param('gpr_page', $this->page);
895         }
897         return $url;
898     }
901 /**
902  * Function central to gradebook for building and printing the navigation (breadcrumb trail).
903  *
904  * @param string $path The path of the calling script (using __FILE__?)
905  * @param string $pagename The language string to use as the last part of the navigation (non-link)
906  * @param mixed  $id Either a plain integer (assuming the key is 'id') or
907  *                   an array of keys and values (e.g courseid => $courseid, itemid...)
908  *
909  * @return string
910  */
911 function grade_build_nav($path, $pagename=null, $id=null) {
912     global $CFG, $COURSE, $PAGE;
914     $strgrades = get_string('grades', 'grades');
916     // Parse the path and build navlinks from its elements
917     $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash
918     $path = substr($path, $dirroot_length);
919     $path = str_replace('\\', '/', $path);
921     $path_elements = explode('/', $path);
923     $path_elements_count = count($path_elements);
925     // First link is always 'grade'
926     $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id)));
928     $link = null;
929     $numberofelements = 3;
931     // Prepare URL params string
932     $linkparams = array();
933     if (!is_null($id)) {
934         if (is_array($id)) {
935             foreach ($id as $idkey => $idvalue) {
936                 $linkparams[$idkey] = $idvalue;
937             }
938         } else {
939             $linkparams['id'] = $id;
940         }
941     }
943     $navlink4 = null;
945     // Remove file extensions from filenames
946     foreach ($path_elements as $key => $filename) {
947         $path_elements[$key] = str_replace('.php', '', $filename);
948     }
950     // Second level links
951     switch ($path_elements[1]) {
952         case 'edit': // No link
953             if ($path_elements[3] != 'index.php') {
954                 $numberofelements = 4;
955             }
956             break;
957         case 'import': // No link
958             break;
959         case 'export': // No link
960             break;
961         case 'report':
962             // $id is required for this link. Do not print it if $id isn't given
963             if (!is_null($id)) {
964                 $link = new moodle_url('/grade/report/index.php', $linkparams);
965             }
967             if ($path_elements[2] == 'grader') {
968                 $numberofelements = 4;
969             }
970             break;
972         default:
973             // If this element isn't among the ones already listed above, it isn't supported, throw an error.
974             debugging("grade_build_nav() doesn't support ". $path_elements[1] .
975                     " as the second path element after 'grade'.");
976             return false;
977     }
978     $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link);
980     // Third level links
981     if (empty($pagename)) {
982         $pagename = get_string($path_elements[2], 'grades');
983     }
985     switch ($numberofelements) {
986         case 3:
987             $PAGE->navbar->add($pagename, $link);
988             break;
989         case 4:
990             if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') {
991                 $PAGE->navbar->add(get_string('pluginname', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams));
992             }
993             $PAGE->navbar->add($pagename);
994             break;
995     }
997     return '';
1000 /**
1001  * General structure representing grade items in course
1002  *
1003  * @package moodlecore
1004  * @copyright 2009 Nicolas Connault
1005  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1006  */
1007 class grade_structure {
1008     public $context;
1010     public $courseid;
1012     /**
1013     * Reference to modinfo for current course (for performance, to save
1014     * retrieving it from courseid every time). Not actually set except for
1015     * the grade_tree type.
1016     * @var course_modinfo
1017     */
1018     public $modinfo;
1020     /**
1021      * 1D array of grade items only
1022      */
1023     public $items;
1025     /**
1026      * Returns icon of element
1027      *
1028      * @param array &$element An array representing an element in the grade_tree
1029      * @param bool  $spacerifnone return spacer if no icon found
1030      *
1031      * @return string icon or spacer
1032      */
1033     public function get_element_icon(&$element, $spacerifnone=false) {
1034         global $CFG, $OUTPUT;
1036         switch ($element['type']) {
1037             case 'item':
1038             case 'courseitem':
1039             case 'categoryitem':
1040                 $is_course   = $element['object']->is_course_item();
1041                 $is_category = $element['object']->is_category_item();
1042                 $is_scale    = $element['object']->gradetype == GRADE_TYPE_SCALE;
1043                 $is_value    = $element['object']->gradetype == GRADE_TYPE_VALUE;
1044                 $is_outcome  = !empty($element['object']->outcomeid);
1046                 if ($element['object']->is_calculated()) {
1047                     $strcalc = get_string('calculatedgrade', 'grades');
1048                     return '<img src="'.$OUTPUT->pix_url('i/calc') . '" class="icon itemicon" title="'.
1049                             s($strcalc).'" alt="'.s($strcalc).'"/>';
1051                 } else if (($is_course or $is_category) and ($is_scale or $is_value)) {
1052                     if ($category = $element['object']->get_item_category()) {
1053                         switch ($category->aggregation) {
1054                             case GRADE_AGGREGATE_MEAN:
1055                             case GRADE_AGGREGATE_MEDIAN:
1056                             case GRADE_AGGREGATE_WEIGHTED_MEAN:
1057                             case GRADE_AGGREGATE_WEIGHTED_MEAN2:
1058                             case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
1059                                 $stragg = get_string('aggregation', 'grades');
1060                                 return '<img src="'.$OUTPUT->pix_url('i/agg_mean') . '" ' .
1061                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1062                             case GRADE_AGGREGATE_SUM:
1063                                 $stragg = get_string('aggregation', 'grades');
1064                                 return '<img src="'.$OUTPUT->pix_url('i/agg_sum') . '" ' .
1065                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1066                         }
1067                     }
1069                 } else if ($element['object']->itemtype == 'mod') {
1070                     //prevent outcomes being displaying the same icon as the activity they are attached to
1071                     if ($is_outcome) {
1072                         $stroutcome = s(get_string('outcome', 'grades'));
1073                         return '<img src="'.$OUTPUT->pix_url('i/outcomes') . '" ' .
1074                             'class="icon itemicon" title="'.$stroutcome.
1075                             '" alt="'.$stroutcome.'"/>';
1076                     } else {
1077                         $strmodname = get_string('modulename', $element['object']->itemmodule);
1078                         return '<img src="'.$OUTPUT->pix_url('icon',
1079                             $element['object']->itemmodule) . '" ' .
1080                             'class="icon itemicon" title="' .s($strmodname).
1081                             '" alt="' .s($strmodname).'"/>';
1082                     }
1083                 } else if ($element['object']->itemtype == 'manual') {
1084                     if ($element['object']->is_outcome_item()) {
1085                         $stroutcome = get_string('outcome', 'grades');
1086                         return '<img src="'.$OUTPUT->pix_url('i/outcomes') . '" ' .
1087                                 'class="icon itemicon" title="'.s($stroutcome).
1088                                 '" alt="'.s($stroutcome).'"/>';
1089                     } else {
1090                         $strmanual = get_string('manualitem', 'grades');
1091                         return '<img src="'.$OUTPUT->pix_url('t/manual_item') . '" '.
1092                                 'class="icon itemicon" title="'.s($strmanual).
1093                                 '" alt="'.s($strmanual).'"/>';
1094                     }
1095                 }
1096                 break;
1098             case 'category':
1099                 $strcat = get_string('category', 'grades');
1100                 return '<img src="'.$OUTPUT->pix_url('f/folder') . '" class="icon itemicon" ' .
1101                         'title="'.s($strcat).'" alt="'.s($strcat).'" />';
1102         }
1104         if ($spacerifnone) {
1105             return $OUTPUT->spacer().' ';
1106         } else {
1107             return '';
1108         }
1109     }
1111     /**
1112      * Returns name of element optionally with icon and link
1113      *
1114      * @param array &$element An array representing an element in the grade_tree
1115      * @param bool  $withlink Whether or not this header has a link
1116      * @param bool  $icon Whether or not to display an icon with this header
1117      * @param bool  $spacerifnone return spacer if no icon found
1118      *
1119      * @return string header
1120      */
1121     public function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false) {
1122         $header = '';
1124         if ($icon) {
1125             $header .= $this->get_element_icon($element, $spacerifnone);
1126         }
1128         $header .= $element['object']->get_name();
1130         if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and
1131             $element['type'] != 'courseitem') {
1132             return $header;
1133         }
1135         if ($withlink) {
1136             $url = $this->get_activity_link($element);
1137             if ($url) {
1138                 $a = new stdClass();
1139                 $a->name = get_string('modulename', $element['object']->itemmodule);
1140                 $title = get_string('linktoactivity', 'grades', $a);
1142                 $header = html_writer::link($url, $header, array('title' => $title));
1143             }
1144         }
1146         return $header;
1147     }
1149     private function get_activity_link($element) {
1150         global $CFG;
1151         /** @var array static cache of the grade.php file existence flags */
1152         static $hasgradephp = array();
1154         $itemtype = $element['object']->itemtype;
1155         $itemmodule = $element['object']->itemmodule;
1156         $iteminstance = $element['object']->iteminstance;
1157         $itemnumber = $element['object']->itemnumber;
1159         // Links only for module items that have valid instance, module and are
1160         // called from grade_tree with valid modinfo
1161         if ($itemtype != 'mod' || !$iteminstance || !$itemmodule || !$this->modinfo) {
1162             return null;
1163         }
1165         // Get $cm efficiently and with visibility information using modinfo
1166         $instances = $this->modinfo->get_instances();
1167         if (empty($instances[$itemmodule][$iteminstance])) {
1168             return null;
1169         }
1170         $cm = $instances[$itemmodule][$iteminstance];
1172         // Do not add link if activity is not visible to the current user
1173         if (!$cm->uservisible) {
1174             return null;
1175         }
1177         if (!array_key_exists($itemmodule, $hasgradephp)) {
1178             if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) {
1179                 $hasgradephp[$itemmodule] = true;
1180             } else {
1181                 $hasgradephp[$itemmodule] = false;
1182             }
1183         }
1185         // If module has grade.php, link to that, otherwise view.php
1186         if ($hasgradephp[$itemmodule]) {
1187             $args = array('id' => $cm->id, 'itemnumber' => $itemnumber);
1188             if (isset($element['userid'])) {
1189                 $args['userid'] = $element['userid'];
1190             }
1191             return new moodle_url('/mod/' . $itemmodule . '/grade.php', $args);
1192         } else {
1193             return new moodle_url('/mod/' . $itemmodule . '/view.php', array('id' => $cm->id));
1194         }
1195     }
1197     /**
1198      * Returns URL of a page that is supposed to contain detailed grade analysis
1199      *
1200      * At the moment, only activity modules are supported. The method generates link
1201      * to the module's file grade.php with the parameters id (cmid), itemid, itemnumber,
1202      * gradeid and userid. If the grade.php does not exist, null is returned.
1203      *
1204      * @return moodle_url|null URL or null if unable to construct it
1205      */
1206     public function get_grade_analysis_url(grade_grade $grade) {
1207         global $CFG;
1208         /** @var array static cache of the grade.php file existence flags */
1209         static $hasgradephp = array();
1211         if (empty($grade->grade_item) or !($grade->grade_item instanceof grade_item)) {
1212             throw new coding_exception('Passed grade without the associated grade item');
1213         }
1214         $item = $grade->grade_item;
1216         if (!$item->is_external_item()) {
1217             // at the moment, only activity modules are supported
1218             return null;
1219         }
1220         if ($item->itemtype !== 'mod') {
1221             throw new coding_exception('Unknown external itemtype: '.$item->itemtype);
1222         }
1223         if (empty($item->iteminstance) or empty($item->itemmodule) or empty($this->modinfo)) {
1224             return null;
1225         }
1227         if (!array_key_exists($item->itemmodule, $hasgradephp)) {
1228             if (file_exists($CFG->dirroot . '/mod/' . $item->itemmodule . '/grade.php')) {
1229                 $hasgradephp[$item->itemmodule] = true;
1230             } else {
1231                 $hasgradephp[$item->itemmodule] = false;
1232             }
1233         }
1235         if (!$hasgradephp[$item->itemmodule]) {
1236             return null;
1237         }
1239         $instances = $this->modinfo->get_instances();
1240         if (empty($instances[$item->itemmodule][$item->iteminstance])) {
1241             return null;
1242         }
1243         $cm = $instances[$item->itemmodule][$item->iteminstance];
1244         if (!$cm->uservisible) {
1245             return null;
1246         }
1248         $url = new moodle_url('/mod/'.$item->itemmodule.'/grade.php', array(
1249             'id'         => $cm->id,
1250             'itemid'     => $item->id,
1251             'itemnumber' => $item->itemnumber,
1252             'gradeid'    => $grade->id,
1253             'userid'     => $grade->userid,
1254         ));
1256         return $url;
1257     }
1259     /**
1260      * Returns an action icon leading to the grade analysis page
1261      *
1262      * @param grade_grade $grade
1263      * @return string
1264      */
1265     public function get_grade_analysis_icon(grade_grade $grade) {
1266         global $OUTPUT;
1268         $url = $this->get_grade_analysis_url($grade);
1269         if (is_null($url)) {
1270             return '';
1271         }
1273         return $OUTPUT->action_icon($url, new pix_icon('t/preview',
1274             get_string('gradeanalysis', 'core_grades')));
1275     }
1277     /**
1278      * Returns the grade eid - the grade may not exist yet.
1279      *
1280      * @param grade_grade $grade_grade A grade_grade object
1281      *
1282      * @return string eid
1283      */
1284     public function get_grade_eid($grade_grade) {
1285         if (empty($grade_grade->id)) {
1286             return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid;
1287         } else {
1288             return 'g'.$grade_grade->id;
1289         }
1290     }
1292     /**
1293      * Returns the grade_item eid
1294      * @param grade_item $grade_item A grade_item object
1295      * @return string eid
1296      */
1297     public function get_item_eid($grade_item) {
1298         return 'i'.$grade_item->id;
1299     }
1301     /**
1302      * Given a grade_tree element, returns an array of parameters
1303      * used to build an icon for that element.
1304      *
1305      * @param array $element An array representing an element in the grade_tree
1306      *
1307      * @return array
1308      */
1309     public function get_params_for_iconstr($element) {
1310         $strparams = new stdClass();
1311         $strparams->category = '';
1312         $strparams->itemname = '';
1313         $strparams->itemmodule = '';
1315         if (!method_exists($element['object'], 'get_name')) {
1316             return $strparams;
1317         }
1319         $strparams->itemname = html_to_text($element['object']->get_name());
1321         // If element name is categorytotal, get the name of the parent category
1322         if ($strparams->itemname == get_string('categorytotal', 'grades')) {
1323             $parent = $element['object']->get_parent_category();
1324             $strparams->category = $parent->get_name() . ' ';
1325         } else {
1326             $strparams->category = '';
1327         }
1329         $strparams->itemmodule = null;
1330         if (isset($element['object']->itemmodule)) {
1331             $strparams->itemmodule = $element['object']->itemmodule;
1332         }
1333         return $strparams;
1334     }
1336     /**
1337      * Return edit icon for give element
1338      *
1339      * @param array  $element An array representing an element in the grade_tree
1340      * @param object $gpr A grade_plugin_return object
1341      *
1342      * @return string
1343      */
1344     public function get_edit_icon($element, $gpr) {
1345         global $CFG, $OUTPUT;
1347         if (!has_capability('moodle/grade:manage', $this->context)) {
1348             if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
1349                 // oki - let them override grade
1350             } else {
1351                 return '';
1352             }
1353         }
1355         static $strfeedback   = null;
1356         static $streditgrade = null;
1357         if (is_null($streditgrade)) {
1358             $streditgrade = get_string('editgrade', 'grades');
1359             $strfeedback  = get_string('feedback');
1360         }
1362         $strparams = $this->get_params_for_iconstr($element);
1364         $object = $element['object'];
1366         switch ($element['type']) {
1367             case 'item':
1368             case 'categoryitem':
1369             case 'courseitem':
1370                 $stredit = get_string('editverbose', 'grades', $strparams);
1371                 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) {
1372                     $url = new moodle_url('/grade/edit/tree/item.php',
1373                             array('courseid' => $this->courseid, 'id' => $object->id));
1374                 } else {
1375                     $url = new moodle_url('/grade/edit/tree/outcomeitem.php',
1376                             array('courseid' => $this->courseid, 'id' => $object->id));
1377                 }
1378                 break;
1380             case 'category':
1381                 $stredit = get_string('editverbose', 'grades', $strparams);
1382                 $url = new moodle_url('/grade/edit/tree/category.php',
1383                         array('courseid' => $this->courseid, 'id' => $object->id));
1384                 break;
1386             case 'grade':
1387                 $stredit = $streditgrade;
1388                 if (empty($object->id)) {
1389                     $url = new moodle_url('/grade/edit/tree/grade.php',
1390                             array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid));
1391                 } else {
1392                     $url = new moodle_url('/grade/edit/tree/grade.php',
1393                             array('courseid' => $this->courseid, 'id' => $object->id));
1394                 }
1395                 if (!empty($object->feedback)) {
1396                     $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat)));
1397                 }
1398                 break;
1400             default:
1401                 $url = null;
1402         }
1404         if ($url) {
1405             return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit));
1407         } else {
1408             return '';
1409         }
1410     }
1412     /**
1413      * Return hiding icon for give element
1414      *
1415      * @param array  $element An array representing an element in the grade_tree
1416      * @param object $gpr A grade_plugin_return object
1417      *
1418      * @return string
1419      */
1420     public function get_hiding_icon($element, $gpr) {
1421         global $CFG, $OUTPUT;
1423         if (!has_capability('moodle/grade:manage', $this->context) and
1424             !has_capability('moodle/grade:hide', $this->context)) {
1425             return '';
1426         }
1428         $strparams = $this->get_params_for_iconstr($element);
1429         $strshow = get_string('showverbose', 'grades', $strparams);
1430         $strhide = get_string('hideverbose', 'grades', $strparams);
1432         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1433         $url = $gpr->add_url_params($url);
1435         if ($element['object']->is_hidden()) {
1436             $type = 'show';
1437             $tooltip = $strshow;
1439             // Change the icon and add a tooltip showing the date
1440             if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) {
1441                 $type = 'hiddenuntil';
1442                 $tooltip = get_string('hiddenuntildate', 'grades',
1443                         userdate($element['object']->get_hidden()));
1444             }
1446             $url->param('action', 'show');
1448             $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'iconsmall')));
1450         } else {
1451             $url->param('action', 'hide');
1452             $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide));
1453         }
1455         return $hideicon;
1456     }
1458     /**
1459      * Return locking icon for given element
1460      *
1461      * @param array  $element An array representing an element in the grade_tree
1462      * @param object $gpr A grade_plugin_return object
1463      *
1464      * @return string
1465      */
1466     public function get_locking_icon($element, $gpr) {
1467         global $CFG, $OUTPUT;
1469         $strparams = $this->get_params_for_iconstr($element);
1470         $strunlock = get_string('unlockverbose', 'grades', $strparams);
1471         $strlock = get_string('lockverbose', 'grades', $strparams);
1473         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1474         $url = $gpr->add_url_params($url);
1476         // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon
1477         if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) {
1478             $strparamobj = new stdClass();
1479             $strparamobj->itemname = $element['object']->grade_item->itemname;
1480             $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj);
1482             $action = $OUTPUT->pix_icon('t/unlock_gray', $strnonunlockable);
1484         } else if ($element['object']->is_locked()) {
1485             $type = 'unlock';
1486             $tooltip = $strunlock;
1488             // Change the icon and add a tooltip showing the date
1489             if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) {
1490                 $type = 'locktime';
1491                 $tooltip = get_string('locktimedate', 'grades',
1492                         userdate($element['object']->get_locktime()));
1493             }
1495             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) {
1496                 $action = '';
1497             } else {
1498                 $url->param('action', 'unlock');
1499                 $action = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strunlock, 'class'=>'smallicon')));
1500             }
1502         } else {
1503             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) {
1504                 $action = '';
1505             } else {
1506                 $url->param('action', 'lock');
1507                 $action = $OUTPUT->action_icon($url, new pix_icon('t/lock', $strlock));
1508             }
1509         }
1511         return $action;
1512     }
1514     /**
1515      * Return calculation icon for given element
1516      *
1517      * @param array  $element An array representing an element in the grade_tree
1518      * @param object $gpr A grade_plugin_return object
1519      *
1520      * @return string
1521      */
1522     public function get_calculation_icon($element, $gpr) {
1523         global $CFG, $OUTPUT;
1524         if (!has_capability('moodle/grade:manage', $this->context)) {
1525             return '';
1526         }
1528         $type   = $element['type'];
1529         $object = $element['object'];
1531         if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
1532             $strparams = $this->get_params_for_iconstr($element);
1533             $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams);
1535             $is_scale = $object->gradetype == GRADE_TYPE_SCALE;
1536             $is_value = $object->gradetype == GRADE_TYPE_VALUE;
1538             // show calculation icon only when calculation possible
1539             if (!$object->is_external_item() and ($is_scale or $is_value)) {
1540                 if ($object->is_calculated()) {
1541                     $icon = 't/calc';
1542                 } else {
1543                     $icon = 't/calc_off';
1544                 }
1546                 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id));
1547                 $url = $gpr->add_url_params($url);
1548                 return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation)) . "\n";
1549             }
1550         }
1552         return '';
1553     }
1556 /**
1557  * Flat structure similar to grade tree.
1558  *
1559  * @uses grade_structure
1560  * @package moodlecore
1561  * @copyright 2009 Nicolas Connault
1562  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1563  */
1564 class grade_seq extends grade_structure {
1566     /**
1567      * 1D array of elements
1568      */
1569     public $elements;
1571     /**
1572      * Constructor, retrieves and stores array of all grade_category and grade_item
1573      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1574      *
1575      * @param int  $courseid The course id
1576      * @param bool $category_grade_last category grade item is the last child
1577      * @param bool $nooutcomes Whether or not outcomes should be included
1578      */
1579     public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) {
1580         global $USER, $CFG;
1582         $this->courseid   = $courseid;
1583         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1585         // get course grade tree
1586         $top_element = grade_category::fetch_course_tree($courseid, true);
1588         $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes);
1590         foreach ($this->elements as $key=>$unused) {
1591             $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object'];
1592         }
1593     }
1595     /**
1596      * Static recursive helper - makes the grade_item for category the last children
1597      *
1598      * @param array &$element The seed of the recursion
1599      * @param bool $category_grade_last category grade item is the last child
1600      * @param bool $nooutcomes Whether or not outcomes should be included
1601      *
1602      * @return array
1603      */
1604     public function flatten(&$element, $category_grade_last, $nooutcomes) {
1605         if (empty($element['children'])) {
1606             return array();
1607         }
1608         $children = array();
1610         foreach ($element['children'] as $sortorder=>$unused) {
1611             if ($nooutcomes and $element['type'] != 'category' and
1612                 $element['children'][$sortorder]['object']->is_outcome_item()) {
1613                 continue;
1614             }
1615             $children[] = $element['children'][$sortorder];
1616         }
1617         unset($element['children']);
1619         if ($category_grade_last and count($children) > 1) {
1620             $cat_item = array_shift($children);
1621             array_push($children, $cat_item);
1622         }
1624         $result = array();
1625         foreach ($children as $child) {
1626             if ($child['type'] == 'category') {
1627                 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes);
1628             } else {
1629                 $child['eid'] = 'i'.$child['object']->id;
1630                 $result[$child['object']->id] = $child;
1631             }
1632         }
1634         return $result;
1635     }
1637     /**
1638      * Parses the array in search of a given eid and returns a element object with
1639      * information about the element it has found.
1640      *
1641      * @param int $eid Gradetree Element ID
1642      *
1643      * @return object element
1644      */
1645     public function locate_element($eid) {
1646         // it is a grade - construct a new object
1647         if (strpos($eid, 'n') === 0) {
1648             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1649                 return null;
1650             }
1652             $itemid = $matches[1];
1653             $userid = $matches[2];
1655             //extra security check - the grade item must be in this tree
1656             if (!$item_el = $this->locate_element('i'.$itemid)) {
1657                 return null;
1658             }
1660             // $gradea->id may be null - means does not exist yet
1661             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1663             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1664             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1666         } else if (strpos($eid, 'g') === 0) {
1667             $id = (int) substr($eid, 1);
1668             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1669                 return null;
1670             }
1671             //extra security check - the grade item must be in this tree
1672             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1673                 return null;
1674             }
1675             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1676             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1677         }
1679         // it is a category or item
1680         foreach ($this->elements as $element) {
1681             if ($element['eid'] == $eid) {
1682                 return $element;
1683             }
1684         }
1686         return null;
1687     }
1690 /**
1691  * This class represents a complete tree of categories, grade_items and final grades,
1692  * organises as an array primarily, but which can also be converted to other formats.
1693  * It has simple method calls with complex implementations, allowing for easy insertion,
1694  * deletion and moving of items and categories within the tree.
1695  *
1696  * @uses grade_structure
1697  * @package moodlecore
1698  * @copyright 2009 Nicolas Connault
1699  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1700  */
1701 class grade_tree extends grade_structure {
1703     /**
1704      * The basic representation of the tree as a hierarchical, 3-tiered array.
1705      * @var object $top_element
1706      */
1707     public $top_element;
1709     /**
1710      * 2D array of grade items and categories
1711      * @var array $levels
1712      */
1713     public $levels;
1715     /**
1716      * Grade items
1717      * @var array $items
1718      */
1719     public $items;
1721     /**
1722      * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item
1723      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1724      *
1725      * @param int   $courseid The Course ID
1726      * @param bool  $fillers include fillers and colspans, make the levels var "rectangular"
1727      * @param bool  $category_grade_last category grade item is the last child
1728      * @param array $collapsed array of collapsed categories
1729      * @param bool  $nooutcomes Whether or not outcomes should be included
1730      */
1731     public function grade_tree($courseid, $fillers=true, $category_grade_last=false,
1732                                $collapsed=null, $nooutcomes=false) {
1733         global $USER, $CFG, $COURSE, $DB;
1735         $this->courseid   = $courseid;
1736         $this->levels     = array();
1737         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1739         if (!empty($COURSE->id) && $COURSE->id == $this->courseid) {
1740             $course = $COURSE;
1741         } else {
1742             $course = $DB->get_record('course', array('id' => $this->courseid));
1743         }
1744         $this->modinfo = get_fast_modinfo($course);
1746         // get course grade tree
1747         $this->top_element = grade_category::fetch_course_tree($courseid, true);
1749         // collapse the categories if requested
1750         if (!empty($collapsed)) {
1751             grade_tree::category_collapse($this->top_element, $collapsed);
1752         }
1754         // no otucomes if requested
1755         if (!empty($nooutcomes)) {
1756             grade_tree::no_outcomes($this->top_element);
1757         }
1759         // move category item to last position in category
1760         if ($category_grade_last) {
1761             grade_tree::category_grade_last($this->top_element);
1762         }
1764         if ($fillers) {
1765             // inject fake categories == fillers
1766             grade_tree::inject_fillers($this->top_element, 0);
1767             // add colspans to categories and fillers
1768             grade_tree::inject_colspans($this->top_element);
1769         }
1771         grade_tree::fill_levels($this->levels, $this->top_element, 0);
1773     }
1775     /**
1776      * Static recursive helper - removes items from collapsed categories
1777      *
1778      * @param array &$element The seed of the recursion
1779      * @param array $collapsed array of collapsed categories
1780      *
1781      * @return void
1782      */
1783     public function category_collapse(&$element, $collapsed) {
1784         if ($element['type'] != 'category') {
1785             return;
1786         }
1787         if (empty($element['children']) or count($element['children']) < 2) {
1788             return;
1789         }
1791         if (in_array($element['object']->id, $collapsed['aggregatesonly'])) {
1792             $category_item = reset($element['children']); //keep only category item
1793             $element['children'] = array(key($element['children'])=>$category_item);
1795         } else {
1796             if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item
1797                 reset($element['children']);
1798                 $first_key = key($element['children']);
1799                 unset($element['children'][$first_key]);
1800             }
1801             foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children
1802                 grade_tree::category_collapse($element['children'][$sortorder], $collapsed);
1803             }
1804         }
1805     }
1807     /**
1808      * Static recursive helper - removes all outcomes
1809      *
1810      * @param array &$element The seed of the recursion
1811      *
1812      * @return void
1813      */
1814     public function no_outcomes(&$element) {
1815         if ($element['type'] != 'category') {
1816             return;
1817         }
1818         foreach ($element['children'] as $sortorder=>$child) {
1819             if ($element['children'][$sortorder]['type'] == 'item'
1820               and $element['children'][$sortorder]['object']->is_outcome_item()) {
1821                 unset($element['children'][$sortorder]);
1823             } else if ($element['children'][$sortorder]['type'] == 'category') {
1824                 grade_tree::no_outcomes($element['children'][$sortorder]);
1825             }
1826         }
1827     }
1829     /**
1830      * Static recursive helper - makes the grade_item for category the last children
1831      *
1832      * @param array &$element The seed of the recursion
1833      *
1834      * @return void
1835      */
1836     public function category_grade_last(&$element) {
1837         if (empty($element['children'])) {
1838             return;
1839         }
1840         if (count($element['children']) < 2) {
1841             return;
1842         }
1843         $first_item = reset($element['children']);
1844         if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') {
1845             // the category item might have been already removed
1846             $order = key($element['children']);
1847             unset($element['children'][$order]);
1848             $element['children'][$order] =& $first_item;
1849         }
1850         foreach ($element['children'] as $sortorder => $child) {
1851             grade_tree::category_grade_last($element['children'][$sortorder]);
1852         }
1853     }
1855     /**
1856      * Static recursive helper - fills the levels array, useful when accessing tree elements of one level
1857      *
1858      * @param array &$levels The levels of the grade tree through which to recurse
1859      * @param array &$element The seed of the recursion
1860      * @param int   $depth How deep are we?
1861      * @return void
1862      */
1863     public function fill_levels(&$levels, &$element, $depth) {
1864         if (!array_key_exists($depth, $levels)) {
1865             $levels[$depth] = array();
1866         }
1868         // prepare unique identifier
1869         if ($element['type'] == 'category') {
1870             $element['eid'] = 'c'.$element['object']->id;
1871         } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
1872             $element['eid'] = 'i'.$element['object']->id;
1873             $this->items[$element['object']->id] =& $element['object'];
1874         }
1876         $levels[$depth][] =& $element;
1877         $depth++;
1878         if (empty($element['children'])) {
1879             return;
1880         }
1881         $prev = 0;
1882         foreach ($element['children'] as $sortorder=>$child) {
1883             grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth);
1884             $element['children'][$sortorder]['prev'] = $prev;
1885             $element['children'][$sortorder]['next'] = 0;
1886             if ($prev) {
1887                 $element['children'][$prev]['next'] = $sortorder;
1888             }
1889             $prev = $sortorder;
1890         }
1891     }
1893     /**
1894      * Static recursive helper - makes full tree (all leafes are at the same level)
1895      *
1896      * @param array &$element The seed of the recursion
1897      * @param int   $depth How deep are we?
1898      *
1899      * @return int
1900      */
1901     public function inject_fillers(&$element, $depth) {
1902         $depth++;
1904         if (empty($element['children'])) {
1905             return $depth;
1906         }
1907         $chdepths = array();
1908         $chids = array_keys($element['children']);
1909         $last_child  = end($chids);
1910         $first_child = reset($chids);
1912         foreach ($chids as $chid) {
1913             $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth);
1914         }
1915         arsort($chdepths);
1917         $maxdepth = reset($chdepths);
1918         foreach ($chdepths as $chid=>$chd) {
1919             if ($chd == $maxdepth) {
1920                 continue;
1921             }
1922             for ($i=0; $i < $maxdepth-$chd; $i++) {
1923                 if ($chid == $first_child) {
1924                     $type = 'fillerfirst';
1925                 } else if ($chid == $last_child) {
1926                     $type = 'fillerlast';
1927                 } else {
1928                     $type = 'filler';
1929                 }
1930                 $oldchild =& $element['children'][$chid];
1931                 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type,
1932                                                     'eid'=>'', 'depth'=>$element['object']->depth,
1933                                                     'children'=>array($oldchild));
1934             }
1935         }
1937         return $maxdepth;
1938     }
1940     /**
1941      * Static recursive helper - add colspan information into categories
1942      *
1943      * @param array &$element The seed of the recursion
1944      *
1945      * @return int
1946      */
1947     public function inject_colspans(&$element) {
1948         if (empty($element['children'])) {
1949             return 1;
1950         }
1951         $count = 0;
1952         foreach ($element['children'] as $key=>$child) {
1953             $count += grade_tree::inject_colspans($element['children'][$key]);
1954         }
1955         $element['colspan'] = $count;
1956         return $count;
1957     }
1959     /**
1960      * Parses the array in search of a given eid and returns a element object with
1961      * information about the element it has found.
1962      * @param int $eid Gradetree Element ID
1963      * @return object element
1964      */
1965     public function locate_element($eid) {
1966         // it is a grade - construct a new object
1967         if (strpos($eid, 'n') === 0) {
1968             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1969                 return null;
1970             }
1972             $itemid = $matches[1];
1973             $userid = $matches[2];
1975             //extra security check - the grade item must be in this tree
1976             if (!$item_el = $this->locate_element('i'.$itemid)) {
1977                 return null;
1978             }
1980             // $gradea->id may be null - means does not exist yet
1981             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1983             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1984             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1986         } else if (strpos($eid, 'g') === 0) {
1987             $id = (int) substr($eid, 1);
1988             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1989                 return null;
1990             }
1991             //extra security check - the grade item must be in this tree
1992             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1993                 return null;
1994             }
1995             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1996             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1997         }
1999         // it is a category or item
2000         foreach ($this->levels as $row) {
2001             foreach ($row as $element) {
2002                 if ($element['type'] == 'filler') {
2003                     continue;
2004                 }
2005                 if ($element['eid'] == $eid) {
2006                     return $element;
2007                 }
2008             }
2009         }
2011         return null;
2012     }
2014     /**
2015      * Returns a well-formed XML representation of the grade-tree using recursion.
2016      *
2017      * @param array  $root The current element in the recursion. If null, starts at the top of the tree.
2018      * @param string $tabs The control character to use for tabs
2019      *
2020      * @return string $xml
2021      */
2022     public function exporttoxml($root=null, $tabs="\t") {
2023         $xml = null;
2024         $first = false;
2025         if (is_null($root)) {
2026             $root = $this->top_element;
2027             $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
2028             $xml .= "<gradetree>\n";
2029             $first = true;
2030         }
2032         $type = 'undefined';
2033         if (strpos($root['object']->table, 'grade_categories') !== false) {
2034             $type = 'category';
2035         } else if (strpos($root['object']->table, 'grade_items') !== false) {
2036             $type = 'item';
2037         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
2038             $type = 'outcome';
2039         }
2041         $xml .= "$tabs<element type=\"$type\">\n";
2042         foreach ($root['object'] as $var => $value) {
2043             if (!is_object($value) && !is_array($value) && !empty($value)) {
2044                 $xml .= "$tabs\t<$var>$value</$var>\n";
2045             }
2046         }
2048         if (!empty($root['children'])) {
2049             $xml .= "$tabs\t<children>\n";
2050             foreach ($root['children'] as $sortorder => $child) {
2051                 $xml .= $this->exportToXML($child, $tabs."\t\t");
2052             }
2053             $xml .= "$tabs\t</children>\n";
2054         }
2056         $xml .= "$tabs</element>\n";
2058         if ($first) {
2059             $xml .= "</gradetree>";
2060         }
2062         return $xml;
2063     }
2065     /**
2066      * Returns a JSON representation of the grade-tree using recursion.
2067      *
2068      * @param array $root The current element in the recursion. If null, starts at the top of the tree.
2069      * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy
2070      *
2071      * @return string
2072      */
2073     public function exporttojson($root=null, $tabs="\t") {
2074         $json = null;
2075         $first = false;
2076         if (is_null($root)) {
2077             $root = $this->top_element;
2078             $first = true;
2079         }
2081         $name = '';
2084         if (strpos($root['object']->table, 'grade_categories') !== false) {
2085             $name = $root['object']->fullname;
2086             if ($name == '?') {
2087                 $name = $root['object']->get_name();
2088             }
2089         } else if (strpos($root['object']->table, 'grade_items') !== false) {
2090             $name = $root['object']->itemname;
2091         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
2092             $name = $root['object']->itemname;
2093         }
2095         $json .= "$tabs {\n";
2096         $json .= "$tabs\t \"type\": \"{$root['type']}\",\n";
2097         $json .= "$tabs\t \"name\": \"$name\",\n";
2099         foreach ($root['object'] as $var => $value) {
2100             if (!is_object($value) && !is_array($value) && !empty($value)) {
2101                 $json .= "$tabs\t \"$var\": \"$value\",\n";
2102             }
2103         }
2105         $json = substr($json, 0, strrpos($json, ','));
2107         if (!empty($root['children'])) {
2108             $json .= ",\n$tabs\t\"children\": [\n";
2109             foreach ($root['children'] as $sortorder => $child) {
2110                 $json .= $this->exportToJSON($child, $tabs."\t\t");
2111             }
2112             $json = substr($json, 0, strrpos($json, ','));
2113             $json .= "\n$tabs\t]\n";
2114         }
2116         if ($first) {
2117             $json .= "\n}";
2118         } else {
2119             $json .= "\n$tabs},\n";
2120         }
2122         return $json;
2123     }
2125     /**
2126      * Returns the array of levels
2127      *
2128      * @return array
2129      */
2130     public function get_levels() {
2131         return $this->levels;
2132     }
2134     /**
2135      * Returns the array of grade items
2136      *
2137      * @return array
2138      */
2139     public function get_items() {
2140         return $this->items;
2141     }
2143     /**
2144      * Returns a specific Grade Item
2145      *
2146      * @param int $itemid The ID of the grade_item object
2147      *
2148      * @return grade_item
2149      */
2150     public function get_item($itemid) {
2151         if (array_key_exists($itemid, $this->items)) {
2152             return $this->items[$itemid];
2153         } else {
2154             return false;
2155         }
2156     }
2159 /**
2160  * Local shortcut function for creating an edit/delete button for a grade_* object.
2161  * @param string $type 'edit' or 'delete'
2162  * @param int $courseid The Course ID
2163  * @param grade_* $object The grade_* object
2164  * @return string html
2165  */
2166 function grade_button($type, $courseid, $object) {
2167     global $CFG, $OUTPUT;
2168     if (preg_match('/grade_(.*)/', get_class($object), $matches)) {
2169         $objectidstring = $matches[1] . 'id';
2170     } else {
2171         throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!');
2172     }
2174     $strdelete = get_string('delete');
2175     $stredit   = get_string('edit');
2177     if ($type == 'delete') {
2178         $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey()));
2179     } else if ($type == 'edit') {
2180         $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id));
2181     }
2183     return $OUTPUT->action_icon($url, new pix_icon('t/'.$type, ${'str'.$type}));
2187 /**
2188  * This method adds settings to the settings block for the grade system and its
2189  * plugins
2190  *
2191  * @global moodle_page $PAGE
2192  */
2193 function grade_extend_settings($plugininfo, $courseid) {
2194     global $PAGE;
2196     $gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER);
2198     $strings = array_shift($plugininfo);
2200     if ($reports = grade_helper::get_plugins_reports($courseid)) {
2201         foreach ($reports as $report) {
2202             $gradenode->add($report->string, $report->link, navigation_node::TYPE_SETTING, null, $report->id, new pix_icon('i/report', ''));
2203         }
2204     }
2206     if ($imports = grade_helper::get_plugins_import($courseid)) {
2207         $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER);
2208         foreach ($imports as $import) {
2209             $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/restore', ''));
2210         }
2211     }
2213     if ($exports = grade_helper::get_plugins_export($courseid)) {
2214         $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER);
2215         foreach ($exports as $export) {
2216             $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/backup', ''));
2217         }
2218     }
2220     if ($setting = grade_helper::get_info_manage_settings($courseid)) {
2221         $gradenode->add(get_string('coursegradesettings', 'grades'), $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', ''));
2222     }
2224     if ($preferences = grade_helper::get_plugins_report_preferences($courseid)) {
2225         $preferencesnode = $gradenode->add(get_string('myreportpreferences', 'grades'), null, navigation_node::TYPE_CONTAINER);
2226         foreach ($preferences as $preference) {
2227             $preferencesnode->add($preference->string, $preference->link, navigation_node::TYPE_SETTING, null, $preference->id, new pix_icon('i/settings', ''));
2228         }
2229     }
2231     if ($letters = grade_helper::get_info_letters($courseid)) {
2232         $letters = array_shift($letters);
2233         $gradenode->add($strings['letter'], $letters->link, navigation_node::TYPE_SETTING, null, $letters->id, new pix_icon('i/settings', ''));
2234     }
2236     if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
2237         $outcomes = array_shift($outcomes);
2238         $gradenode->add($strings['outcome'], $outcomes->link, navigation_node::TYPE_SETTING, null, $outcomes->id, new pix_icon('i/outcomes', ''));
2239     }
2241     if ($scales = grade_helper::get_info_scales($courseid)) {
2242         $gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', ''));
2243     }
2245     if ($categories = grade_helper::get_info_edit_structure($courseid)) {
2246         $categoriesnode = $gradenode->add(get_string('categoriesanditems','grades'), null, navigation_node::TYPE_CONTAINER);
2247         foreach ($categories as $category) {
2248             $categoriesnode->add($category->string, $category->link, navigation_node::TYPE_SETTING, null, $category->id, new pix_icon('i/report', ''));
2249         }
2250     }
2252     if ($gradenode->contains_active_node()) {
2253         // If the gradenode is active include the settings base node (gradeadministration) in
2254         // the navbar, typcially this is ignored.
2255         $PAGE->navbar->includesettingsbase = true;
2257         // If we can get the course admin node make sure it is closed by default
2258         // as in this case the gradenode will be opened
2259         if ($coursenode = $PAGE->settingsnav->get('courseadmin', navigation_node::TYPE_COURSE)){
2260             $coursenode->make_inactive();
2261             $coursenode->forceopen = false;
2262         }
2263     }
2266 /**
2267  * Grade helper class
2268  *
2269  * This class provides several helpful functions that work irrespective of any
2270  * current state.
2271  *
2272  * @copyright 2010 Sam Hemelryk
2273  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2274  */
2275 abstract class grade_helper {
2276     /**
2277      * Cached manage settings info {@see get_info_settings}
2278      * @var grade_plugin_info|false
2279      */
2280     protected static $managesetting = null;
2281     /**
2282      * Cached grade report plugins {@see get_plugins_reports}
2283      * @var array|false
2284      */
2285     protected static $gradereports = null;
2286     /**
2287      * Cached grade report plugins preferences {@see get_info_scales}
2288      * @var array|false
2289      */
2290     protected static $gradereportpreferences = null;
2291     /**
2292      * Cached scale info {@see get_info_scales}
2293      * @var grade_plugin_info|false
2294      */
2295     protected static $scaleinfo = null;
2296     /**
2297      * Cached outcome info {@see get_info_outcomes}
2298      * @var grade_plugin_info|false
2299      */
2300     protected static $outcomeinfo = null;
2301     /**
2302      * Cached info on edit structure {@see get_info_edit_structure}
2303      * @var array|false
2304      */
2305     protected static $edittree = null;
2306     /**
2307      * Cached leftter info {@see get_info_letters}
2308      * @var grade_plugin_info|false
2309      */
2310     protected static $letterinfo = null;
2311     /**
2312      * Cached grade import plugins {@see get_plugins_import}
2313      * @var array|false
2314      */
2315     protected static $importplugins = null;
2316     /**
2317      * Cached grade export plugins {@see get_plugins_export}
2318      * @var array|false
2319      */
2320     protected static $exportplugins = null;
2321     /**
2322      * Cached grade plugin strings
2323      * @var array
2324      */
2325     protected static $pluginstrings = null;
2327     /**
2328      * Gets strings commonly used by the describe plugins
2329      *
2330      * report => get_string('view'),
2331      * edittree => get_string('edittree', 'grades'),
2332      * scale => get_string('scales'),
2333      * outcome => get_string('outcomes', 'grades'),
2334      * letter => get_string('letters', 'grades'),
2335      * export => get_string('export', 'grades'),
2336      * import => get_string('import'),
2337      * preferences => get_string('mypreferences', 'grades'),
2338      * settings => get_string('settings')
2339      *
2340      * @return array
2341      */
2342     public static function get_plugin_strings() {
2343         if (self::$pluginstrings === null) {
2344             self::$pluginstrings = array(
2345                 'report' => get_string('view'),
2346                 'edittree' => get_string('edittree', 'grades'),
2347                 'scale' => get_string('scales'),
2348                 'outcome' => get_string('outcomes', 'grades'),
2349                 'letter' => get_string('letters', 'grades'),
2350                 'export' => get_string('export', 'grades'),
2351                 'import' => get_string('import'),
2352                 'preferences' => get_string('mypreferences', 'grades'),
2353                 'settings' => get_string('settings')
2354             );
2355         }
2356         return self::$pluginstrings;
2357     }
2358     /**
2359      * Get grade_plugin_info object for managing settings if the user can
2360      *
2361      * @param int $courseid
2362      * @return grade_plugin_info
2363      */
2364     public static function get_info_manage_settings($courseid) {
2365         if (self::$managesetting !== null) {
2366             return self::$managesetting;
2367         }
2368         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2369         if (has_capability('moodle/course:update', $context)) {
2370             self::$managesetting = new grade_plugin_info('coursesettings', new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)), get_string('course'));
2371         } else {
2372             self::$managesetting = false;
2373         }
2374         return self::$managesetting;
2375     }
2376     /**
2377      * Returns an array of plugin reports as grade_plugin_info objects
2378      *
2379      * @param int $courseid
2380      * @return array
2381      */
2382     public static function get_plugins_reports($courseid) {
2383         global $SITE;
2385         if (self::$gradereports !== null) {
2386             return self::$gradereports;
2387         }
2388         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2389         $gradereports = array();
2390         $gradepreferences = array();
2391         foreach (get_plugin_list('gradereport') as $plugin => $plugindir) {
2392             //some reports make no sense if we're not within a course
2393             if ($courseid==$SITE->id && ($plugin=='grader' || $plugin=='user')) {
2394                 continue;
2395             }
2397             // Remove ones we can't see
2398             if (!has_capability('gradereport/'.$plugin.':view', $context)) {
2399                 continue;
2400             }
2402             $pluginstr = get_string('pluginname', 'gradereport_'.$plugin);
2403             $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid));
2404             $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2406             // Add link to preferences tab if such a page exists
2407             if (file_exists($plugindir.'/preferences.php')) {
2408                 $url = new moodle_url('/grade/report/'.$plugin.'/preferences.php', array('id'=>$courseid));
2409                 $gradepreferences[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2410             }
2411         }
2412         if (count($gradereports) == 0) {
2413             $gradereports = false;
2414             $gradepreferences = false;
2415         } else if (count($gradepreferences) == 0) {
2416             $gradepreferences = false;
2417             asort($gradereports);
2418         } else {
2419             asort($gradereports);
2420             asort($gradepreferences);
2421         }
2422         self::$gradereports = $gradereports;
2423         self::$gradereportpreferences = $gradepreferences;
2424         return self::$gradereports;
2425     }
2426     /**
2427      * Returns an array of grade plugin report preferences for plugin reports that
2428      * support preferences
2429      * @param int $courseid
2430      * @return array
2431      */
2432     public static function get_plugins_report_preferences($courseid) {
2433         if (self::$gradereportpreferences !== null) {
2434             return self::$gradereportpreferences;
2435         }
2436         self::get_plugins_reports($courseid);
2437         return self::$gradereportpreferences;
2438     }
2439     /**
2440      * Get information on scales
2441      * @param int $courseid
2442      * @return grade_plugin_info
2443      */
2444     public static function get_info_scales($courseid) {
2445         if (self::$scaleinfo !== null) {
2446             return self::$scaleinfo;
2447         }
2448         if (has_capability('moodle/course:managescales', get_context_instance(CONTEXT_COURSE, $courseid))) {
2449             $url = new moodle_url('/grade/edit/scale/index.php', array('id'=>$courseid));
2450             self::$scaleinfo = new grade_plugin_info('scale', $url, get_string('view'));
2451         } else {
2452             self::$scaleinfo = false;
2453         }
2454         return self::$scaleinfo;
2455     }
2456     /**
2457      * Get information on outcomes
2458      * @param int $courseid
2459      * @return grade_plugin_info
2460      */
2461     public static function get_info_outcomes($courseid) {
2462         global $CFG, $SITE;
2464         if (self::$outcomeinfo !== null) {
2465             return self::$outcomeinfo;
2466         }
2467         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2468         $canmanage = has_capability('moodle/grade:manage', $context);
2469         $canupdate = has_capability('moodle/course:update', $context);
2470         if (!empty($CFG->enableoutcomes) && ($canmanage || $canupdate)) {
2471             $outcomes = array();
2472             if ($canupdate) {
2473                 if ($courseid!=$SITE->id) {
2474                     $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
2475                     $outcomes['course'] = new grade_plugin_info('course', $url, get_string('outcomescourse', 'grades'));
2476                 }
2477                 $url = new moodle_url('/grade/edit/outcome/index.php', array('id'=>$courseid));
2478                 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('editoutcomes', 'grades'));
2479                 $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid'=>$courseid));
2480                 $outcomes['import'] = new grade_plugin_info('import', $url, get_string('importoutcomes', 'grades'));
2481             } else {
2482                 if ($courseid!=$SITE->id) {
2483                     $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
2484                     $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('outcomescourse', 'grades'));
2485                 }
2486             }
2487             self::$outcomeinfo = $outcomes;
2488         } else {
2489             self::$outcomeinfo = false;
2490         }
2491         return self::$outcomeinfo;
2492     }
2493     /**
2494      * Get information on editing structures
2495      * @param int $courseid
2496      * @return array
2497      */
2498     public static function get_info_edit_structure($courseid) {
2499         if (self::$edittree !== null) {
2500             return self::$edittree;
2501         }
2502         if (has_capability('moodle/grade:manage', get_context_instance(CONTEXT_COURSE, $courseid))) {
2503             $url = new moodle_url('/grade/edit/tree/index.php', array('sesskey'=>sesskey(), 'showadvanced'=>'0', 'id'=>$courseid));
2504             self::$edittree = array(
2505                 'simpleview' => new grade_plugin_info('simpleview', $url, get_string('simpleview', 'grades')),
2506                 'fullview' => new grade_plugin_info('fullview', new moodle_url($url, array('showadvanced'=>'1')), get_string('fullview', 'grades'))
2507             );
2508         } else {
2509             self::$edittree = false;
2510         }
2511         return self::$edittree;
2512     }
2513     /**
2514      * Get information on letters
2515      * @param int $courseid
2516      * @return array
2517      */
2518     public static function get_info_letters($courseid) {
2519         if (self::$letterinfo !== null) {
2520             return self::$letterinfo;
2521         }
2522         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2523         $canmanage = has_capability('moodle/grade:manage', $context);
2524         $canmanageletters = has_capability('moodle/grade:manageletters', $context);
2525         if ($canmanage || $canmanageletters) {
2526             self::$letterinfo = array(
2527                 'view' => new grade_plugin_info('view', new moodle_url('/grade/edit/letter/index.php', array('id'=>$context->id)), get_string('view')),
2528                 'edit' => new grade_plugin_info('edit', new moodle_url('/grade/edit/letter/index.php', array('edit'=>1,'id'=>$context->id)), get_string('edit'))
2529             );
2530         } else {
2531             self::$letterinfo = false;
2532         }
2533         return self::$letterinfo;
2534     }
2535     /**
2536      * Get information import plugins
2537      * @param int $courseid
2538      * @return array
2539      */
2540     public static function get_plugins_import($courseid) {
2541         global $CFG;
2543         if (self::$importplugins !== null) {
2544             return self::$importplugins;
2545         }
2546         $importplugins = array();
2547         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2549         if (has_capability('moodle/grade:import', $context)) {
2550             foreach (get_plugin_list('gradeimport') as $plugin => $plugindir) {
2551                 if (!has_capability('gradeimport/'.$plugin.':view', $context)) {
2552                     continue;
2553                 }
2554                 $pluginstr = get_string('pluginname', 'gradeimport_'.$plugin);
2555                 $url = new moodle_url('/grade/import/'.$plugin.'/index.php', array('id'=>$courseid));
2556                 $importplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2557             }
2560             if ($CFG->gradepublishing) {
2561                 $url = new moodle_url('/grade/import/keymanager.php', array('id'=>$courseid));
2562                 $importplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
2563             }
2564         }
2566         if (count($importplugins) > 0) {
2567             asort($importplugins);
2568             self::$importplugins = $importplugins;
2569         } else {
2570             self::$importplugins = false;
2571         }
2572         return self::$importplugins;
2573     }
2574     /**
2575      * Get information export plugins
2576      * @param int $courseid
2577      * @return array
2578      */
2579     public static function get_plugins_export($courseid) {
2580         global $CFG;
2582         if (self::$exportplugins !== null) {
2583             return self::$exportplugins;
2584         }
2585         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2586         $exportplugins = array();
2587         if (has_capability('moodle/grade:export', $context)) {
2588             foreach (get_plugin_list('gradeexport') as $plugin => $plugindir) {
2589                 if (!has_capability('gradeexport/'.$plugin.':view', $context)) {
2590                     continue;
2591                 }
2592                 $pluginstr = get_string('pluginname', 'gradeexport_'.$plugin);
2593                 $url = new moodle_url('/grade/export/'.$plugin.'/index.php', array('id'=>$courseid));
2594                 $exportplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2595             }
2597             if ($CFG->gradepublishing) {
2598                 $url = new moodle_url('/grade/export/keymanager.php', array('id'=>$courseid));
2599                 $exportplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
2600             }
2601         }
2602         if (count($exportplugins) > 0) {
2603             asort($exportplugins);
2604             self::$exportplugins = $exportplugins;
2605         } else {
2606             self::$exportplugins = false;
2607         }
2608         return self::$exportplugins;
2609     }