PageRenderTime 99ms CodeModel.GetById 3ms app.highlight 76ms RepoModel.GetById 1ms app.codeStats 1ms

/grade/lib.php

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