[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/grade/ -> lib.php (source)

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


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7