MDL-12991 Updated groups UI so that it categorises members and potential members...
[moodle.git] / grade / lib.php
CommitLineData
3af29899 1<?php //$Id$
cbff94ba 2
8ad36f4c 3///////////////////////////////////////////////////////////////////////////
4// //
5// NOTICE OF COPYRIGHT //
6// //
7// Moodle - Modular Object-Oriented Dynamic Learning Environment //
8// http://moodle.com //
9// //
10// Copyright (C) 1999 onwards Martin Dougiamas http://moodle.com //
11// //
12// This program is free software; you can redistribute it and/or modify //
13// it under the terms of the GNU General Public License as published by //
14// the Free Software Foundation; either version 2 of the License, or //
15// (at your option) any later version. //
16// //
17// This program is distributed in the hope that it will be useful, //
18// but WITHOUT ANY WARRANTY; without even the implied warranty of //
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
20// GNU General Public License for more details: //
21// //
22// http://www.gnu.org/copyleft/gpl.html //
23// //
24///////////////////////////////////////////////////////////////////////////
25
7a6b7acf 26require_once $CFG->libdir.'/gradelib.php';
27
0f5660f7 28/**
29 * This class iterates over all users that are graded in a course.
b9f49659 30 * Returns detailed info about users and their grades.
0f5660f7 31 */
32class graded_users_iterator {
33 var $course;
34 var $grade_items;
35 var $groupid;
36 var $users_rs;
37 var $grades_rs;
38 var $gradestack;
39
40 /**
41 * Constructor
42 * @param $coruse object
43 * @param array grade_items array of grade items, if not specified only user info returned
44 * @param int $groupid iterate only group users if present
45 */
46 function graded_users_iterator($course, $grade_items=null, $groupid=0) {
47 $this->course = $course;
48 $this->grade_items = $grade_items;
49 $this->groupid = $groupid;
50
51 $this->gradestack = array();
52 }
53
54 /**
55 * Initialise the iterator
56 * @return boolean success
57 */
58 function init() {
59 global $CFG;
60
61 $this->close();
62
63 grade_regrade_final_grades($this->course->id);
64 $course_item = grade_item::fetch_course_item($this->course->id);
65 if ($course_item->needsupdate) {
66 // can not calculate all final grades - sorry
67 return false;
68 }
69
c0e9f877 70 if (strpos($CFG->gradebookroles, ',') === false) {
0f5660f7 71 $gradebookroles = " = {$CFG->gradebookroles}";
72 } else {
73 $gradebookroles = " IN ({$CFG->gradebookroles})";
74 }
75
76 $relatedcontexts = get_related_contexts_string(get_context_instance(CONTEXT_COURSE, $this->course->id));
77
78 if ($this->groupid) {
79 $groupsql = "INNER JOIN {$CFG->prefix}groups_members gm ON gm.userid = u.id";
80 $groupwheresql = "AND gm.groupid = {$this->groupid}";
81 } else {
82 $groupsql = "";
83 $groupwheresql = "";
84 }
85
86 $users_sql = "SELECT u.*
87 FROM {$CFG->prefix}user u
88 INNER JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid
89 $groupsql
90 WHERE ra.roleid $gradebookroles
91 AND ra.contextid $relatedcontexts
92 $groupwheresql
93 ORDER BY u.id ASC";
caffc55a 94 $this->users_rs = get_recordset_sql($users_sql);
0f5660f7 95
96 if (!empty($this->grade_items)) {
97 $itemids = array_keys($this->grade_items);
98 $itemids = implode(',', $itemids);
99
3f2b0c8a 100 $grades_sql = "SELECT g.*
0f5660f7 101 FROM {$CFG->prefix}grade_grades g
0f5660f7 102 INNER JOIN {$CFG->prefix}user u ON g.userid = u.id
103 INNER JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid
104 $groupsql
105 WHERE ra.roleid $gradebookroles
106 AND ra.contextid $relatedcontexts
107 AND g.itemid IN ($itemids)
108 $groupwheresql
109 ORDER BY g.userid ASC, g.itemid ASC";
caffc55a 110 $this->grades_rs = get_recordset_sql($grades_sql);
0f5660f7 111 }
112
113 return true;
114 }
115
116 /**
117 * Returns information about the next user
118 * @return mixed array of user info, all grades and feedback or null when no more users found
119 */
120 function next_user() {
03cedd62 121 if (!$this->users_rs) {
0f5660f7 122 return false; // no users present
123 }
124
caffc55a 125 if (!$user = rs_fetch_next_record($this->users_rs)) {
0f5660f7 126 return false; // no more users
127 }
128
129 //find the first grade of this user
130 $grade_records = array();
131 while (true) {
132 if (!$current = $this->_pop()) {
133 break; // no more grades
134 }
135
136 if ($current->userid < $user->id) {
137 // this should not happen, could be caused by concurrent updates - skip this record
138 continue;
139
140 } else if ($current->userid > $user->id) {
141 // this user does not have any more grades
142 $this->_push($current);
143 break;
144 }
145
146 $grade_records[$current->itemid] = $current;
147 }
148
149 $grades = array();
150 $feedbacks = array();
151
152 foreach ($this->grade_items as $grade_item) {
153 if (array_key_exists($grade_item->id, $grade_records)) {
154 $feedbacks[$grade_item->id]->feedback = $grade_records[$grade_item->id]->feedback;
155 $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat;
156 unset($grade_records[$grade_item->id]->feedback);
157 unset($grade_records[$grade_item->id]->feedbackformat);
158 $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false);
159 } else {
160 $feedbacks[$grade_item->id]->feedback = '';
161 $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE;
162 $grades[$grade_item->id] = new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false);
163 }
164 }
165
166 $result = new object();
167 $result->user = $user;
168 $result->grades = $grades;
169 $result->feedbacks = $feedbacks;
170
171 return $result;
172 }
173
174 /**
175 * Close the iterator, do not forget to call this function.
176 * @return void
177 */
178 function close() {
caffc55a 179 if ($this->users_rs) {
180 rs_close($this->users_rs);
181 $this->users_rs = null;
0f5660f7 182 }
caffc55a 183 if ($this->grades_rs) {
184 rs_close($this->grades_rs);
185 $this->grades_rs = null;
0f5660f7 186 }
187 $this->gradestack = array();
188 }
189
190 /**
191 * Internal function
192 */
193 function _push($grade) {
194 array_push($this->gradestack, $grade);
195 }
196
197 /**
198 * Internal function
199 */
200 function _pop() {
201 if (empty($this->gradestack)) {
03cedd62 202 if (!$this->grades_rs) {
0f5660f7 203 return NULL; // no grades present
204 }
205
caffc55a 206 if (!$grade = rs_fetch_next_record($this->grades_rs)) {
0f5660f7 207 return NULL; // no more grades
208 }
209
210 return $grade;
211 } else {
212 return array_pop($this->gradestack);
213 }
214 }
215}
216
0610812a 217/**
218 * Print grading plugin selection popup form.
219 *
220 * @param int $courseid id of course
221 * @param string $active_type type of plugin on current page - import, export, report or edit
222 * @param string $active_plugin active plugin type - grader, user, cvs, ...
223 * @param boolean $return return as string
224 * @return nothing or string if $return true
225 */
3af29899 226function print_grade_plugin_selector($courseid, $active_type, $active_plugin, $return=false) {
cbff94ba 227 global $CFG;
cbff94ba 228
3af29899 229 $context = get_context_instance(CONTEXT_COURSE, $courseid);
cbff94ba 230
3af29899 231 $menu = array();
6e2f3121 232 $count = 0;
3af29899 233 $active = '';
cbff94ba 234
3af29899 235/// report plugins with its special structure
236 if ($reports = get_list_of_plugins('grade/report', 'CVS')) { // Get all installed reports
237 foreach ($reports as $key => $plugin) { // Remove ones we can't see
238 if (!has_capability('gradereport/'.$plugin.':view', $context)) {
239 unset($reports[$key]);
cbff94ba 240 }
241 }
04678d8e 242 }
3af29899 243 $reportnames = array();
244 if (!empty($reports)) {
245 foreach ($reports as $plugin) {
65dd61bd 246 $url = 'report/'.$plugin.'/index.php?id='.$courseid;
3af29899 247 if ($active_type == 'report' and $active_plugin == $plugin ) {
248 $active = $url;
cbff94ba 249 }
6e2f3121 250 $reportnames[$url] = get_string('modulename', 'gradereport_'.$plugin);
251 $count++;
cbff94ba 252 }
3af29899 253 asort($reportnames);
cbff94ba 254 }
3af29899 255 if (!empty($reportnames)) {
256 $menu['reportgroup']='--'.get_string('reportplugins', 'grades');
257 $menu = $menu+$reportnames;
cbff94ba 258 }
cbff94ba 259
3af29899 260/// standard import plugins
e2008be2 261 if ($imports = get_list_of_plugins('grade/import', 'CVS')) { // Get all installed import plugins
3af29899 262 foreach ($imports as $key => $plugin) { // Remove ones we can't see
263 if (!has_capability('gradeimport/'.$plugin.':view', $context)) {
264 unset($imports[$key]);
cbff94ba 265 }
266 }
267 }
3af29899 268 $importnames = array();
269 if (!empty($imports)) {
270 foreach ($imports as $plugin) {
271 $url = 'import/'.$plugin.'/index.php?id='.$courseid;
65dd61bd 272 if ($active_type == 'import' and $active_plugin == $plugin ) {
3af29899 273 $active = $url;
274 }
6e2f3121 275 $importnames[$url] = get_string('modulename', 'gradeimport_'.$plugin);
276 $count++;
281ffa4a 277 }
3af29899 278 asort($importnames);
281ffa4a 279 }
3af29899 280 if (!empty($importnames)) {
281 $menu['importgroup']='--'.get_string('importplugins', 'grades');
282 $menu = $menu+$importnames;
281ffa4a 283 }
281ffa4a 284
3af29899 285/// standard export plugins
e2008be2 286 if ($exports = get_list_of_plugins('grade/export', 'CVS')) { // Get all installed export plugins
3af29899 287 foreach ($exports as $key => $plugin) { // Remove ones we can't see
288 if (!has_capability('gradeexport/'.$plugin.':view', $context)) {
289 unset($exports[$key]);
281ffa4a 290 }
291 }
cbff94ba 292 }
3af29899 293 $exportnames = array();
294 if (!empty($exports)) {
295 foreach ($exports as $plugin) {
296 $url = 'export/'.$plugin.'/index.php?id='.$courseid;
65dd61bd 297 if ($active_type == 'export' and $active_plugin == $plugin ) {
3af29899 298 $active = $url;
299 }
6e2f3121 300 $exportnames[$url] = get_string('modulename', 'gradeexport_'.$plugin);
301 $count++;
281ffa4a 302 }
3af29899 303 asort($exportnames);
cbff94ba 304 }
3af29899 305 if (!empty($exportnames)) {
306 $menu['exportgroup']='--'.get_string('exportplugins', 'grades');
307 $menu = $menu+$exportnames;
281ffa4a 308 }
cbff94ba 309
3af29899 310/// editing scripts - not real plugins
78ad5f3f 311 if (has_capability('moodle/grade:manage', $context)
9376f5a6 312 or has_capability('moodle/grade:manageletters', $context)
04259694 313 or has_capability('moodle/course:managescales', $context)
314 or has_capability('moodle/course:update', $context)) {
3af29899 315 $menu['edit']='--'.get_string('edit');
78ad5f3f 316
317 if (has_capability('moodle/grade:manage', $context)) {
318 $url = 'edit/tree/index.php?id='.$courseid;
319 if ($active_type == 'edit' and $active_plugin == 'tree' ) {
320 $active = $url;
321 }
322 $menu[$url] = get_string('edittree', 'grades');
6e2f3121 323 $count++;
78ad5f3f 324 }
325
326 if (has_capability('moodle/course:managescales', $context)) {
327 $url = 'edit/scale/index.php?id='.$courseid;
328 if ($active_type == 'edit' and $active_plugin == 'scale' ) {
329 $active = $url;
330 }
331 $menu[$url] = get_string('scales');
6e2f3121 332 $count++;
78ad5f3f 333 }
334
2b0f65e2 335 if (!empty($CFG->enableoutcomes) && (has_capability('moodle/grade:manage', $context) or
2a598439 336 has_capability('moodle/course:update', $context))) {
337 if (has_capability('moodle/course:update', $context)) { // Default to course assignment
04259694 338 $url = 'edit/outcome/course.php?id='.$courseid;
2a598439 339 } else {
340 $url = 'edit/outcome/index.php?id='.$courseid;
04259694 341 }
78ad5f3f 342 if ($active_type == 'edit' and $active_plugin == 'outcome' ) {
343 $active = $url;
344 }
345 $menu[$url] = get_string('outcomes', 'grades');
6e2f3121 346 $count++;
cbff94ba 347 }
284abb09 348
9376f5a6 349 if (has_capability('moodle/grade:manage', $context) or has_capability('moodle/grade:manageletters', $context)) {
284abb09 350 $url = 'edit/letter/index.php?id='.$courseid;
351 if ($active_type == 'edit' and $active_plugin == 'letter' ) {
352 $active = $url;
353 }
354 $menu[$url] = get_string('letters', 'grades');
6e2f3121 355 $count++;
284abb09 356 }
357
e0724506 358 if (has_capability('moodle/grade:manage', $context)) {
359 $url = 'edit/settings/index.php?id='.$courseid;
360 if ($active_type == 'edit' and $active_plugin == 'settings' ) {
361 $active = $url;
362 }
363 $menu[$url] = get_string('coursesettings', 'grades');
6e2f3121 364 $count++;
e0724506 365 }
366
281ffa4a 367 }
368
3af29899 369/// finally print/return the popup form
6e2f3121 370 if ($count > 1) {
371 return popup_form($CFG->wwwroot.'/grade/', $menu, 'choosepluginreport', $active, 'choose', '', '', $return, 'self', get_string('view'));
372 } else {
373 // only one option - no plugin selector needed
374 return '';
375 }
cbff94ba 376}
377
0610812a 378/**
7a6b7acf 379 * Utility class used for return tracking when using edit and other forms in grade plugins
0610812a 380 */
3af29899 381class grade_plugin_return {
382 var $type;
383 var $plugin;
384 var $courseid;
385 var $userid;
386 var $page;
281ffa4a 387
0610812a 388 /**
389 * Constructor
390 * @param array $params - associative array with return parameters, if null parameter are taken from _GET or _POST
391 */
3af29899 392 function grade_plugin_return ($params=null) {
393 if (empty($params)) {
394 $this->type = optional_param('gpr_type', null, PARAM_SAFEDIR);
395 $this->plugin = optional_param('gpr_plugin', null, PARAM_SAFEDIR);
396 $this->courseid = optional_param('gpr_courseid', null, PARAM_INT);
397 $this->userid = optional_param('gpr_userid', null, PARAM_INT);
398 $this->page = optional_param('gpr_page', null, PARAM_INT);
a983b6ec 399
a983b6ec 400 } else {
3af29899 401 foreach ($params as $key=>$value) {
402 if (array_key_exists($key, $this)) {
403 $this->$key = $value;
404 }
cbff94ba 405 }
406 }
6cd8c592 407 }
408
0610812a 409 /**
410 * Returns return parameters as options array suitable for buttons.
411 * @return array options
412 */
3af29899 413 function get_options() {
7a6b7acf 414 if (empty($this->type)) {
3af29899 415 return array();
865e9a82 416 }
6cd8c592 417
3af29899 418 $params = array();
6cd8c592 419
7a6b7acf 420 if (!empty($this->plugin)) {
421 $params['plugin'] = $this->plugin;
422 }
6cd8c592 423
3af29899 424 if (!empty($this->courseid)) {
425 $params['id'] = $this->courseid;
6cd8c592 426 }
9c61ba4d 427
3af29899 428 if (!empty($this->userid)) {
429 $params['userid'] = $this->userid;
9c61ba4d 430 }
9c61ba4d 431
3af29899 432 if (!empty($this->page)) {
433 $params['page'] = $this->page;
cbff94ba 434 }
865e9a82 435
3af29899 436 return $params;
cbff94ba 437 }
cbff94ba 438
0610812a 439 /**
440 * Returns return url
441 * @param string $default default url when params not set
442 * @return string url
443 */
65dd61bd 444 function get_return_url($default, $extras=null) {
3af29899 445 global $CFG;
cbff94ba 446
3af29899 447 if (empty($this->type) or empty($this->plugin)) {
448 return $default;
cbff94ba 449 }
450
65dd61bd 451 $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php';
452 $glue = '?';
cbff94ba 453
3af29899 454 if (!empty($this->courseid)) {
455 $url .= $glue.'id='.$this->courseid;
456 $glue = '&amp;';
cbff94ba 457 }
cbff94ba 458
3af29899 459 if (!empty($this->userid)) {
460 $url .= $glue.'userid='.$this->userid;
461 $glue = '&amp;';
cbff94ba 462 }
7e2d7c92 463
3af29899 464 if (!empty($this->page)) {
465 $url .= $glue.'page='.$this->page;
65dd61bd 466 $glue = '&amp;';
467 }
468
469 if (!empty($extras)) {
470 foreach($extras as $key=>$value) {
471 $url .= $glue.$key.'='.$value;
472 $glue = '&amp;';
7a6b7acf 473 }
cbff94ba 474 }
cbff94ba 475
3af29899 476 return $url;
cbff94ba 477 }
cbff94ba 478
0610812a 479 /**
480 * Returns string with hidden return tracking form elements.
481 * @return string
482 */
3af29899 483 function get_form_fields() {
7a6b7acf 484 if (empty($this->type)) {
3af29899 485 return '';
cbff94ba 486 }
cbff94ba 487
3af29899 488 $result = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />';
7a6b7acf 489
490 if (!empty($this->plugin)) {
491 $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />';
492 }
0ca5abd6 493
3af29899 494 if (!empty($this->courseid)) {
495 $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />';
cbff94ba 496 }
cbff94ba 497
3af29899 498 if (!empty($this->userid)) {
499 $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />';
cbff94ba 500 }
cbff94ba 501
3af29899 502 if (!empty($this->page)) {
503 $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />';
cbff94ba 504 }
505 }
cbff94ba 506
0610812a 507 /**
508 * Add hidden elements into mform
509 * @param object $mform moodle form object
510 * @return void
511 */
3af29899 512 function add_mform_elements(&$mform) {
7a6b7acf 513 if (empty($this->type)) {
3af29899 514 return;
cbff94ba 515 }
cbff94ba 516
3af29899 517 $mform->addElement('hidden', 'gpr_type', $this->type);
518 $mform->setType('gpr_type', PARAM_SAFEDIR);
cbff94ba 519
7a6b7acf 520 if (!empty($this->plugin)) {
521 $mform->addElement('hidden', 'gpr_plugin', $this->plugin);
522 $mform->setType('gpr_plugin', PARAM_SAFEDIR);
523 }
97033c86 524
3af29899 525 if (!empty($this->courseid)) {
526 $mform->addElement('hidden', 'gpr_courseid', $this->courseid);
527 $mform->setType('gpr_courseid', PARAM_INT);
cbff94ba 528 }
cbff94ba 529
3af29899 530 if (!empty($this->userid)) {
531 $mform->addElement('hidden', 'gpr_userid', $this->userid);
532 $mform->setType('gpr_userid', PARAM_INT);
cbff94ba 533 }
cbff94ba 534
3af29899 535 if (!empty($this->page)) {
536 $mform->addElement('hidden', 'gpr_page', $this->page);
537 $mform->setType('gpr_page', PARAM_INT);
cbff94ba 538 }
539 }
281ffa4a 540
0610812a 541 /**
542 * Add return tracking params into url
543 * @param string $url
544 * @return string $url with erturn tracking params
545 */
3af29899 546 function add_url_params($url) {
7a6b7acf 547 if (empty($this->type)) {
3af29899 548 return $url;
cbff94ba 549 }
5609f9e6 550
3af29899 551 if (strpos($url, '?') === false) {
552 $url .= '?gpr_type='.$this->type;
553 } else {
554 $url .= '&amp;gpr_type='.$this->type;
cbff94ba 555 }
cbff94ba 556
7a6b7acf 557 if (!empty($this->plugin)) {
558 $url .= '&amp;gpr_plugin='.$this->plugin;
559 }
cbff94ba 560
3af29899 561 if (!empty($this->courseid)) {
562 $url .= '&amp;gpr_courseid='.$this->courseid;
cbff94ba 563 }
cbff94ba 564
3af29899 565 if (!empty($this->userid)) {
566 $url .= '&amp;gpr_userid='.$this->userid;
cbff94ba 567 }
0a8a95c9 568
3af29899 569 if (!empty($this->page)) {
570 $url .= '&amp;gpr_page='.$this->page;
0a8a95c9 571 }
5a412dbf 572
3af29899 573 return $url;
5a412dbf 574 }
5a412dbf 575}
7a6b7acf 576
826c5f86 577/**
578 * Function central to gradebook for building and printing the navigation (breadcrumb trail).
579 * @param string $path The path of the calling script (using __FILE__?)
580 * @param string $pagename The language string to use as the last part of the navigation (non-link)
581 * @param mixed $id Either a plain integer (assuming the key is 'id') or an array of keys and values (e.g courseid => $courseid, itemid...)
582 * @return string
583 */
584function grade_build_nav($path, $pagename=null, $id=null) {
585 global $CFG, $COURSE;
586
587 $strgrades = get_string('grades', 'grades');
588
589 // Parse the path and build navlinks from its elements
590 $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash
591 $path = substr($path, $dirroot_length);
592 $path = str_replace('\\', '/', $path);
593
594 $path_elements = explode('/', $path);
595
596 $path_elements_count = count($path_elements);
597
826c5f86 598 // First link is always 'grade'
599 $navlinks = array();
600 $navlinks[] = array('name' => $strgrades,
601 'link' => $CFG->wwwroot.'/grade/index.php?id='.$COURSE->id,
602 'type' => 'misc');
603
604 $link = '';
605 $numberofelements = 3;
606
607 // Prepare URL params string
608 $id_string = '?';
609 if (!is_null($id)) {
610 if (is_array($id)) {
611 foreach ($id as $idkey => $idvalue) {
612 $id_string .= "$idkey=$idvalue&amp;";
613 }
614 } else {
615 $id_string .= "id=$id";
616 }
617 }
618
619 $navlink4 = null;
620
0f78c4de 621 // Remove file extensions from filenames
622 foreach ($path_elements as $key => $filename) {
623 $path_elements[$key] = str_replace('.php', '', $filename);
624 }
625
826c5f86 626 // Second level links
627 switch ($path_elements[1]) {
628 case 'edit': // No link
629 if ($path_elements[3] != 'index.php') {
630 $numberofelements = 4;
631 }
632 break;
633 case 'import': // No link
634 break;
635 case 'export': // No link
636 break;
637 case 'report':
638 // $id is required for this link. Do not print it if $id isn't given
639 if (!is_null($id)) {
640 $link = $CFG->wwwroot . '/grade/report/index.php' . $id_string;
641 }
642
643 if ($path_elements[2] == 'grader') {
644 $numberofelements = 4;
645 }
646 break;
647
648 default:
649 // If this element isn't among the ones already listed above, it isn't supported, throw an error.
650 debugging("grade_build_nav() doesn't support ". $path_elements[1] . " as the second path element after 'grade'.");
651 return false;
652 }
653
654 $navlinks[] = array('name' => get_string($path_elements[1], 'grades'), 'link' => $link, 'type' => 'misc');
655
656 // Third level links
657 if (empty($pagename)) {
658 $pagename = get_string($path_elements[2], 'grades');
659 }
660
661 switch ($numberofelements) {
662 case 3:
663 $navlinks[] = array('name' => $pagename, 'link' => $link, 'type' => 'misc');
664 break;
665 case 4:
666
667 if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') {
3cf6a6d5 668 $navlinks[] = array('name' => get_string('modulename', 'gradereport_grader'),
826c5f86 669 'link' => "$CFG->wwwroot/grade/report/grader/index.php$id_string",
670 'type' => 'misc');
671 }
672 $navlinks[] = array('name' => $pagename, 'link' => '', 'type' => 'misc');
673 break;
674 }
675 $navigation = build_navigation($navlinks);
676
677 return $navigation;
d4795a07 678}
7a6b7acf 679
e98871a2 680/**
6cc3e350 681 * General structure representing grade items in course
e98871a2 682 */
6cc3e350 683class grade_structure {
684 var $context;
e98871a2 685
6cc3e350 686 var $courseid;
e98871a2 687
688 /**
689 * 1D array of grade items only
690 */
691 var $items;
692
6391ebe7 693 /**
6cc3e350 694 * Returns icon of element
695 * @param object $element
696 * @param bool $spacerifnone return spacer if no icon found
697 * @return string icon or spacer
6391ebe7 698 */
6cc3e350 699 function get_element_icon(&$element, $spacerifnone=false) {
700 global $CFG;
701
702 switch ($element['type']) {
703 case 'item':
704 case 'courseitem':
705 case 'categoryitem':
706 if ($element['object']->is_calculated()) {
707 return '<img src="'.$CFG->pixpath.'/i/calc.gif" class="icon itemicon" alt="'.get_string('calculation', 'grades').'"/>';
708
709 } else if (($element['object']->is_course_item() or $element['object']->is_category_item())
710 and ($element['object']->gradetype == GRADE_TYPE_SCALE or $element['object']->gradetype == GRADE_TYPE_VALUE)) {
711 if ($category = $element['object']->get_item_category()) {
712 switch ($category->aggregation) {
713 case GRADE_AGGREGATE_MEAN:
714 case GRADE_AGGREGATE_MEDIAN:
715 case GRADE_AGGREGATE_WEIGHTED_MEAN:
1426edac 716 case GRADE_AGGREGATE_WEIGHTED_MEAN2:
6cc3e350 717 case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
718 return '<img src="'.$CFG->pixpath.'/i/agg_mean.gif" class="icon itemicon" alt="'.get_string('aggregation', 'grades').'"/>';
0758a08e 719 case GRADE_AGGREGATE_SUM:
720 return '<img src="'.$CFG->pixpath.'/i/agg_sum.gif" class="icon itemicon" alt="'.get_string('aggregation', 'grades').'"/>';
6cc3e350 721 }
722 }
723
724 } else if ($element['object']->itemtype == 'mod') {
725 return '<img src="'.$CFG->modpixpath.'/'.$element['object']->itemmodule.'/icon.gif" class="icon itemicon" alt="'
726 .get_string('modulename', $element['object']->itemmodule).'"/>';
727
728 } else if ($element['object']->itemtype == 'manual') {
729 if ($element['object']->is_outcome_item()) {
730 return '<img src="'.$CFG->pixpath.'/i/outcomes.gif" class="icon itemicon" alt="'.get_string('outcome', 'grades').'"/>';
731 } else {
732 //TODO: add better icon
733 return '<img src="'.$CFG->pixpath.'/t/edit.gif" class="icon itemicon" alt="'.get_string('manualitem', 'grades').'"/>';
734 }
735 }
736 break;
737
738 case 'category':
739 return '<img src="'.$CFG->pixpath.'/f/folder.gif" class="icon itemicon" alt="'.get_string('category', 'grades').'"/>';
740 }
741
742 if ($spacerifnone) {
743 return '<img src="'.$CFG->wwwroot.'/pix/spacer.gif" class="icon itemicon" alt=""/>';
744 } else {
745 return '';
746 }
747 }
6391ebe7 748
e98871a2 749 /**
6cc3e350 750 * Returns name of element optionally with icon and link
751 * @param object $element
752 * @param bool $withlinks
753 * @param bool $icons
754 * @param bool $spacerifnone return spacer if no icon found
755 * @return header string
e98871a2 756 */
6cc3e350 757 function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false) {
758 global $CFG;
759
760 $header = '';
761
762 if ($icon) {
763 $header .= $this->get_element_icon($element, $spacerifnone);
764 }
765
766 $header .= $element['object']->get_name();
767
768 if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and $element['type'] != 'courseitem') {
769 return $header;
770 }
771
772 $itemtype = $element['object']->itemtype;
773 $itemmodule = $element['object']->itemmodule;
774 $iteminstance = $element['object']->iteminstance;
775
776 if ($withlink and $itemtype=='mod' and $iteminstance and $itemmodule) {
777 $cm = get_coursemodule_from_instance($itemmodule, $iteminstance, $this->courseid);
778
779 $dir = $CFG->dirroot.'/mod/'.$itemmodule;
780
781 if (file_exists($dir.'/grade.php')) {
782 $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/grade.php?id='.$cm->id;
783 } else {
784 $url = $CFG->wwwroot.'/mod/'.$itemmodule.'/view.php?id='.$cm->id;
785 }
786
787 $header = '<a href="'.$url.'">'.$header.'</a>';
788 }
789
790 return $header;
791 }
792
793 /**
794 * Returns the grade eid - the grade may not exist yet.
795 * @param $grade_grade object
796 * @return string eid
797 */
798 function get_grade_eid($grade_grade) {
799 if (empty($grade_grade->id)) {
800 return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid;
801 } else {
802 return 'g'.$grade_grade->id;
803 }
804 }
805
806 /**
807 * Returns the grade_item eid
808 * @param $grade_item object
809 * @return string eid
810 */
811 function get_item_eid($grade_item) {
812 return 'i'.$grade_item->id;
813 }
814
9ecd4386 815 function get_params_for_iconstr($element) {
816 $strparams = new stdClass();
817 $strparams->category = '';
818 $strparams->itemname = '';
819 $strparams->itemmodule = '';
820 if (!method_exists($element['object'], 'get_name')) {
821 return $strparams;
822 }
823
824 $strparams->itemname = $element['object']->get_name();
825
826 // If element name is categorytotal, get the name of the parent category
827 if ($strparams->itemname == get_string('categorytotal', 'grades')) {
828 $parent = $element['object']->get_parent_category();
829 $strparams->category = $parent->get_name() . ' ';
830 } else {
831 $strparams->category = '';
832 }
833
834 $strparams->itemmodule = null;
835 if (isset($element['object']->itemmodule)) {
836 $strparams->itemmodule = $element['object']->itemmodule;
837 }
838 return $strparams;
839 }
840
6cc3e350 841 /**
842 * Return edit icon for give element
843 * @param object $element
844 * @return string
845 */
846 function get_edit_icon($element, $gpr) {
847 global $CFG;
848
849 if (!has_capability('moodle/grade:manage', $this->context)) {
850 if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
851 // oki - let them override grade
852 } else {
853 return '';
854 }
855 }
856
857 static $stredit = null;
858 static $strfeedback = null;
859 if (is_null($stredit)) {
860 $stredit = get_string('edit');
861 $strfeedback = get_string('feedback');
862 }
863
9ecd4386 864 $strparams = $this->get_params_for_iconstr($element);
865 if ($element['type'] == 'item' or $element['type'] == 'category') {
866 }
867
6cc3e350 868 $object = $element['object'];
869 $overlib = '';
870
871 switch ($element['type']) {
872 case 'item':
873 case 'categoryitem':
874 case 'courseitem':
9ecd4386 875 $stredit = get_string('editverbose', 'grades', $strparams);
6cc3e350 876 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) {
877 $url = $CFG->wwwroot.'/grade/edit/tree/item.php?courseid='.$this->courseid.'&amp;id='.$object->id;
878 } else {
879 $url = $CFG->wwwroot.'/grade/edit/tree/outcomeitem.php?courseid='.$this->courseid.'&amp;id='.$object->id;
880 }
881 $url = $gpr->add_url_params($url);
882 break;
883
884 case 'category':
9ecd4386 885 $stredit = get_string('editverbose', 'grades', $strparams);
6cc3e350 886 $url = $CFG->wwwroot.'/grade/edit/tree/category.php?courseid='.$this->courseid.'&amp;id='.$object->id;
887 $url = $gpr->add_url_params($url);
888 break;
889
890 case 'grade':
891 if (empty($object->id)) {
892 $url = $CFG->wwwroot.'/grade/edit/tree/grade.php?courseid='.$this->courseid.'&amp;itemid='.$object->itemid.'&amp;userid='.$object->userid;
893 } else {
894 $url = $CFG->wwwroot.'/grade/edit/tree/grade.php?courseid='.$this->courseid.'&amp;id='.$object->id;
895 }
896 $url = $gpr->add_url_params($url);
897 if (!empty($object->feedback)) {
898 $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat)));
899 $function = "return overlib('$feedback', BORDER, 0, FGCLASS, 'feedback', "
900 ."CAPTIONFONTCLASS, 'caption', CAPTION, '$strfeedback');";
901 $overlib = 'onmouseover="'.s($function).'" onmouseout="return nd();"';
902 }
903 break;
904
905 default:
906 $url = null;
907 }
908
909 if ($url) {
910 return '<a href="'.$url.'"><img '.$overlib.' src="'.$CFG->pixpath.'/t/edit.gif" class="iconsmall" alt="'.$stredit.'" title="'.$stredit.'"/></a>';
911
912 } else {
913 return '';
914 }
915 }
916
917 /**
918 * Return hiding icon for give element
919 * @param object $element
920 * @return string
921 */
922 function get_hiding_icon($element, $gpr) {
923 global $CFG;
924
925 if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:hide', $this->context)) {
926 return '';
927 }
928
9ecd4386 929 $strparams = $this->get_params_for_iconstr($element);
930 $strshow = get_string('showverbose', 'grades', $strparams);
931 $strhide = get_string('hideverbose', 'grades', $strparams);
6cc3e350 932
933 if ($element['object']->is_hidden()) {
934 $icon = 'show';
935 $tooltip = $strshow;
936
937 if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) { // Change the icon and add a tooltip showing the date
938 $icon = 'hiddenuntil';
939 $tooltip = get_string('hiddenuntildate', 'grades', userdate($element['object']->get_hidden()));
940 }
941
942 $url = $CFG->wwwroot.'/grade/edit/tree/action.php?id='.$this->courseid.'&amp;action=show&amp;sesskey='.sesskey()
943 . '&amp;eid='.$element['eid'];
944 $url = $gpr->add_url_params($url);
945 $action = '<a href="'.$url.'"><img alt="'.$strshow.'" src="'.$CFG->pixpath.'/t/'.$icon.'.gif" class="iconsmall" title="'.$tooltip.'"/></a>';
946
947 } else {
948 $url = $CFG->wwwroot.'/grade/edit/tree/action.php?id='.$this->courseid.'&amp;action=hide&amp;sesskey='.sesskey()
949 . '&amp;eid='.$element['eid'];
950 $url = $gpr->add_url_params($url);
951 $action = '<a href="'.$url.'"><img src="'.$CFG->pixpath.'/t/hide.gif" class="iconsmall" alt="'.$strhide.'" title="'.$strhide.'"/></a>';
952 }
953 return $action;
954 }
955
956 /**
957 * Return locking icon for give element
958 * @param object $element
959 * @return string
960 */
961 function get_locking_icon($element, $gpr) {
962 global $CFG;
963
9ecd4386 964 $strparams = $this->get_params_for_iconstr($element);
965 $strunlock = get_string('unlockverbose', 'grades', $strparams);
966 $strlock = get_string('lockverbose', 'grades', $strparams);
6cc3e350 967
968 if ($element['object']->is_locked()) {
969 $icon = 'unlock';
970 $tooltip = $strunlock;
971
972 if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) { // Change the icon and add a tooltip showing the date
973 $icon = 'locktime';
974 $tooltip = get_string('locktimedate', 'grades', userdate($element['object']->get_locktime()));
975 }
976
977 if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) {
978 return '';
979 }
980 $url = $CFG->wwwroot.'/grade/edit/tree/action.php?id='.$this->courseid.'&amp;action=unlock&amp;sesskey='.sesskey()
981 . '&amp;eid='.$element['eid'];
982 $url = $gpr->add_url_params($url);
983 $action = '<a href="'.$url.'"><img src="'.$CFG->pixpath.'/t/'.$icon.'.gif" alt="'.$strunlock.'" class="iconsmall" title="'.$tooltip.'"/></a>';
984
985 } else {
986 if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) {
987 return '';
988 }
989 $url = $CFG->wwwroot.'/grade/edit/tree/action.php?id='.$this->courseid.'&amp;action=lock&amp;sesskey='.sesskey()
990 . '&amp;eid='.$element['eid'];
991 $url = $gpr->add_url_params($url);
992 $action = '<a href="'.$url.'"><img src="'.$CFG->pixpath.'/t/lock.gif" class="iconsmall" alt="'.$strlock.'" title="'
993 . $strlock.'"/></a>';
994 }
995 return $action;
996 }
997
998 /**
999 * Return calculation icon for given element
1000 * @param object $element
1001 * @return string
1002 */
1003 function get_calculation_icon($element, $gpr) {
1004 global $CFG;
1005 if (!has_capability('moodle/grade:manage', $this->context)) {
1006 return '';
1007 }
1008
1009 $calculation_icon = '';
1010
1011 $type = $element['type'];
1012 $object = $element['object'];
1013
9ecd4386 1014
6cc3e350 1015 if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
9ecd4386 1016 $strparams = $this->get_params_for_iconstr($element);
1017 $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams);
6cc3e350 1018
1019 // show calculation icon only when calculation possible
1020 if ((!$object->is_external_item() or $object->is_outcome_item())
1021 and ($object->gradetype == GRADE_TYPE_SCALE or $object->gradetype == GRADE_TYPE_VALUE)) {
1022 if ($object->is_calculated()) {
1023 $icon = 'calc.gif';
1024 } else {
1025 $icon = 'calc_off.gif';
1026 }
1027 $url = $CFG->wwwroot.'/grade/edit/tree/calculation.php?courseid='.$this->courseid.'&amp;id='.$object->id;
1028 $url = $gpr->add_url_params($url);
1029 $calculation_icon = '<a href="'. $url.'"><img src="'.$CFG->pixpath.'/t/'.$icon.'" class="iconsmall" alt="'
1030 . $streditcalculation.'" title="'.$streditcalculation.'" /></a>'. "\n";
1031 }
1032 }
1033
1034 return $calculation_icon;
1035 }
1036}
1037
1038/**
1039 * Flat structure similar to grade tree.
1040 */
1041class grade_seq extends grade_structure {
1042
1043 /**
1044 * A string of GET URL variables, namely courseid and sesskey, used in most URLs built by this class.
1045 * @var string $commonvars
1046 */
1047 var $commonvars;
1048
1049 /**
1050 * 1D array of elements
1051 */
1052 var $elements;
e98871a2 1053
1054 /**
1055 * Constructor, retrieves and stores array of all grade_category and grade_item
1056 * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
1057 * @param int $courseid
1058 * @param boolean $category_grade_last category grade item is the last child
1059 * @param array $collapsed array of collapsed categories
1060 */
1061 function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) {
1062 global $USER, $CFG;
1063
1064 $this->courseid = $courseid;
1065 $this->commonvars = "&amp;sesskey=$USER->sesskey&amp;id=$this->courseid";
1066 $this->context = get_context_instance(CONTEXT_COURSE, $courseid);
1067
1068 // get course grade tree
1069 $top_element = grade_category::fetch_course_tree($courseid, true);
1070
6391ebe7 1071 $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes);
1072
1073 foreach ($this->elements as $key=>$unused) {
b89a70ce 1074 $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object'];
6391ebe7 1075 }
e98871a2 1076 }
1077
1078 /**
1079 * Static recursive helper - makes the grade_item for category the last children
1080 * @static
1081 * @param array $element The seed of the recursion
1082 * @return void
1083 */
1084 function flatten(&$element, $category_grade_last, $nooutcomes) {
1085 if (empty($element['children'])) {
1086 return array();
1087 }
1088 $children = array();
1089
1090 foreach ($element['children'] as $sortorder=>$unused) {
1091 if ($nooutcomes and $element['type'] != 'category' and $element['children'][$sortorder]['object']->is_outcome_item()) {
1092 continue;
1093 }
1094 $children[] = $element['children'][$sortorder];
1095 }
1096 unset($element['children']);
1097
1098 if ($category_grade_last and count($children) > 1) {
1099 $cat_item = array_shift($children);
1100 array_push($children, $cat_item);
1101 }
1102
1103 $result = array();
1104 foreach ($children as $child) {
e0724506 1105 if ($child['type'] == 'category') {
6391ebe7 1106 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes);
e98871a2 1107 } else {
1108 $child['eid'] = 'i'.$child['object']->id;
6391ebe7 1109 $result[$child['object']->id] = $child;
e98871a2 1110 }
1111 }
1112
1113 return $result;
1114 }
1115
1116 /**
1117 * Parses the array in search of a given eid and returns a element object with
1118 * information about the element it has found.
1119 * @param int $eid
1120 * @return object element
1121 */
1122 function locate_element($eid) {
1123 // it is a grade - construct a new object
1124 if (strpos($eid, 'n') === 0) {
1125 if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1126 return null;
1127 }
1128
1129 $itemid = $matches[1];
1130 $userid = $matches[2];
1131
1132 //extra security check - the grade item must be in this tree
1133 if (!$item_el = $this->locate_element('i'.$itemid)) {
1134 return null;
1135 }
1136
1137 // $gradea->id may be null - means does not exist yet
1138 $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1139
1140 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1141 return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1142
1143 } else if (strpos($eid, 'g') === 0) {
1144 $id = (int)substr($eid, 1);
1145 if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1146 return null;
1147 }
1148 //extra security check - the grade item must be in this tree
1149 if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1150 return null;
1151 }
1152 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1153 return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1154 }
1155
1156 // it is a category or item
6391ebe7 1157 foreach ($this->elements as $element) {
6cc3e350 1158 if ($element['eid'] == $eid) {
1159 return $element;
1160 }
e98871a2 1161 }
6cc3e350 1162
1163 return null;
e98871a2 1164 }
e98871a2 1165}
1166
7a6b7acf 1167/**
1168 * This class represents a complete tree of categories, grade_items and final grades,
1169 * organises as an array primarily, but which can also be converted to other formats.
1170 * It has simple method calls with complex implementations, allowing for easy insertion,
1171 * deletion and moving of items and categories within the tree.
1172 */
6cc3e350 1173class grade_tree extends grade_structure {
7a6b7acf 1174
1175 /**
1176 * The basic representation of the tree as a hierarchical, 3-tiered array.
1177 * @var object $top_element
1178 */
1179 var $top_element;
1180
1181 /**
1182 * A string of GET URL variables, namely courseid and sesskey, used in most URLs built by this class.
1183 * @var string $commonvars
1184 */
1185 var $commonvars;
1186
1187 /**
1188 * 2D array of grade items and categories
1189 */
1190 var $levels;
1191
b89a70ce 1192 /**
1193 * Grade items
1194 */
1195 var $items;
1196
7a6b7acf 1197 /**
1198 * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item
e98871a2 1199 * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
7a6b7acf 1200 * @param int $courseid
1201 * @param boolean $fillers include fillers and colspans, make the levels var "rectangular"
1202 * @param boolean $category_grade_last category grade item is the last child
4faf5f99 1203 * @param array $collapsed array of collapsed categories
7a6b7acf 1204 */
aea4df41 1205 function grade_tree($courseid, $fillers=true, $category_grade_last=false, $collapsed=null, $nooutcomes=false) {
7a6b7acf 1206 global $USER, $CFG;
1207
1208 $this->courseid = $courseid;
1209 $this->commonvars = "&amp;sesskey=$USER->sesskey&amp;id=$this->courseid";
1210 $this->levels = array();
2cc773f5 1211 $this->context = get_context_instance(CONTEXT_COURSE, $courseid);
7a6b7acf 1212
1213 // get course grade tree
1214 $this->top_element = grade_category::fetch_course_tree($courseid, true);
1215
4faf5f99 1216 // collapse the categories if requested
1217 if (!empty($collapsed)) {
1218 grade_tree::category_collapse($this->top_element, $collapsed);
1219 }
1220
aea4df41 1221 // no otucomes if requested
1222 if (!empty($nooutcomes)) {
1223 grade_tree::no_outcomes($this->top_element);
1224 }
1225
4faf5f99 1226 // move category item to last position in category
7a6b7acf 1227 if ($category_grade_last) {
1228 grade_tree::category_grade_last($this->top_element);
1229 }
1230
1231 if ($fillers) {
1232 // inject fake categories == fillers
1233 grade_tree::inject_fillers($this->top_element, 0);
1234 // add colspans to categories and fillers
1235 grade_tree::inject_colspans($this->top_element);
1236 }
1237
1238 grade_tree::fill_levels($this->levels, $this->top_element, 0);
d297269d 1239
7a6b7acf 1240 }
1241
4faf5f99 1242 /**
1243 * Static recursive helper - removes items from collapsed categories
1244 * @static
1245 * @param array $element The seed of the recursion
1246 * @param array $collapsed array of collapsed categories
1247 * @return void
1248 */
1249 function category_collapse(&$element, $collapsed) {
1250 if ($element['type'] != 'category') {
1251 return;
1252 }
1253 if (empty($element['children']) or count($element['children']) < 2) {
1254 return;
1255 }
1256
384960dd 1257 if (in_array($element['object']->id, $collapsed['aggregatesonly'])) {
4faf5f99 1258 $category_item = reset($element['children']); //keep only category item
1259 $element['children'] = array(key($element['children'])=>$category_item);
1260
1261 } else {
384960dd 1262 if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item
1263 reset($element['children']);
1264 $first_key = key($element['children']);
1265 unset($element['children'][$first_key]);
1266 }
1267 foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children
4faf5f99 1268 grade_tree::category_collapse($element['children'][$sortorder], $collapsed);
1269 }
1270 }
1271 }
7a6b7acf 1272
aea4df41 1273 /**
1274 * Static recursive helper - removes all outcomes
1275 * @static
1276 * @param array $element The seed of the recursion
1277 * @return void
1278 */
1279 function no_outcomes(&$element) {
1280 if ($element['type'] != 'category') {
1281 return;
1282 }
1283 foreach ($element['children'] as $sortorder=>$child) {
1284 if ($element['children'][$sortorder]['type'] == 'item'
1285 and $element['children'][$sortorder]['object']->is_outcome_item()) {
1286 unset($element['children'][$sortorder]);
1287
d4795a07 1288 } else if ($element['children'][$sortorder]['type'] == 'category') {
aea4df41 1289 grade_tree::no_outcomes($element['children'][$sortorder]);
1290 }
1291 }
1292 }
1293
7a6b7acf 1294 /**
1295 * Static recursive helper - makes the grade_item for category the last children
1296 * @static
1297 * @param array $element The seed of the recursion
1298 * @return void
1299 */
1300 function category_grade_last(&$element) {
1301 if (empty($element['children'])) {
1302 return;
1303 }
1304 if (count($element['children']) < 2) {
1305 return;
1306 }
3e0e2436 1307 $first_item = reset($element['children']);
4a3dfd9a 1308 if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') {
3e0e2436 1309 // the category item might have been already removed
1310 $order = key($element['children']);
1311 unset($element['children'][$order]);
1312 $element['children'][$order] =& $first_item;
1313 }
206f9953 1314 foreach ($element['children'] as $sortorder => $child) {
7a6b7acf 1315 grade_tree::category_grade_last($element['children'][$sortorder]);
1316 }
1317 }
1318
1319 /**
1320 * Static recursive helper - fills the levels array, useful when accessing tree elements of one level
1321 * @static
1322 * @param int $levels
1323 * @param array $element The seed of the recursion
1324 * @param int $depth
1325 * @return void
1326 */
1327 function fill_levels(&$levels, &$element, $depth) {
1328 if (!array_key_exists($depth, $levels)) {
1329 $levels[$depth] = array();
1330 }
1331
1332 // prepare unique identifier
1333 if ($element['type'] == 'category') {
1334 $element['eid'] = 'c'.$element['object']->id;
1335 } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
1336 $element['eid'] = 'i'.$element['object']->id;
b89a70ce 1337 $this->items[$element['object']->id] =& $element['object'];
7a6b7acf 1338 }
1339
1340 $levels[$depth][] =& $element;
1341 $depth++;
1342 if (empty($element['children'])) {
1343 return;
1344 }
1345 $prev = 0;
1346 foreach ($element['children'] as $sortorder=>$child) {
1347 grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth);
1348 $element['children'][$sortorder]['prev'] = $prev;
1349 $element['children'][$sortorder]['next'] = 0;
1350 if ($prev) {
1351 $element['children'][$prev]['next'] = $sortorder;
1352 }
1353 $prev = $sortorder;
1354 }
1355 }
1356
1357 /**
1358 * Static recursive helper - makes full tree (all leafes are at the same level)
1359 */
1360 function inject_fillers(&$element, $depth) {
1361 $depth++;
1362
1363 if (empty($element['children'])) {
1364 return $depth;
1365 }
1366 $chdepths = array();
1367 $chids = array_keys($element['children']);
1368 $last_child = end($chids);
1369 $first_child = reset($chids);
1370
1371 foreach ($chids as $chid) {
1372 $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth);
1373 }
1374 arsort($chdepths);
1375
1376 $maxdepth = reset($chdepths);
1377 foreach ($chdepths as $chid=>$chd) {
1378 if ($chd == $maxdepth) {
1379 continue;
1380 }
1381 for ($i=0; $i < $maxdepth-$chd; $i++) {
1382 if ($chid == $first_child) {
1383 $type = 'fillerfirst';
1384 } else if ($chid == $last_child) {
1385 $type = 'fillerlast';
1386 } else {
1387 $type = 'filler';
1388 }
1389 $oldchild =& $element['children'][$chid];
1390 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type, 'eid'=>'', 'depth'=>$element['object']->depth,'children'=>array($oldchild));
1391 }
1392 }
1393
1394 return $maxdepth;
1395 }
1396
1397 /**
1398 * Static recursive helper - add colspan information into categories
1399 */
1400 function inject_colspans(&$element) {
1401 if (empty($element['children'])) {
1402 return 1;
1403 }
1404 $count = 0;
1405 foreach ($element['children'] as $key=>$child) {
1406 $count += grade_tree::inject_colspans($element['children'][$key]);
1407 }
1408 $element['colspan'] = $count;
1409 return $count;
1410 }
1411
1412 /**
1413 * Parses the array in search of a given eid and returns a element object with
1414 * information about the element it has found.
1415 * @param int $eid
1416 * @return object element
1417 */
1418 function locate_element($eid) {
d3c3da1b 1419 // it is a grade - construct a new object
1420 if (strpos($eid, 'n') === 0) {
1421 if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
1422 return null;
1423 }
1424
1425 $itemid = $matches[1];
1426 $userid = $matches[2];
1427
1428 //extra security check - the grade item must be in this tree
1429 if (!$item_el = $this->locate_element('i'.$itemid)) {
1430 return null;
1431 }
1432
1433 // $gradea->id may be null - means does not exist yet
1434 $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
1435
1436 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1437 return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
1438
1439 } else if (strpos($eid, 'g') === 0) {
7a6b7acf 1440 $id = (int)substr($eid, 1);
1441 if (!$grade = grade_grade::fetch(array('id'=>$id))) {
1442 return null;
1443 }
1444 //extra security check - the grade item must be in this tree
1445 if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
1446 return null;
1447 }
1448 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
1449 return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
1450 }
1451
1452 // it is a category or item
1453 foreach ($this->levels as $row) {
1454 foreach ($row as $element) {
1455 if ($element['type'] == 'filler') {
1456 continue;
1457 }
1458 if ($element['eid'] == $eid) {
1459 return $element;
1460 }
1461 }
1462 }
1463
1464 return null;
1465 }
7a6b7acf 1466}
1467
e2008be2 1468?>