8319ab527af55096b139b34aff3cd6aef7ee0bb9
[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 ra.roleid $gradebookroles_sql
129                              AND ra.contextid $relatedcontexts
130                              $groupwheresql
131                     ORDER BY $order";
133         $this->users_rs = $DB->get_recordset_sql($users_sql, $params);
135         if (!empty($this->grade_items)) {
136             $itemids = array_keys($this->grade_items);
137             list($itemidsql, $grades_params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED, 'items0');
138             $params = array_merge($params, $grades_params);
140             // $params contents: gradebookroles, groupid (for $groupwheresql) and itemids
141             $grades_sql = "SELECT g.* $ofields
142                              FROM {grade_grades} g
143                                   INNER JOIN {user} u ON g.userid = u.id
144                                   INNER JOIN {role_assignments} ra ON u.id = ra.userid
145                                   $groupsql
146                             WHERE ra.roleid $gradebookroles_sql
147                                   AND ra.contextid $relatedcontexts
148                                   $groupwheresql
149                                   AND g.itemid $itemidsql
150                          ORDER BY $order, g.itemid ASC";
151             $this->grades_rs = $DB->get_recordset_sql($grades_sql, $params);
152         } else {
153             $this->grades_rs = false;
154         }
156         return true;
157     }
159     /**
160      * Returns information about the next user
161      * @return mixed array of user info, all grades and feedback or null when no more users found
162      */
163     function next_user() {
164         if (!$this->users_rs) {
165             return false; // no users present
166         }
168         if (!$this->users_rs->valid()) {
169             if ($current = $this->_pop()) {
170                 // this is not good - user or grades updated between the two reads above :-(
171             }
173             return false; // no more users
174         } else {
175             $user = $this->users_rs->current();
176             $this->users_rs->next();
177         }
179         // find grades of this user
180         $grade_records = array();
181         while (true) {
182             if (!$current = $this->_pop()) {
183                 break; // no more grades
184             }
186             if (empty($current->userid)) {
187                 break;
188             }
190             if ($current->userid != $user->id) {
191                 // grade of the next user, we have all for this user
192                 $this->_push($current);
193                 break;
194             }
196             $grade_records[$current->itemid] = $current;
197         }
199         $grades = array();
200         $feedbacks = array();
202         if (!empty($this->grade_items)) {
203             foreach ($this->grade_items as $grade_item) {
204                 if (array_key_exists($grade_item->id, $grade_records)) {
205                     $feedbacks[$grade_item->id]->feedback       = $grade_records[$grade_item->id]->feedback;
206                     $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat;
207                     unset($grade_records[$grade_item->id]->feedback);
208                     unset($grade_records[$grade_item->id]->feedbackformat);
209                     $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false);
210                 } else {
211                     $feedbacks[$grade_item->id]->feedback       = '';
212                     $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE;
213                     $grades[$grade_item->id] =
214                         new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false);
215                 }
216             }
217         }
219         $result = new object();
220         $result->user      = $user;
221         $result->grades    = $grades;
222         $result->feedbacks = $feedbacks;
224         return $result;
225     }
227     /**
228      * Close the iterator, do not forget to call this function.
229      * @return void
230      */
231     function close() {
232         if ($this->users_rs) {
233             $this->users_rs->close();
234             $this->users_rs = null;
235         }
236         if ($this->grades_rs) {
237             $this->grades_rs->close();
238             $this->grades_rs = null;
239         }
240         $this->gradestack = array();
241     }
244     /**
245      * _push
246      *
247      * @param grade_grade $grade Grade object
248      *
249      * @return void
250      */
251     function _push($grade) {
252         array_push($this->gradestack, $grade);
253     }
256     /**
257      * _pop
258      *
259      * @return void
260      */
261     function _pop() {
262         global $DB;
263         if (empty($this->gradestack)) {
264             if (!$this->grades_rs) {
265                 return null; // no grades present
266             }
268             if ($this->grades_rs->next()) {
269                 return null; // no more grades
270             }
272             return $this->grades_rs->current();
273         } else {
274             return array_pop($this->gradestack);
275         }
276     }
279 /**
280  * Print a selection popup form of the graded users in a course.
281  *
282  * @param int    $course id of the course
283  * @param string $actionpage The page receiving the data from the popoup form
284  * @param int    $userid   id of the currently selected user (or 'all' if they are all selected)
285  * @param int    $groupid id of requested group, 0 means all
286  * @param int    $includeall bool include all option
287  * @param bool   $return If true, will return the HTML, otherwise, will print directly
288  * @return null
289  */
290 function print_graded_users_selector($course, $actionpage, $userid=0, $groupid=0, $includeall=true, $return=false) {
291     global $CFG, $USER, $OUTPUT;
293     if (is_null($userid)) {
294         $userid = $USER->id;
295     }
297     $context = get_context_instance(CONTEXT_COURSE, $course->id);
299     $menu = array(); // Will be a list of userid => user name
301     $gui = new graded_users_iterator($course, null, $groupid);
302     $gui->init();
305     $label = get_string('selectauser', 'grades');
306     if ($includeall) {
307         $menu[0] = get_string('allusers', 'grades');
308         $label = get_string('selectalloroneuser', 'grades');
309     }
311     while ($userdata = $gui->next_user()) {
312         $user = $userdata->user;
313         $menu[$user->id] = fullname($user);
314     }
316     $gui->close();
318     if ($includeall) {
319         $menu[0] .= " (" . (count($menu) - 1) . ")";
320     }
321     $select = new single_select(new moodle_url('/grade/'.$actionpage), 'userid', $menu, $userid);
322     $select->label = $label;
323     $select->formid = 'choosegradeuser';
324     return $OUTPUT->render($select);
327 /**
328  * Print grading plugin selection popup form.
329  *
330  * @param array   $plugin_info An array of plugins containing information for the selector
331  * @param boolean $return return as string
332  *
333  * @return nothing or string if $return true
334  */
335 function print_grade_plugin_selector($plugin_info, $return=false) {
336     global $CFG, $OUTPUT, $PAGE;
338     $menu = array();
339     $count = 0;
340     $active = '';
342     foreach ($plugin_info as $plugin_type => $plugins) {
343         if ($plugin_type == 'strings') {
344             continue;
345         }
347         $first_plugin = reset($plugins);
349         $menu[$first_plugin->link.'&'] = '--'.$plugin_info['strings'][$plugin_type];
351         if (empty($plugins->id)) {
352             foreach ($plugins as $plugin) {
353                 $menu[$plugin->link] = $plugin->string;
354                 $count++;
355             }
356         }
357     }
359     // finally print/return the popup form
360     if ($count > 1) {
361         $select = html_select::make_popup_form('', '', $menu, 'choosepluginreport', '');
362         $select->override_option_values($menu);
364         if ($return) {
365             return $OUTPUT->select($select);
366         } else {
367             echo $OUTPUT->select($select);
368         }
369     } else {
370         // only one option - no plugin selector needed
371         return '';
372     }
375 /**
376  * Print grading plugin selection tab-based navigation.
377  *
378  * @param string  $active_type type of plugin on current page - import, export, report or edit
379  * @param string  $active_plugin active plugin type - grader, user, cvs, ...
380  * @param array   $plugin_info Array of plugins
381  * @param boolean $return return as string
382  *
383  * @return nothing or string if $return true
384  */
385 function grade_print_tabs($active_type, $active_plugin, $plugin_info, $return=false) {
386     global $CFG, $COURSE;
388     if (!isset($currenttab)) {
389         $currenttab = '';
390     }
392     $tabs = array();
393     $top_row  = array();
394     $bottom_row = array();
395     $inactive = array($active_plugin);
396     $activated = array();
398     $count = 0;
399     $active = '';
401     foreach ($plugin_info as $plugin_type => $plugins) {
402         if ($plugin_type == 'strings') {
403             continue;
404         }
406         // If $plugins is actually the definition of a child-less parent link:
407         if (!empty($plugins->id)) {
408             $string = $plugins->string;
409             if (!empty($plugin_info[$active_type]->parent)) {
410                 $string = $plugin_info[$active_type]->parent->string;
411             }
413             $top_row[] = new tabobject($plugin_type, $plugins->link, $string);
414             continue;
415         }
417         $first_plugin = reset($plugins);
418         $url = $first_plugin->link;
420         if ($plugin_type == 'report') {
421             $url = $CFG->wwwroot.'/grade/report/index.php?id='.$COURSE->id;
422         }
424         $top_row[] = new tabobject($plugin_type, $url, $plugin_info['strings'][$plugin_type]);
426         if ($active_type == $plugin_type) {
427             foreach ($plugins as $plugin) {
428                 $bottom_row[] = new tabobject($plugin->id, $plugin->link, $plugin->string);
429                 if ($plugin->id == $active_plugin) {
430                     $inactive = array($plugin->id);
431                 }
432             }
433         }
434     }
436     $tabs[] = $top_row;
437     $tabs[] = $bottom_row;
439     if ($return) {
440         return print_tabs($tabs, $active_type, $inactive, $activated, true);
441     } else {
442         print_tabs($tabs, $active_type, $inactive, $activated);
443     }
446 /**
447  * grade_get_plugin_info
448  *
449  * @param int    $courseid The course id
450  * @param string $active_type type of plugin on current page - import, export, report or edit
451  * @param string $active_plugin active plugin type - grader, user, cvs, ...
452  *
453  * @return array
454  */
455 function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
456     global $CFG;
458     $context = get_context_instance(CONTEXT_COURSE, $courseid);
460     $plugin_info = array();
461     $count = 0;
462     $active = '';
463     $url_prefix = $CFG->wwwroot . '/grade/';
465     // Language strings
466     $plugin_info['strings'] = array(
467         'report' => get_string('view'),
468         'edittree' => get_string('edittree', 'grades'),
469         'scale' => get_string('scales'),
470         'outcome' => get_string('outcomes', 'grades'),
471         'letter' => get_string('letters', 'grades'),
472         'export' => get_string('export', 'grades'),
473         'import' => get_string('import'),
474         'preferences' => get_string('mypreferences', 'grades'),
475         'settings' => get_string('settings'));
477     // Settings tab first
478     if (has_capability('moodle/course:update', $context)) {
479         $url = $url_prefix.'edit/settings/index.php?id='.$courseid;
481         if ($active_type == 'settings' and $active_plugin == 'course') {
482             $active = $url;
483         }
485         $plugin_info['settings'] = array();
486         $plugin_info['settings']['course'] =
487                 new grade_plugin_info('coursesettings', $url, get_string('course'));
488         $count++;
489     }
492     // report plugins with its special structure
494     // Get all installed reports
495     if ($reports = get_plugin_list('gradereport')) {
497         // Remove ones we can't see
498         foreach ($reports as $plugin => $unused) {
499             if (!has_capability('gradereport/'.$plugin.':view', $context)) {
500                 unset($reports[$plugin]);
501             }
502         }
503     }
505     $reportnames = array();
507     if (!empty($reports)) {
508         foreach ($reports as $plugin => $plugindir) {
509             $pluginstr = get_string('modulename', 'gradereport_'.$plugin);
510             $url = $url_prefix.'report/'.$plugin.'/index.php?id='.$courseid;
511             if ($active_type == 'report' and $active_plugin == $plugin ) {
512                 $active = $url;
513             }
514             $reportnames[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
516             // Add link to preferences tab if such a page exists
517             if (file_exists($plugindir.'/preferences.php')) {
518                 $pref_url = $url_prefix.'report/'.$plugin.'/preferences.php?id='.$courseid;
519                 $plugin_info['preferences'][$plugin] = new grade_plugin_info($plugin, $pref_url, $pluginstr);
520             }
522             $count++;
523         }
524         asort($reportnames);
525     }
526     if (!empty($reportnames)) {
527         $plugin_info['report']=$reportnames;
528     }
530     // editing scripts - not real plugins
531     if (has_capability('moodle/grade:manage', $context)
532       or has_capability('moodle/grade:manageletters', $context)
533       or has_capability('moodle/course:managescales', $context)
534       or has_capability('moodle/course:update', $context)) {
536         if (has_capability('moodle/grade:manage', $context)) {
537             $url = $url_prefix.'edit/tree/index.php?sesskey='.sesskey().
538                     '&amp;showadvanced=0&amp;id='.$courseid;
539             $url_adv = $url_prefix.'edit/tree/index.php?sesskey='.sesskey().
540                     '&amp;showadvanced=1&amp;id='.$courseid;
542             if ($active_type == 'edittree' and $active_plugin == 'simpleview') {
543                 $active = $url;
544             } else if ($active_type == 'edittree' and $active_plugin == 'fullview') {
545                 $active = $url_adv;
546             }
548             $plugin_info['edittree'] = array();
549             $plugin_info['edittree']['simpleview'] =
550                     new grade_plugin_info('simpleview', $url, get_string('simpleview', 'grades'));
551             $plugin_info['edittree']['fullview'] =
552                     new grade_plugin_info('fullview', $url_adv, get_string('fullview', 'grades'));
553             $count++;
554         }
556         if (has_capability('moodle/course:managescales', $context)) {
557             $url = $url_prefix.'edit/scale/index.php?id='.$courseid;
559             if ($active_type == 'scale' and is_null($active_plugin)) {
560                 $active = $url;
561             }
563             $plugin_info['scale'] = array();
565             if ($active_type == 'scale' and $active_plugin == 'edit') {
566                 $edit_url = $url_prefix.'edit/scale/edit.php?courseid='.$courseid.
567                         '&amp;id='.optional_param('id', 0, PARAM_INT);
568                 $active = $edit_url;
569                 $parent = new grade_plugin_info('scale', $url, get_string('scales'));
570                 $plugin_info['scale']['view'] =
571                         new grade_plugin_info('edit', $edit_url, get_string('edit'), $parent);
572             } else {
573                 $plugin_info['scale']['view'] =
574                         new grade_plugin_info('scale', $url, get_string('view'));
575             }
577             $count++;
578         }
580         if (!empty($CFG->enableoutcomes) && (has_capability('moodle/grade:manage', $context) or
581                                              has_capability('moodle/course:update', $context))) {
583             $url_course = $url_prefix.'edit/outcome/course.php?id='.$courseid;
584             $url_edit = $url_prefix.'edit/outcome/index.php?id='.$courseid;
586             $plugin_info['outcome'] = array();
588             if (has_capability('moodle/course:update', $context)) {  // Default to course assignment
589                 $plugin_info['outcome']['course'] =
590                         new grade_plugin_info('course', $url_course, get_string('outcomescourse', 'grades'));
591                 $plugin_info['outcome']['edit'] =
592                         new grade_plugin_info('edit', $url_edit, get_string('editoutcomes', 'grades'));
593             } else {
594                 $plugin_info['outcome'] =
595                         new grade_plugin_info('edit', $url_course, get_string('outcomescourse', 'grades'));
596             }
598             if ($active_type == 'outcome' and is_null($active_plugin)) {
599                 $active = $url_edit;
600             } else if ($active_type == 'outcome' and $active_plugin == 'course' ) {
601                 $active = $url_course;
602             } else if ($active_type == 'outcome' and $active_plugin == 'edit' ) {
603                 $active = $url_edit;
604             } else if ($active_type == 'outcome' and $active_plugin == 'import') {
605                 $plugin_info['outcome']['import'] =
606                         new grade_plugin_info('import', null, get_string('importoutcomes', 'grades'));
607             }
609             $count++;
610         }
612         if (has_capability('moodle/grade:manage', $context) or
613                     has_capability('moodle/grade:manageletters', $context)) {
614             $course_context = get_context_instance(CONTEXT_COURSE, $courseid);
615             $url = $url_prefix.'edit/letter/index.php?id='.$courseid;
616             $url_edit = $url_prefix.'edit/letter/edit.php?id='.$course_context->id;
618             if ($active_type == 'letter' and $active_plugin == 'view' ) {
619                 $active = $url;
620             } else if ($active_type == 'letter' and $active_plugin == 'edit' ) {
621                 $active = $url_edit;
622             }
624             $plugin_info['letter'] = array();
625             $plugin_info['letter']['view'] = new grade_plugin_info('view', $url, get_string('view'));
626             $plugin_info['letter']['edit'] = new grade_plugin_info('edit', $url_edit, get_string('edit'));
627             $count++;
628         }
629     }
631     // standard import plugins
632     if ($imports = get_plugin_list('gradeimport')) { // Get all installed import plugins
633         foreach ($imports as $plugin => $plugindir) { // Remove ones we can't see
634             if (!has_capability('gradeimport/'.$plugin.':view', $context)) {
635                 unset($imports[$plugin]);
636             }
637         }
638     }
639     $importnames = array();
640     if (!empty($imports)) {
641         foreach ($imports as $plugin => $plugindir) {
642             $pluginstr = get_string('modulename', 'gradeimport_'.$plugin);
643             $url = $url_prefix.'import/'.$plugin.'/index.php?id='.$courseid;
644             if ($active_type == 'import' and $active_plugin == $plugin ) {
645                 $active = $url;
646             }
647             $importnames[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
648             $count++;
649         }
650         asort($importnames);
651     }
652     if (!empty($importnames)) {
653         $plugin_info['import']=$importnames;
654     }
656     // standard export plugins
657     if ($exports = get_plugin_list('gradeexport')) { // Get all installed export plugins
658         foreach ($exports as $plugin => $plugindir) { // Remove ones we can't see
659             if (!has_capability('gradeexport/'.$plugin.':view', $context)) {
660                 unset($exports[$plugin]);
661             }
662         }
663     }
664     $exportnames = array();
665     if (!empty($exports)) {
666         foreach ($exports as $plugin => $plugindir) {
667             $pluginstr = get_string('modulename', 'gradeexport_'.$plugin);
668             $url = $url_prefix.'export/'.$plugin.'/index.php?id='.$courseid;
669             if ($active_type == 'export' and $active_plugin == $plugin ) {
670                 $active = $url;
671             }
672             $exportnames[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
673             $count++;
674         }
675         asort($exportnames);
676     }
678     if (!empty($exportnames)) {
679         $plugin_info['export']=$exportnames;
680     }
682     // Key managers
683     if ($CFG->gradepublishing) {
684         $keymanager_url = $url_prefix.'export/keymanager.php?id='.$courseid;
685         $plugin_info['export']['keymanager'] =
686                 new grade_plugin_info('keymanager', $keymanager_url, get_string('keymanager', 'grades'));
687         if ($active_type == 'export' and $active_plugin == 'keymanager' ) {
688             $active = $keymanager_url;
689         }
690         $count++;
692         $keymanager_url = $url_prefix.'import/keymanager.php?id='.$courseid;
693         $plugin_info['import']['keymanager'] =
694                 new grade_plugin_info('keymanager', $keymanager_url, get_string('keymanager', 'grades'));
695         if ($active_type == 'import' and $active_plugin == 'keymanager' ) {
696             $active = $keymanager_url;
697         }
698         $count++;
699     }
702     foreach ($plugin_info as $plugin_type => $plugins) {
703         if (!empty($plugins->id) && $active_plugin == $plugins->id) {
704             $plugin_info['strings']['active_plugin_str'] = $plugins->string;
705             break;
706         }
707         foreach ($plugins as $plugin) {
708             if (is_a($plugin, 'grade_plugin_info')) {
709                 if ($active_plugin == $plugin->id) {
710                     $plugin_info['strings']['active_plugin_str'] = $plugin->string;
711                 }
712             }
713         }
714     }
716     // Put settings last
717     if (!empty($plugin_info['settings'])) {
718         $settings = $plugin_info['settings'];
719         unset($plugin_info['settings']);
720         $plugin_info['settings'] = $settings;
721     }
723     // Put preferences last
724     if (!empty($plugin_info['preferences'])) {
725         $prefs = $plugin_info['preferences'];
726         unset($plugin_info['preferences']);
727         $plugin_info['preferences'] = $prefs;
728     }
730     // Check import and export caps
731     if (!has_capability('moodle/grade:export', $context)) {
732         unset($plugin_info['export']);
733     }
734     if (!has_capability('moodle/grade:import', $context)) {
735         unset($plugin_info['import']);
736     }
737     return $plugin_info;
740 /**
741  * A simple class containing info about grade plugins.
742  * Can be subclassed for special rules
743  *
744  * @package moodlecore
745  * @copyright 2009 Nicolas Connault
746  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
747  */
748 class grade_plugin_info {
749     /**
750      * A unique id for this plugin
751      *
752      * @var mixed
753      */
754     public $id;
755     /**
756      * A URL to access this plugin
757      *
758      * @var mixed
759      */
760     public $link;
761     /**
762      * The name of this plugin
763      *
764      * @var mixed
765      */
766     public $string;
767     /**
768      * Another grade_plugin_info object, parent of the current one
769      *
770      * @var mixed
771      */
772     public $parent;
774     /**
775      * Constructor
776      *
777      * @param int $id A unique id for this plugin
778      * @param string $link A URL to access this plugin
779      * @param string $string The name of this plugin
780      * @param object $parent Another grade_plugin_info object, parent of the current one
781      *
782      * @return void
783      */
784     public function __construct($id, $link, $string, $parent=null) {
785         $this->id = $id;
786         $this->link = $link;
787         $this->string = $string;
788         $this->parent = $parent;
789     }
792 /**
793  * Prints the page headers, breadcrumb trail, page heading, (optional) dropdown navigation menu and
794  * (optional) navigation tabs for any gradebook page. All gradebook pages MUST use these functions
795  * in favour of the usual print_header(), print_header_simple(), print_heading() etc.
796  * !IMPORTANT! Use of tabs.php file in gradebook pages is forbidden unless tabs are switched off at
797  * the site level for the gradebook ($CFG->grade_navmethod = GRADE_NAVMETHOD_DROPDOWN).
798  *
799  * @param int     $courseid Course id
800  * @param string  $active_type The type of the current page (report, settings,
801  *                             import, export, scales, outcomes, letters)
802  * @param string  $active_plugin The plugin of the current page (grader, fullview etc...)
803  * @param string  $heading The heading of the page. Tries to guess if none is given
804  * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function
805  * @param string  $bodytags Additional attributes that will be added to the <body> tag
806  * @param string  $buttons Additional buttons to display on the page
807  *
808  * @return string HTML code or nothing if $return == false
809  */
810 function print_grade_page_head($courseid, $active_type, $active_plugin=null,
811                                $heading = false, $return=false,
812                                $buttons=false) {
813     global $CFG, $COURSE, $OUTPUT, $PAGE;
814     $strgrades = get_string('grades');
815     $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin);
817     // Determine the string of the active plugin
818     $stractive_plugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading;
819     $stractive_type = $plugin_info['strings'][$active_type];
821     $first_link = '';
823     if ($active_type == 'settings' && $active_plugin != 'coursesettings') {
824         $first_link = $plugin_info['report'][$active_plugin]->link;
825     } else if ($active_type != 'report') {
826         $first_link = $CFG->wwwroot.'/grade/index.php?id='.$COURSE->id;
827     }
830     $PAGE->navbar->add($strgrades, $first_link);
832     $active_type_link = '';
834     if (!empty($plugin_info[$active_type]->link) && $plugin_info[$active_type]->link != qualified_me()) {
835         $active_type_link = $plugin_info[$active_type]->link;
836     }
838     if (!empty($plugin_info[$active_type]->parent->link)) {
839         $active_type_link = $plugin_info[$active_type]->parent->link;
840         $PAGE->navbar->add($stractive_type, $active_type_link);
841     }
843     if (empty($plugin_info[$active_type]->id)) {
844         $PAGE->navbar->add($stractive_type, $active_type_link);
845     }
847     $PAGE->navbar->add($stractive_plugin);
849     $title = ': ' . $stractive_plugin;
850     if (empty($plugin_info[$active_type]->id) || !empty($plugin_info[$active_type]->parent)) {
851         $title = ': ' . $stractive_type . ': ' . $stractive_plugin;
852     }
854     $PAGE->set_title($strgrades . ': ' . $stractive_type);
855     $PAGE->set_heading($title);
856     $PAGE->set_button($buttons);
857     $returnval = $OUTPUT->header();
858     if (!$return) {
859         echo $returnval;
860     }
862     // Guess heading if not given explicitly
863     if (!$heading) {
864         $heading = $stractive_plugin;
865     }
867     if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN) {
868         $returnval .= print_grade_plugin_selector($plugin_info, $return);
869     }
870     $returnval .= $OUTPUT->heading($heading);
872     if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS) {
873         $returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
874     }
876     if ($return) {
877         return $returnval;
878     }
881 /**
882  * Utility class used for return tracking when using edit and other forms in grade plugins
883  *
884  * @package moodlecore
885  * @copyright 2009 Nicolas Connault
886  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
887  */
888 class grade_plugin_return {
889     public $type;
890     public $plugin;
891     public $courseid;
892     public $userid;
893     public $page;
895     /**
896      * Constructor
897      *
898      * @param array $params - associative array with return parameters, if null parameter are taken from _GET or _POST
899      */
900     public function grade_plugin_return($params = null) {
901         if (empty($params)) {
902             $this->type     = optional_param('gpr_type', null, PARAM_SAFEDIR);
903             $this->plugin   = optional_param('gpr_plugin', null, PARAM_SAFEDIR);
904             $this->courseid = optional_param('gpr_courseid', null, PARAM_INT);
905             $this->userid   = optional_param('gpr_userid', null, PARAM_INT);
906             $this->page     = optional_param('gpr_page', null, PARAM_INT);
908         } else {
909             foreach ($params as $key=>$value) {
910                 if (array_key_exists($key, $this)) {
911                     $this->$key = $value;
912                 }
913             }
914         }
915     }
917     /**
918      * Returns return parameters as options array suitable for buttons.
919      * @return array options
920      */
921     public function get_options() {
922         if (empty($this->type)) {
923             return array();
924         }
926         $params = array();
928         if (!empty($this->plugin)) {
929             $params['plugin'] = $this->plugin;
930         }
932         if (!empty($this->courseid)) {
933             $params['id'] = $this->courseid;
934         }
936         if (!empty($this->userid)) {
937             $params['userid'] = $this->userid;
938         }
940         if (!empty($this->page)) {
941             $params['page'] = $this->page;
942         }
944         return $params;
945     }
947     /**
948      * Returns return url
949      *
950      * @param string $default default url when params not set
951      * @param array  $extras Extra URL parameters
952      *
953      * @return string url
954      */
955     public function get_return_url($default, $extras=null) {
956         global $CFG;
958         if (empty($this->type) or empty($this->plugin)) {
959             return $default;
960         }
962         $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php';
963         $glue = '?';
965         if (!empty($this->courseid)) {
966             $url .= $glue.'id='.$this->courseid;
967             $glue = '&amp;';
968         }
970         if (!empty($this->userid)) {
971             $url .= $glue.'userid='.$this->userid;
972             $glue = '&amp;';
973         }
975         if (!empty($this->page)) {
976             $url .= $glue.'page='.$this->page;
977             $glue = '&amp;';
978         }
980         if (!empty($extras)) {
981             foreach ($extras as $key=>$value) {
982                 $url .= $glue.$key.'='.$value;
983                 $glue = '&amp;';
984             }
985         }
987         return $url;
988     }
990     /**
991      * Returns string with hidden return tracking form elements.
992      * @return string
993      */
994     public function get_form_fields() {
995         if (empty($this->type)) {
996             return '';
997         }
999         $result  = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />';
1001         if (!empty($this->plugin)) {
1002             $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />';
1003         }
1005         if (!empty($this->courseid)) {
1006             $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />';
1007         }
1009         if (!empty($this->userid)) {
1010             $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />';
1011         }
1013         if (!empty($this->page)) {
1014             $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />';
1015         }
1016     }
1018     /**
1019      * Add hidden elements into mform
1020      *
1021      * @param object &$mform moodle form object
1022      *
1023      * @return void
1024      */
1025     public function add_mform_elements(&$mform) {
1026         if (empty($this->type)) {
1027             return;
1028         }
1030         $mform->addElement('hidden', 'gpr_type', $this->type);
1031         $mform->setType('gpr_type', PARAM_SAFEDIR);
1033         if (!empty($this->plugin)) {
1034             $mform->addElement('hidden', 'gpr_plugin', $this->plugin);
1035             $mform->setType('gpr_plugin', PARAM_SAFEDIR);
1036         }
1038         if (!empty($this->courseid)) {
1039             $mform->addElement('hidden', 'gpr_courseid', $this->courseid);
1040             $mform->setType('gpr_courseid', PARAM_INT);
1041         }
1043         if (!empty($this->userid)) {
1044             $mform->addElement('hidden', 'gpr_userid', $this->userid);
1045             $mform->setType('gpr_userid', PARAM_INT);
1046         }
1048         if (!empty($this->page)) {
1049             $mform->addElement('hidden', 'gpr_page', $this->page);
1050             $mform->setType('gpr_page', PARAM_INT);
1051         }
1052     }
1054     /**
1055      * Add return tracking params into url
1056      *
1057      * @param moodle_url $url A URL
1058      *
1059      * @return string $url with erturn tracking params
1060      */
1061     public function add_url_params(moodle_url $url) {
1062         if (empty($this->type)) {
1063             return $url;
1064         }
1066         $url->param('gpr_type', $this->type);
1068         if (!empty($this->plugin)) {
1069             $url->param('gpr_plugin', $this->plugin);
1070         }
1072         if (!empty($this->courseid)) {
1073             $url->param('gpr_courseid' ,$this->courseid);
1074         }
1076         if (!empty($this->userid)) {
1077             $url->param('gpr_userid', $this->userid);
1078         }
1080         if (!empty($this->page)) {
1081             $url->param('gpr_page', $this->page);
1082         }
1084         return $url;
1085     }
1088 /**
1089  * Function central to gradebook for building and printing the navigation (breadcrumb trail).
1090  *
1091  * @param string $path The path of the calling script (using __FILE__?)
1092  * @param string $pagename The language string to use as the last part of the navigation (non-link)
1093  * @param mixed  $id Either a plain integer (assuming the key is 'id') or
1094  *                   an array of keys and values (e.g courseid => $courseid, itemid...)
1095  *
1096  * @return string
1097  */
1098 function grade_build_nav($path, $pagename=null, $id=null) {
1099     global $CFG, $COURSE, $PAGE;
1101     $strgrades = get_string('grades', 'grades');
1103     // Parse the path and build navlinks from its elements
1104     $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash
1105     $path = substr($path, $dirroot_length);
1106     $path = str_replace('\\', '/', $path);
1108     $path_elements = explode('/', $path);
1110     $path_elements_count = count($path_elements);
1112     // First link is always 'grade'
1113     $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id)));
1115     $link = null;
1116     $numberofelements = 3;
1118     // Prepare URL params string
1119     $linkparams = array();
1120     if (!is_null($id)) {
1121         if (is_array($id)) {
1122             foreach ($id as $idkey => $idvalue) {
1123                 $linkparams[$idkey] = $idvalue;
1124             }
1125         } else {
1126             $linkparams['id'] = $id;
1127         }
1128     }
1130     $navlink4 = null;
1132     // Remove file extensions from filenames
1133     foreach ($path_elements as $key => $filename) {
1134         $path_elements[$key] = str_replace('.php', '', $filename);
1135     }
1137     // Second level links
1138     switch ($path_elements[1]) {
1139         case 'edit': // No link
1140             if ($path_elements[3] != 'index.php') {
1141                 $numberofelements = 4;
1142             }
1143             break;
1144         case 'import': // No link
1145             break;
1146         case 'export': // No link
1147             break;
1148         case 'report':
1149             // $id is required for this link. Do not print it if $id isn't given
1150             if (!is_null($id)) {
1151                 $link = new moodle_url('/grade/report/index.php', $linkparams);
1152             }
1154             if ($path_elements[2] == 'grader') {
1155                 $numberofelements = 4;
1156             }
1157             break;
1159         default:
1160             // If this element isn't among the ones already listed above, it isn't supported, throw an error.
1161             debugging("grade_build_nav() doesn't support ". $path_elements[1] .
1162                     " as the second path element after 'grade'.");
1163             return false;
1164     }
1165     $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link);
1167     // Third level links
1168     if (empty($pagename)) {
1169         $pagename = get_string($path_elements[2], 'grades');
1170     }
1172     switch ($numberofelements) {
1173         case 3:
1174             $PAGE->navbar->add($pagename, $link);
1175             break;
1176         case 4:
1177             if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') {
1178                 $PAGE->navbar->add(get_string('modulename', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams));
1179             }
1180             $PAGE->navbar->add($pagename);
1181             break;
1182     }
1184     return '';
1187 /**
1188  * General structure representing grade items in course
1189  *
1190  * @package moodlecore
1191  * @copyright 2009 Nicolas Connault
1192  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1193  */
1194 class grade_structure {
1195     public $context;
1197     public $courseid;
1199     /**
1200      * 1D array of grade items only
1201      */
1202     public $items;
1204     /**
1205      * Returns icon of element
1206      *
1207      * @param array &$element An array representing an element in the grade_tree
1208      * @param bool  $spacerifnone return spacer if no icon found
1209      *
1210      * @return string icon or spacer
1211      */
1212     public function get_element_icon(&$element, $spacerifnone=false) {
1213         global $CFG, $OUTPUT;
1215         switch ($element['type']) {
1216             case 'item':
1217             case 'courseitem':
1218             case 'categoryitem':
1219                 $is_course   = $element['object']->is_course_item();
1220                 $is_category = $element['object']->is_category_item();
1221                 $is_scale    = $element['object']->gradetype == GRADE_TYPE_SCALE;
1222                 $is_value    = $element['object']->gradetype == GRADE_TYPE_VALUE;
1224                 if ($element['object']->is_calculated()) {
1225                     $strcalc = get_string('calculatedgrade', 'grades');
1226                     return '<img src="'.$OUTPUT->pix_url('i/calc') . '" class="icon itemicon" title="'.
1227                             s($strcalc).'" alt="'.s($strcalc).'"/>';
1229                 } else if (($is_course or $is_category) and ($is_scale or $is_value)) {
1230                     if ($category = $element['object']->get_item_category()) {
1231                         switch ($category->aggregation) {
1232                             case GRADE_AGGREGATE_MEAN:
1233                             case GRADE_AGGREGATE_MEDIAN:
1234                             case GRADE_AGGREGATE_WEIGHTED_MEAN:
1235                             case GRADE_AGGREGATE_WEIGHTED_MEAN2:
1236                             case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
1237                                 $stragg = get_string('aggregation', 'grades');
1238                                 return '<img src="'.$OUTPUT->pix_url('i/agg_mean') . '" ' .
1239                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1240                             case GRADE_AGGREGATE_SUM:
1241                                 $stragg = get_string('aggregation', 'grades');
1242                                 return '<img src="'.$OUTPUT->pix_url('i/agg_sum') . '" ' .
1243                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1244                         }
1245                     }
1247                 } else if ($element['object']->itemtype == 'mod') {
1248                     $strmodname = get_string('modulename', $element['object']->itemmodule);
1249                     return '<img src="'.$OUTPUT->pix_url('icon',
1250                             $element['object']->itemmodule) . '" ' .
1251                             'class="icon itemicon" title="' .s($strmodname).
1252                             '" alt="' .s($strmodname).'"/>';
1254                 } else if ($element['object']->itemtype == 'manual') {
1255                     if ($element['object']->is_outcome_item()) {
1256                         $stroutcome = get_string('outcome', 'grades');
1257                         return '<img src="'.$OUTPUT->pix_url('i/outcomes') . '" ' .
1258                                 'class="icon itemicon" title="'.s($stroutcome).
1259                                 '" alt="'.s($stroutcome).'"/>';
1260                     } else {
1261                         $strmanual = get_string('manualitem', 'grades');
1262                         return '<img src="'.$OUTPUT->pix_url('t/manual_item') . '" '.
1263                                 'class="icon itemicon" title="'.s($strmanual).
1264                                 '" alt="'.s($strmanual).'"/>';
1265                     }
1266                 }
1267                 break;
1269             case 'category':
1270                 $strcat = get_string('category', 'grades');
1271                 return '<img src="'.$OUTPUT->pix_url('f/folder') . '" class="icon itemicon" ' .
1272                         'title="'.s($strcat).'" alt="'.s($strcat).'" />';
1273         }
1275         if ($spacerifnone) {
1276             return '<img src="'.$CFG->wwwroot.'/pix/spacer.gif" class="icon itemicon" alt=""/>';
1277         } else {
1278             return '';
1279         }
1280     }
1282     /**
1283      * Returns name of element optionally with icon and link
1284      *
1285      * @param array &$element An array representing an element in the grade_tree
1286      * @param bool  $withlink Whether or not this header has a link
1287      * @param bool  $icon Whether or not to display an icon with this header
1288      * @param bool  $spacerifnone return spacer if no icon found
1289      *
1290      * @return string header
1291      */
1292     public function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false) {
1293         global $CFG;
1295         $header = '';
1297         if ($icon) {
1298             $header .= $this->get_element_icon($element, $spacerifnone);
1299         }
1301         $header .= $element['object']->get_name();
1303         if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and
1304             $element['type'] != 'courseitem') {
1305             return $header;
1306         }
1308         $itemtype     = $element['object']->itemtype;
1309         $itemmodule   = $element['object']->itemmodule;
1310         $iteminstance = $element['object']->iteminstance;
1312         if ($withlink and $itemtype=='mod' and $iteminstance and $itemmodule) {
1313             if ($cm = get_coursemodule_from_instance($itemmodule, $iteminstance, $this->courseid)) {
1315                 $a->name = get_string('modulename', $element['object']->itemmodule);
1316                 $title = get_string('linktoactivity', 'grades', $a);
1317                 $dir = $CFG->dirroot.'/mod/'.$itemmodule;
1319                 if (file_exists($dir.'/grade.php')) {
1320                     $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/grade.php?id='.$cm->id;
1321                 } else {
1322                     $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/view.php?id='.$cm->id;
1323                 }
1325                 $header = '<a href="'.$url.'" title="'.s($title).'">'.$header.'</a>';
1326             }
1327         }
1329         return $header;
1330     }
1332     /**
1333      * Returns the grade eid - the grade may not exist yet.
1334      *
1335      * @param grade_grade $grade_grade A grade_grade object
1336      *
1337      * @return string eid
1338      */
1339     public function get_grade_eid($grade_grade) {
1340         if (empty($grade_grade->id)) {
1341             return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid;
1342         } else {
1343             return 'g'.$grade_grade->id;
1344         }
1345     }
1347     /**
1348      * Returns the grade_item eid
1349      * @param grade_item $grade_item A grade_item object
1350      * @return string eid
1351      */
1352     public function get_item_eid($grade_item) {
1353         return 'i'.$grade_item->id;
1354     }
1356     /**
1357      * Given a grade_tree element, returns an array of parameters
1358      * used to build an icon for that element.
1359      *
1360      * @param array $element An array representing an element in the grade_tree
1361      *
1362      * @return array
1363      */
1364     public function get_params_for_iconstr($element) {
1365         $strparams = new stdClass();
1366         $strparams->category = '';
1367         $strparams->itemname = '';
1368         $strparams->itemmodule = '';
1370         if (!method_exists($element['object'], 'get_name')) {
1371             return $strparams;
1372         }
1374         $strparams->itemname = $element['object']->get_name();
1376         // If element name is categorytotal, get the name of the parent category
1377         if ($strparams->itemname == get_string('categorytotal', 'grades')) {
1378             $parent = $element['object']->get_parent_category();
1379             $strparams->category = $parent->get_name() . ' ';
1380         } else {
1381             $strparams->category = '';
1382         }
1384         $strparams->itemmodule = null;
1385         if (isset($element['object']->itemmodule)) {
1386             $strparams->itemmodule = $element['object']->itemmodule;
1387         }
1388         return $strparams;
1389     }
1391     /**
1392      * Return edit icon for give element
1393      *
1394      * @param array  $element An array representing an element in the grade_tree
1395      * @param object $gpr A grade_plugin_return object
1396      *
1397      * @return string
1398      */
1399     public function get_edit_icon($element, $gpr) {
1400         global $CFG, $OUTPUT;
1402         if (!has_capability('moodle/grade:manage', $this->context)) {
1403             if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
1404                 // oki - let them override grade
1405             } else {
1406                 return '';
1407             }
1408         }
1410         static $strfeedback   = null;
1411         static $streditgrade = null;
1412         if (is_null($streditgrade)) {
1413             $streditgrade = get_string('editgrade', 'grades');
1414             $strfeedback  = get_string('feedback');
1415         }
1417         $strparams = $this->get_params_for_iconstr($element);
1419         $object = $element['object'];
1421         switch ($element['type']) {
1422             case 'item':
1423             case 'categoryitem':
1424             case 'courseitem':
1425                 $stredit = get_string('editverbose', 'grades', $strparams);
1426                 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) {
1427                     $url = new moodle_url('/grade/edit/tree/item.php',
1428                             array('courseid' => $this->courseid, 'id' => $object->id));
1429                 } else {
1430                     $url = new moodle_url('/grade/edit/tree/outcomeitem.php',
1431                             array('courseid' => $this->courseid, 'id' => $object->id));
1432                 }
1433                 break;
1435             case 'category':
1436                 $stredit = get_string('editverbose', 'grades', $strparams);
1437                 $url = new moodle_url('/grade/edit/tree/category.php',
1438                         array('courseid' => $this->courseid, 'id' => $object->id));
1439                 break;
1441             case 'grade':
1442                 $stredit = $streditgrade;
1443                 if (empty($object->id)) {
1444                     $url = new moodle_url('/grade/edit/tree/grade.php',
1445                             array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid));
1446                 } else {
1447                     $url = new moodle_url('/grade/edit/tree/grade.php',
1448                             array('courseid' => $this->courseid, 'id' => $object->id));
1449                 }
1450                 if (!empty($object->feedback)) {
1451                     $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat)));
1452                 }
1453                 break;
1455             default:
1456                 $url = null;
1457         }
1459         if ($url) {
1460             return $OUTPUT->action_icon($gpr->add_url_params($url), $stredit, 't/edit', array('class'=>'iconsmall'));
1462         } else {
1463             return '';
1464         }
1465     }
1467     /**
1468      * Return hiding icon for give element
1469      *
1470      * @param array  $element An array representing an element in the grade_tree
1471      * @param object $gpr A grade_plugin_return object
1472      *
1473      * @return string
1474      */
1475     public function get_hiding_icon($element, $gpr) {
1476         global $CFG, $OUTPUT;
1478         if (!has_capability('moodle/grade:manage', $this->context) and
1479             !has_capability('moodle/grade:hide', $this->context)) {
1480             return '';
1481         }
1483         $strparams = $this->get_params_for_iconstr($element);
1484         $strshow = get_string('showverbose', 'grades', $strparams);
1485         $strhide = get_string('hideverbose', 'grades', $strparams);
1487         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1488         $url = $gpr->add_url_params($url);
1490         if ($element['object']->is_hidden()) {
1491             $type = 'show';
1492             $tooltip = $strshow;
1494             // Change the icon and add a tooltip showing the date
1495             if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) {
1496                 $type = 'hiddenuntil';
1497                 $tooltip = get_string('hiddenuntildate', 'grades',
1498                         userdate($element['object']->get_hidden()));
1499             }
1501             $url->param('action', 'show');
1503             $hideicon = $OUTPUT->action_icon($url, $tooltip, 't/'.$type, array('alt'=>$strshow, 'class'=>'iconsmall'));
1505         } else {
1506             $url->param('action', 'hide');
1507             $hideicon = $OUTPUT->action_icon($url, $strhide, 't/hide', array('class'=>'iconsmall'));
1508         }
1510         return $hideicon;
1511     }
1513     /**
1514      * Return locking icon for given element
1515      *
1516      * @param array  $element An array representing an element in the grade_tree
1517      * @param object $gpr A grade_plugin_return object
1518      *
1519      * @return string
1520      */
1521     public function get_locking_icon($element, $gpr) {
1522         global $CFG, $OUTPUT;
1524         $strparams = $this->get_params_for_iconstr($element);
1525         $strunlock = get_string('unlockverbose', 'grades', $strparams);
1526         $strlock = get_string('lockverbose', 'grades', $strparams);
1528         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1529         $url = $gpr->add_url_params($url);
1531         // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon
1532         if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) {
1533             $strparamobj = new stdClass();
1534             $strparamobj->itemname = $element['object']->grade_item->itemname;
1535             $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj);
1537             $action = $OUTPUT->image('t/unlock_gray', array('alt'=>$strnonunlockable, 'title'=>$strnonunlockable, 'class'=>'iconsmall'));
1539         } else if ($element['object']->is_locked()) {
1540             $type = 'unlock';
1541             $tooltip = $strunlock;
1543             // Change the icon and add a tooltip showing the date
1544             if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) {
1545                 $type = 'locktime';
1546                 $tooltip = get_string('locktimedate', 'grades',
1547                         userdate($element['object']->get_locktime()));
1548             }
1550             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) {
1551                 $action = '';
1552             } else {
1553                 $url->param('action', 'unlock');
1554                 $action = $OUTPUT->action_icon($url, $tooltip, 't/'.$type, array('alt'=>$strunlock, 'class'=>'smallicon'));
1555             }
1557         } else {
1558             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) {
1559                 $action = '';
1560             } else {
1561                 $url->param('action', 'lock');
1562                 $action = $OUTPUT->action_icon($url, $strlock, 't/lock', array('class'=>'smallicon'));
1563             }
1564         }
1566         return $action;
1567     }
1569     /**
1570      * Return calculation icon for given element
1571      *
1572      * @param array  $element An array representing an element in the grade_tree
1573      * @param object $gpr A grade_plugin_return object
1574      *
1575      * @return string
1576      */
1577     public function get_calculation_icon($element, $gpr) {
1578         global $CFG, $OUTPUT;
1579         if (!has_capability('moodle/grade:manage', $this->context)) {
1580             return '';
1581         }
1583         $type   = $element['type'];
1584         $object = $element['object'];
1586         if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
1587             $strparams = $this->get_params_for_iconstr($element);
1588             $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams);
1590             $is_scale = $object->gradetype == GRADE_TYPE_SCALE;
1591             $is_value = $object->gradetype == GRADE_TYPE_VALUE;
1593             // show calculation icon only when calculation possible
1594             if (!$object->is_external_item() and ($is_scale or $is_value)) {
1595                 if ($object->is_calculated()) {
1596                     $icon = 't/calc';
1597                 } else {
1598                     $icon = 't/calc_off';
1599                 }
1601                 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id));
1602                 $url = $gpr->add_url_params($url);
1603                 return $OUTPUT->action_icon($url, $streditcalculation, $icon, array('class'=>'smallicon')) . "\n";
1604             }
1605         }
1607         return '';
1608     }
1611 /**
1612  * Flat structure similar to grade tree.
1613  *
1614  * @uses grade_structure
1615  * @package moodlecore
1616  * @copyright 2009 Nicolas Connault
1617  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1618  */
1619 class grade_seq extends grade_structure {
1621     /**
1622      * 1D array of elements
1623      */
1624     public $elements;
1626     /**
1627      * Constructor, retrieves and stores array of all grade_category and grade_item
1628      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1629      *
1630      * @param int  $courseid The course id
1631      * @param bool $category_grade_last category grade item is the last child
1632      * @param bool $nooutcomes Whether or not outcomes should be included
1633      */
1634     public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) {
1635         global $USER, $CFG;
1637         $this->courseid   = $courseid;
1638         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1640         // get course grade tree
1641         $top_element = grade_category::fetch_course_tree($courseid, true);
1643         $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes);
1645         foreach ($this->elements as $key=>$unused) {
1646             $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object'];
1647         }
1648     }
1650     /**
1651      * Static recursive helper - makes the grade_item for category the last children
1652      *
1653      * @param array &$element The seed of the recursion
1654      * @param bool $category_grade_last category grade item is the last child
1655      * @param bool $nooutcomes Whether or not outcomes should be included
1656      *
1657      * @return array
1658      */
1659     public function flatten(&$element, $category_grade_last, $nooutcomes) {
1660         if (empty($element['children'])) {
1661             return array();
1662         }
1663         $children = array();
1665         foreach ($element['children'] as $sortorder=>$unused) {
1666             if ($nooutcomes and $element['type'] != 'category' and
1667                 $element['children'][$sortorder]['object']->is_outcome_item()) {
1668                 continue;
1669             }
1670             $children[] = $element['children'][$sortorder];
1671         }
1672         unset($element['children']);
1674         if ($category_grade_last and count($children) > 1) {
1675             $cat_item = array_shift($children);
1676             array_push($children, $cat_item);
1677         }
1679         $result = array();
1680         foreach ($children as $child) {
1681             if ($child['type'] == 'category') {
1682                 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes);
1683             } else {
1684                 $child['eid'] = 'i'.$child['object']->id;
1685                 $result[$child['object']->id] = $child;
1686             }
1687         }
1689         return $result;
1690     }
1692     /**
1693      * Parses the array in search of a given eid and returns a element object with
1694      * information about the element it has found.
1695      *
1696      * @param int $eid Gradetree Element ID
1697      *
1698      * @return object element
1699      */
1700     public function locate_element($eid) {
1701         // it is a grade - construct a new object
1702         if (strpos($eid, 'n') === 0) {
1703             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1704                 return null;
1705             }
1707             $itemid = $matches[1];
1708             $userid = $matches[2];
1710             //extra security check - the grade item must be in this tree
1711             if (!$item_el = $this->locate_element('i'.$itemid)) {
1712                 return null;
1713             }
1715             // $gradea->id may be null - means does not exist yet
1716             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1718             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1719             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1721         } else if (strpos($eid, 'g') === 0) {
1722             $id = (int) substr($eid, 1);
1723             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1724                 return null;
1725             }
1726             //extra security check - the grade item must be in this tree
1727             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1728                 return null;
1729             }
1730             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1731             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1732         }
1734         // it is a category or item
1735         foreach ($this->elements as $element) {
1736             if ($element['eid'] == $eid) {
1737                 return $element;
1738             }
1739         }
1741         return null;
1742     }
1745 /**
1746  * This class represents a complete tree of categories, grade_items and final grades,
1747  * organises as an array primarily, but which can also be converted to other formats.
1748  * It has simple method calls with complex implementations, allowing for easy insertion,
1749  * deletion and moving of items and categories within the tree.
1750  *
1751  * @uses grade_structure
1752  * @package moodlecore
1753  * @copyright 2009 Nicolas Connault
1754  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1755  */
1756 class grade_tree extends grade_structure {
1758     /**
1759      * The basic representation of the tree as a hierarchical, 3-tiered array.
1760      * @var object $top_element
1761      */
1762     public $top_element;
1764     /**
1765      * 2D array of grade items and categories
1766      * @var array $levels
1767      */
1768     public $levels;
1770     /**
1771      * Grade items
1772      * @var array $items
1773      */
1774     public $items;
1776     /**
1777      * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item
1778      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1779      *
1780      * @param int   $courseid The Course ID
1781      * @param bool  $fillers include fillers and colspans, make the levels var "rectangular"
1782      * @param bool  $category_grade_last category grade item is the last child
1783      * @param array $collapsed array of collapsed categories
1784      * @param bool  $nooutcomes Whether or not outcomes should be included
1785      */
1786     public function grade_tree($courseid, $fillers=true, $category_grade_last=false,
1787                                $collapsed=null, $nooutcomes=false) {
1788         global $USER, $CFG;
1790         $this->courseid   = $courseid;
1791         $this->levels     = array();
1792         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1794         // get course grade tree
1795         $this->top_element = grade_category::fetch_course_tree($courseid, true);
1797         // collapse the categories if requested
1798         if (!empty($collapsed)) {
1799             grade_tree::category_collapse($this->top_element, $collapsed);
1800         }
1802         // no otucomes if requested
1803         if (!empty($nooutcomes)) {
1804             grade_tree::no_outcomes($this->top_element);
1805         }
1807         // move category item to last position in category
1808         if ($category_grade_last) {
1809             grade_tree::category_grade_last($this->top_element);
1810         }
1812         if ($fillers) {
1813             // inject fake categories == fillers
1814             grade_tree::inject_fillers($this->top_element, 0);
1815             // add colspans to categories and fillers
1816             grade_tree::inject_colspans($this->top_element);
1817         }
1819         grade_tree::fill_levels($this->levels, $this->top_element, 0);
1821     }
1823     /**
1824      * Static recursive helper - removes items from collapsed categories
1825      *
1826      * @param array &$element The seed of the recursion
1827      * @param array $collapsed array of collapsed categories
1828      *
1829      * @return void
1830      */
1831     public function category_collapse(&$element, $collapsed) {
1832         if ($element['type'] != 'category') {
1833             return;
1834         }
1835         if (empty($element['children']) or count($element['children']) < 2) {
1836             return;
1837         }
1839         if (in_array($element['object']->id, $collapsed['aggregatesonly'])) {
1840             $category_item = reset($element['children']); //keep only category item
1841             $element['children'] = array(key($element['children'])=>$category_item);
1843         } else {
1844             if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item
1845                 reset($element['children']);
1846                 $first_key = key($element['children']);
1847                 unset($element['children'][$first_key]);
1848             }
1849             foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children
1850                 grade_tree::category_collapse($element['children'][$sortorder], $collapsed);
1851             }
1852         }
1853     }
1855     /**
1856      * Static recursive helper - removes all outcomes
1857      *
1858      * @param array &$element The seed of the recursion
1859      *
1860      * @return void
1861      */
1862     public function no_outcomes(&$element) {
1863         if ($element['type'] != 'category') {
1864             return;
1865         }
1866         foreach ($element['children'] as $sortorder=>$child) {
1867             if ($element['children'][$sortorder]['type'] == 'item'
1868               and $element['children'][$sortorder]['object']->is_outcome_item()) {
1869                 unset($element['children'][$sortorder]);
1871             } else if ($element['children'][$sortorder]['type'] == 'category') {
1872                 grade_tree::no_outcomes($element['children'][$sortorder]);
1873             }
1874         }
1875     }
1877     /**
1878      * Static recursive helper - makes the grade_item for category the last children
1879      *
1880      * @param array &$element The seed of the recursion
1881      *
1882      * @return void
1883      */
1884     public function category_grade_last(&$element) {
1885         if (empty($element['children'])) {
1886             return;
1887         }
1888         if (count($element['children']) < 2) {
1889             return;
1890         }
1891         $first_item = reset($element['children']);
1892         if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') {
1893             // the category item might have been already removed
1894             $order = key($element['children']);
1895             unset($element['children'][$order]);
1896             $element['children'][$order] =& $first_item;
1897         }
1898         foreach ($element['children'] as $sortorder => $child) {
1899             grade_tree::category_grade_last($element['children'][$sortorder]);
1900         }
1901     }
1903     /**
1904      * Static recursive helper - fills the levels array, useful when accessing tree elements of one level
1905      *
1906      * @param array &$levels The levels of the grade tree through which to recurse
1907      * @param array &$element The seed of the recursion
1908      * @param int   $depth How deep are we?
1909      * @return void
1910      */
1911     public function fill_levels(&$levels, &$element, $depth) {
1912         if (!array_key_exists($depth, $levels)) {
1913             $levels[$depth] = array();
1914         }
1916         // prepare unique identifier
1917         if ($element['type'] == 'category') {
1918             $element['eid'] = 'c'.$element['object']->id;
1919         } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
1920             $element['eid'] = 'i'.$element['object']->id;
1921             $this->items[$element['object']->id] =& $element['object'];
1922         }
1924         $levels[$depth][] =& $element;
1925         $depth++;
1926         if (empty($element['children'])) {
1927             return;
1928         }
1929         $prev = 0;
1930         foreach ($element['children'] as $sortorder=>$child) {
1931             grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth);
1932             $element['children'][$sortorder]['prev'] = $prev;
1933             $element['children'][$sortorder]['next'] = 0;
1934             if ($prev) {
1935                 $element['children'][$prev]['next'] = $sortorder;
1936             }
1937             $prev = $sortorder;
1938         }
1939     }
1941     /**
1942      * Static recursive helper - makes full tree (all leafes are at the same level)
1943      *
1944      * @param array &$element The seed of the recursion
1945      * @param int   $depth How deep are we?
1946      *
1947      * @return int
1948      */
1949     public function inject_fillers(&$element, $depth) {
1950         $depth++;
1952         if (empty($element['children'])) {
1953             return $depth;
1954         }
1955         $chdepths = array();
1956         $chids = array_keys($element['children']);
1957         $last_child  = end($chids);
1958         $first_child = reset($chids);
1960         foreach ($chids as $chid) {
1961             $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth);
1962         }
1963         arsort($chdepths);
1965         $maxdepth = reset($chdepths);
1966         foreach ($chdepths as $chid=>$chd) {
1967             if ($chd == $maxdepth) {
1968                 continue;
1969             }
1970             for ($i=0; $i < $maxdepth-$chd; $i++) {
1971                 if ($chid == $first_child) {
1972                     $type = 'fillerfirst';
1973                 } else if ($chid == $last_child) {
1974                     $type = 'fillerlast';
1975                 } else {
1976                     $type = 'filler';
1977                 }
1978                 $oldchild =& $element['children'][$chid];
1979                 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type,
1980                                                     'eid'=>'', 'depth'=>$element['object']->depth,
1981                                                     'children'=>array($oldchild));
1982             }
1983         }
1985         return $maxdepth;
1986     }
1988     /**
1989      * Static recursive helper - add colspan information into categories
1990      *
1991      * @param array &$element The seed of the recursion
1992      *
1993      * @return int
1994      */
1995     public function inject_colspans(&$element) {
1996         if (empty($element['children'])) {
1997             return 1;
1998         }
1999         $count = 0;
2000         foreach ($element['children'] as $key=>$child) {
2001             $count += grade_tree::inject_colspans($element['children'][$key]);
2002         }
2003         $element['colspan'] = $count;
2004         return $count;
2005     }
2007     /**
2008      * Parses the array in search of a given eid and returns a element object with
2009      * information about the element it has found.
2010      * @param int $eid Gradetree Element ID
2011      * @return object element
2012      */
2013     public function locate_element($eid) {
2014         // it is a grade - construct a new object
2015         if (strpos($eid, 'n') === 0) {
2016             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
2017                 return null;
2018             }
2020             $itemid = $matches[1];
2021             $userid = $matches[2];
2023             //extra security check - the grade item must be in this tree
2024             if (!$item_el = $this->locate_element('i'.$itemid)) {
2025                 return null;
2026             }
2028             // $gradea->id may be null - means does not exist yet
2029             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
2031             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
2032             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
2034         } else if (strpos($eid, 'g') === 0) {
2035             $id = (int) substr($eid, 1);
2036             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
2037                 return null;
2038             }
2039             //extra security check - the grade item must be in this tree
2040             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
2041                 return null;
2042             }
2043             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
2044             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
2045         }
2047         // it is a category or item
2048         foreach ($this->levels as $row) {
2049             foreach ($row as $element) {
2050                 if ($element['type'] == 'filler') {
2051                     continue;
2052                 }
2053                 if ($element['eid'] == $eid) {
2054                     return $element;
2055                 }
2056             }
2057         }
2059         return null;
2060     }
2062     /**
2063      * Returns a well-formed XML representation of the grade-tree using recursion.
2064      *
2065      * @param array  $root The current element in the recursion. If null, starts at the top of the tree.
2066      * @param string $tabs The control character to use for tabs
2067      *
2068      * @return string $xml
2069      */
2070     public function exporttoxml($root=null, $tabs="\t") {
2071         $xml = null;
2072         $first = false;
2073         if (is_null($root)) {
2074             $root = $this->top_element;
2075             $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
2076             $xml .= "<gradetree>\n";
2077             $first = true;
2078         }
2080         $type = 'undefined';
2081         if (strpos($root['object']->table, 'grade_categories') !== false) {
2082             $type = 'category';
2083         } else if (strpos($root['object']->table, 'grade_items') !== false) {
2084             $type = 'item';
2085         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
2086             $type = 'outcome';
2087         }
2089         $xml .= "$tabs<element type=\"$type\">\n";
2090         foreach ($root['object'] as $var => $value) {
2091             if (!is_object($value) && !is_array($value) && !empty($value)) {
2092                 $xml .= "$tabs\t<$var>$value</$var>\n";
2093             }
2094         }
2096         if (!empty($root['children'])) {
2097             $xml .= "$tabs\t<children>\n";
2098             foreach ($root['children'] as $sortorder => $child) {
2099                 $xml .= $this->exportToXML($child, $tabs."\t\t");
2100             }
2101             $xml .= "$tabs\t</children>\n";
2102         }
2104         $xml .= "$tabs</element>\n";
2106         if ($first) {
2107             $xml .= "</gradetree>";
2108         }
2110         return $xml;
2111     }
2113     /**
2114      * Returns a JSON representation of the grade-tree using recursion.
2115      *
2116      * @param array $root The current element in the recursion. If null, starts at the top of the tree.
2117      * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy
2118      *
2119      * @return string
2120      */
2121     public function exporttojson($root=null, $tabs="\t") {
2122         $json = null;
2123         $first = false;
2124         if (is_null($root)) {
2125             $root = $this->top_element;
2126             $first = true;
2127         }
2129         $name = '';
2132         if (strpos($root['object']->table, 'grade_categories') !== false) {
2133             $name = $root['object']->fullname;
2134             if ($name == '?') {
2135                 $name = $root['object']->get_name();
2136             }
2137         } else if (strpos($root['object']->table, 'grade_items') !== false) {
2138             $name = $root['object']->itemname;
2139         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
2140             $name = $root['object']->itemname;
2141         }
2143         $json .= "$tabs {\n";
2144         $json .= "$tabs\t \"type\": \"{$root['type']}\",\n";
2145         $json .= "$tabs\t \"name\": \"$name\",\n";
2147         foreach ($root['object'] as $var => $value) {
2148             if (!is_object($value) && !is_array($value) && !empty($value)) {
2149                 $json .= "$tabs\t \"$var\": \"$value\",\n";
2150             }
2151         }
2153         $json = substr($json, 0, strrpos($json, ','));
2155         if (!empty($root['children'])) {
2156             $json .= ",\n$tabs\t\"children\": [\n";
2157             foreach ($root['children'] as $sortorder => $child) {
2158                 $json .= $this->exportToJSON($child, $tabs."\t\t");
2159             }
2160             $json = substr($json, 0, strrpos($json, ','));
2161             $json .= "\n$tabs\t]\n";
2162         }
2164         if ($first) {
2165             $json .= "\n}";
2166         } else {
2167             $json .= "\n$tabs},\n";
2168         }
2170         return $json;
2171     }
2173     /**
2174      * Returns the array of levels
2175      *
2176      * @return array
2177      */
2178     public function get_levels() {
2179         return $this->levels;
2180     }
2182     /**
2183      * Returns the array of grade items
2184      *
2185      * @return array
2186      */
2187     public function get_items() {
2188         return $this->items;
2189     }
2191     /**
2192      * Returns a specific Grade Item
2193      *
2194      * @param int $itemid The ID of the grade_item object
2195      *
2196      * @return grade_item
2197      */
2198     public function get_item($itemid) {
2199         if (array_key_exists($itemid, $this->items)) {
2200             return $this->items[$itemid];
2201         } else {
2202             return false;
2203         }
2204     }
2207 /**
2208  * Local shortcut function for creating an edit/delete button for a grade_* object.
2209  * @param strong $type 'edit' or 'delete'
2210  * @param int $courseid The Course ID
2211  * @param grade_* $object The grade_* object
2212  * @return string html
2213  */
2214 function grade_button($type, $courseid, $object) {
2215     global $CFG, $OUTPUT;
2216     if (preg_match('/grade_(.*)/', get_class($object), $matches)) {
2217         $objectidstring = $matches[1] . 'id';
2218     } else {
2219         throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!');
2220     }
2222     $strdelete = get_string('delete');
2223     $stredit   = get_string('edit');
2225     if ($type == 'delete') {
2226         $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey()));
2227     } else if ($type == 'edit') {
2228         $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id));
2229     }
2231     return $OUTPUT->action_icon($url, ${'str'.$type}, 't/'.$type, array('class'=>'iconsmall'));