MDL-20204 using url_select
[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, $active_type, $active_plugin, $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         $sectionname = $plugin_info['strings'][$plugin_type];
350         $section = array();
351         
352         foreach ($plugins as $plugin) {
353             // TODO: this is messy, fix plugin_info instead...
354             $link = str_replace($CFG->wwwroot, '', $plugin->link);
355             $link = str_replace('&amp;', '&', $link);
356             $section[$link] = $plugin->string;
357             $count++;
358             if ($plugin_type === $active_type and $plugin->id === $active_plugin) {
359                 $active = $link;
360             }
361         }
363         if ($section) {
364             $menu[] = array($sectionname=>$section);
365         }
366     }
368     // finally print/return the popup form
369     if ($count > 1) {
370         $select = new url_select($menu, $active, null, 'choosepluginreport');
372         if ($return) {
373             return $OUTPUT->render($select);
374         } else {
375             echo $OUTPUT->render($select);
376         }
377     } else {
378         // only one option - no plugin selector needed
379         return '';
380     }
383 /**
384  * Print grading plugin selection tab-based navigation.
385  *
386  * @param string  $active_type type of plugin on current page - import, export, report or edit
387  * @param string  $active_plugin active plugin type - grader, user, cvs, ...
388  * @param array   $plugin_info Array of plugins
389  * @param boolean $return return as string
390  *
391  * @return nothing or string if $return true
392  */
393 function grade_print_tabs($active_type, $active_plugin, $plugin_info, $return=false) {
394     global $CFG, $COURSE;
396     if (!isset($currenttab)) {
397         $currenttab = '';
398     }
400     $tabs = array();
401     $top_row  = array();
402     $bottom_row = array();
403     $inactive = array($active_plugin);
404     $activated = array();
406     $count = 0;
407     $active = '';
409     foreach ($plugin_info as $plugin_type => $plugins) {
410         if ($plugin_type == 'strings') {
411             continue;
412         }
414         // If $plugins is actually the definition of a child-less parent link:
415         if (!empty($plugins->id)) {
416             $string = $plugins->string;
417             if (!empty($plugin_info[$active_type]->parent)) {
418                 $string = $plugin_info[$active_type]->parent->string;
419             }
421             $top_row[] = new tabobject($plugin_type, $plugins->link, $string);
422             continue;
423         }
425         $first_plugin = reset($plugins);
426         $url = $first_plugin->link;
428         if ($plugin_type == 'report') {
429             $url = $CFG->wwwroot.'/grade/report/index.php?id='.$COURSE->id;
430         }
432         $top_row[] = new tabobject($plugin_type, $url, $plugin_info['strings'][$plugin_type]);
434         if ($active_type == $plugin_type) {
435             foreach ($plugins as $plugin) {
436                 $bottom_row[] = new tabobject($plugin->id, $plugin->link, $plugin->string);
437                 if ($plugin->id == $active_plugin) {
438                     $inactive = array($plugin->id);
439                 }
440             }
441         }
442     }
444     $tabs[] = $top_row;
445     $tabs[] = $bottom_row;
447     if ($return) {
448         return print_tabs($tabs, $active_type, $inactive, $activated, true);
449     } else {
450         print_tabs($tabs, $active_type, $inactive, $activated);
451     }
454 /**
455  * grade_get_plugin_info
456  *
457  * @param int    $courseid The course id
458  * @param string $active_type type of plugin on current page - import, export, report or edit
459  * @param string $active_plugin active plugin type - grader, user, cvs, ...
460  *
461  * @return array
462  */
463 function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
464     global $CFG;
466     $context = get_context_instance(CONTEXT_COURSE, $courseid);
468     $plugin_info = array();
469     $count = 0;
470     $active = '';
471     $url_prefix = $CFG->wwwroot . '/grade/';
473     // Language strings
474     $plugin_info['strings'] = array(
475         'report' => get_string('view'),
476         'edittree' => get_string('edittree', 'grades'),
477         'scale' => get_string('scales'),
478         'outcome' => get_string('outcomes', 'grades'),
479         'letter' => get_string('letters', 'grades'),
480         'export' => get_string('export', 'grades'),
481         'import' => get_string('import'),
482         'preferences' => get_string('mypreferences', 'grades'),
483         'settings' => get_string('settings'));
485     // Settings tab first
486     if (has_capability('moodle/course:update', $context)) {
487         $url = $url_prefix.'edit/settings/index.php?id='.$courseid;
489         if ($active_type == 'settings' and $active_plugin == 'course') {
490             $active = $url;
491         }
493         $plugin_info['settings'] = array();
494         $plugin_info['settings']['course'] =
495                 new grade_plugin_info('coursesettings', $url, get_string('course'));
496         $count++;
497     }
500     // report plugins with its special structure
502     // Get all installed reports
503     if ($reports = get_plugin_list('gradereport')) {
505         // Remove ones we can't see
506         foreach ($reports as $plugin => $unused) {
507             if (!has_capability('gradereport/'.$plugin.':view', $context)) {
508                 unset($reports[$plugin]);
509             }
510         }
511     }
513     $reportnames = array();
515     if (!empty($reports)) {
516         foreach ($reports as $plugin => $plugindir) {
517             $pluginstr = get_string('modulename', 'gradereport_'.$plugin);
518             $url = $url_prefix.'report/'.$plugin.'/index.php?id='.$courseid;
519             if ($active_type == 'report' and $active_plugin == $plugin ) {
520                 $active = $url;
521             }
522             $reportnames[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
524             // Add link to preferences tab if such a page exists
525             if (file_exists($plugindir.'/preferences.php')) {
526                 $pref_url = $url_prefix.'report/'.$plugin.'/preferences.php?id='.$courseid;
527                 $plugin_info['preferences'][$plugin] = new grade_plugin_info($plugin, $pref_url, $pluginstr);
528             }
530             $count++;
531         }
532         asort($reportnames);
533     }
534     if (!empty($reportnames)) {
535         $plugin_info['report']=$reportnames;
536     }
538     // editing scripts - not real plugins
539     if (has_capability('moodle/grade:manage', $context)
540       or has_capability('moodle/grade:manageletters', $context)
541       or has_capability('moodle/course:managescales', $context)
542       or has_capability('moodle/course:update', $context)) {
544         if (has_capability('moodle/grade:manage', $context)) {
545             $url = $url_prefix.'edit/tree/index.php?sesskey='.sesskey().
546                     '&amp;showadvanced=0&amp;id='.$courseid;
547             $url_adv = $url_prefix.'edit/tree/index.php?sesskey='.sesskey().
548                     '&amp;showadvanced=1&amp;id='.$courseid;
550             if ($active_type == 'edittree' and $active_plugin == 'simpleview') {
551                 $active = $url;
552             } else if ($active_type == 'edittree' and $active_plugin == 'fullview') {
553                 $active = $url_adv;
554             }
556             $plugin_info['edittree'] = array();
557             $plugin_info['edittree']['simpleview'] =
558                     new grade_plugin_info('simpleview', $url, get_string('simpleview', 'grades'));
559             $plugin_info['edittree']['fullview'] =
560                     new grade_plugin_info('fullview', $url_adv, get_string('fullview', 'grades'));
561             $count++;
562         }
564         if (has_capability('moodle/course:managescales', $context)) {
565             $url = $url_prefix.'edit/scale/index.php?id='.$courseid;
567             if ($active_type == 'scale' and is_null($active_plugin)) {
568                 $active = $url;
569             }
571             $plugin_info['scale'] = array();
573             if ($active_type == 'scale' and $active_plugin == 'edit') {
574                 $edit_url = $url_prefix.'edit/scale/edit.php?courseid='.$courseid.
575                         '&amp;id='.optional_param('id', 0, PARAM_INT);
576                 $active = $edit_url;
577                 $parent = new grade_plugin_info('scale', $url, get_string('scales'));
578                 $plugin_info['scale']['view'] =
579                         new grade_plugin_info('edit', $edit_url, get_string('edit'), $parent);
580             } else {
581                 $plugin_info['scale']['view'] =
582                         new grade_plugin_info('scale', $url, get_string('view'));
583             }
585             $count++;
586         }
588         if (!empty($CFG->enableoutcomes) && (has_capability('moodle/grade:manage', $context) or
589                                              has_capability('moodle/course:update', $context))) {
591             $url_course = $url_prefix.'edit/outcome/course.php?id='.$courseid;
592             $url_edit = $url_prefix.'edit/outcome/index.php?id='.$courseid;
594             $plugin_info['outcome'] = array();
596             if (has_capability('moodle/course:update', $context)) {  // Default to course assignment
597                 $plugin_info['outcome']['course'] =
598                         new grade_plugin_info('course', $url_course, get_string('outcomescourse', 'grades'));
599                 $plugin_info['outcome']['edit'] =
600                         new grade_plugin_info('edit', $url_edit, get_string('editoutcomes', 'grades'));
601             } else {
602                 $plugin_info['outcome'] =
603                         new grade_plugin_info('edit', $url_course, get_string('outcomescourse', 'grades'));
604             }
606             if ($active_type == 'outcome' and is_null($active_plugin)) {
607                 $active = $url_edit;
608             } else if ($active_type == 'outcome' and $active_plugin == 'course' ) {
609                 $active = $url_course;
610             } else if ($active_type == 'outcome' and $active_plugin == 'edit' ) {
611                 $active = $url_edit;
612             } else if ($active_type == 'outcome' and $active_plugin == 'import') {
613                 $plugin_info['outcome']['import'] =
614                         new grade_plugin_info('import', null, get_string('importoutcomes', 'grades'));
615             }
617             $count++;
618         }
620         if (has_capability('moodle/grade:manage', $context) or
621                     has_capability('moodle/grade:manageletters', $context)) {
622             $course_context = get_context_instance(CONTEXT_COURSE, $courseid);
623             $url = $url_prefix.'edit/letter/index.php?id='.$courseid;
624             $url_edit = $url_prefix.'edit/letter/edit.php?id='.$course_context->id;
626             if ($active_type == 'letter' and $active_plugin == 'view' ) {
627                 $active = $url;
628             } else if ($active_type == 'letter' and $active_plugin == 'edit' ) {
629                 $active = $url_edit;
630             }
632             $plugin_info['letter'] = array();
633             $plugin_info['letter']['view'] = new grade_plugin_info('view', $url, get_string('view'));
634             $plugin_info['letter']['edit'] = new grade_plugin_info('edit', $url_edit, get_string('edit'));
635             $count++;
636         }
637     }
639     // standard import plugins
640     if ($imports = get_plugin_list('gradeimport')) { // Get all installed import plugins
641         foreach ($imports as $plugin => $plugindir) { // Remove ones we can't see
642             if (!has_capability('gradeimport/'.$plugin.':view', $context)) {
643                 unset($imports[$plugin]);
644             }
645         }
646     }
647     $importnames = array();
648     if (!empty($imports)) {
649         foreach ($imports as $plugin => $plugindir) {
650             $pluginstr = get_string('modulename', 'gradeimport_'.$plugin);
651             $url = $url_prefix.'import/'.$plugin.'/index.php?id='.$courseid;
652             if ($active_type == 'import' and $active_plugin == $plugin ) {
653                 $active = $url;
654             }
655             $importnames[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
656             $count++;
657         }
658         asort($importnames);
659     }
660     if (!empty($importnames)) {
661         $plugin_info['import']=$importnames;
662     }
664     // standard export plugins
665     if ($exports = get_plugin_list('gradeexport')) { // Get all installed export plugins
666         foreach ($exports as $plugin => $plugindir) { // Remove ones we can't see
667             if (!has_capability('gradeexport/'.$plugin.':view', $context)) {
668                 unset($exports[$plugin]);
669             }
670         }
671     }
672     $exportnames = array();
673     if (!empty($exports)) {
674         foreach ($exports as $plugin => $plugindir) {
675             $pluginstr = get_string('modulename', 'gradeexport_'.$plugin);
676             $url = $url_prefix.'export/'.$plugin.'/index.php?id='.$courseid;
677             if ($active_type == 'export' and $active_plugin == $plugin ) {
678                 $active = $url;
679             }
680             $exportnames[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
681             $count++;
682         }
683         asort($exportnames);
684     }
686     if (!empty($exportnames)) {
687         $plugin_info['export']=$exportnames;
688     }
690     // Key managers
691     if ($CFG->gradepublishing) {
692         $keymanager_url = $url_prefix.'export/keymanager.php?id='.$courseid;
693         $plugin_info['export']['keymanager'] =
694                 new grade_plugin_info('keymanager', $keymanager_url, get_string('keymanager', 'grades'));
695         if ($active_type == 'export' and $active_plugin == 'keymanager' ) {
696             $active = $keymanager_url;
697         }
698         $count++;
700         $keymanager_url = $url_prefix.'import/keymanager.php?id='.$courseid;
701         $plugin_info['import']['keymanager'] =
702                 new grade_plugin_info('keymanager', $keymanager_url, get_string('keymanager', 'grades'));
703         if ($active_type == 'import' and $active_plugin == 'keymanager' ) {
704             $active = $keymanager_url;
705         }
706         $count++;
707     }
710     foreach ($plugin_info as $plugin_type => $plugins) {
711         if (!empty($plugins->id) && $active_plugin == $plugins->id) {
712             $plugin_info['strings']['active_plugin_str'] = $plugins->string;
713             break;
714         }
715         foreach ($plugins as $plugin) {
716             if (is_a($plugin, 'grade_plugin_info')) {
717                 if ($active_plugin == $plugin->id) {
718                     $plugin_info['strings']['active_plugin_str'] = $plugin->string;
719                 }
720             }
721         }
722     }
724     // Put settings last
725     if (!empty($plugin_info['settings'])) {
726         $settings = $plugin_info['settings'];
727         unset($plugin_info['settings']);
728         $plugin_info['settings'] = $settings;
729     }
731     // Put preferences last
732     if (!empty($plugin_info['preferences'])) {
733         $prefs = $plugin_info['preferences'];
734         unset($plugin_info['preferences']);
735         $plugin_info['preferences'] = $prefs;
736     }
738     // Check import and export caps
739     if (!has_capability('moodle/grade:export', $context)) {
740         unset($plugin_info['export']);
741     }
742     if (!has_capability('moodle/grade:import', $context)) {
743         unset($plugin_info['import']);
744     }
745     return $plugin_info;
748 /**
749  * A simple class containing info about grade plugins.
750  * Can be subclassed for special rules
751  *
752  * @package moodlecore
753  * @copyright 2009 Nicolas Connault
754  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
755  */
756 class grade_plugin_info {
757     /**
758      * A unique id for this plugin
759      *
760      * @var mixed
761      */
762     public $id;
763     /**
764      * A URL to access this plugin
765      *
766      * @var mixed
767      */
768     public $link;
769     /**
770      * The name of this plugin
771      *
772      * @var mixed
773      */
774     public $string;
775     /**
776      * Another grade_plugin_info object, parent of the current one
777      *
778      * @var mixed
779      */
780     public $parent;
782     /**
783      * Constructor
784      *
785      * @param int $id A unique id for this plugin
786      * @param string $link A URL to access this plugin
787      * @param string $string The name of this plugin
788      * @param object $parent Another grade_plugin_info object, parent of the current one
789      *
790      * @return void
791      */
792     public function __construct($id, $link, $string, $parent=null) {
793         $this->id = $id;
794         $this->link = $link;
795         $this->string = $string;
796         $this->parent = $parent;
797     }
800 /**
801  * Prints the page headers, breadcrumb trail, page heading, (optional) dropdown navigation menu and
802  * (optional) navigation tabs for any gradebook page. All gradebook pages MUST use these functions
803  * in favour of the usual print_header(), print_header_simple(), print_heading() etc.
804  * !IMPORTANT! Use of tabs.php file in gradebook pages is forbidden unless tabs are switched off at
805  * the site level for the gradebook ($CFG->grade_navmethod = GRADE_NAVMETHOD_DROPDOWN).
806  *
807  * @param int     $courseid Course id
808  * @param string  $active_type The type of the current page (report, settings,
809  *                             import, export, scales, outcomes, letters)
810  * @param string  $active_plugin The plugin of the current page (grader, fullview etc...)
811  * @param string  $heading The heading of the page. Tries to guess if none is given
812  * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function
813  * @param string  $bodytags Additional attributes that will be added to the <body> tag
814  * @param string  $buttons Additional buttons to display on the page
815  *
816  * @return string HTML code or nothing if $return == false
817  */
818 function print_grade_page_head($courseid, $active_type, $active_plugin=null,
819                                $heading = false, $return=false,
820                                $buttons=false) {
821     global $CFG, $COURSE, $OUTPUT, $PAGE;
822     $strgrades = get_string('grades');
823     $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin);
825     // Determine the string of the active plugin
826     $stractive_plugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading;
827     $stractive_type = $plugin_info['strings'][$active_type];
829     $first_link = '';
831     if ($active_type == 'settings' && $active_plugin != 'coursesettings') {
832         $first_link = $plugin_info['report'][$active_plugin]->link;
833     } else if ($active_type != 'report') {
834         $first_link = $CFG->wwwroot.'/grade/index.php?id='.$COURSE->id;
835     }
838     $PAGE->navbar->add($strgrades, $first_link);
840     $active_type_link = '';
842     if (!empty($plugin_info[$active_type]->link) && $plugin_info[$active_type]->link != qualified_me()) {
843         $active_type_link = $plugin_info[$active_type]->link;
844     }
846     if (!empty($plugin_info[$active_type]->parent->link)) {
847         $active_type_link = $plugin_info[$active_type]->parent->link;
848         $PAGE->navbar->add($stractive_type, $active_type_link);
849     }
851     if (empty($plugin_info[$active_type]->id)) {
852         $PAGE->navbar->add($stractive_type, $active_type_link);
853     }
855     $PAGE->navbar->add($stractive_plugin);
857     $title = ': ' . $stractive_plugin;
858     if (empty($plugin_info[$active_type]->id) || !empty($plugin_info[$active_type]->parent)) {
859         $title = ': ' . $stractive_type . ': ' . $stractive_plugin;
860     }
862     $PAGE->set_title($strgrades . ': ' . $stractive_type);
863     $PAGE->set_heading($title);
864     $PAGE->set_button($buttons);
865     $returnval = $OUTPUT->header();
866     if (!$return) {
867         echo $returnval;
868     }
870     // Guess heading if not given explicitly
871     if (!$heading) {
872         $heading = $stractive_plugin;
873     }
875     if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN) {
876         $returnval .= print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return);
877     }
878     $returnval .= $OUTPUT->heading($heading);
880     if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS) {
881         $returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
882     }
884     if ($return) {
885         return $returnval;
886     }
889 /**
890  * Utility class used for return tracking when using edit and other forms in grade plugins
891  *
892  * @package moodlecore
893  * @copyright 2009 Nicolas Connault
894  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
895  */
896 class grade_plugin_return {
897     public $type;
898     public $plugin;
899     public $courseid;
900     public $userid;
901     public $page;
903     /**
904      * Constructor
905      *
906      * @param array $params - associative array with return parameters, if null parameter are taken from _GET or _POST
907      */
908     public function grade_plugin_return($params = null) {
909         if (empty($params)) {
910             $this->type     = optional_param('gpr_type', null, PARAM_SAFEDIR);
911             $this->plugin   = optional_param('gpr_plugin', null, PARAM_SAFEDIR);
912             $this->courseid = optional_param('gpr_courseid', null, PARAM_INT);
913             $this->userid   = optional_param('gpr_userid', null, PARAM_INT);
914             $this->page     = optional_param('gpr_page', null, PARAM_INT);
916         } else {
917             foreach ($params as $key=>$value) {
918                 if (array_key_exists($key, $this)) {
919                     $this->$key = $value;
920                 }
921             }
922         }
923     }
925     /**
926      * Returns return parameters as options array suitable for buttons.
927      * @return array options
928      */
929     public function get_options() {
930         if (empty($this->type)) {
931             return array();
932         }
934         $params = array();
936         if (!empty($this->plugin)) {
937             $params['plugin'] = $this->plugin;
938         }
940         if (!empty($this->courseid)) {
941             $params['id'] = $this->courseid;
942         }
944         if (!empty($this->userid)) {
945             $params['userid'] = $this->userid;
946         }
948         if (!empty($this->page)) {
949             $params['page'] = $this->page;
950         }
952         return $params;
953     }
955     /**
956      * Returns return url
957      *
958      * @param string $default default url when params not set
959      * @param array  $extras Extra URL parameters
960      *
961      * @return string url
962      */
963     public function get_return_url($default, $extras=null) {
964         global $CFG;
966         if (empty($this->type) or empty($this->plugin)) {
967             return $default;
968         }
970         $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php';
971         $glue = '?';
973         if (!empty($this->courseid)) {
974             $url .= $glue.'id='.$this->courseid;
975             $glue = '&amp;';
976         }
978         if (!empty($this->userid)) {
979             $url .= $glue.'userid='.$this->userid;
980             $glue = '&amp;';
981         }
983         if (!empty($this->page)) {
984             $url .= $glue.'page='.$this->page;
985             $glue = '&amp;';
986         }
988         if (!empty($extras)) {
989             foreach ($extras as $key=>$value) {
990                 $url .= $glue.$key.'='.$value;
991                 $glue = '&amp;';
992             }
993         }
995         return $url;
996     }
998     /**
999      * Returns string with hidden return tracking form elements.
1000      * @return string
1001      */
1002     public function get_form_fields() {
1003         if (empty($this->type)) {
1004             return '';
1005         }
1007         $result  = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />';
1009         if (!empty($this->plugin)) {
1010             $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />';
1011         }
1013         if (!empty($this->courseid)) {
1014             $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />';
1015         }
1017         if (!empty($this->userid)) {
1018             $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />';
1019         }
1021         if (!empty($this->page)) {
1022             $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />';
1023         }
1024     }
1026     /**
1027      * Add hidden elements into mform
1028      *
1029      * @param object &$mform moodle form object
1030      *
1031      * @return void
1032      */
1033     public function add_mform_elements(&$mform) {
1034         if (empty($this->type)) {
1035             return;
1036         }
1038         $mform->addElement('hidden', 'gpr_type', $this->type);
1039         $mform->setType('gpr_type', PARAM_SAFEDIR);
1041         if (!empty($this->plugin)) {
1042             $mform->addElement('hidden', 'gpr_plugin', $this->plugin);
1043             $mform->setType('gpr_plugin', PARAM_SAFEDIR);
1044         }
1046         if (!empty($this->courseid)) {
1047             $mform->addElement('hidden', 'gpr_courseid', $this->courseid);
1048             $mform->setType('gpr_courseid', PARAM_INT);
1049         }
1051         if (!empty($this->userid)) {
1052             $mform->addElement('hidden', 'gpr_userid', $this->userid);
1053             $mform->setType('gpr_userid', PARAM_INT);
1054         }
1056         if (!empty($this->page)) {
1057             $mform->addElement('hidden', 'gpr_page', $this->page);
1058             $mform->setType('gpr_page', PARAM_INT);
1059         }
1060     }
1062     /**
1063      * Add return tracking params into url
1064      *
1065      * @param moodle_url $url A URL
1066      *
1067      * @return string $url with erturn tracking params
1068      */
1069     public function add_url_params(moodle_url $url) {
1070         if (empty($this->type)) {
1071             return $url;
1072         }
1074         $url->param('gpr_type', $this->type);
1076         if (!empty($this->plugin)) {
1077             $url->param('gpr_plugin', $this->plugin);
1078         }
1080         if (!empty($this->courseid)) {
1081             $url->param('gpr_courseid' ,$this->courseid);
1082         }
1084         if (!empty($this->userid)) {
1085             $url->param('gpr_userid', $this->userid);
1086         }
1088         if (!empty($this->page)) {
1089             $url->param('gpr_page', $this->page);
1090         }
1092         return $url;
1093     }
1096 /**
1097  * Function central to gradebook for building and printing the navigation (breadcrumb trail).
1098  *
1099  * @param string $path The path of the calling script (using __FILE__?)
1100  * @param string $pagename The language string to use as the last part of the navigation (non-link)
1101  * @param mixed  $id Either a plain integer (assuming the key is 'id') or
1102  *                   an array of keys and values (e.g courseid => $courseid, itemid...)
1103  *
1104  * @return string
1105  */
1106 function grade_build_nav($path, $pagename=null, $id=null) {
1107     global $CFG, $COURSE, $PAGE;
1109     $strgrades = get_string('grades', 'grades');
1111     // Parse the path and build navlinks from its elements
1112     $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash
1113     $path = substr($path, $dirroot_length);
1114     $path = str_replace('\\', '/', $path);
1116     $path_elements = explode('/', $path);
1118     $path_elements_count = count($path_elements);
1120     // First link is always 'grade'
1121     $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id)));
1123     $link = null;
1124     $numberofelements = 3;
1126     // Prepare URL params string
1127     $linkparams = array();
1128     if (!is_null($id)) {
1129         if (is_array($id)) {
1130             foreach ($id as $idkey => $idvalue) {
1131                 $linkparams[$idkey] = $idvalue;
1132             }
1133         } else {
1134             $linkparams['id'] = $id;
1135         }
1136     }
1138     $navlink4 = null;
1140     // Remove file extensions from filenames
1141     foreach ($path_elements as $key => $filename) {
1142         $path_elements[$key] = str_replace('.php', '', $filename);
1143     }
1145     // Second level links
1146     switch ($path_elements[1]) {
1147         case 'edit': // No link
1148             if ($path_elements[3] != 'index.php') {
1149                 $numberofelements = 4;
1150             }
1151             break;
1152         case 'import': // No link
1153             break;
1154         case 'export': // No link
1155             break;
1156         case 'report':
1157             // $id is required for this link. Do not print it if $id isn't given
1158             if (!is_null($id)) {
1159                 $link = new moodle_url('/grade/report/index.php', $linkparams);
1160             }
1162             if ($path_elements[2] == 'grader') {
1163                 $numberofelements = 4;
1164             }
1165             break;
1167         default:
1168             // If this element isn't among the ones already listed above, it isn't supported, throw an error.
1169             debugging("grade_build_nav() doesn't support ". $path_elements[1] .
1170                     " as the second path element after 'grade'.");
1171             return false;
1172     }
1173     $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link);
1175     // Third level links
1176     if (empty($pagename)) {
1177         $pagename = get_string($path_elements[2], 'grades');
1178     }
1180     switch ($numberofelements) {
1181         case 3:
1182             $PAGE->navbar->add($pagename, $link);
1183             break;
1184         case 4:
1185             if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') {
1186                 $PAGE->navbar->add(get_string('modulename', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams));
1187             }
1188             $PAGE->navbar->add($pagename);
1189             break;
1190     }
1192     return '';
1195 /**
1196  * General structure representing grade items in course
1197  *
1198  * @package moodlecore
1199  * @copyright 2009 Nicolas Connault
1200  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1201  */
1202 class grade_structure {
1203     public $context;
1205     public $courseid;
1207     /**
1208      * 1D array of grade items only
1209      */
1210     public $items;
1212     /**
1213      * Returns icon of element
1214      *
1215      * @param array &$element An array representing an element in the grade_tree
1216      * @param bool  $spacerifnone return spacer if no icon found
1217      *
1218      * @return string icon or spacer
1219      */
1220     public function get_element_icon(&$element, $spacerifnone=false) {
1221         global $CFG, $OUTPUT;
1223         switch ($element['type']) {
1224             case 'item':
1225             case 'courseitem':
1226             case 'categoryitem':
1227                 $is_course   = $element['object']->is_course_item();
1228                 $is_category = $element['object']->is_category_item();
1229                 $is_scale    = $element['object']->gradetype == GRADE_TYPE_SCALE;
1230                 $is_value    = $element['object']->gradetype == GRADE_TYPE_VALUE;
1232                 if ($element['object']->is_calculated()) {
1233                     $strcalc = get_string('calculatedgrade', 'grades');
1234                     return '<img src="'.$OUTPUT->pix_url('i/calc') . '" class="icon itemicon" title="'.
1235                             s($strcalc).'" alt="'.s($strcalc).'"/>';
1237                 } else if (($is_course or $is_category) and ($is_scale or $is_value)) {
1238                     if ($category = $element['object']->get_item_category()) {
1239                         switch ($category->aggregation) {
1240                             case GRADE_AGGREGATE_MEAN:
1241                             case GRADE_AGGREGATE_MEDIAN:
1242                             case GRADE_AGGREGATE_WEIGHTED_MEAN:
1243                             case GRADE_AGGREGATE_WEIGHTED_MEAN2:
1244                             case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
1245                                 $stragg = get_string('aggregation', 'grades');
1246                                 return '<img src="'.$OUTPUT->pix_url('i/agg_mean') . '" ' .
1247                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1248                             case GRADE_AGGREGATE_SUM:
1249                                 $stragg = get_string('aggregation', 'grades');
1250                                 return '<img src="'.$OUTPUT->pix_url('i/agg_sum') . '" ' .
1251                                         'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
1252                         }
1253                     }
1255                 } else if ($element['object']->itemtype == 'mod') {
1256                     $strmodname = get_string('modulename', $element['object']->itemmodule);
1257                     return '<img src="'.$OUTPUT->pix_url('icon',
1258                             $element['object']->itemmodule) . '" ' .
1259                             'class="icon itemicon" title="' .s($strmodname).
1260                             '" alt="' .s($strmodname).'"/>';
1262                 } else if ($element['object']->itemtype == 'manual') {
1263                     if ($element['object']->is_outcome_item()) {
1264                         $stroutcome = get_string('outcome', 'grades');
1265                         return '<img src="'.$OUTPUT->pix_url('i/outcomes') . '" ' .
1266                                 'class="icon itemicon" title="'.s($stroutcome).
1267                                 '" alt="'.s($stroutcome).'"/>';
1268                     } else {
1269                         $strmanual = get_string('manualitem', 'grades');
1270                         return '<img src="'.$OUTPUT->pix_url('t/manual_item') . '" '.
1271                                 'class="icon itemicon" title="'.s($strmanual).
1272                                 '" alt="'.s($strmanual).'"/>';
1273                     }
1274                 }
1275                 break;
1277             case 'category':
1278                 $strcat = get_string('category', 'grades');
1279                 return '<img src="'.$OUTPUT->pix_url('f/folder') . '" class="icon itemicon" ' .
1280                         'title="'.s($strcat).'" alt="'.s($strcat).'" />';
1281         }
1283         if ($spacerifnone) {
1284             return '<img src="'.$CFG->wwwroot.'/pix/spacer.gif" class="icon itemicon" alt=""/>';
1285         } else {
1286             return '';
1287         }
1288     }
1290     /**
1291      * Returns name of element optionally with icon and link
1292      *
1293      * @param array &$element An array representing an element in the grade_tree
1294      * @param bool  $withlink Whether or not this header has a link
1295      * @param bool  $icon Whether or not to display an icon with this header
1296      * @param bool  $spacerifnone return spacer if no icon found
1297      *
1298      * @return string header
1299      */
1300     public function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false) {
1301         global $CFG;
1303         $header = '';
1305         if ($icon) {
1306             $header .= $this->get_element_icon($element, $spacerifnone);
1307         }
1309         $header .= $element['object']->get_name();
1311         if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and
1312             $element['type'] != 'courseitem') {
1313             return $header;
1314         }
1316         $itemtype     = $element['object']->itemtype;
1317         $itemmodule   = $element['object']->itemmodule;
1318         $iteminstance = $element['object']->iteminstance;
1320         if ($withlink and $itemtype=='mod' and $iteminstance and $itemmodule) {
1321             if ($cm = get_coursemodule_from_instance($itemmodule, $iteminstance, $this->courseid)) {
1323                 $a->name = get_string('modulename', $element['object']->itemmodule);
1324                 $title = get_string('linktoactivity', 'grades', $a);
1325                 $dir = $CFG->dirroot.'/mod/'.$itemmodule;
1327                 if (file_exists($dir.'/grade.php')) {
1328                     $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/grade.php?id='.$cm->id;
1329                 } else {
1330                     $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/view.php?id='.$cm->id;
1331                 }
1333                 $header = '<a href="'.$url.'" title="'.s($title).'">'.$header.'</a>';
1334             }
1335         }
1337         return $header;
1338     }
1340     /**
1341      * Returns the grade eid - the grade may not exist yet.
1342      *
1343      * @param grade_grade $grade_grade A grade_grade object
1344      *
1345      * @return string eid
1346      */
1347     public function get_grade_eid($grade_grade) {
1348         if (empty($grade_grade->id)) {
1349             return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid;
1350         } else {
1351             return 'g'.$grade_grade->id;
1352         }
1353     }
1355     /**
1356      * Returns the grade_item eid
1357      * @param grade_item $grade_item A grade_item object
1358      * @return string eid
1359      */
1360     public function get_item_eid($grade_item) {
1361         return 'i'.$grade_item->id;
1362     }
1364     /**
1365      * Given a grade_tree element, returns an array of parameters
1366      * used to build an icon for that element.
1367      *
1368      * @param array $element An array representing an element in the grade_tree
1369      *
1370      * @return array
1371      */
1372     public function get_params_for_iconstr($element) {
1373         $strparams = new stdClass();
1374         $strparams->category = '';
1375         $strparams->itemname = '';
1376         $strparams->itemmodule = '';
1378         if (!method_exists($element['object'], 'get_name')) {
1379             return $strparams;
1380         }
1382         $strparams->itemname = $element['object']->get_name();
1384         // If element name is categorytotal, get the name of the parent category
1385         if ($strparams->itemname == get_string('categorytotal', 'grades')) {
1386             $parent = $element['object']->get_parent_category();
1387             $strparams->category = $parent->get_name() . ' ';
1388         } else {
1389             $strparams->category = '';
1390         }
1392         $strparams->itemmodule = null;
1393         if (isset($element['object']->itemmodule)) {
1394             $strparams->itemmodule = $element['object']->itemmodule;
1395         }
1396         return $strparams;
1397     }
1399     /**
1400      * Return edit icon for give element
1401      *
1402      * @param array  $element An array representing an element in the grade_tree
1403      * @param object $gpr A grade_plugin_return object
1404      *
1405      * @return string
1406      */
1407     public function get_edit_icon($element, $gpr) {
1408         global $CFG, $OUTPUT;
1410         if (!has_capability('moodle/grade:manage', $this->context)) {
1411             if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
1412                 // oki - let them override grade
1413             } else {
1414                 return '';
1415             }
1416         }
1418         static $strfeedback   = null;
1419         static $streditgrade = null;
1420         if (is_null($streditgrade)) {
1421             $streditgrade = get_string('editgrade', 'grades');
1422             $strfeedback  = get_string('feedback');
1423         }
1425         $strparams = $this->get_params_for_iconstr($element);
1427         $object = $element['object'];
1429         switch ($element['type']) {
1430             case 'item':
1431             case 'categoryitem':
1432             case 'courseitem':
1433                 $stredit = get_string('editverbose', 'grades', $strparams);
1434                 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) {
1435                     $url = new moodle_url('/grade/edit/tree/item.php',
1436                             array('courseid' => $this->courseid, 'id' => $object->id));
1437                 } else {
1438                     $url = new moodle_url('/grade/edit/tree/outcomeitem.php',
1439                             array('courseid' => $this->courseid, 'id' => $object->id));
1440                 }
1441                 break;
1443             case 'category':
1444                 $stredit = get_string('editverbose', 'grades', $strparams);
1445                 $url = new moodle_url('/grade/edit/tree/category.php',
1446                         array('courseid' => $this->courseid, 'id' => $object->id));
1447                 break;
1449             case 'grade':
1450                 $stredit = $streditgrade;
1451                 if (empty($object->id)) {
1452                     $url = new moodle_url('/grade/edit/tree/grade.php',
1453                             array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid));
1454                 } else {
1455                     $url = new moodle_url('/grade/edit/tree/grade.php',
1456                             array('courseid' => $this->courseid, 'id' => $object->id));
1457                 }
1458                 if (!empty($object->feedback)) {
1459                     $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat)));
1460                 }
1461                 break;
1463             default:
1464                 $url = null;
1465         }
1467         if ($url) {
1468             return $OUTPUT->action_icon($gpr->add_url_params($url), $stredit, 't/edit', array('class'=>'iconsmall'));
1470         } else {
1471             return '';
1472         }
1473     }
1475     /**
1476      * Return hiding icon for give element
1477      *
1478      * @param array  $element An array representing an element in the grade_tree
1479      * @param object $gpr A grade_plugin_return object
1480      *
1481      * @return string
1482      */
1483     public function get_hiding_icon($element, $gpr) {
1484         global $CFG, $OUTPUT;
1486         if (!has_capability('moodle/grade:manage', $this->context) and
1487             !has_capability('moodle/grade:hide', $this->context)) {
1488             return '';
1489         }
1491         $strparams = $this->get_params_for_iconstr($element);
1492         $strshow = get_string('showverbose', 'grades', $strparams);
1493         $strhide = get_string('hideverbose', 'grades', $strparams);
1495         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1496         $url = $gpr->add_url_params($url);
1498         if ($element['object']->is_hidden()) {
1499             $type = 'show';
1500             $tooltip = $strshow;
1502             // Change the icon and add a tooltip showing the date
1503             if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) {
1504                 $type = 'hiddenuntil';
1505                 $tooltip = get_string('hiddenuntildate', 'grades',
1506                         userdate($element['object']->get_hidden()));
1507             }
1509             $url->param('action', 'show');
1511             $hideicon = $OUTPUT->action_icon($url, $tooltip, 't/'.$type, array('alt'=>$strshow, 'class'=>'iconsmall'));
1513         } else {
1514             $url->param('action', 'hide');
1515             $hideicon = $OUTPUT->action_icon($url, $strhide, 't/hide', array('class'=>'iconsmall'));
1516         }
1518         return $hideicon;
1519     }
1521     /**
1522      * Return locking icon for given element
1523      *
1524      * @param array  $element An array representing an element in the grade_tree
1525      * @param object $gpr A grade_plugin_return object
1526      *
1527      * @return string
1528      */
1529     public function get_locking_icon($element, $gpr) {
1530         global $CFG, $OUTPUT;
1532         $strparams = $this->get_params_for_iconstr($element);
1533         $strunlock = get_string('unlockverbose', 'grades', $strparams);
1534         $strlock = get_string('lockverbose', 'grades', $strparams);
1536         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
1537         $url = $gpr->add_url_params($url);
1539         // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon
1540         if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) {
1541             $strparamobj = new stdClass();
1542             $strparamobj->itemname = $element['object']->grade_item->itemname;
1543             $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj);
1545             $action = $OUTPUT->image('t/unlock_gray', array('alt'=>$strnonunlockable, 'title'=>$strnonunlockable, 'class'=>'iconsmall'));
1547         } else if ($element['object']->is_locked()) {
1548             $type = 'unlock';
1549             $tooltip = $strunlock;
1551             // Change the icon and add a tooltip showing the date
1552             if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) {
1553                 $type = 'locktime';
1554                 $tooltip = get_string('locktimedate', 'grades',
1555                         userdate($element['object']->get_locktime()));
1556             }
1558             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) {
1559                 $action = '';
1560             } else {
1561                 $url->param('action', 'unlock');
1562                 $action = $OUTPUT->action_icon($url, $tooltip, 't/'.$type, array('alt'=>$strunlock, 'class'=>'smallicon'));
1563             }
1565         } else {
1566             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) {
1567                 $action = '';
1568             } else {
1569                 $url->param('action', 'lock');
1570                 $action = $OUTPUT->action_icon($url, $strlock, 't/lock', array('class'=>'smallicon'));
1571             }
1572         }
1574         return $action;
1575     }
1577     /**
1578      * Return calculation icon for given element
1579      *
1580      * @param array  $element An array representing an element in the grade_tree
1581      * @param object $gpr A grade_plugin_return object
1582      *
1583      * @return string
1584      */
1585     public function get_calculation_icon($element, $gpr) {
1586         global $CFG, $OUTPUT;
1587         if (!has_capability('moodle/grade:manage', $this->context)) {
1588             return '';
1589         }
1591         $type   = $element['type'];
1592         $object = $element['object'];
1594         if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
1595             $strparams = $this->get_params_for_iconstr($element);
1596             $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams);
1598             $is_scale = $object->gradetype == GRADE_TYPE_SCALE;
1599             $is_value = $object->gradetype == GRADE_TYPE_VALUE;
1601             // show calculation icon only when calculation possible
1602             if (!$object->is_external_item() and ($is_scale or $is_value)) {
1603                 if ($object->is_calculated()) {
1604                     $icon = 't/calc';
1605                 } else {
1606                     $icon = 't/calc_off';
1607                 }
1609                 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id));
1610                 $url = $gpr->add_url_params($url);
1611                 return $OUTPUT->action_icon($url, $streditcalculation, $icon, array('class'=>'smallicon')) . "\n";
1612             }
1613         }
1615         return '';
1616     }
1619 /**
1620  * Flat structure similar to grade tree.
1621  *
1622  * @uses grade_structure
1623  * @package moodlecore
1624  * @copyright 2009 Nicolas Connault
1625  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1626  */
1627 class grade_seq extends grade_structure {
1629     /**
1630      * 1D array of elements
1631      */
1632     public $elements;
1634     /**
1635      * Constructor, retrieves and stores array of all grade_category and grade_item
1636      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1637      *
1638      * @param int  $courseid The course id
1639      * @param bool $category_grade_last category grade item is the last child
1640      * @param bool $nooutcomes Whether or not outcomes should be included
1641      */
1642     public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) {
1643         global $USER, $CFG;
1645         $this->courseid   = $courseid;
1646         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1648         // get course grade tree
1649         $top_element = grade_category::fetch_course_tree($courseid, true);
1651         $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes);
1653         foreach ($this->elements as $key=>$unused) {
1654             $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object'];
1655         }
1656     }
1658     /**
1659      * Static recursive helper - makes the grade_item for category the last children
1660      *
1661      * @param array &$element The seed of the recursion
1662      * @param bool $category_grade_last category grade item is the last child
1663      * @param bool $nooutcomes Whether or not outcomes should be included
1664      *
1665      * @return array
1666      */
1667     public function flatten(&$element, $category_grade_last, $nooutcomes) {
1668         if (empty($element['children'])) {
1669             return array();
1670         }
1671         $children = array();
1673         foreach ($element['children'] as $sortorder=>$unused) {
1674             if ($nooutcomes and $element['type'] != 'category' and
1675                 $element['children'][$sortorder]['object']->is_outcome_item()) {
1676                 continue;
1677             }
1678             $children[] = $element['children'][$sortorder];
1679         }
1680         unset($element['children']);
1682         if ($category_grade_last and count($children) > 1) {
1683             $cat_item = array_shift($children);
1684             array_push($children, $cat_item);
1685         }
1687         $result = array();
1688         foreach ($children as $child) {
1689             if ($child['type'] == 'category') {
1690                 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes);
1691             } else {
1692                 $child['eid'] = 'i'.$child['object']->id;
1693                 $result[$child['object']->id] = $child;
1694             }
1695         }
1697         return $result;
1698     }
1700     /**
1701      * Parses the array in search of a given eid and returns a element object with
1702      * information about the element it has found.
1703      *
1704      * @param int $eid Gradetree Element ID
1705      *
1706      * @return object element
1707      */
1708     public function locate_element($eid) {
1709         // it is a grade - construct a new object
1710         if (strpos($eid, 'n') === 0) {
1711             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1712                 return null;
1713             }
1715             $itemid = $matches[1];
1716             $userid = $matches[2];
1718             //extra security check - the grade item must be in this tree
1719             if (!$item_el = $this->locate_element('i'.$itemid)) {
1720                 return null;
1721             }
1723             // $gradea->id may be null - means does not exist yet
1724             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1726             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1727             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1729         } else if (strpos($eid, 'g') === 0) {
1730             $id = (int) substr($eid, 1);
1731             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1732                 return null;
1733             }
1734             //extra security check - the grade item must be in this tree
1735             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1736                 return null;
1737             }
1738             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1739             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1740         }
1742         // it is a category or item
1743         foreach ($this->elements as $element) {
1744             if ($element['eid'] == $eid) {
1745                 return $element;
1746             }
1747         }
1749         return null;
1750     }
1753 /**
1754  * This class represents a complete tree of categories, grade_items and final grades,
1755  * organises as an array primarily, but which can also be converted to other formats.
1756  * It has simple method calls with complex implementations, allowing for easy insertion,
1757  * deletion and moving of items and categories within the tree.
1758  *
1759  * @uses grade_structure
1760  * @package moodlecore
1761  * @copyright 2009 Nicolas Connault
1762  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1763  */
1764 class grade_tree extends grade_structure {
1766     /**
1767      * The basic representation of the tree as a hierarchical, 3-tiered array.
1768      * @var object $top_element
1769      */
1770     public $top_element;
1772     /**
1773      * 2D array of grade items and categories
1774      * @var array $levels
1775      */
1776     public $levels;
1778     /**
1779      * Grade items
1780      * @var array $items
1781      */
1782     public $items;
1784     /**
1785      * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item
1786      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1787      *
1788      * @param int   $courseid The Course ID
1789      * @param bool  $fillers include fillers and colspans, make the levels var "rectangular"
1790      * @param bool  $category_grade_last category grade item is the last child
1791      * @param array $collapsed array of collapsed categories
1792      * @param bool  $nooutcomes Whether or not outcomes should be included
1793      */
1794     public function grade_tree($courseid, $fillers=true, $category_grade_last=false,
1795                                $collapsed=null, $nooutcomes=false) {
1796         global $USER, $CFG;
1798         $this->courseid   = $courseid;
1799         $this->levels     = array();
1800         $this->context    = get_context_instance(CONTEXT_COURSE, $courseid);
1802         // get course grade tree
1803         $this->top_element = grade_category::fetch_course_tree($courseid, true);
1805         // collapse the categories if requested
1806         if (!empty($collapsed)) {
1807             grade_tree::category_collapse($this->top_element, $collapsed);
1808         }
1810         // no otucomes if requested
1811         if (!empty($nooutcomes)) {
1812             grade_tree::no_outcomes($this->top_element);
1813         }
1815         // move category item to last position in category
1816         if ($category_grade_last) {
1817             grade_tree::category_grade_last($this->top_element);
1818         }
1820         if ($fillers) {
1821             // inject fake categories == fillers
1822             grade_tree::inject_fillers($this->top_element, 0);
1823             // add colspans to categories and fillers
1824             grade_tree::inject_colspans($this->top_element);
1825         }
1827         grade_tree::fill_levels($this->levels, $this->top_element, 0);
1829     }
1831     /**
1832      * Static recursive helper - removes items from collapsed categories
1833      *
1834      * @param array &$element The seed of the recursion
1835      * @param array $collapsed array of collapsed categories
1836      *
1837      * @return void
1838      */
1839     public function category_collapse(&$element, $collapsed) {
1840         if ($element['type'] != 'category') {
1841             return;
1842         }
1843         if (empty($element['children']) or count($element['children']) < 2) {
1844             return;
1845         }
1847         if (in_array($element['object']->id, $collapsed['aggregatesonly'])) {
1848             $category_item = reset($element['children']); //keep only category item
1849             $element['children'] = array(key($element['children'])=>$category_item);
1851         } else {
1852             if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item
1853                 reset($element['children']);
1854                 $first_key = key($element['children']);
1855                 unset($element['children'][$first_key]);
1856             }
1857             foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children
1858                 grade_tree::category_collapse($element['children'][$sortorder], $collapsed);
1859             }
1860         }
1861     }
1863     /**
1864      * Static recursive helper - removes all outcomes
1865      *
1866      * @param array &$element The seed of the recursion
1867      *
1868      * @return void
1869      */
1870     public function no_outcomes(&$element) {
1871         if ($element['type'] != 'category') {
1872             return;
1873         }
1874         foreach ($element['children'] as $sortorder=>$child) {
1875             if ($element['children'][$sortorder]['type'] == 'item'
1876               and $element['children'][$sortorder]['object']->is_outcome_item()) {
1877                 unset($element['children'][$sortorder]);
1879             } else if ($element['children'][$sortorder]['type'] == 'category') {
1880                 grade_tree::no_outcomes($element['children'][$sortorder]);
1881             }
1882         }
1883     }
1885     /**
1886      * Static recursive helper - makes the grade_item for category the last children
1887      *
1888      * @param array &$element The seed of the recursion
1889      *
1890      * @return void
1891      */
1892     public function category_grade_last(&$element) {
1893         if (empty($element['children'])) {
1894             return;
1895         }
1896         if (count($element['children']) < 2) {
1897             return;
1898         }
1899         $first_item = reset($element['children']);
1900         if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') {
1901             // the category item might have been already removed
1902             $order = key($element['children']);
1903             unset($element['children'][$order]);
1904             $element['children'][$order] =& $first_item;
1905         }
1906         foreach ($element['children'] as $sortorder => $child) {
1907             grade_tree::category_grade_last($element['children'][$sortorder]);
1908         }
1909     }
1911     /**
1912      * Static recursive helper - fills the levels array, useful when accessing tree elements of one level
1913      *
1914      * @param array &$levels The levels of the grade tree through which to recurse
1915      * @param array &$element The seed of the recursion
1916      * @param int   $depth How deep are we?
1917      * @return void
1918      */
1919     public function fill_levels(&$levels, &$element, $depth) {
1920         if (!array_key_exists($depth, $levels)) {
1921             $levels[$depth] = array();
1922         }
1924         // prepare unique identifier
1925         if ($element['type'] == 'category') {
1926             $element['eid'] = 'c'.$element['object']->id;
1927         } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
1928             $element['eid'] = 'i'.$element['object']->id;
1929             $this->items[$element['object']->id] =& $element['object'];
1930         }
1932         $levels[$depth][] =& $element;
1933         $depth++;
1934         if (empty($element['children'])) {
1935             return;
1936         }
1937         $prev = 0;
1938         foreach ($element['children'] as $sortorder=>$child) {
1939             grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth);
1940             $element['children'][$sortorder]['prev'] = $prev;
1941             $element['children'][$sortorder]['next'] = 0;
1942             if ($prev) {
1943                 $element['children'][$prev]['next'] = $sortorder;
1944             }
1945             $prev = $sortorder;
1946         }
1947     }
1949     /**
1950      * Static recursive helper - makes full tree (all leafes are at the same level)
1951      *
1952      * @param array &$element The seed of the recursion
1953      * @param int   $depth How deep are we?
1954      *
1955      * @return int
1956      */
1957     public function inject_fillers(&$element, $depth) {
1958         $depth++;
1960         if (empty($element['children'])) {
1961             return $depth;
1962         }
1963         $chdepths = array();
1964         $chids = array_keys($element['children']);
1965         $last_child  = end($chids);
1966         $first_child = reset($chids);
1968         foreach ($chids as $chid) {
1969             $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth);
1970         }
1971         arsort($chdepths);
1973         $maxdepth = reset($chdepths);
1974         foreach ($chdepths as $chid=>$chd) {
1975             if ($chd == $maxdepth) {
1976                 continue;
1977             }
1978             for ($i=0; $i < $maxdepth-$chd; $i++) {
1979                 if ($chid == $first_child) {
1980                     $type = 'fillerfirst';
1981                 } else if ($chid == $last_child) {
1982                     $type = 'fillerlast';
1983                 } else {
1984                     $type = 'filler';
1985                 }
1986                 $oldchild =& $element['children'][$chid];
1987                 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type,
1988                                                     'eid'=>'', 'depth'=>$element['object']->depth,
1989                                                     'children'=>array($oldchild));
1990             }
1991         }
1993         return $maxdepth;
1994     }
1996     /**
1997      * Static recursive helper - add colspan information into categories
1998      *
1999      * @param array &$element The seed of the recursion
2000      *
2001      * @return int
2002      */
2003     public function inject_colspans(&$element) {
2004         if (empty($element['children'])) {
2005             return 1;
2006         }
2007         $count = 0;
2008         foreach ($element['children'] as $key=>$child) {
2009             $count += grade_tree::inject_colspans($element['children'][$key]);
2010         }
2011         $element['colspan'] = $count;
2012         return $count;
2013     }
2015     /**
2016      * Parses the array in search of a given eid and returns a element object with
2017      * information about the element it has found.
2018      * @param int $eid Gradetree Element ID
2019      * @return object element
2020      */
2021     public function locate_element($eid) {
2022         // it is a grade - construct a new object
2023         if (strpos($eid, 'n') === 0) {
2024             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
2025                 return null;
2026             }
2028             $itemid = $matches[1];
2029             $userid = $matches[2];
2031             //extra security check - the grade item must be in this tree
2032             if (!$item_el = $this->locate_element('i'.$itemid)) {
2033                 return null;
2034             }
2036             // $gradea->id may be null - means does not exist yet
2037             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
2039             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
2040             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
2042         } else if (strpos($eid, 'g') === 0) {
2043             $id = (int) substr($eid, 1);
2044             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
2045                 return null;
2046             }
2047             //extra security check - the grade item must be in this tree
2048             if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
2049                 return null;
2050             }
2051             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
2052             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
2053         }
2055         // it is a category or item
2056         foreach ($this->levels as $row) {
2057             foreach ($row as $element) {
2058                 if ($element['type'] == 'filler') {
2059                     continue;
2060                 }
2061                 if ($element['eid'] == $eid) {
2062                     return $element;
2063                 }
2064             }
2065         }
2067         return null;
2068     }
2070     /**
2071      * Returns a well-formed XML representation of the grade-tree using recursion.
2072      *
2073      * @param array  $root The current element in the recursion. If null, starts at the top of the tree.
2074      * @param string $tabs The control character to use for tabs
2075      *
2076      * @return string $xml
2077      */
2078     public function exporttoxml($root=null, $tabs="\t") {
2079         $xml = null;
2080         $first = false;
2081         if (is_null($root)) {
2082             $root = $this->top_element;
2083             $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
2084             $xml .= "<gradetree>\n";
2085             $first = true;
2086         }
2088         $type = 'undefined';
2089         if (strpos($root['object']->table, 'grade_categories') !== false) {
2090             $type = 'category';
2091         } else if (strpos($root['object']->table, 'grade_items') !== false) {
2092             $type = 'item';
2093         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
2094             $type = 'outcome';
2095         }
2097         $xml .= "$tabs<element type=\"$type\">\n";
2098         foreach ($root['object'] as $var => $value) {
2099             if (!is_object($value) && !is_array($value) && !empty($value)) {
2100                 $xml .= "$tabs\t<$var>$value</$var>\n";
2101             }
2102         }
2104         if (!empty($root['children'])) {
2105             $xml .= "$tabs\t<children>\n";
2106             foreach ($root['children'] as $sortorder => $child) {
2107                 $xml .= $this->exportToXML($child, $tabs."\t\t");
2108             }
2109             $xml .= "$tabs\t</children>\n";
2110         }
2112         $xml .= "$tabs</element>\n";
2114         if ($first) {
2115             $xml .= "</gradetree>";
2116         }
2118         return $xml;
2119     }
2121     /**
2122      * Returns a JSON representation of the grade-tree using recursion.
2123      *
2124      * @param array $root The current element in the recursion. If null, starts at the top of the tree.
2125      * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy
2126      *
2127      * @return string
2128      */
2129     public function exporttojson($root=null, $tabs="\t") {
2130         $json = null;
2131         $first = false;
2132         if (is_null($root)) {
2133             $root = $this->top_element;
2134             $first = true;
2135         }
2137         $name = '';
2140         if (strpos($root['object']->table, 'grade_categories') !== false) {
2141             $name = $root['object']->fullname;
2142             if ($name == '?') {
2143                 $name = $root['object']->get_name();
2144             }
2145         } else if (strpos($root['object']->table, 'grade_items') !== false) {
2146             $name = $root['object']->itemname;
2147         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
2148             $name = $root['object']->itemname;
2149         }
2151         $json .= "$tabs {\n";
2152         $json .= "$tabs\t \"type\": \"{$root['type']}\",\n";
2153         $json .= "$tabs\t \"name\": \"$name\",\n";
2155         foreach ($root['object'] as $var => $value) {
2156             if (!is_object($value) && !is_array($value) && !empty($value)) {
2157                 $json .= "$tabs\t \"$var\": \"$value\",\n";
2158             }
2159         }
2161         $json = substr($json, 0, strrpos($json, ','));
2163         if (!empty($root['children'])) {
2164             $json .= ",\n$tabs\t\"children\": [\n";
2165             foreach ($root['children'] as $sortorder => $child) {
2166                 $json .= $this->exportToJSON($child, $tabs."\t\t");
2167             }
2168             $json = substr($json, 0, strrpos($json, ','));
2169             $json .= "\n$tabs\t]\n";
2170         }
2172         if ($first) {
2173             $json .= "\n}";
2174         } else {
2175             $json .= "\n$tabs},\n";
2176         }
2178         return $json;
2179     }
2181     /**
2182      * Returns the array of levels
2183      *
2184      * @return array
2185      */
2186     public function get_levels() {
2187         return $this->levels;
2188     }
2190     /**
2191      * Returns the array of grade items
2192      *
2193      * @return array
2194      */
2195     public function get_items() {
2196         return $this->items;
2197     }
2199     /**
2200      * Returns a specific Grade Item
2201      *
2202      * @param int $itemid The ID of the grade_item object
2203      *
2204      * @return grade_item
2205      */
2206     public function get_item($itemid) {
2207         if (array_key_exists($itemid, $this->items)) {
2208             return $this->items[$itemid];
2209         } else {
2210             return false;
2211         }
2212     }
2215 /**
2216  * Local shortcut function for creating an edit/delete button for a grade_* object.
2217  * @param strong $type 'edit' or 'delete'
2218  * @param int $courseid The Course ID
2219  * @param grade_* $object The grade_* object
2220  * @return string html
2221  */
2222 function grade_button($type, $courseid, $object) {
2223     global $CFG, $OUTPUT;
2224     if (preg_match('/grade_(.*)/', get_class($object), $matches)) {
2225         $objectidstring = $matches[1] . 'id';
2226     } else {
2227         throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!');
2228     }
2230     $strdelete = get_string('delete');
2231     $stredit   = get_string('edit');
2233     if ($type == 'delete') {
2234         $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey()));
2235     } else if ($type == 'edit') {
2236         $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id));
2237     }
2239     return $OUTPUT->action_icon($url, ${'str'.$type}, 't/'.$type, array('class'=>'iconsmall'));