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