bc2a810ec31188207add793c942b71971593c83b
[moodle.git] / grade / lib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Functions used by gradebook plugins and reports.
20  *
21  * @package   moodlecore
22  * @copyright 2009 Petr Skoda and Nicolas Connault
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 require_once $CFG->libdir.'/gradelib.php';
28 /**
29  * This class iterates over all users that are graded in a course.
30  * Returns detailed info about users and their grades.
31  *
32  * @author Petr Skoda <skodak@moodle.org>
33  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class graded_users_iterator {
36     public $course;
37     public $grade_items;
38     public $groupid;
39     public $users_rs;
40     public $grades_rs;
41     public $gradestack;
42     public $sortfield1;
43     public $sortorder1;
44     public $sortfield2;
45     public $sortorder2;
47     /**
48      * Constructor
49      *
50      * @param object $course A course object
51      * @param array  $grade_items array of grade items, if not specified only user info returned
52      * @param int    $groupid iterate only group users if present
53      * @param string $sortfield1 The first field of the users table by which the array of users will be sorted
54      * @param string $sortorder1 The order in which the first sorting field will be sorted (ASC or DESC)
55      * @param string $sortfield2 The second field of the users table by which the array of users will be sorted
56      * @param string $sortorder2 The order in which the second sorting field will be sorted (ASC or DESC)
57      */
58     public function graded_users_iterator($course, $grade_items=null, $groupid=0,
59                                           $sortfield1='lastname', $sortorder1='ASC',
60                                           $sortfield2='firstname', $sortorder2='ASC') {
61         $this->course      = $course;
62         $this->grade_items = $grade_items;
63         $this->groupid     = $groupid;
64         $this->sortfield1  = $sortfield1;
65         $this->sortorder1  = $sortorder1;
66         $this->sortfield2  = $sortfield2;
67         $this->sortorder2  = $sortorder2;
69         $this->gradestack  = array();
70     }
72     /**
73      * Initialise the iterator
74      * @return boolean success
75      */
76     public function init() {
77         global $CFG, $DB;
79         $this->close();
81         grade_regrade_final_grades($this->course->id);
82         $course_item = grade_item::fetch_course_item($this->course->id);
83         if ($course_item->needsupdate) {
84             // can not calculate all final grades - sorry
85             return false;
86         }
88         list($gradebookroles_sql, $params) =
89             $DB->get_in_or_equal(explode(',', $CFG->gradebookroles), SQL_PARAMS_NAMED, 'grbr0');
91         $relatedcontexts = get_related_contexts_string(get_context_instance(CONTEXT_COURSE, $this->course->id));
93         if ($this->groupid) {
94             $groupsql = "INNER JOIN {groups_members} gm ON gm.userid = u.id";
95             $groupwheresql = "AND gm.groupid = :groupid";
96             // $params contents: gradebookroles
97             $params['groupid'] = $this->groupid;
98         } else {
99             $groupsql = "";
100             $groupwheresql = "";
101         }
103         if (empty($this->sortfield1)) {
104             // we must do some sorting even if not specified
105             $ofields = ", u.id AS usrt";
106             $order   = "usrt ASC";
108         } else {
109             $ofields = ", u.$this->sortfield1 AS usrt1";
110             $order   = "usrt1 $this->sortorder1";
111             if (!empty($this->sortfield2)) {
112                 $ofields .= ", u.$this->sortfield2 AS usrt2";
113                 $order   .= ", usrt2 $this->sortorder2";
114             }
115             if ($this->sortfield1 != 'id' and $this->sortfield2 != 'id') {
116                 // user order MUST be the same in both queries,
117                 // must include the only unique user->id if not already present
118                 $ofields .= ", u.id AS usrt";
119                 $order   .= ", usrt ASC";
120             }
121         }
123         // $params contents: gradebookroles and groupid (for $groupwheresql)
124         $users_sql = "SELECT u.* $ofields
125                         FROM {user} u
126                              INNER JOIN {role_assignments} ra ON u.id = ra.userid
127                              $groupsql
128                        WHERE u.deleted=0
129                              AND ra.roleid $gradebookroles_sql
130                              AND ra.contextid $relatedcontexts
131                              $groupwheresql
132                     ORDER BY $order";
134         $this->users_rs = $DB->get_recordset_sql($users_sql, $params);
136         if (!empty($this->grade_items)) {
137             $itemids = array_keys($this->grade_items);
138             list($itemidsql, $grades_params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED, 'items0');
139             $params = array_merge($params, $grades_params);
141             // $params contents: gradebookroles, groupid (for $groupwheresql) and itemids
142             $grades_sql = "SELECT g.* $ofields
143                              FROM {grade_grades} g
144                                   INNER JOIN {user} u ON g.userid = u.id
145                                   INNER JOIN {role_assignments} ra ON u.id = ra.userid
146                                   $groupsql
147                             WHERE ra.roleid $gradebookroles_sql
148                                   AND ra.contextid $relatedcontexts
149                                   $groupwheresql
150                                   AND g.itemid $itemidsql
151                          ORDER BY $order, g.itemid ASC";
152             $this->grades_rs = $DB->get_recordset_sql($grades_sql, $params);
153         } else {
154             $this->grades_rs = false;
155         }
157         return true;
158     }
160     /**
161      * Returns information about the next user
162      * @return mixed array of user info, all grades and feedback or null when no more users found
163      */
164     function next_user() {
165         if (!$this->users_rs) {
166             return false; // no users present
167         }
169         if (!$this->users_rs->valid()) {
170             if ($current = $this->_pop()) {
171                 // this is not good - user or grades updated between the two reads above :-(
172             }
174             return false; // no more users
175         } else {
176             $user = $this->users_rs->current();
177             $this->users_rs->next();
178         }
180         // find grades of this user
181         $grade_records = array();
182         while (true) {
183             if (!$current = $this->_pop()) {
184                 break; // no more grades
185             }
187             if (empty($current->userid)) {
188                 break;
189             }
191             if ($current->userid != $user->id) {
192                 // grade of the next user, we have all for this user
193                 $this->_push($current);
194                 break;
195             }
197             $grade_records[$current->itemid] = $current;
198         }
200         $grades = array();
201         $feedbacks = array();
203         if (!empty($this->grade_items)) {
204             foreach ($this->grade_items as $grade_item) {
205                 if (array_key_exists($grade_item->id, $grade_records)) {
206                     $feedbacks[$grade_item->id]->feedback       = $grade_records[$grade_item->id]->feedback;
207                     $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat;
208                     unset($grade_records[$grade_item->id]->feedback);
209                     unset($grade_records[$grade_item->id]->feedbackformat);
210                     $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false);
211                 } else {
212                     $feedbacks[$grade_item->id]->feedback       = '';
213                     $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE;
214                     $grades[$grade_item->id] =
215                         new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false);
216                 }
217             }
218         }
220         $result = new stdClass();
221         $result->user      = $user;
222         $result->grades    = $grades;
223         $result->feedbacks = $feedbacks;
225         return $result;
226     }
228     /**
229      * Close the iterator, do not forget to call this function.
230      * @return void
231      */
232     function close() {
233         if ($this->users_rs) {
234             $this->users_rs->close();
235             $this->users_rs = null;
236         }
237         if ($this->grades_rs) {
238             $this->grades_rs->close();
239             $this->grades_rs = null;
240         }
241         $this->gradestack = array();
242     }
245     /**
246      * _push
247      *
248      * @param grade_grade $grade Grade object
249      *
250      * @return void
251      */
252     function _push($grade) {
253         array_push($this->gradestack, $grade);
254     }
257     /**
258      * _pop
259      *
260      * @return void
261      */
262     function _pop() {
263         global $DB;
264         if (empty($this->gradestack)) {
265             if (!$this->grades_rs) {
266                 return null; // no grades present
267             }
269             if ($this->grades_rs->next()) {
270                 return null; // no more grades
271             }
273             return $this->grades_rs->current();
274         } else {
275             return array_pop($this->gradestack);
276         }
277     }
280 /**
281  * Print a selection popup form of the graded users in a course.
282  *
283  * @deprecated since 2.0
284  *
285  * @param int    $course id of the course
286  * @param string $actionpage The page receiving the data from the popoup form
287  * @param int    $userid   id of the currently selected user (or 'all' if they are all selected)
288  * @param int    $groupid id of requested group, 0 means all
289  * @param int    $includeall bool include all option
290  * @param bool   $return If true, will return the HTML, otherwise, will print directly
291  * @return null
292  */
293 function print_graded_users_selector($course, $actionpage, $userid=0, $groupid=0, $includeall=true, $return=false) {
294     global $CFG, $USER, $OUTPUT;
295     return $OUTPUT->render(grade_get_graded_users_select(substr($actionpage, 0, strpos($actionpage, '/')), $course, $userid, $groupid, $includeall));
298 function grade_get_graded_users_select($report, $course, $userid, $groupid, $includeall) {
299     global $USER;
301     if (is_null($userid)) {
302         $userid = $USER->id;
303     }
305     $menu = array(); // Will be a list of userid => user name
306     $gui = new graded_users_iterator($course, null, $groupid);
307     $gui->init();
308     $label = get_string('selectauser', 'grades');
309     if ($includeall) {
310         $menu[0] = get_string('allusers', 'grades');
311         $label = get_string('selectalloroneuser', 'grades');
312     }
313     while ($userdata = $gui->next_user()) {
314         $user = $userdata->user;
315         $menu[$user->id] = fullname($user);
316     }
317     $gui->close();
319     if ($includeall) {
320         $menu[0] .= " (" . (count($menu) - 1) . ")";
321     }
322     $select = new single_select(new moodle_url('/grade/report/'.$report.'/index.php', array('id'=>$course->id)), 'userid', $menu, $userid);
323     $select->label = $label;
324     $select->formid = 'choosegradeuser';
325     return $select;
328 /**
329  * Print grading plugin selection popup form.
330  *
331  * @param array   $plugin_info An array of plugins containing information for the selector
332  * @param boolean $return return as string
333  *
334  * @return nothing or string if $return true
335  */
336 function print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return=false) {
337     global $CFG, $OUTPUT, $PAGE;
339     $menu = array();
340     $count = 0;
341     $active = '';
343     foreach ($plugin_info as $plugin_type => $plugins) {
344         if ($plugin_type == 'strings') {
345             continue;
346         }
348         $first_plugin = reset($plugins);
350         $sectionname = $plugin_info['strings'][$plugin_type];
351         $section = array();
353         foreach ($plugins as $plugin) {
354             $link = $plugin->link->out(false);
355             $section[$link] = $plugin->string;
356             $count++;
357             if ($plugin_type === $active_type and $plugin->id === $active_plugin) {
358                 $active = $link;
359             }
360         }
362         if ($section) {
363             $menu[] = array($sectionname=>$section);
364         }
365     }
367     // finally print/return the popup form
368     if ($count > 1) {
369         $select = new url_select($menu, $active, null, 'choosepluginreport');
371         if ($return) {
372             return $OUTPUT->render($select);
373         } else {
374             echo $OUTPUT->render($select);
375         }
376     } else {
377         // only one option - no plugin selector needed
378         return '';
379     }
382 /**
383  * Print grading plugin selection tab-based navigation.
384  *
385  * @param string  $active_type type of plugin on current page - import, export, report or edit
386  * @param string  $active_plugin active plugin type - grader, user, cvs, ...
387  * @param array   $plugin_info Array of plugins
388  * @param boolean $return return as string
389  *
390  * @return nothing or string if $return true
391  */
392 function grade_print_tabs($active_type, $active_plugin, $plugin_info, $return=false) {
393     global $CFG, $COURSE;
395     if (!isset($currenttab)) { //TODO: this is weird
396         $currenttab = '';
397     }
399     $tabs = array();
400     $top_row  = array();
401     $bottom_row = array();
402     $inactive = array($active_plugin);
403     $activated = array();
405     $count = 0;
406     $active = '';
408     foreach ($plugin_info as $plugin_type => $plugins) {
409         if ($plugin_type == 'strings') {
410             continue;
411         }
413         // If $plugins is actually the definition of a child-less parent link:
414         if (!empty($plugins->id)) {
415             $string = $plugins->string;
416             if (!empty($plugin_info[$active_type]->parent)) {
417                 $string = $plugin_info[$active_type]->parent->string;
418             }
420             $top_row[] = new tabobject($plugin_type, $plugins->link, $string);
421             continue;
422         }
424         $first_plugin = reset($plugins);
425         $url = $first_plugin->link;
427         if ($plugin_type == 'report') {
428             $url = $CFG->wwwroot.'/grade/report/index.php?id='.$COURSE->id;
429         }
431         $top_row[] = new tabobject($plugin_type, $url, $plugin_info['strings'][$plugin_type]);
433         if ($active_type == $plugin_type) {
434             foreach ($plugins as $plugin) {
435                 $bottom_row[] = new tabobject($plugin->id, $plugin->link, $plugin->string);
436                 if ($plugin->id == $active_plugin) {
437                     $inactive = array($plugin->id);
438                 }
439             }
440         }
441     }
443     $tabs[] = $top_row;
444     $tabs[] = $bottom_row;
446     if ($return) {
447         return print_tabs($tabs, $active_type, $inactive, $activated, true);
448     } else {
449         print_tabs($tabs, $active_type, $inactive, $activated);
450     }
453 /**
454  * grade_get_plugin_info
455  *
456  * @param int    $courseid The course id
457  * @param string $active_type type of plugin on current page - import, export, report or edit
458  * @param string $active_plugin active plugin type - grader, user, cvs, ...
459  *
460  * @return array
461  */
462 function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
463     global $CFG;
465     $context = get_context_instance(CONTEXT_COURSE, $courseid);
467     $plugin_info = array();
468     $count = 0;
469     $active = '';
470     $url_prefix = $CFG->wwwroot . '/grade/';
472     // Language strings
473     $plugin_info['strings'] = grade_helper::get_plugin_strings();
475     if ($reports = grade_helper::get_plugins_reports($courseid)) {
476         $plugin_info['report'] = $reports;
477     }
479     if ($edittree = grade_helper::get_info_edit_structure($courseid)) {
480         $plugin_info['edittree'] = $edittree;
481     }
483     if ($scale = grade_helper::get_info_scales($courseid)) {
484         $plugin_info['scale'] = array('view'=>$scale);
485     }
487     if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
488         $plugin_info['outcome'] = $outcomes;
489     }
491     if ($letters = grade_helper::get_info_letters($courseid)) {
492         $plugin_info['letter'] = $letters;
493     }
495     if ($imports = grade_helper::get_plugins_import($courseid)) {
496         $plugin_info['import'] = $imports;
497     }
499     if ($exports = grade_helper::get_plugins_export($courseid)) {
500         $plugin_info['export'] = $exports;
501     }
503     foreach ($plugin_info as $plugin_type => $plugins) {
504         if (!empty($plugins->id) && $active_plugin == $plugins->id) {
505             $plugin_info['strings']['active_plugin_str'] = $plugins->string;
506             break;
507         }
508         foreach ($plugins as $plugin) {
509             if (is_a($plugin, 'grade_plugin_info')) {
510                 if ($active_plugin == $plugin->id) {
511                     $plugin_info['strings']['active_plugin_str'] = $plugin->string;
512                 }
513             }
514         }
515     }
517     // Put settings last
518     if ($setting = grade_helper::get_info_manage_settings($courseid)) {
519         $plugin_info['settings'] = array('course'=>$setting);
520     }
522     // Put preferences last
523     if ($preferences = grade_helper::get_plugins_report_preferences($courseid)) {
524         $plugin_info['preferences'] = $preferences;
525     }
527     foreach ($plugin_info as $plugin_type => $plugins) {
528         if (!empty($plugins->id) && $active_plugin == $plugins->id) {
529             $plugin_info['strings']['active_plugin_str'] = $plugins->string;
530             break;
531         }
532         foreach ($plugins as $plugin) {
533             if (is_a($plugin, 'grade_plugin_info')) {
534                 if ($active_plugin == $plugin->id) {
535                     $plugin_info['strings']['active_plugin_str'] = $plugin->string;
536                 }
537             }
538         }
539     }
541     return $plugin_info;
544 /**
545  * A simple class containing info about grade plugins.
546  * Can be subclassed for special rules
547  *
548  * @package moodlecore
549  * @copyright 2009 Nicolas Connault
550  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
551  */
552 class grade_plugin_info {
553     /**
554      * A unique id for this plugin
555      *
556      * @var mixed
557      */
558     public $id;
559     /**
560      * A URL to access this plugin
561      *
562      * @var mixed
563      */
564     public $link;
565     /**
566      * The name of this plugin
567      *
568      * @var mixed
569      */
570     public $string;
571     /**
572      * Another grade_plugin_info object, parent of the current one
573      *
574      * @var mixed
575      */
576     public $parent;
578     /**
579      * Constructor
580      *
581      * @param int $id A unique id for this plugin
582      * @param string $link A URL to access this plugin
583      * @param string $string The name of this plugin
584      * @param object $parent Another grade_plugin_info object, parent of the current one
585      *
586      * @return void
587      */
588     public function __construct($id, $link, $string, $parent=null) {
589         $this->id = $id;
590         $this->link = $link;
591         $this->string = $string;
592         $this->parent = $parent;
593     }
596 /**
597  * Prints the page headers, breadcrumb trail, page heading, (optional) dropdown navigation menu and
598  * (optional) navigation tabs for any gradebook page. All gradebook pages MUST use these functions
599  * in favour of the usual print_header(), print_header_simple(), print_heading() etc.
600  * !IMPORTANT! Use of tabs.php file in gradebook pages is forbidden unless tabs are switched off at
601  * the site level for the gradebook ($CFG->grade_navmethod = GRADE_NAVMETHOD_DROPDOWN).
602  *
603  * @param int     $courseid Course id
604  * @param string  $active_type The type of the current page (report, settings,
605  *                             import, export, scales, outcomes, letters)
606  * @param string  $active_plugin The plugin of the current page (grader, fullview etc...)
607  * @param string  $heading The heading of the page. Tries to guess if none is given
608  * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function
609  * @param string  $bodytags Additional attributes that will be added to the <body> tag
610  * @param string  $buttons Additional buttons to display on the page
611  *
612  * @return string HTML code or nothing if $return == false
613  */
614 function print_grade_page_head($courseid, $active_type, $active_plugin=null,
615                                $heading = false, $return=false,
616                                $buttons=false, $shownavigation=true) {
617     global $CFG, $OUTPUT, $PAGE;
619     $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin);
621     // Determine the string of the active plugin
622     $stractive_plugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading;
623     $stractive_type = $plugin_info['strings'][$active_type];
625     if (empty($plugin_info[$active_type]->id) || !empty($plugin_info[$active_type]->parent)) {
626         $title = $PAGE->course->fullname.': ' . $stractive_type . ': ' . $stractive_plugin;
627     } else {
628         $title = $PAGE->course->fullname.': ' . $stractive_plugin;
629     }
631     if ($active_type == 'report') {
632         $PAGE->set_pagelayout('report');
633     } else {
634         $PAGE->set_pagelayout('admin');
635     }
636     $PAGE->set_title(get_string('grades') . ': ' . $stractive_type);
637     $PAGE->set_heading($title);
638     if ($buttons instanceof single_button) {
639         $buttons = $OUTPUT->render($buttons);
640     }
641     $PAGE->set_button($buttons);
642     grade_extend_settings($plugin_info, $courseid);
644     $returnval = $OUTPUT->header();
645     if (!$return) {
646         echo $returnval;
647     }
649     // Guess heading if not given explicitly
650     if (!$heading) {
651         $heading = $stractive_plugin;
652     }
654     if ($shownavigation) {
655         if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN) {
656             $returnval .= print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return);
657         }
658         $returnval .= $OUTPUT->heading($heading);
659         if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS) {
660             $returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
661         }
662     }
664     if ($return) {
665         return $returnval;
666     }
669 /**
670  * Utility class used for return tracking when using edit and other forms in grade plugins
671  *
672  * @package moodlecore
673  * @copyright 2009 Nicolas Connault
674  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
675  */
676 class grade_plugin_return {
677     public $type;
678     public $plugin;
679     public $courseid;
680     public $userid;
681     public $page;
683     /**
684      * Constructor
685      *
686      * @param array $params - associative array with return parameters, if null parameter are taken from _GET or _POST
687      */
688     public function grade_plugin_return($params = null) {
689         if (empty($params)) {
690             $this->type     = optional_param('gpr_type', null, PARAM_SAFEDIR);
691             $this->plugin   = optional_param('gpr_plugin', null, PARAM_SAFEDIR);
692             $this->courseid = optional_param('gpr_courseid', null, PARAM_INT);
693             $this->userid   = optional_param('gpr_userid', null, PARAM_INT);
694             $this->page     = optional_param('gpr_page', null, PARAM_INT);
696         } else {
697             foreach ($params as $key=>$value) {
698                 if (property_exists($this, $key)) {
699                     $this->$key = $value;
700                 }
701             }
702         }
703     }
705     /**
706      * Returns return parameters as options array suitable for buttons.
707      * @return array options
708      */
709     public function get_options() {
710         if (empty($this->type)) {
711             return array();
712         }
714         $params = array();
716         if (!empty($this->plugin)) {
717             $params['plugin'] = $this->plugin;
718         }
720         if (!empty($this->courseid)) {
721             $params['id'] = $this->courseid;
722         }
724         if (!empty($this->userid)) {
725             $params['userid'] = $this->userid;
726         }
728         if (!empty($this->page)) {
729             $params['page'] = $this->page;
730         }
732         return $params;
733     }
735     /**
736      * Returns return url
737      *
738      * @param string $default default url when params not set
739      * @param array  $extras Extra URL parameters
740      *
741      * @return string url
742      */
743     public function get_return_url($default, $extras=null) {
744         global $CFG;
746         if (empty($this->type) or empty($this->plugin)) {
747             return $default;
748         }
750         $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php';
751         $glue = '?';
753         if (!empty($this->courseid)) {
754             $url .= $glue.'id='.$this->courseid;
755             $glue = '&amp;';
756         }
758         if (!empty($this->userid)) {
759             $url .= $glue.'userid='.$this->userid;
760             $glue = '&amp;';
761         }
763         if (!empty($this->page)) {
764             $url .= $glue.'page='.$this->page;
765             $glue = '&amp;';
766         }
768         if (!empty($extras)) {
769             foreach ($extras as $key=>$value) {
770                 $url .= $glue.$key.'='.$value;
771                 $glue = '&amp;';
772             }
773         }
775         return $url;
776     }
778     /**
779      * Returns string with hidden return tracking form elements.
780      * @return string
781      */
782     public function get_form_fields() {
783         if (empty($this->type)) {
784             return '';
785         }
787         $result  = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />';
789         if (!empty($this->plugin)) {
790             $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />';
791         }
793         if (!empty($this->courseid)) {
794             $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />';
795         }
797         if (!empty($this->userid)) {
798             $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />';
799         }
801         if (!empty($this->page)) {
802             $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />';
803         }
804     }
806     /**
807      * Add hidden elements into mform
808      *
809      * @param object &$mform moodle form object
810      *
811      * @return void
812      */
813     public function add_mform_elements(&$mform) {
814         if (empty($this->type)) {
815             return;
816         }
818         $mform->addElement('hidden', 'gpr_type', $this->type);
819         $mform->setType('gpr_type', PARAM_SAFEDIR);
821         if (!empty($this->plugin)) {
822             $mform->addElement('hidden', 'gpr_plugin', $this->plugin);
823             $mform->setType('gpr_plugin', PARAM_SAFEDIR);
824         }
826         if (!empty($this->courseid)) {
827             $mform->addElement('hidden', 'gpr_courseid', $this->courseid);
828             $mform->setType('gpr_courseid', PARAM_INT);
829         }
831         if (!empty($this->userid)) {
832             $mform->addElement('hidden', 'gpr_userid', $this->userid);
833             $mform->setType('gpr_userid', PARAM_INT);
834         }
836         if (!empty($this->page)) {
837             $mform->addElement('hidden', 'gpr_page', $this->page);
838             $mform->setType('gpr_page', PARAM_INT);
839         }
840     }
842     /**
843      * Add return tracking params into url
844      *
845      * @param moodle_url $url A URL
846      *
847      * @return string $url with return tracking params
848      */
849     public function add_url_params(moodle_url $url) {
850         if (empty($this->type)) {
851             return $url;
852         }
854         $url->param('gpr_type', $this->type);
856         if (!empty($this->plugin)) {
857             $url->param('gpr_plugin', $this->plugin);
858         }
860         if (!empty($this->courseid)) {
861             $url->param('gpr_courseid' ,$this->courseid);
862         }
864         if (!empty($this->userid)) {
865             $url->param('gpr_userid', $this->userid);
866         }
868         if (!empty($this->page)) {
869             $url->param('gpr_page', $this->page);
870         }
872         return $url;
873     }
876 /**
877  * Function central to gradebook for building and printing the navigation (breadcrumb trail).
878  *
879  * @param string $path The path of the calling script (using __FILE__?)
880  * @param string $pagename The language string to use as the last part of the navigation (non-link)
881  * @param mixed  $id Either a plain integer (assuming the key is 'id') or
882  *                   an array of keys and values (e.g courseid => $courseid, itemid...)
883  *
884  * @return string
885  */
886 function grade_build_nav($path, $pagename=null, $id=null) {
887     global $CFG, $COURSE, $PAGE;
889     $strgrades = get_string('grades', 'grades');
891     // Parse the path and build navlinks from its elements
892     $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash
893     $path = substr($path, $dirroot_length);
894     $path = str_replace('\\', '/', $path);
896     $path_elements = explode('/', $path);
898     $path_elements_count = count($path_elements);
900     // First link is always 'grade'
901     $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id)));
903     $link = null;
904     $numberofelements = 3;
906     // Prepare URL params string
907     $linkparams = array();
908     if (!is_null($id)) {
909         if (is_array($id)) {
910             foreach ($id as $idkey => $idvalue) {
911                 $linkparams[$idkey] = $idvalue;
912             }
913         } else {
914             $linkparams['id'] = $id;
915         }
916     }
918     $navlink4 = null;
920     // Remove file extensions from filenames
921     foreach ($path_elements as $key => $filename) {
922         $path_elements[$key] = str_replace('.php', '', $filename);
923     }
925     // Second level links
926     switch ($path_elements[1]) {
927         case 'edit': // No link
928             if ($path_elements[3] != 'index.php') {
929                 $numberofelements = 4;
930             }
931             break;
932         case 'import': // No link
933             break;
934         case 'export': // No link
935             break;
936         case 'report':
937             // $id is required for this link. Do not print it if $id isn't given
938             if (!is_null($id)) {
939                 $link = new moodle_url('/grade/report/index.php', $linkparams);
940             }
942             if ($path_elements[2] == 'grader') {
943                 $numberofelements = 4;
944             }
945             break;
947         default:
948             // If this element isn't among the ones already listed above, it isn't supported, throw an error.
949             debugging("grade_build_nav() doesn't support ". $path_elements[1] .
950                     " as the second path element after 'grade'.");
951             return false;
952     }
953     $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link);
955     // Third level links
956     if (empty($pagename)) {
957         $pagename = get_string($path_elements[2], 'grades');
958     }
960     switch ($numberofelements) {
961         case 3:
962             $PAGE->navbar->add($pagename, $link);
963             break;
964         case 4:
965             if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') {
966                 $PAGE->navbar->add(get_string('pluginname', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams));
967             }
968             $PAGE->navbar->add($pagename);
969             break;
970     }
972     return '';
975 /**
976  * General structure representing grade items in course
977  *
978  * @package moodlecore
979  * @copyright 2009 Nicolas Connault
980  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
981  */
982 class grade_structure {
983     public $context;
985     public $courseid;
987     /**
988      * 1D array of grade items only
989      */
990     public $items;
992     /**
993      * Returns icon of element
994      *
995      * @param array &$element An array representing an element in the grade_tree
996      * @param bool  $spacerifnone return spacer if no icon found
997      *
998      * @return string icon or spacer
999      */
1000     public function get_element_icon(&$element, $spacerifnone=false) {
1001         global $CFG, $OUTPUT;
1003         switch ($element['type']) {
1004             case 'item':
1005             case 'courseitem':
1006             case 'categoryitem':
1007                 $is_course   = $element['object']->is_course_item();
1008                 $is_category = $element['object']->is_category_item();
1009                 $is_scale    = $element['object']->gradetype == GRADE_TYPE_SCALE;
1010                 $is_value    = $element['object']->gradetype == GRADE_TYPE_VALUE;
1012                 if ($element['object']->is_calculated()) {
1013                     $strcalc = get_string('calculatedgrade', 'grades');
1014                     return '<img src="'.$OUTPUT->pix_url('i/calc') . '" class="icon itemicon" title="'.
1015                             s($strcalc).'" alt="'.s($strcalc).'"/>';
1017                 } else if (($is_course or $is_category) and ($is_scale or $is_value)) {
1018                     if ($category = $element['object']->get_item_category()) {
1019                         switch ($category->aggregation) {
1020                             case GRADE_AGGREGATE_MEAN:
1021                             case GRADE_AGGREGATE_MEDIAN:
1022                             case GRADE_AGGREGATE_WEIGHTED_MEAN:
1023                             case GRADE_AGGREGATE_WEIGHTED_MEAN2:
1024                             case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
1025                                 $stragg = get_string('aggregation', 'grades');
1026                                 return '<img src="'.$OUTPUT->pix_url('i/agg_mean') . '" ' .
1027                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1028                             case GRADE_AGGREGATE_SUM:
1029                                 $stragg = get_string('aggregation', 'grades');
1030                                 return '<img src="'.$OUTPUT->pix_url('i/agg_sum') . '" ' .
1031                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1032                         }
1033                     }
1035                 } else if ($element['object']->itemtype == 'mod') {
1036                     $strmodname = get_string('modulename', $element['object']->itemmodule);
1037                     return '<img src="'.$OUTPUT->pix_url('icon',
1038                             $element['object']->itemmodule) . '" ' .
1039                             'class="icon itemicon" title="' .s($strmodname).
1040                             '" alt="' .s($strmodname).'"/>';
1042                 } else if ($element['object']->itemtype == 'manual') {
1043                     if ($element['object']->is_outcome_item()) {
1044                         $stroutcome = get_string('outcome', 'grades');
1045                         return '<img src="'.$OUTPUT->pix_url('i/outcomes') . '" ' .
1046                                 'class="icon itemicon" title="'.s($stroutcome).
1047                                 '" alt="'.s($stroutcome).'"/>';
1048                     } else {
1049                         $strmanual = get_string('manualitem', 'grades');
1050                         return '<img src="'.$OUTPUT->pix_url('t/manual_item') . '" '.
1051                                 'class="icon itemicon" title="'.s($strmanual).
1052                                 '" alt="'.s($strmanual).'"/>';
1053                     }
1054                 }
1055                 break;
1057             case 'category':
1058                 $strcat = get_string('category', 'grades');
1059                 return '<img src="'.$OUTPUT->pix_url('f/folder') . '" class="icon itemicon" ' .
1060                         'title="'.s($strcat).'" alt="'.s($strcat).'" />';
1061         }
1063         if ($spacerifnone) {
1064             return $OUTPUT->spacer().' ';
1065         } else {
1066             return '';
1067         }
1068     }
1070     /**
1071      * Returns name of element optionally with icon and link
1072      *
1073      * @param array &$element An array representing an element in the grade_tree
1074      * @param bool  $withlink Whether or not this header has a link
1075      * @param bool  $icon Whether or not to display an icon with this header
1076      * @param bool  $spacerifnone return spacer if no icon found
1077      *
1078      * @return string header
1079      */
1080     public function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false) {
1081         global $CFG;
1083         $header = '';
1085         if ($icon) {
1086             $header .= $this->get_element_icon($element, $spacerifnone);
1087         }
1089         $header .= $element['object']->get_name();
1091         if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and
1092             $element['type'] != 'courseitem') {
1093             return $header;
1094         }
1096         $itemtype     = $element['object']->itemtype;
1097         $itemmodule   = $element['object']->itemmodule;
1098         $iteminstance = $element['object']->iteminstance;
1100         if ($withlink and $itemtype=='mod' and $iteminstance and $itemmodule) {
1101             if ($cm = get_coursemodule_from_instance($itemmodule, $iteminstance, $this->courseid)) {
1103                 $a = new stdClass();
1104                 $a->name = get_string('modulename', $element['object']->itemmodule);
1105                 $title = get_string('linktoactivity', 'grades', $a);
1106                 $dir = $CFG->dirroot.'/mod/'.$itemmodule;
1108                 if (file_exists($dir.'/grade.php')) {
1109                     $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/grade.php?id='.$cm->id;
1110                 } else {
1111                     $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/view.php?id='.$cm->id;
1112                 }
1114                 $header = '<a href="'.$url.'" title="'.s($title).'">'.$header.'</a>';
1115             }
1116         }
1118         return $header;
1119     }
1121     /**
1122      * Returns the grade eid - the grade may not exist yet.
1123      *
1124      * @param grade_grade $grade_grade A grade_grade object
1125      *
1126      * @return string eid
1127      */
1128     public function get_grade_eid($grade_grade) {
1129         if (empty($grade_grade->id)) {
1130             return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid;
1131         } else {
1132             return 'g'.$grade_grade->id;
1133         }
1134     }
1136     /**
1137      * Returns the grade_item eid
1138      * @param grade_item $grade_item A grade_item object
1139      * @return string eid
1140      */
1141     public function get_item_eid($grade_item) {
1142         return 'i'.$grade_item->id;
1143     }
1145     /**
1146      * Given a grade_tree element, returns an array of parameters
1147      * used to build an icon for that element.
1148      *
1149      * @param array $element An array representing an element in the grade_tree
1150      *
1151      * @return array
1152      */
1153     public function get_params_for_iconstr($element) {
1154         $strparams = new stdClass();
1155         $strparams->category = '';
1156         $strparams->itemname = '';
1157         $strparams->itemmodule = '';
1159         if (!method_exists($element['object'], 'get_name')) {
1160             return $strparams;
1161         }
1163         $strparams->itemname = html_to_text($element['object']->get_name());
1165         // If element name is categorytotal, get the name of the parent category
1166         if ($strparams->itemname == get_string('categorytotal', 'grades')) {
1167             $parent = $element['object']->get_parent_category();
1168             $strparams->category = $parent->get_name() . ' ';
1169         } else {
1170             $strparams->category = '';
1171         }
1173         $strparams->itemmodule = null;
1174         if (isset($element['object']->itemmodule)) {
1175             $strparams->itemmodule = $element['object']->itemmodule;
1176         }
1177         return $strparams;
1178     }
1180     /**
1181      * Return edit icon for give element
1182      *
1183      * @param array  $element An array representing an element in the grade_tree
1184      * @param object $gpr A grade_plugin_return object
1185      *
1186      * @return string
1187      */
1188     public function get_edit_icon($element, $gpr) {
1189         global $CFG, $OUTPUT;
1191         if (!has_capability('moodle/grade:manage', $this->context)) {
1192             if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
1193                 // oki - let them override grade
1194             } else {
1195                 return '';
1196             }
1197         }
1199         static $strfeedback   = null;
1200         static $streditgrade = null;
1201         if (is_null($streditgrade)) {
1202             $streditgrade = get_string('editgrade', 'grades');
1203             $strfeedback  = get_string('feedback');
1204         }
1206         $strparams = $this->get_params_for_iconstr($element);
1208         $object = $element['object'];
1210         switch ($element['type']) {
1211             case 'item':
1212             case 'categoryitem':
1213             case 'courseitem':
1214                 $stredit = get_string('editverbose', 'grades', $strparams);
1215                 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) {
1216                     $url = new moodle_url('/grade/edit/tree/item.php',
1217                             array('courseid' => $this->courseid, 'id' => $object->id));
1218                 } else {
1219                     $url = new moodle_url('/grade/edit/tree/outcomeitem.php',
1220                             array('courseid' => $this->courseid, 'id' => $object->id));
1221                 }
1222                 break;
1224             case 'category':
1225                 $stredit = get_string('editverbose', 'grades', $strparams);
1226                 $url = new moodle_url('/grade/edit/tree/category.php',
1227                         array('courseid' => $this->courseid, 'id' => $object->id));
1228                 break;
1230             case 'grade':
1231                 $stredit = $streditgrade;
1232                 if (empty($object->id)) {
1233                     $url = new moodle_url('/grade/edit/tree/grade.php',
1234                             array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid));
1235                 } else {
1236                     $url = new moodle_url('/grade/edit/tree/grade.php',
1237                             array('courseid' => $this->courseid, 'id' => $object->id));
1238                 }
1239                 if (!empty($object->feedback)) {
1240                     $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat)));
1241                 }
1242                 break;
1244             default:
1245                 $url = null;
1246         }
1248         if ($url) {
1249             return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit));
1251         } else {
1252             return '';
1253         }
1254     }
1256     /**
1257      * Return hiding icon for give element
1258      *
1259      * @param array  $element An array representing an element in the grade_tree
1260      * @param object $gpr A grade_plugin_return object
1261      *
1262      * @return string
1263      */
1264     public function get_hiding_icon($element, $gpr) {
1265         global $CFG, $OUTPUT;
1267         if (!has_capability('moodle/grade:manage', $this->context) and
1268             !has_capability('moodle/grade:hide', $this->context)) {
1269             return '';
1270         }
1272         $strparams = $this->get_params_for_iconstr($element);
1273         $strshow = get_string('showverbose', 'grades', $strparams);
1274         $strhide = get_string('hideverbose', 'grades', $strparams);
1276         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1277         $url = $gpr->add_url_params($url);
1279         if ($element['object']->is_hidden()) {
1280             $type = 'show';
1281             $tooltip = $strshow;
1283             // Change the icon and add a tooltip showing the date
1284             if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) {
1285                 $type = 'hiddenuntil';
1286                 $tooltip = get_string('hiddenuntildate', 'grades',
1287                         userdate($element['object']->get_hidden()));
1288             }
1290             $url->param('action', 'show');
1292             $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'iconsmall')));
1294         } else {
1295             $url->param('action', 'hide');
1296             $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide));
1297         }
1299         return $hideicon;
1300     }
1302     /**
1303      * Return locking icon for given element
1304      *
1305      * @param array  $element An array representing an element in the grade_tree
1306      * @param object $gpr A grade_plugin_return object
1307      *
1308      * @return string
1309      */
1310     public function get_locking_icon($element, $gpr) {
1311         global $CFG, $OUTPUT;
1313         $strparams = $this->get_params_for_iconstr($element);
1314         $strunlock = get_string('unlockverbose', 'grades', $strparams);
1315         $strlock = get_string('lockverbose', 'grades', $strparams);
1317         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1318         $url = $gpr->add_url_params($url);
1320         // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon
1321         if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) {
1322             $strparamobj = new stdClass();
1323             $strparamobj->itemname = $element['object']->grade_item->itemname;
1324             $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj);
1326             $action = $OUTPUT->pix_icon('t/unlock_gray', $strnonunlockable);
1328         } else if ($element['object']->is_locked()) {
1329             $type = 'unlock';
1330             $tooltip = $strunlock;
1332             // Change the icon and add a tooltip showing the date
1333             if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) {
1334                 $type = 'locktime';
1335                 $tooltip = get_string('locktimedate', 'grades',
1336                         userdate($element['object']->get_locktime()));
1337             }
1339             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) {
1340                 $action = '';
1341             } else {
1342                 $url->param('action', 'unlock');
1343                 $action = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strunlock, 'class'=>'smallicon')));
1344             }
1346         } else {
1347             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) {
1348                 $action = '';
1349             } else {
1350                 $url->param('action', 'lock');
1351                 $action = $OUTPUT->action_icon($url, new pix_icon('t/lock', $strlock));
1352             }
1353         }
1355         return $action;
1356     }
1358     /**
1359      * Return calculation icon for given element
1360      *
1361      * @param array  $element An array representing an element in the grade_tree
1362      * @param object $gpr A grade_plugin_return object
1363      *
1364      * @return string
1365      */
1366     public function get_calculation_icon($element, $gpr) {
1367         global $CFG, $OUTPUT;
1368         if (!has_capability('moodle/grade:manage', $this->context)) {
1369             return '';
1370         }
1372         $type   = $element['type'];
1373         $object = $element['object'];
1375         if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
1376             $strparams = $this->get_params_for_iconstr($element);
1377             $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams);
1379             $is_scale = $object->gradetype == GRADE_TYPE_SCALE;
1380             $is_value = $object->gradetype == GRADE_TYPE_VALUE;
1382             // show calculation icon only when calculation possible
1383             if (!$object->is_external_item() and ($is_scale or $is_value)) {
1384                 if ($object->is_calculated()) {
1385                     $icon = 't/calc';
1386                 } else {
1387                     $icon = 't/calc_off';
1388                 }
1390                 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id));
1391                 $url = $gpr->add_url_params($url);
1392                 return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation)) . "\n";
1393             }
1394         }
1396         return '';
1397     }
1400 /**
1401  * Flat structure similar to grade tree.
1402  *
1403  * @uses grade_structure
1404  * @package moodlecore
1405  * @copyright 2009 Nicolas Connault
1406  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1407  */
1408 class grade_seq extends grade_structure {
1410     /**
1411      * 1D array of elements
1412      */
1413     public $elements;
1415     /**
1416      * Constructor, retrieves and stores array of all grade_category and grade_item
1417      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1418      *
1419      * @param int  $courseid The course id
1420      * @param bool $category_grade_last category grade item is the last child
1421      * @param bool $nooutcomes Whether or not outcomes should be included
1422      */
1423     public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) {
1424         global $USER, $CFG;
1426         $this->courseid   = $courseid;
1427         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1429         // get course grade tree
1430         $top_element = grade_category::fetch_course_tree($courseid, true);
1432         $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes);
1434         foreach ($this->elements as $key=>$unused) {
1435             $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object'];
1436         }
1437     }
1439     /**
1440      * Static recursive helper - makes the grade_item for category the last children
1441      *
1442      * @param array &$element The seed of the recursion
1443      * @param bool $category_grade_last category grade item is the last child
1444      * @param bool $nooutcomes Whether or not outcomes should be included
1445      *
1446      * @return array
1447      */
1448     public function flatten(&$element, $category_grade_last, $nooutcomes) {
1449         if (empty($element['children'])) {
1450             return array();
1451         }
1452         $children = array();
1454         foreach ($element['children'] as $sortorder=>$unused) {
1455             if ($nooutcomes and $element['type'] != 'category' and
1456                 $element['children'][$sortorder]['object']->is_outcome_item()) {
1457                 continue;
1458             }
1459             $children[] = $element['children'][$sortorder];
1460         }
1461         unset($element['children']);
1463         if ($category_grade_last and count($children) > 1) {
1464             $cat_item = array_shift($children);
1465             array_push($children, $cat_item);
1466         }
1468         $result = array();
1469         foreach ($children as $child) {
1470             if ($child['type'] == 'category') {
1471                 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes);
1472             } else {
1473                 $child['eid'] = 'i'.$child['object']->id;
1474                 $result[$child['object']->id] = $child;
1475             }
1476         }
1478         return $result;
1479     }
1481     /**
1482      * Parses the array in search of a given eid and returns a element object with
1483      * information about the element it has found.
1484      *
1485      * @param int $eid Gradetree Element ID
1486      *
1487      * @return object element
1488      */
1489     public function locate_element($eid) {
1490         // it is a grade - construct a new object
1491         if (strpos($eid, 'n') === 0) {
1492             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1493                 return null;
1494             }
1496             $itemid = $matches[1];
1497             $userid = $matches[2];
1499             //extra security check - the grade item must be in this tree
1500             if (!$item_el = $this->locate_element('i'.$itemid)) {
1501                 return null;
1502             }
1504             // $gradea->id may be null - means does not exist yet
1505             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1507             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1508             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1510         } else if (strpos($eid, 'g') === 0) {
1511             $id = (int) substr($eid, 1);
1512             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1513                 return null;
1514             }
1515             //extra security check - the grade item must be in this tree
1516             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1517                 return null;
1518             }
1519             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1520             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1521         }
1523         // it is a category or item
1524         foreach ($this->elements as $element) {
1525             if ($element['eid'] == $eid) {
1526                 return $element;
1527             }
1528         }
1530         return null;
1531     }
1534 /**
1535  * This class represents a complete tree of categories, grade_items and final grades,
1536  * organises as an array primarily, but which can also be converted to other formats.
1537  * It has simple method calls with complex implementations, allowing for easy insertion,
1538  * deletion and moving of items and categories within the tree.
1539  *
1540  * @uses grade_structure
1541  * @package moodlecore
1542  * @copyright 2009 Nicolas Connault
1543  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1544  */
1545 class grade_tree extends grade_structure {
1547     /**
1548      * The basic representation of the tree as a hierarchical, 3-tiered array.
1549      * @var object $top_element
1550      */
1551     public $top_element;
1553     /**
1554      * 2D array of grade items and categories
1555      * @var array $levels
1556      */
1557     public $levels;
1559     /**
1560      * Grade items
1561      * @var array $items
1562      */
1563     public $items;
1565     /**
1566      * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item
1567      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1568      *
1569      * @param int   $courseid The Course ID
1570      * @param bool  $fillers include fillers and colspans, make the levels var "rectangular"
1571      * @param bool  $category_grade_last category grade item is the last child
1572      * @param array $collapsed array of collapsed categories
1573      * @param bool  $nooutcomes Whether or not outcomes should be included
1574      */
1575     public function grade_tree($courseid, $fillers=true, $category_grade_last=false,
1576                                $collapsed=null, $nooutcomes=false) {
1577         global $USER, $CFG;
1579         $this->courseid   = $courseid;
1580         $this->levels     = array();
1581         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1583         // get course grade tree
1584         $this->top_element = grade_category::fetch_course_tree($courseid, true);
1586         // collapse the categories if requested
1587         if (!empty($collapsed)) {
1588             grade_tree::category_collapse($this->top_element, $collapsed);
1589         }
1591         // no otucomes if requested
1592         if (!empty($nooutcomes)) {
1593             grade_tree::no_outcomes($this->top_element);
1594         }
1596         // move category item to last position in category
1597         if ($category_grade_last) {
1598             grade_tree::category_grade_last($this->top_element);
1599         }
1601         if ($fillers) {
1602             // inject fake categories == fillers
1603             grade_tree::inject_fillers($this->top_element, 0);
1604             // add colspans to categories and fillers
1605             grade_tree::inject_colspans($this->top_element);
1606         }
1608         grade_tree::fill_levels($this->levels, $this->top_element, 0);
1610     }
1612     /**
1613      * Static recursive helper - removes items from collapsed categories
1614      *
1615      * @param array &$element The seed of the recursion
1616      * @param array $collapsed array of collapsed categories
1617      *
1618      * @return void
1619      */
1620     public function category_collapse(&$element, $collapsed) {
1621         if ($element['type'] != 'category') {
1622             return;
1623         }
1624         if (empty($element['children']) or count($element['children']) < 2) {
1625             return;
1626         }
1628         if (in_array($element['object']->id, $collapsed['aggregatesonly'])) {
1629             $category_item = reset($element['children']); //keep only category item
1630             $element['children'] = array(key($element['children'])=>$category_item);
1632         } else {
1633             if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item
1634                 reset($element['children']);
1635                 $first_key = key($element['children']);
1636                 unset($element['children'][$first_key]);
1637             }
1638             foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children
1639                 grade_tree::category_collapse($element['children'][$sortorder], $collapsed);
1640             }
1641         }
1642     }
1644     /**
1645      * Static recursive helper - removes all outcomes
1646      *
1647      * @param array &$element The seed of the recursion
1648      *
1649      * @return void
1650      */
1651     public function no_outcomes(&$element) {
1652         if ($element['type'] != 'category') {
1653             return;
1654         }
1655         foreach ($element['children'] as $sortorder=>$child) {
1656             if ($element['children'][$sortorder]['type'] == 'item'
1657               and $element['children'][$sortorder]['object']->is_outcome_item()) {
1658                 unset($element['children'][$sortorder]);
1660             } else if ($element['children'][$sortorder]['type'] == 'category') {
1661                 grade_tree::no_outcomes($element['children'][$sortorder]);
1662             }
1663         }
1664     }
1666     /**
1667      * Static recursive helper - makes the grade_item for category the last children
1668      *
1669      * @param array &$element The seed of the recursion
1670      *
1671      * @return void
1672      */
1673     public function category_grade_last(&$element) {
1674         if (empty($element['children'])) {
1675             return;
1676         }
1677         if (count($element['children']) < 2) {
1678             return;
1679         }
1680         $first_item = reset($element['children']);
1681         if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') {
1682             // the category item might have been already removed
1683             $order = key($element['children']);
1684             unset($element['children'][$order]);
1685             $element['children'][$order] =& $first_item;
1686         }
1687         foreach ($element['children'] as $sortorder => $child) {
1688             grade_tree::category_grade_last($element['children'][$sortorder]);
1689         }
1690     }
1692     /**
1693      * Static recursive helper - fills the levels array, useful when accessing tree elements of one level
1694      *
1695      * @param array &$levels The levels of the grade tree through which to recurse
1696      * @param array &$element The seed of the recursion
1697      * @param int   $depth How deep are we?
1698      * @return void
1699      */
1700     public function fill_levels(&$levels, &$element, $depth) {
1701         if (!array_key_exists($depth, $levels)) {
1702             $levels[$depth] = array();
1703         }
1705         // prepare unique identifier
1706         if ($element['type'] == 'category') {
1707             $element['eid'] = 'c'.$element['object']->id;
1708         } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
1709             $element['eid'] = 'i'.$element['object']->id;
1710             $this->items[$element['object']->id] =& $element['object'];
1711         }
1713         $levels[$depth][] =& $element;
1714         $depth++;
1715         if (empty($element['children'])) {
1716             return;
1717         }
1718         $prev = 0;
1719         foreach ($element['children'] as $sortorder=>$child) {
1720             grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth);
1721             $element['children'][$sortorder]['prev'] = $prev;
1722             $element['children'][$sortorder]['next'] = 0;
1723             if ($prev) {
1724                 $element['children'][$prev]['next'] = $sortorder;
1725             }
1726             $prev = $sortorder;
1727         }
1728     }
1730     /**
1731      * Static recursive helper - makes full tree (all leafes are at the same level)
1732      *
1733      * @param array &$element The seed of the recursion
1734      * @param int   $depth How deep are we?
1735      *
1736      * @return int
1737      */
1738     public function inject_fillers(&$element, $depth) {
1739         $depth++;
1741         if (empty($element['children'])) {
1742             return $depth;
1743         }
1744         $chdepths = array();
1745         $chids = array_keys($element['children']);
1746         $last_child  = end($chids);
1747         $first_child = reset($chids);
1749         foreach ($chids as $chid) {
1750             $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth);
1751         }
1752         arsort($chdepths);
1754         $maxdepth = reset($chdepths);
1755         foreach ($chdepths as $chid=>$chd) {
1756             if ($chd == $maxdepth) {
1757                 continue;
1758             }
1759             for ($i=0; $i < $maxdepth-$chd; $i++) {
1760                 if ($chid == $first_child) {
1761                     $type = 'fillerfirst';
1762                 } else if ($chid == $last_child) {
1763                     $type = 'fillerlast';
1764                 } else {
1765                     $type = 'filler';
1766                 }
1767                 $oldchild =& $element['children'][$chid];
1768                 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type,
1769                                                     'eid'=>'', 'depth'=>$element['object']->depth,
1770                                                     'children'=>array($oldchild));
1771             }
1772         }
1774         return $maxdepth;
1775     }
1777     /**
1778      * Static recursive helper - add colspan information into categories
1779      *
1780      * @param array &$element The seed of the recursion
1781      *
1782      * @return int
1783      */
1784     public function inject_colspans(&$element) {
1785         if (empty($element['children'])) {
1786             return 1;
1787         }
1788         $count = 0;
1789         foreach ($element['children'] as $key=>$child) {
1790             $count += grade_tree::inject_colspans($element['children'][$key]);
1791         }
1792         $element['colspan'] = $count;
1793         return $count;
1794     }
1796     /**
1797      * Parses the array in search of a given eid and returns a element object with
1798      * information about the element it has found.
1799      * @param int $eid Gradetree Element ID
1800      * @return object element
1801      */
1802     public function locate_element($eid) {
1803         // it is a grade - construct a new object
1804         if (strpos($eid, 'n') === 0) {
1805             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1806                 return null;
1807             }
1809             $itemid = $matches[1];
1810             $userid = $matches[2];
1812             //extra security check - the grade item must be in this tree
1813             if (!$item_el = $this->locate_element('i'.$itemid)) {
1814                 return null;
1815             }
1817             // $gradea->id may be null - means does not exist yet
1818             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1820             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1821             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1823         } else if (strpos($eid, 'g') === 0) {
1824             $id = (int) substr($eid, 1);
1825             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1826                 return null;
1827             }
1828             //extra security check - the grade item must be in this tree
1829             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1830                 return null;
1831             }
1832             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1833             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1834         }
1836         // it is a category or item
1837         foreach ($this->levels as $row) {
1838             foreach ($row as $element) {
1839                 if ($element['type'] == 'filler') {
1840                     continue;
1841                 }
1842                 if ($element['eid'] == $eid) {
1843                     return $element;
1844                 }
1845             }
1846         }
1848         return null;
1849     }
1851     /**
1852      * Returns a well-formed XML representation of the grade-tree using recursion.
1853      *
1854      * @param array  $root The current element in the recursion. If null, starts at the top of the tree.
1855      * @param string $tabs The control character to use for tabs
1856      *
1857      * @return string $xml
1858      */
1859     public function exporttoxml($root=null, $tabs="\t") {
1860         $xml = null;
1861         $first = false;
1862         if (is_null($root)) {
1863             $root = $this->top_element;
1864             $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
1865             $xml .= "<gradetree>\n";
1866             $first = true;
1867         }
1869         $type = 'undefined';
1870         if (strpos($root['object']->table, 'grade_categories') !== false) {
1871             $type = 'category';
1872         } else if (strpos($root['object']->table, 'grade_items') !== false) {
1873             $type = 'item';
1874         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
1875             $type = 'outcome';
1876         }
1878         $xml .= "$tabs<element type=\"$type\">\n";
1879         foreach ($root['object'] as $var => $value) {
1880             if (!is_object($value) && !is_array($value) && !empty($value)) {
1881                 $xml .= "$tabs\t<$var>$value</$var>\n";
1882             }
1883         }
1885         if (!empty($root['children'])) {
1886             $xml .= "$tabs\t<children>\n";
1887             foreach ($root['children'] as $sortorder => $child) {
1888                 $xml .= $this->exportToXML($child, $tabs."\t\t");
1889             }
1890             $xml .= "$tabs\t</children>\n";
1891         }
1893         $xml .= "$tabs</element>\n";
1895         if ($first) {
1896             $xml .= "</gradetree>";
1897         }
1899         return $xml;
1900     }
1902     /**
1903      * Returns a JSON representation of the grade-tree using recursion.
1904      *
1905      * @param array $root The current element in the recursion. If null, starts at the top of the tree.
1906      * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy
1907      *
1908      * @return string
1909      */
1910     public function exporttojson($root=null, $tabs="\t") {
1911         $json = null;
1912         $first = false;
1913         if (is_null($root)) {
1914             $root = $this->top_element;
1915             $first = true;
1916         }
1918         $name = '';
1921         if (strpos($root['object']->table, 'grade_categories') !== false) {
1922             $name = $root['object']->fullname;
1923             if ($name == '?') {
1924                 $name = $root['object']->get_name();
1925             }
1926         } else if (strpos($root['object']->table, 'grade_items') !== false) {
1927             $name = $root['object']->itemname;
1928         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
1929             $name = $root['object']->itemname;
1930         }
1932         $json .= "$tabs {\n";
1933         $json .= "$tabs\t \"type\": \"{$root['type']}\",\n";
1934         $json .= "$tabs\t \"name\": \"$name\",\n";
1936         foreach ($root['object'] as $var => $value) {
1937             if (!is_object($value) && !is_array($value) && !empty($value)) {
1938                 $json .= "$tabs\t \"$var\": \"$value\",\n";
1939             }
1940         }
1942         $json = substr($json, 0, strrpos($json, ','));
1944         if (!empty($root['children'])) {
1945             $json .= ",\n$tabs\t\"children\": [\n";
1946             foreach ($root['children'] as $sortorder => $child) {
1947                 $json .= $this->exportToJSON($child, $tabs."\t\t");
1948             }
1949             $json = substr($json, 0, strrpos($json, ','));
1950             $json .= "\n$tabs\t]\n";
1951         }
1953         if ($first) {
1954             $json .= "\n}";
1955         } else {
1956             $json .= "\n$tabs},\n";
1957         }
1959         return $json;
1960     }
1962     /**
1963      * Returns the array of levels
1964      *
1965      * @return array
1966      */
1967     public function get_levels() {
1968         return $this->levels;
1969     }
1971     /**
1972      * Returns the array of grade items
1973      *
1974      * @return array
1975      */
1976     public function get_items() {
1977         return $this->items;
1978     }
1980     /**
1981      * Returns a specific Grade Item
1982      *
1983      * @param int $itemid The ID of the grade_item object
1984      *
1985      * @return grade_item
1986      */
1987     public function get_item($itemid) {
1988         if (array_key_exists($itemid, $this->items)) {
1989             return $this->items[$itemid];
1990         } else {
1991             return false;
1992         }
1993     }
1996 /**
1997  * Local shortcut function for creating an edit/delete button for a grade_* object.
1998  * @param string $type 'edit' or 'delete'
1999  * @param int $courseid The Course ID
2000  * @param grade_* $object The grade_* object
2001  * @return string html
2002  */
2003 function grade_button($type, $courseid, $object) {
2004     global $CFG, $OUTPUT;
2005     if (preg_match('/grade_(.*)/', get_class($object), $matches)) {
2006         $objectidstring = $matches[1] . 'id';
2007     } else {
2008         throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!');
2009     }
2011     $strdelete = get_string('delete');
2012     $stredit   = get_string('edit');
2014     if ($type == 'delete') {
2015         $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey()));
2016     } else if ($type == 'edit') {
2017         $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id));
2018     }
2020     return $OUTPUT->action_icon($url, new pix_icon('t/'.$type, ${'str'.$type}));
2024 /**
2025  * This method adds settings to the settings block for the grade system and its
2026  * plugins
2027  *
2028  * @global moodle_page $PAGE
2029  */
2030 function grade_extend_settings($plugininfo, $courseid) {
2031     global $PAGE;
2033     $gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER);
2035     $strings = array_shift($plugininfo);
2037     if ($reports = grade_helper::get_plugins_reports($courseid)) {
2038         foreach ($reports as $report) {
2039             $gradenode->add($report->string, $report->link, navigation_node::TYPE_SETTING, null, $report->id, new pix_icon('i/report', ''));
2040         }
2041     }
2043     if ($imports = grade_helper::get_plugins_import($courseid)) {
2044         $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER);
2045         foreach ($imports as $import) {
2046             $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/restore', ''));
2047         }
2048     }
2050     if ($exports = grade_helper::get_plugins_export($courseid)) {
2051         $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER);
2052         foreach ($exports as $export) {
2053             $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/backup', ''));
2054         }
2055     }
2057     if ($setting = grade_helper::get_info_manage_settings($courseid)) {
2058         $gradenode->add(get_string('coursegradesettings', 'grades'), $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', ''));
2059     }
2061     if ($preferences = grade_helper::get_plugins_report_preferences($courseid)) {
2062         $preferencesnode = $gradenode->add(get_string('myreportpreferences', 'grades'), null, navigation_node::TYPE_CONTAINER);
2063         foreach ($preferences as $preference) {
2064             $preferencesnode->add($preference->string, $preference->link, navigation_node::TYPE_SETTING, null, $preference->id, new pix_icon('i/settings', ''));
2065         }
2066     }
2068     if ($letters = grade_helper::get_info_letters($courseid)) {
2069         $letters = array_shift($letters);
2070         $gradenode->add($strings['letter'], $letters->link, navigation_node::TYPE_SETTING, null, $letters->id, new pix_icon('i/settings', ''));
2071     }
2073     if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
2074         $outcomes = array_shift($outcomes);
2075         $gradenode->add($strings['outcome'], $outcomes->link, navigation_node::TYPE_SETTING, null, $outcomes->id, new pix_icon('i/outcomes', ''));
2076     }
2078     if ($scales = grade_helper::get_info_scales($courseid)) {
2079         $gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', ''));
2080     }
2082     if ($categories = grade_helper::get_info_edit_structure($courseid)) {
2083         $categoriesnode = $gradenode->add(get_string('categoriesanditems','grades'), null, navigation_node::TYPE_CONTAINER);
2084         foreach ($categories as $category) {
2085             $categoriesnode->add($category->string, $category->link, navigation_node::TYPE_SETTING, null, $category->id, new pix_icon('i/report', ''));
2086         }
2087     }
2089     if ($gradenode->contains_active_node()) {
2090         // If the gradenode is active include the settings base node (gradeadministration) in
2091         // the navbar, typcially this is ignored.
2092         $PAGE->navbar->includesettingsbase = true;
2094         // If we can get the course admin node make sure it is closed by default
2095         // as in this case the gradenode will be opened
2096         if ($coursenode = $PAGE->settingsnav->get('courseadmin', navigation_node::TYPE_COURSE)){
2097             $coursenode->make_inactive();
2098             $coursenode->forceopen = false;
2099         }
2100     }
2103 /**
2104  * Grade helper class
2105  *
2106  * This class provides several helpful functions that work irrespective of any
2107  * current state.
2108  *
2109  * @copyright 2010 Sam Hemelryk
2110  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2111  */
2112 abstract class grade_helper {
2113     /**
2114      * Cached manage settings info {@see get_info_settings}
2115      * @var grade_plugin_info|false
2116      */
2117     protected static $managesetting = null;
2118     /**
2119      * Cached grade report plugins {@see get_plugins_reports}
2120      * @var array|false
2121      */
2122     protected static $gradereports = null;
2123     /**
2124      * Cached grade report plugins preferences {@see get_info_scales}
2125      * @var array|false
2126      */
2127     protected static $gradereportpreferences = null;
2128     /**
2129      * Cached scale info {@see get_info_scales}
2130      * @var grade_plugin_info|false
2131      */
2132     protected static $scaleinfo = null;
2133     /**
2134      * Cached outcome info {@see get_info_outcomes}
2135      * @var grade_plugin_info|false
2136      */
2137     protected static $outcomeinfo = null;
2138     /**
2139      * Cached info on edit structure {@see get_info_edit_structure}
2140      * @var array|false
2141      */
2142     protected static $edittree = null;
2143     /**
2144      * Cached leftter info {@see get_info_letters}
2145      * @var grade_plugin_info|false
2146      */
2147     protected static $letterinfo = null;
2148     /**
2149      * Cached grade import plugins {@see get_plugins_import}
2150      * @var array|false
2151      */
2152     protected static $importplugins = null;
2153     /**
2154      * Cached grade export plugins {@see get_plugins_export}
2155      * @var array|false
2156      */
2157     protected static $exportplugins = null;
2158     /**
2159      * Cached grade plugin strings
2160      * @var array
2161      */
2162     protected static $pluginstrings = null;
2164     /**
2165      * Gets strings commonly used by the describe plugins
2166      *
2167      * report => get_string('view'),
2168      * edittree => get_string('edittree', 'grades'),
2169      * scale => get_string('scales'),
2170      * outcome => get_string('outcomes', 'grades'),
2171      * letter => get_string('letters', 'grades'),
2172      * export => get_string('export', 'grades'),
2173      * import => get_string('import'),
2174      * preferences => get_string('mypreferences', 'grades'),
2175      * settings => get_string('settings')
2176      *
2177      * @return array
2178      */
2179     public static function get_plugin_strings() {
2180         if (self::$pluginstrings === null) {
2181             self::$pluginstrings = array(
2182                 'report' => get_string('view'),
2183                 'edittree' => get_string('edittree', 'grades'),
2184                 'scale' => get_string('scales'),
2185                 'outcome' => get_string('outcomes', 'grades'),
2186                 'letter' => get_string('letters', 'grades'),
2187                 'export' => get_string('export', 'grades'),
2188                 'import' => get_string('import'),
2189                 'preferences' => get_string('mypreferences', 'grades'),
2190                 'settings' => get_string('settings')
2191             );
2192         }
2193         return self::$pluginstrings;
2194     }
2195     /**
2196      * Get grade_plugin_info object for managing settings if the user can
2197      *
2198      * @param int $courseid
2199      * @return grade_plugin_info
2200      */
2201     public static function get_info_manage_settings($courseid) {
2202         if (self::$managesetting !== null) {
2203             return self::$managesetting;
2204         }
2205         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2206         if (has_capability('moodle/course:update', $context)) {
2207             self::$managesetting = new grade_plugin_info('coursesettings', new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)), get_string('course'));
2208         } else {
2209             self::$managesetting = false;
2210         }
2211         return self::$managesetting;
2212     }
2213     /**
2214      * Returns an array of plugin reports as grade_plugin_info objects
2215      *
2216      * @param int $courseid
2217      * @return array
2218      */
2219     public static function get_plugins_reports($courseid) {
2220         if (self::$gradereports !== null) {
2221             return self::$gradereports;
2222         }
2223         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2224         $gradereports = array();
2225         $gradepreferences = array();
2226         foreach (get_plugin_list('gradereport') as $plugin => $plugindir) {
2227             // Remove ones we can't see
2228             if (!has_capability('gradereport/'.$plugin.':view', $context)) {
2229                 continue;
2230             }
2232             $pluginstr = get_string('pluginname', 'gradereport_'.$plugin);
2233             $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid));
2234             $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2236             // Add link to preferences tab if such a page exists
2237             if (file_exists($plugindir.'/preferences.php')) {
2238                 $url = new moodle_url('/grade/report/'.$plugin.'/preferences.php', array('id'=>$courseid));
2239                 $gradepreferences[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2240             }
2241         }
2242         if (count($gradereports) == 0) {
2243             $gradereports = false;
2244             $gradepreferences = false;
2245         } else if (count($gradepreferences) == 0) {
2246             $gradepreferences = false;
2247             asort($gradereports);
2248         } else {
2249             asort($gradereports);
2250             asort($gradepreferences);
2251         }
2252         self::$gradereports = $gradereports;
2253         self::$gradereportpreferences = $gradepreferences;
2254         return self::$gradereports;
2255     }
2256     /**
2257      * Returns an array of grade plugin report preferences for plugin reports that
2258      * support preferences
2259      * @param int $courseid
2260      * @return array
2261      */
2262     public static function get_plugins_report_preferences($courseid) {
2263         if (self::$gradereportpreferences !== null) {
2264             return self::$gradereportpreferences;
2265         }
2266         self::get_plugins_reports($courseid);
2267         return self::$gradereportpreferences;
2268     }
2269     /**
2270      * Get information on scales
2271      * @param int $courseid
2272      * @return grade_plugin_info
2273      */
2274     public static function get_info_scales($courseid) {
2275         if (self::$scaleinfo !== null) {
2276             return self::$scaleinfo;
2277         }
2278         if (has_capability('moodle/course:managescales', get_context_instance(CONTEXT_COURSE, $courseid))) {
2279             $url = new moodle_url('/grade/edit/scale/index.php', array('id'=>$courseid));
2280             self::$scaleinfo = new grade_plugin_info('scale', $url, get_string('view'));
2281         } else {
2282             self::$scaleinfo = false;
2283         }
2284         return self::$scaleinfo;
2285     }
2286     /**
2287      * Get information on outcomes
2288      * @param int $courseid
2289      * @return grade_plugin_info
2290      */
2291     public static function get_info_outcomes($courseid) {
2292         global $CFG;
2294         if (self::$outcomeinfo !== null) {
2295             return self::$outcomeinfo;
2296         }
2297         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2298         $canmanage = has_capability('moodle/grade:manage', $context);
2299         $canupdate = has_capability('moodle/course:update', $context);
2300         if (!empty($CFG->enableoutcomes) && ($canmanage || $canupdate)) {
2301             $outcomes = array();
2302             if ($canupdate) {
2303                 $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
2304                 $outcomes['course'] = new grade_plugin_info('course', $url, get_string('outcomescourse', 'grades'));
2305                 $url = new moodle_url('/grade/edit/outcome/index.php', array('id'=>$courseid));
2306                 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('editoutcomes', 'grades'));
2307                 $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid'=>$courseid));
2308                 $outcomes['import'] = new grade_plugin_info('import', $url, get_string('importoutcomes', 'grades'));
2309             } else {
2310                 $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
2311                 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('outcomescourse', 'grades'));
2312             }
2313             self::$outcomeinfo = $outcomes;
2314         } else {
2315             self::$outcomeinfo = false;
2316         }
2317         return self::$outcomeinfo;
2318     }
2319     /**
2320      * Get information on editing structures
2321      * @param int $courseid
2322      * @return array
2323      */
2324     public static function get_info_edit_structure($courseid) {
2325         if (self::$edittree !== null) {
2326             return self::$edittree;
2327         }
2328         if (has_capability('moodle/grade:manage', get_context_instance(CONTEXT_COURSE, $courseid))) {
2329             $url = new moodle_url('/grade/edit/tree/index.php', array('sesskey'=>sesskey(), 'showadvanced'=>'0', 'id'=>$courseid));
2330             self::$edittree = array(
2331                 'simpleview' => new grade_plugin_info('simpleview', $url, get_string('simpleview', 'grades')),
2332                 'fullview' => new grade_plugin_info('fullview', new moodle_url($url, array('showadvanced'=>'1')), get_string('fullview', 'grades'))
2333             );
2334         } else {
2335             self::$edittree = false;
2336         }
2337         return self::$edittree;
2338     }
2339     /**
2340      * Get information on letters
2341      * @param int $courseid
2342      * @return array
2343      */
2344     public static function get_info_letters($courseid) {
2345         if (self::$letterinfo !== null) {
2346             return self::$letterinfo;
2347         }
2348         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2349         $canmanage = has_capability('moodle/grade:manage', $context);
2350         $canmanageletters = has_capability('moodle/grade:manageletters', $context);
2351         if ($canmanage || $canmanageletters) {
2352             self::$letterinfo = array(
2353                 'view' => new grade_plugin_info('view', new moodle_url('/grade/edit/letter/index.php', array('id'=>$context->id)), get_string('view')),
2354                 'edit' => new grade_plugin_info('edit', new moodle_url('/grade/edit/letter/index.php', array('edit'=>1,'id'=>$context->id)), get_string('edit'))
2355             );
2356         } else {
2357             self::$letterinfo = false;
2358         }
2359         return self::$letterinfo;
2360     }
2361     /**
2362      * Get information import plugins
2363      * @param int $courseid
2364      * @return array
2365      */
2366     public static function get_plugins_import($courseid) {
2367         global $CFG;
2369         if (self::$importplugins !== null) {
2370             return self::$importplugins;
2371         }
2372         $importplugins = array();
2373         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2375         if (has_capability('moodle/grade:import', $context)) {
2376             foreach (get_plugin_list('gradeimport') as $plugin => $plugindir) {
2377                 if (!has_capability('gradeimport/'.$plugin.':view', $context)) {
2378                     continue;
2379                 }
2380                 $pluginstr = get_string('pluginname', 'gradeimport_'.$plugin);
2381                 $url = new moodle_url('/grade/import/'.$plugin.'/index.php', array('id'=>$courseid));
2382                 $importplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2383             }
2386             if ($CFG->gradepublishing) {
2387                 $url = new moodle_url('/grade/import/keymanager.php', array('id'=>$courseid));
2388                 $importplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
2389             }
2390         }
2392         if (count($importplugins) > 0) {
2393             asort($importplugins);
2394             self::$importplugins = $importplugins;
2395         } else {
2396             self::$importplugins = false;
2397         }
2398         return self::$importplugins;
2399     }
2400     /**
2401      * Get information export plugins
2402      * @param int $courseid
2403      * @return array
2404      */
2405     public static function get_plugins_export($courseid) {
2406         global $CFG;
2408         if (self::$exportplugins !== null) {
2409             return self::$exportplugins;
2410         }
2411         $context = get_context_instance(CONTEXT_COURSE, $courseid);
2412         $exportplugins = array();
2413         if (has_capability('moodle/grade:export', $context)) {
2414             foreach (get_plugin_list('gradeexport') as $plugin => $plugindir) {
2415                 if (!has_capability('gradeexport/'.$plugin.':view', $context)) {
2416                     continue;
2417                 }
2418                 $pluginstr = get_string('pluginname', 'gradeexport_'.$plugin);
2419                 $url = new moodle_url('/grade/export/'.$plugin.'/index.php', array('id'=>$courseid));
2420                 $exportplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
2421             }
2423             if ($CFG->gradepublishing) {
2424                 $url = new moodle_url('/grade/export/keymanager.php', array('id'=>$courseid));
2425                 $exportplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
2426             }
2427         }
2428         if (count($exportplugins) > 0) {
2429             asort($exportplugins);
2430             self::$exportplugins = $exportplugins;
2431         } else {
2432             self::$exportplugins = false;
2433         }
2434         return self::$exportplugins;
2435     }