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