"Fossies" - the Fresh Open Source Software Archive

Member "moodle/grade/lib.php" (6 Sep 2019, 123832 Bytes) of package /linux/www/moodle-3.6.6.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "lib.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.6.5_vs_3.6.6.

    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 
   25 require_once($CFG->libdir . '/gradelib.php');
   26 require_once($CFG->dirroot . '/grade/export/lib.php');
   27 
   28 /**
   29  * This class iterates over all users that are graded in a course.
   30  * Returns detailed info about users and their grades.
   31  *
   32  * @author Petr Skoda <skodak@moodle.org>
   33  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
   34  */
   35 class graded_users_iterator {
   36 
   37     /**
   38      * The couse whose users we are interested in
   39      */
   40     protected $course;
   41 
   42     /**
   43      * An array of grade items or null if only user data was requested
   44      */
   45     protected $grade_items;
   46 
   47     /**
   48      * The group ID we are interested in. 0 means all groups.
   49      */
   50     protected $groupid;
   51 
   52     /**
   53      * A recordset of graded users
   54      */
   55     protected $users_rs;
   56 
   57     /**
   58      * A recordset of user grades (grade_grade instances)
   59      */
   60     protected $grades_rs;
   61 
   62     /**
   63      * Array used when moving to next user while iterating through the grades recordset
   64      */
   65     protected $gradestack;
   66 
   67     /**
   68      * The first field of the users table by which the array of users will be sorted
   69      */
   70     protected $sortfield1;
   71 
   72     /**
   73      * Should sortfield1 be ASC or DESC
   74      */
   75     protected $sortorder1;
   76 
   77     /**
   78      * The second field of the users table by which the array of users will be sorted
   79      */
   80     protected $sortfield2;
   81 
   82     /**
   83      * Should sortfield2 be ASC or DESC
   84      */
   85     protected $sortorder2;
   86 
   87     /**
   88      * Should users whose enrolment has been suspended be ignored?
   89      */
   90     protected $onlyactive = false;
   91 
   92     /**
   93      * Enable user custom fields
   94      */
   95     protected $allowusercustomfields = false;
   96 
   97     /**
   98      * List of suspended users in course. This includes users whose enrolment status is suspended
   99      * or enrolment has expired or not started.
  100      */
  101     protected $suspendedusers = array();
  102 
  103     /**
  104      * Constructor
  105      *
  106      * @param object $course A course object
  107      * @param array  $grade_items array of grade items, if not specified only user info returned
  108      * @param int    $groupid iterate only group users if present
  109      * @param string $sortfield1 The first field of the users table by which the array of users will be sorted
  110      * @param string $sortorder1 The order in which the first sorting field will be sorted (ASC or DESC)
  111      * @param string $sortfield2 The second field of the users table by which the array of users will be sorted
  112      * @param string $sortorder2 The order in which the second sorting field will be sorted (ASC or DESC)
  113      */
  114     public function __construct($course, $grade_items=null, $groupid=0,
  115                                           $sortfield1='lastname', $sortorder1='ASC',
  116                                           $sortfield2='firstname', $sortorder2='ASC') {
  117         $this->course      = $course;
  118         $this->grade_items = $grade_items;
  119         $this->groupid     = $groupid;
  120         $this->sortfield1  = $sortfield1;
  121         $this->sortorder1  = $sortorder1;
  122         $this->sortfield2  = $sortfield2;
  123         $this->sortorder2  = $sortorder2;
  124 
  125         $this->gradestack  = array();
  126     }
  127 
  128     /**
  129      * Initialise the iterator
  130      *
  131      * @return boolean success
  132      */
  133     public function init() {
  134         global $CFG, $DB;
  135 
  136         $this->close();
  137 
  138         export_verify_grades($this->course->id);
  139         $course_item = grade_item::fetch_course_item($this->course->id);
  140         if ($course_item->needsupdate) {
  141             // Can not calculate all final grades - sorry.
  142             return false;
  143         }
  144 
  145         $coursecontext = context_course::instance($this->course->id);
  146 
  147         list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx');
  148         list($gradebookroles_sql, $params) = $DB->get_in_or_equal(explode(',', $CFG->gradebookroles), SQL_PARAMS_NAMED, 'grbr');
  149         list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, '', 0, $this->onlyactive);
  150 
  151         $params = array_merge($params, $enrolledparams, $relatedctxparams);
  152 
  153         if ($this->groupid) {
  154             $groupsql = "INNER JOIN {groups_members} gm ON gm.userid = u.id";
  155             $groupwheresql = "AND gm.groupid = :groupid";
  156             // $params contents: gradebookroles
  157             $params['groupid'] = $this->groupid;
  158         } else {
  159             $groupsql = "";
  160             $groupwheresql = "";
  161         }
  162 
  163         if (empty($this->sortfield1)) {
  164             // We must do some sorting even if not specified.
  165             $ofields = ", u.id AS usrt";
  166             $order   = "usrt ASC";
  167 
  168         } else {
  169             $ofields = ", u.$this->sortfield1 AS usrt1";
  170             $order   = "usrt1 $this->sortorder1";
  171             if (!empty($this->sortfield2)) {
  172                 $ofields .= ", u.$this->sortfield2 AS usrt2";
  173                 $order   .= ", usrt2 $this->sortorder2";
  174             }
  175             if ($this->sortfield1 != 'id' and $this->sortfield2 != 'id') {
  176                 // User order MUST be the same in both queries,
  177                 // must include the only unique user->id if not already present.
  178                 $ofields .= ", u.id AS usrt";
  179                 $order   .= ", usrt ASC";
  180             }
  181         }
  182 
  183         $userfields = 'u.*';
  184         $customfieldssql = '';
  185         if ($this->allowusercustomfields && !empty($CFG->grade_export_customprofilefields)) {
  186             $customfieldscount = 0;
  187             $customfieldsarray = grade_helper::get_user_profile_fields($this->course->id, $this->allowusercustomfields);
  188             foreach ($customfieldsarray as $field) {
  189                 if (!empty($field->customid)) {
  190                     $customfieldssql .= "
  191                             LEFT JOIN (SELECT * FROM {user_info_data}
  192                                 WHERE fieldid = :cf$customfieldscount) cf$customfieldscount
  193                             ON u.id = cf$customfieldscount.userid";
  194                     $userfields .= ", cf$customfieldscount.data AS customfield_{$field->customid}";
  195                     $params['cf'.$customfieldscount] = $field->customid;
  196                     $customfieldscount++;
  197                 }
  198             }
  199         }
  200 
  201         $users_sql = "SELECT $userfields $ofields
  202                         FROM {user} u
  203                         JOIN ($enrolledsql) je ON je.id = u.id
  204                              $groupsql $customfieldssql
  205                         JOIN (
  206                                   SELECT DISTINCT ra.userid
  207                                     FROM {role_assignments} ra
  208                                    WHERE ra.roleid $gradebookroles_sql
  209                                      AND ra.contextid $relatedctxsql
  210                              ) rainner ON rainner.userid = u.id
  211                          WHERE u.deleted = 0
  212                              $groupwheresql
  213                     ORDER BY $order";
  214         $this->users_rs = $DB->get_recordset_sql($users_sql, $params);
  215 
  216         if (!$this->onlyactive) {
  217             $context = context_course::instance($this->course->id);
  218             $this->suspendedusers = get_suspended_userids($context);
  219         } else {
  220             $this->suspendedusers = array();
  221         }
  222 
  223         if (!empty($this->grade_items)) {
  224             $itemids = array_keys($this->grade_items);
  225             list($itemidsql, $grades_params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED, 'items');
  226             $params = array_merge($params, $grades_params);
  227 
  228             $grades_sql = "SELECT g.* $ofields
  229                              FROM {grade_grades} g
  230                              JOIN {user} u ON g.userid = u.id
  231                              JOIN ($enrolledsql) je ON je.id = u.id
  232                                   $groupsql
  233                              JOIN (
  234                                       SELECT DISTINCT ra.userid
  235                                         FROM {role_assignments} ra
  236                                        WHERE ra.roleid $gradebookroles_sql
  237                                          AND ra.contextid $relatedctxsql
  238                                   ) rainner ON rainner.userid = u.id
  239                               WHERE u.deleted = 0
  240                               AND g.itemid $itemidsql
  241                               $groupwheresql
  242                          ORDER BY $order, g.itemid ASC";
  243             $this->grades_rs = $DB->get_recordset_sql($grades_sql, $params);
  244         } else {
  245             $this->grades_rs = false;
  246         }
  247 
  248         return true;
  249     }
  250 
  251     /**
  252      * Returns information about the next user
  253      * @return mixed array of user info, all grades and feedback or null when no more users found
  254      */
  255     public function next_user() {
  256         if (!$this->users_rs) {
  257             return false; // no users present
  258         }
  259 
  260         if (!$this->users_rs->valid()) {
  261             if ($current = $this->_pop()) {
  262                 // this is not good - user or grades updated between the two reads above :-(
  263             }
  264 
  265             return false; // no more users
  266         } else {
  267             $user = $this->users_rs->current();
  268             $this->users_rs->next();
  269         }
  270 
  271         // find grades of this user
  272         $grade_records = array();
  273         while (true) {
  274             if (!$current = $this->_pop()) {
  275                 break; // no more grades
  276             }
  277 
  278             if (empty($current->userid)) {
  279                 break;
  280             }
  281 
  282             if ($current->userid != $user->id) {
  283                 // grade of the next user, we have all for this user
  284                 $this->_push($current);
  285                 break;
  286             }
  287 
  288             $grade_records[$current->itemid] = $current;
  289         }
  290 
  291         $grades = array();
  292         $feedbacks = array();
  293 
  294         if (!empty($this->grade_items)) {
  295             foreach ($this->grade_items as $grade_item) {
  296                 if (!isset($feedbacks[$grade_item->id])) {
  297                     $feedbacks[$grade_item->id] = new stdClass();
  298                 }
  299                 if (array_key_exists($grade_item->id, $grade_records)) {
  300                     $feedbacks[$grade_item->id]->feedback       = $grade_records[$grade_item->id]->feedback;
  301                     $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat;
  302                     unset($grade_records[$grade_item->id]->feedback);
  303                     unset($grade_records[$grade_item->id]->feedbackformat);
  304                     $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false);
  305                 } else {
  306                     $feedbacks[$grade_item->id]->feedback       = '';
  307                     $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE;
  308                     $grades[$grade_item->id] =
  309                         new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false);
  310                 }
  311                 $grades[$grade_item->id]->grade_item = $grade_item;
  312             }
  313         }
  314 
  315         // Set user suspended status.
  316         $user->suspendedenrolment = isset($this->suspendedusers[$user->id]);
  317         $result = new stdClass();
  318         $result->user      = $user;
  319         $result->grades    = $grades;
  320         $result->feedbacks = $feedbacks;
  321         return $result;
  322     }
  323 
  324     /**
  325      * Close the iterator, do not forget to call this function
  326      */
  327     public function close() {
  328         if ($this->users_rs) {
  329             $this->users_rs->close();
  330             $this->users_rs = null;
  331         }
  332         if ($this->grades_rs) {
  333             $this->grades_rs->close();
  334             $this->grades_rs = null;
  335         }
  336         $this->gradestack = array();
  337     }
  338 
  339     /**
  340      * Should all enrolled users be exported or just those with an active enrolment?
  341      *
  342      * @param bool $onlyactive True to limit the export to users with an active enrolment
  343      */
  344     public function require_active_enrolment($onlyactive = true) {
  345         if (!empty($this->users_rs)) {
  346             debugging('Calling require_active_enrolment() has no effect unless you call init() again', DEBUG_DEVELOPER);
  347         }
  348         $this->onlyactive  = $onlyactive;
  349     }
  350 
  351     /**
  352      * Allow custom fields to be included
  353      *
  354      * @param bool $allow Whether to allow custom fields or not
  355      * @return void
  356      */
  357     public function allow_user_custom_fields($allow = true) {
  358         if ($allow) {
  359             $this->allowusercustomfields = true;
  360         } else {
  361             $this->allowusercustomfields = false;
  362         }
  363     }
  364 
  365     /**
  366      * Add a grade_grade instance to the grade stack
  367      *
  368      * @param grade_grade $grade Grade object
  369      *
  370      * @return void
  371      */
  372     private function _push($grade) {
  373         array_push($this->gradestack, $grade);
  374     }
  375 
  376 
  377     /**
  378      * Remove a grade_grade instance from the grade stack
  379      *
  380      * @return grade_grade current grade object
  381      */
  382     private function _pop() {
  383         global $DB;
  384         if (empty($this->gradestack)) {
  385             if (empty($this->grades_rs) || !$this->grades_rs->valid()) {
  386                 return null; // no grades present
  387             }
  388 
  389             $current = $this->grades_rs->current();
  390 
  391             $this->grades_rs->next();
  392 
  393             return $current;
  394         } else {
  395             return array_pop($this->gradestack);
  396         }
  397     }
  398 }
  399 
  400 /**
  401  * Print a selection popup form of the graded users in a course.
  402  *
  403  * @deprecated since 2.0
  404  *
  405  * @param int    $course id of the course
  406  * @param string $actionpage The page receiving the data from the popoup form
  407  * @param int    $userid   id of the currently selected user (or 'all' if they are all selected)
  408  * @param int    $groupid id of requested group, 0 means all
  409  * @param int    $includeall bool include all option
  410  * @param bool   $return If true, will return the HTML, otherwise, will print directly
  411  * @return null
  412  */
  413 function print_graded_users_selector($course, $actionpage, $userid=0, $groupid=0, $includeall=true, $return=false) {
  414     global $CFG, $USER, $OUTPUT;
  415     return $OUTPUT->render(grade_get_graded_users_select(substr($actionpage, 0, strpos($actionpage, '/')), $course, $userid, $groupid, $includeall));
  416 }
  417 
  418 function grade_get_graded_users_select($report, $course, $userid, $groupid, $includeall) {
  419     global $USER, $CFG;
  420 
  421     if (is_null($userid)) {
  422         $userid = $USER->id;
  423     }
  424     $coursecontext = context_course::instance($course->id);
  425     $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
  426     $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
  427     $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext);
  428     $menu = array(); // Will be a list of userid => user name
  429     $menususpendedusers = array(); // Suspended users go to a separate optgroup.
  430     $gui = new graded_users_iterator($course, null, $groupid);
  431     $gui->require_active_enrolment($showonlyactiveenrol);
  432     $gui->init();
  433     $label = get_string('selectauser', 'grades');
  434     if ($includeall) {
  435         $menu[0] = get_string('allusers', 'grades');
  436         $label = get_string('selectalloroneuser', 'grades');
  437     }
  438     while ($userdata = $gui->next_user()) {
  439         $user = $userdata->user;
  440         $userfullname = fullname($user);
  441         if ($user->suspendedenrolment) {
  442             $menususpendedusers[$user->id] = $userfullname;
  443         } else {
  444             $menu[$user->id] = $userfullname;
  445         }
  446     }
  447     $gui->close();
  448 
  449     if ($includeall) {
  450         $menu[0] .= " (" . (count($menu) + count($menususpendedusers) - 1) . ")";
  451     }
  452 
  453     if (!empty($menususpendedusers)) {
  454         $menu[] = array(get_string('suspendedusers') => $menususpendedusers);
  455     }
  456     $gpr = new grade_plugin_return(array('type' => 'report', 'course' => $course, 'groupid' => $groupid));
  457     $select = new single_select(
  458         new moodle_url('/grade/report/'.$report.'/index.php', $gpr->get_options()),
  459         'userid', $menu, $userid
  460     );
  461     $select->label = $label;
  462     $select->formid = 'choosegradeuser';
  463     return $select;
  464 }
  465 
  466 /**
  467  * Hide warning about changed grades during upgrade to 2.8.
  468  *
  469  * @param int $courseid The current course id.
  470  */
  471 function hide_natural_aggregation_upgrade_notice($courseid) {
  472     unset_config('show_sumofgrades_upgrade_' . $courseid);
  473 }
  474 
  475 /**
  476  * Hide warning about changed grades during upgrade from 2.8.0-2.8.6 and 2.9.0.
  477  *
  478  * @param int $courseid The current course id.
  479  */
  480 function grade_hide_min_max_grade_upgrade_notice($courseid) {
  481     unset_config('show_min_max_grades_changed_' . $courseid);
  482 }
  483 
  484 /**
  485  * Use the grade min and max from the grade_grade.
  486  *
  487  * This is reserved for core use after an upgrade.
  488  *
  489  * @param int $courseid The current course id.
  490  */
  491 function grade_upgrade_use_min_max_from_grade_grade($courseid) {
  492     grade_set_setting($courseid, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_GRADE);
  493 
  494     grade_force_full_regrading($courseid);
  495     // Do this now, because it probably happened to late in the page load to be happen automatically.
  496     grade_regrade_final_grades($courseid);
  497 }
  498 
  499 /**
  500  * Use the grade min and max from the grade_item.
  501  *
  502  * This is reserved for core use after an upgrade.
  503  *
  504  * @param int $courseid The current course id.
  505  */
  506 function grade_upgrade_use_min_max_from_grade_item($courseid) {
  507     grade_set_setting($courseid, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_ITEM);
  508 
  509     grade_force_full_regrading($courseid);
  510     // Do this now, because it probably happened to late in the page load to be happen automatically.
  511     grade_regrade_final_grades($courseid);
  512 }
  513 
  514 /**
  515  * Hide warning about changed grades during upgrade to 2.8.
  516  *
  517  * @param int $courseid The current course id.
  518  */
  519 function hide_aggregatesubcats_upgrade_notice($courseid) {
  520     unset_config('show_aggregatesubcats_upgrade_' . $courseid);
  521 }
  522 
  523 /**
  524  * Hide warning about changed grades due to bug fixes
  525  *
  526  * @param int $courseid The current course id.
  527  */
  528 function hide_gradebook_calculations_freeze_notice($courseid) {
  529     unset_config('gradebook_calculations_freeze_' . $courseid);
  530 }
  531 
  532 /**
  533  * Print warning about changed grades during upgrade to 2.8.
  534  *
  535  * @param int $courseid The current course id.
  536  * @param context $context The course context.
  537  * @param string $thispage The relative path for the current page. E.g. /grade/report/user/index.php
  538  * @param boolean $return return as string
  539  *
  540  * @return nothing or string if $return true
  541  */
  542 function print_natural_aggregation_upgrade_notice($courseid, $context, $thispage, $return=false) {
  543     global $CFG, $OUTPUT;
  544     $html = '';
  545 
  546     // Do not do anything if they cannot manage the grades of this course.
  547     if (!has_capability('moodle/grade:manage', $context)) {
  548         return $html;
  549     }
  550 
  551     $hidesubcatswarning = optional_param('seenaggregatesubcatsupgradedgrades', false, PARAM_BOOL) && confirm_sesskey();
  552     $showsubcatswarning = get_config('core', 'show_aggregatesubcats_upgrade_' . $courseid);
  553     $hidenaturalwarning = optional_param('seensumofgradesupgradedgrades', false, PARAM_BOOL) && confirm_sesskey();
  554     $shownaturalwarning = get_config('core', 'show_sumofgrades_upgrade_' . $courseid);
  555 
  556     $hideminmaxwarning = optional_param('seenminmaxupgradedgrades', false, PARAM_BOOL) && confirm_sesskey();
  557     $showminmaxwarning = get_config('core', 'show_min_max_grades_changed_' . $courseid);
  558 
  559     $useminmaxfromgradeitem = optional_param('useminmaxfromgradeitem', false, PARAM_BOOL) && confirm_sesskey();
  560     $useminmaxfromgradegrade = optional_param('useminmaxfromgradegrade', false, PARAM_BOOL) && confirm_sesskey();
  561 
  562     $minmaxtouse = grade_get_setting($courseid, 'minmaxtouse', $CFG->grade_minmaxtouse);
  563 
  564     $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid);
  565     $acceptgradebookchanges = optional_param('acceptgradebookchanges', false, PARAM_BOOL) && confirm_sesskey();
  566 
  567     // Hide the warning if the user told it to go away.
  568     if ($hidenaturalwarning) {
  569         hide_natural_aggregation_upgrade_notice($courseid);
  570     }
  571     // Hide the warning if the user told it to go away.
  572     if ($hidesubcatswarning) {
  573         hide_aggregatesubcats_upgrade_notice($courseid);
  574     }
  575 
  576     // Hide the min/max warning if the user told it to go away.
  577     if ($hideminmaxwarning) {
  578         grade_hide_min_max_grade_upgrade_notice($courseid);
  579         $showminmaxwarning = false;
  580     }
  581 
  582     if ($useminmaxfromgradegrade) {
  583         // Revert to the new behaviour, we now use the grade_grade for min/max.
  584         grade_upgrade_use_min_max_from_grade_grade($courseid);
  585         grade_hide_min_max_grade_upgrade_notice($courseid);
  586         $showminmaxwarning = false;
  587 
  588     } else if ($useminmaxfromgradeitem) {
  589         // Apply the new logic, we now use the grade_item for min/max.
  590         grade_upgrade_use_min_max_from_grade_item($courseid);
  591         grade_hide_min_max_grade_upgrade_notice($courseid);
  592         $showminmaxwarning = false;
  593     }
  594 
  595 
  596     if (!$hidenaturalwarning && $shownaturalwarning) {
  597         $message = get_string('sumofgradesupgradedgrades', 'grades');
  598         $hidemessage = get_string('upgradedgradeshidemessage', 'grades');
  599         $urlparams = array( 'id' => $courseid,
  600                             'seensumofgradesupgradedgrades' => true,
  601                             'sesskey' => sesskey());
  602         $goawayurl = new moodle_url($thispage, $urlparams);
  603         $goawaybutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get');
  604         $html .= $OUTPUT->notification($message, 'notifysuccess');
  605         $html .= $goawaybutton;
  606     }
  607 
  608     if (!$hidesubcatswarning && $showsubcatswarning) {
  609         $message = get_string('aggregatesubcatsupgradedgrades', 'grades');
  610         $hidemessage = get_string('upgradedgradeshidemessage', 'grades');
  611         $urlparams = array( 'id' => $courseid,
  612                             'seenaggregatesubcatsupgradedgrades' => true,
  613                             'sesskey' => sesskey());
  614         $goawayurl = new moodle_url($thispage, $urlparams);
  615         $goawaybutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get');
  616         $html .= $OUTPUT->notification($message, 'notifysuccess');
  617         $html .= $goawaybutton;
  618     }
  619 
  620     if ($showminmaxwarning) {
  621         $hidemessage = get_string('upgradedgradeshidemessage', 'grades');
  622         $urlparams = array( 'id' => $courseid,
  623                             'seenminmaxupgradedgrades' => true,
  624                             'sesskey' => sesskey());
  625 
  626         $goawayurl = new moodle_url($thispage, $urlparams);
  627         $hideminmaxbutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get');
  628         $moreinfo = html_writer::link(get_docs_url(get_string('minmaxtouse_link', 'grades')), get_string('moreinfo'),
  629             array('target' => '_blank'));
  630 
  631         if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_ITEM) {
  632             // Show the message that there were min/max issues that have been resolved.
  633             $message = get_string('minmaxupgradedgrades', 'grades') . ' ' . $moreinfo;
  634 
  635             $revertmessage = get_string('upgradedminmaxrevertmessage', 'grades');
  636             $urlparams = array('id' => $courseid,
  637                                'useminmaxfromgradegrade' => true,
  638                                'sesskey' => sesskey());
  639             $reverturl = new moodle_url($thispage, $urlparams);
  640             $revertbutton = $OUTPUT->single_button($reverturl, $revertmessage, 'get');
  641 
  642             $html .= $OUTPUT->notification($message);
  643             $html .= $revertbutton . $hideminmaxbutton;
  644 
  645         } else if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE) {
  646             // Show the warning that there are min/max issues that have not be resolved.
  647             $message = get_string('minmaxupgradewarning', 'grades') . ' ' . $moreinfo;
  648 
  649             $fixmessage = get_string('minmaxupgradefixbutton', 'grades');
  650             $urlparams = array('id' => $courseid,
  651                                'useminmaxfromgradeitem' => true,
  652                                'sesskey' => sesskey());
  653             $fixurl = new moodle_url($thispage, $urlparams);
  654             $fixbutton = $OUTPUT->single_button($fixurl, $fixmessage, 'get');
  655 
  656             $html .= $OUTPUT->notification($message);
  657             $html .= $fixbutton . $hideminmaxbutton;
  658         }
  659     }
  660 
  661     if ($gradebookcalculationsfreeze) {
  662         if ($acceptgradebookchanges) {
  663             // Accept potential changes in grades caused by extra credit bug MDL-49257.
  664             hide_gradebook_calculations_freeze_notice($courseid);
  665             $courseitem = grade_item::fetch_course_item($courseid);
  666             $courseitem->force_regrading();
  667             grade_regrade_final_grades($courseid);
  668 
  669             $html .= $OUTPUT->notification(get_string('gradebookcalculationsuptodate', 'grades'), 'notifysuccess');
  670         } else {
  671             // Show the warning that there may be extra credit weights problems.
  672             $a = new stdClass();
  673             $a->gradebookversion = $gradebookcalculationsfreeze;
  674             if (preg_match('/(\d{8,})/', $CFG->release, $matches)) {
  675                 $a->currentversion = $matches[1];
  676             } else {
  677                 $a->currentversion = $CFG->release;
  678             }
  679             $a->url = get_docs_url('Gradebook_calculation_changes');
  680             $message = get_string('gradebookcalculationswarning', 'grades', $a);
  681 
  682             $fixmessage = get_string('gradebookcalculationsfixbutton', 'grades');
  683             $urlparams = array('id' => $courseid,
  684                 'acceptgradebookchanges' => true,
  685                 'sesskey' => sesskey());
  686             $fixurl = new moodle_url($thispage, $urlparams);
  687             $fixbutton = $OUTPUT->single_button($fixurl, $fixmessage, 'get');
  688 
  689             $html .= $OUTPUT->notification($message);
  690             $html .= $fixbutton;
  691         }
  692     }
  693 
  694     if (!empty($html)) {
  695         $html = html_writer::tag('div', $html, array('class' => 'core_grades_notices'));
  696     }
  697 
  698     if ($return) {
  699         return $html;
  700     } else {
  701         echo $html;
  702     }
  703 }
  704 
  705 /**
  706  * Print grading plugin selection popup form.
  707  *
  708  * @param array   $plugin_info An array of plugins containing information for the selector
  709  * @param boolean $return return as string
  710  *
  711  * @return nothing or string if $return true
  712  */
  713 function print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return=false) {
  714     global $CFG, $OUTPUT, $PAGE;
  715 
  716     $menu = array();
  717     $count = 0;
  718     $active = '';
  719 
  720     foreach ($plugin_info as $plugin_type => $plugins) {
  721         if ($plugin_type == 'strings') {
  722             continue;
  723         }
  724 
  725         $first_plugin = reset($plugins);
  726 
  727         $sectionname = $plugin_info['strings'][$plugin_type];
  728         $section = array();
  729 
  730         foreach ($plugins as $plugin) {
  731             $link = $plugin->link->out(false);
  732             $section[$link] = $plugin->string;
  733             $count++;
  734             if ($plugin_type === $active_type and $plugin->id === $active_plugin) {
  735                 $active = $link;
  736             }
  737         }
  738 
  739         if ($section) {
  740             $menu[] = array($sectionname=>$section);
  741         }
  742     }
  743 
  744     // finally print/return the popup form
  745     if ($count > 1) {
  746         $select = new url_select($menu, $active, null, 'choosepluginreport');
  747         $select->set_label(get_string('gradereport', 'grades'), array('class' => 'accesshide'));
  748         if ($return) {
  749             return $OUTPUT->render($select);
  750         } else {
  751             echo $OUTPUT->render($select);
  752         }
  753     } else {
  754         // only one option - no plugin selector needed
  755         return '';
  756     }
  757 }
  758 
  759 /**
  760  * Print grading plugin selection tab-based navigation.
  761  *
  762  * @param string  $active_type type of plugin on current page - import, export, report or edit
  763  * @param string  $active_plugin active plugin type - grader, user, cvs, ...
  764  * @param array   $plugin_info Array of plugins
  765  * @param boolean $return return as string
  766  *
  767  * @return nothing or string if $return true
  768  */
  769 function grade_print_tabs($active_type, $active_plugin, $plugin_info, $return=false) {
  770     global $CFG, $COURSE;
  771 
  772     if (!isset($currenttab)) { //TODO: this is weird
  773         $currenttab = '';
  774     }
  775 
  776     $tabs = array();
  777     $top_row  = array();
  778     $bottom_row = array();
  779     $inactive = array($active_plugin);
  780     $activated = array($active_type);
  781 
  782     $count = 0;
  783     $active = '';
  784 
  785     foreach ($plugin_info as $plugin_type => $plugins) {
  786         if ($plugin_type == 'strings') {
  787             continue;
  788         }
  789 
  790         // If $plugins is actually the definition of a child-less parent link:
  791         if (!empty($plugins->id)) {
  792             $string = $plugins->string;
  793             if (!empty($plugin_info[$active_type]->parent)) {
  794                 $string = $plugin_info[$active_type]->parent->string;
  795             }
  796 
  797             $top_row[] = new tabobject($plugin_type, $plugins->link, $string);
  798             continue;
  799         }
  800 
  801         $first_plugin = reset($plugins);
  802         $url = $first_plugin->link;
  803 
  804         if ($plugin_type == 'report') {
  805             $url = $CFG->wwwroot.'/grade/report/index.php?id='.$COURSE->id;
  806         }
  807 
  808         $top_row[] = new tabobject($plugin_type, $url, $plugin_info['strings'][$plugin_type]);
  809 
  810         if ($active_type == $plugin_type) {
  811             foreach ($plugins as $plugin) {
  812                 $bottom_row[] = new tabobject($plugin->id, $plugin->link, $plugin->string);
  813                 if ($plugin->id == $active_plugin) {
  814                     $inactive = array($plugin->id);
  815                 }
  816             }
  817         }
  818     }
  819 
  820     // Do not display rows that contain only one item, they are not helpful.
  821     if (count($top_row) > 1) {
  822         $tabs[] = $top_row;
  823     }
  824     if (count($bottom_row) > 1) {
  825         $tabs[] = $bottom_row;
  826     }
  827     if (empty($tabs)) {
  828         return;
  829     }
  830 
  831     $rv = html_writer::div(print_tabs($tabs, $active_plugin, $inactive, $activated, true), 'grade-navigation');
  832 
  833     if ($return) {
  834         return $rv;
  835     } else {
  836         echo $rv;
  837     }
  838 }
  839 
  840 /**
  841  * grade_get_plugin_info
  842  *
  843  * @param int    $courseid The course id
  844  * @param string $active_type type of plugin on current page - import, export, report or edit
  845  * @param string $active_plugin active plugin type - grader, user, cvs, ...
  846  *
  847  * @return array
  848  */
  849 function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
  850     global $CFG, $SITE;
  851 
  852     $context = context_course::instance($courseid);
  853 
  854     $plugin_info = array();
  855     $count = 0;
  856     $active = '';
  857     $url_prefix = $CFG->wwwroot . '/grade/';
  858 
  859     // Language strings
  860     $plugin_info['strings'] = grade_helper::get_plugin_strings();
  861 
  862     if ($reports = grade_helper::get_plugins_reports($courseid)) {
  863         $plugin_info['report'] = $reports;
  864     }
  865 
  866     if ($settings = grade_helper::get_info_manage_settings($courseid)) {
  867         $plugin_info['settings'] = $settings;
  868     }
  869 
  870     if ($scale = grade_helper::get_info_scales($courseid)) {
  871         $plugin_info['scale'] = array('view'=>$scale);
  872     }
  873 
  874     if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
  875         $plugin_info['outcome'] = $outcomes;
  876     }
  877 
  878     if ($letters = grade_helper::get_info_letters($courseid)) {
  879         $plugin_info['letter'] = $letters;
  880     }
  881 
  882     if ($imports = grade_helper::get_plugins_import($courseid)) {
  883         $plugin_info['import'] = $imports;
  884     }
  885 
  886     if ($exports = grade_helper::get_plugins_export($courseid)) {
  887         $plugin_info['export'] = $exports;
  888     }
  889 
  890     foreach ($plugin_info as $plugin_type => $plugins) {
  891         if (!empty($plugins->id) && $active_plugin == $plugins->id) {
  892             $plugin_info['strings']['active_plugin_str'] = $plugins->string;
  893             break;
  894         }
  895         foreach ($plugins as $plugin) {
  896             if (is_a($plugin, 'grade_plugin_info')) {
  897                 if ($active_plugin == $plugin->id) {
  898                     $plugin_info['strings']['active_plugin_str'] = $plugin->string;
  899                 }
  900             }
  901         }
  902     }
  903 
  904     return $plugin_info;
  905 }
  906 
  907 /**
  908  * A simple class containing info about grade plugins.
  909  * Can be subclassed for special rules
  910  *
  911  * @package core_grades
  912  * @copyright 2009 Nicolas Connault
  913  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  914  */
  915 class grade_plugin_info {
  916     /**
  917      * A unique id for this plugin
  918      *
  919      * @var mixed
  920      */
  921     public $id;
  922     /**
  923      * A URL to access this plugin
  924      *
  925      * @var mixed
  926      */
  927     public $link;
  928     /**
  929      * The name of this plugin
  930      *
  931      * @var mixed
  932      */
  933     public $string;
  934     /**
  935      * Another grade_plugin_info object, parent of the current one
  936      *
  937      * @var mixed
  938      */
  939     public $parent;
  940 
  941     /**
  942      * Constructor
  943      *
  944      * @param int $id A unique id for this plugin
  945      * @param string $link A URL to access this plugin
  946      * @param string $string The name of this plugin
  947      * @param object $parent Another grade_plugin_info object, parent of the current one
  948      *
  949      * @return void
  950      */
  951     public function __construct($id, $link, $string, $parent=null) {
  952         $this->id = $id;
  953         $this->link = $link;
  954         $this->string = $string;
  955         $this->parent = $parent;
  956     }
  957 }
  958 
  959 /**
  960  * Prints the page headers, breadcrumb trail, page heading, (optional) dropdown navigation menu and
  961  * (optional) navigation tabs for any gradebook page. All gradebook pages MUST use these functions
  962  * in favour of the usual print_header(), print_header_simple(), print_heading() etc.
  963  * !IMPORTANT! Use of tabs.php file in gradebook pages is forbidden unless tabs are switched off at
  964  * the site level for the gradebook ($CFG->grade_navmethod = GRADE_NAVMETHOD_DROPDOWN).
  965  *
  966  * @param int     $courseid Course id
  967  * @param string  $active_type The type of the current page (report, settings,
  968  *                             import, export, scales, outcomes, letters)
  969  * @param string  $active_plugin The plugin of the current page (grader, fullview etc...)
  970  * @param string  $heading The heading of the page. Tries to guess if none is given
  971  * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function
  972  * @param string  $bodytags Additional attributes that will be added to the <body> tag
  973  * @param string  $buttons Additional buttons to display on the page
  974  * @param boolean $shownavigation should the gradebook navigation drop down (or tabs) be shown?
  975  * @param string  $headerhelpidentifier The help string identifier if required.
  976  * @param string  $headerhelpcomponent The component for the help string.
  977  * @param stdClass $user The user object for use with the user context header.
  978  *
  979  * @return string HTML code or nothing if $return == false
  980  */
  981 function print_grade_page_head($courseid, $active_type, $active_plugin=null,
  982                                $heading = false, $return=false,
  983                                $buttons=false, $shownavigation=true, $headerhelpidentifier = null, $headerhelpcomponent = null,
  984                                $user = null) {
  985     global $CFG, $OUTPUT, $PAGE;
  986 
  987     // Put a warning on all gradebook pages if the course has modules currently scheduled for background deletion.
  988     require_once($CFG->dirroot . '/course/lib.php');
  989     if (course_modules_pending_deletion($courseid, true)) {
  990         \core\notification::add(get_string('gradesmoduledeletionpendingwarning', 'grades'),
  991             \core\output\notification::NOTIFY_WARNING);
  992     }
  993 
  994     if ($active_type === 'preferences') {
  995         // In Moodle 2.8 report preferences were moved under 'settings'. Allow backward compatibility for 3rd party grade reports.
  996         $active_type = 'settings';
  997     }
  998 
  999     $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin);
 1000 
 1001     // Determine the string of the active plugin
 1002     $stractive_plugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading;
 1003     $stractive_type = $plugin_info['strings'][$active_type];
 1004 
 1005     if (empty($plugin_info[$active_type]->id) || !empty($plugin_info[$active_type]->parent)) {
 1006         $title = $PAGE->course->fullname.': ' . $stractive_type . ': ' . $stractive_plugin;
 1007     } else {
 1008         $title = $PAGE->course->fullname.': ' . $stractive_plugin;
 1009     }
 1010 
 1011     if ($active_type == 'report') {
 1012         $PAGE->set_pagelayout('report');
 1013     } else {
 1014         $PAGE->set_pagelayout('admin');
 1015     }
 1016     $PAGE->set_title(get_string('grades') . ': ' . $stractive_type);
 1017     $PAGE->set_heading($title);
 1018     if ($buttons instanceof single_button) {
 1019         $buttons = $OUTPUT->render($buttons);
 1020     }
 1021     $PAGE->set_button($buttons);
 1022     if ($courseid != SITEID) {
 1023         grade_extend_settings($plugin_info, $courseid);
 1024     }
 1025 
 1026     // Set the current report as active in the breadcrumbs.
 1027     if ($active_plugin !== null && $reportnav = $PAGE->settingsnav->find($active_plugin, navigation_node::TYPE_SETTING)) {
 1028         $reportnav->make_active();
 1029     }
 1030 
 1031     $returnval = $OUTPUT->header();
 1032 
 1033     if (!$return) {
 1034         echo $returnval;
 1035     }
 1036 
 1037     // Guess heading if not given explicitly
 1038     if (!$heading) {
 1039         $heading = $stractive_plugin;
 1040     }
 1041 
 1042     if ($shownavigation) {
 1043         $navselector = null;
 1044         if ($courseid != SITEID &&
 1045                 ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN)) {
 1046             // It's absolutely essential that this grade plugin selector is shown after the user header. Just ask Fred.
 1047             $navselector = print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, true);
 1048             if ($return) {
 1049                 $returnval .= $navselector;
 1050             } else if (!isset($user)) {
 1051                 echo $navselector;
 1052             }
 1053         }
 1054 
 1055         $output = '';
 1056         // Add a help dialogue box if provided.
 1057         if (isset($headerhelpidentifier)) {
 1058             $output = $OUTPUT->heading_with_help($heading, $headerhelpidentifier, $headerhelpcomponent);
 1059         } else {
 1060             if (isset($user)) {
 1061                 $output = $OUTPUT->context_header(
 1062                         array(
 1063                             'heading' => html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id,
 1064                                 'course' => $courseid)), fullname($user)),
 1065                             'user' => $user,
 1066                             'usercontext' => context_user::instance($user->id)
 1067                         ), 2
 1068                     ) . $navselector;
 1069             } else {
 1070                 $output = $OUTPUT->heading($heading);
 1071             }
 1072         }
 1073 
 1074         if ($return) {
 1075             $returnval .= $output;
 1076         } else {
 1077             echo $output;
 1078         }
 1079 
 1080         if ($courseid != SITEID &&
 1081                 ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS)) {
 1082             $returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
 1083         }
 1084     }
 1085 
 1086     $returnval .= print_natural_aggregation_upgrade_notice($courseid,
 1087                                                            context_course::instance($courseid),
 1088                                                            $PAGE->url,
 1089                                                            $return);
 1090 
 1091     if ($return) {
 1092         return $returnval;
 1093     }
 1094 }
 1095 
 1096 /**
 1097  * Utility class used for return tracking when using edit and other forms in grade plugins
 1098  *
 1099  * @package core_grades
 1100  * @copyright 2009 Nicolas Connault
 1101  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 1102  */
 1103 class grade_plugin_return {
 1104     /**
 1105      * Type of grade plugin (e.g. 'edit', 'report')
 1106      *
 1107      * @var string
 1108      */
 1109     public $type;
 1110     /**
 1111      * Name of grade plugin (e.g. 'grader', 'overview')
 1112      *
 1113      * @var string
 1114      */
 1115     public $plugin;
 1116     /**
 1117      * Course id being viewed
 1118      *
 1119      * @var int
 1120      */
 1121     public $courseid;
 1122     /**
 1123      * Id of user whose information is being viewed/edited
 1124      *
 1125      * @var int
 1126      */
 1127     public $userid;
 1128     /**
 1129      * Id of group for which information is being viewed/edited
 1130      *
 1131      * @var int
 1132      */
 1133     public $groupid;
 1134     /**
 1135      * Current page # within output
 1136      *
 1137      * @var int
 1138      */
 1139     public $page;
 1140 
 1141     /**
 1142      * Constructor
 1143      *
 1144      * @param array $params - associative array with return parameters, if not supplied parameter are taken from _GET or _POST
 1145      */
 1146     public function __construct($params = []) {
 1147         $this->type     = optional_param('gpr_type', null, PARAM_SAFEDIR);
 1148         $this->plugin   = optional_param('gpr_plugin', null, PARAM_PLUGIN);
 1149         $this->courseid = optional_param('gpr_courseid', null, PARAM_INT);
 1150         $this->userid   = optional_param('gpr_userid', null, PARAM_INT);
 1151         $this->groupid  = optional_param('gpr_groupid', null, PARAM_INT);
 1152         $this->page     = optional_param('gpr_page', null, PARAM_INT);
 1153 
 1154         foreach ($params as $key => $value) {
 1155             if (property_exists($this, $key)) {
 1156                 $this->$key = $value;
 1157             }
 1158         }
 1159         // Allow course object rather than id to be used to specify course
 1160         // - avoid unnecessary use of get_course.
 1161         if (array_key_exists('course', $params)) {
 1162             $course = $params['course'];
 1163             $this->courseid = $course->id;
 1164         } else {
 1165             $course = null;
 1166         }
 1167         // If group has been explicitly set in constructor parameters,
 1168         // we should respect that.
 1169         if (!array_key_exists('groupid', $params)) {
 1170             // Otherwise, 'group' in request parameters is a request for a change.
 1171             // In that case, or if we have no group at all, we should get groupid from
 1172             // groups_get_course_group, which will do some housekeeping as well as
 1173             // give us the correct value.
 1174             $changegroup = optional_param('group', -1, PARAM_INT);
 1175             if ($changegroup !== -1 or (empty($this->groupid) and !empty($this->courseid))) {
 1176                 if ($course === null) {
 1177                     $course = get_course($this->courseid);
 1178                 }
 1179                 $this->groupid = groups_get_course_group($course, true);
 1180             }
 1181         }
 1182     }
 1183 
 1184     /**
 1185      * Old syntax of class constructor. Deprecated in PHP7.
 1186      *
 1187      * @deprecated since Moodle 3.1
 1188      */
 1189     public function grade_plugin_return($params = null) {
 1190         debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 1191         self::__construct($params);
 1192     }
 1193 
 1194     /**
 1195      * Returns return parameters as options array suitable for buttons.
 1196      * @return array options
 1197      */
 1198     public function get_options() {
 1199         if (empty($this->type)) {
 1200             return array();
 1201         }
 1202 
 1203         $params = array();
 1204 
 1205         if (!empty($this->plugin)) {
 1206             $params['plugin'] = $this->plugin;
 1207         }
 1208 
 1209         if (!empty($this->courseid)) {
 1210             $params['id'] = $this->courseid;
 1211         }
 1212 
 1213         if (!empty($this->userid)) {
 1214             $params['userid'] = $this->userid;
 1215         }
 1216 
 1217         if (!empty($this->groupid)) {
 1218             $params['group'] = $this->groupid;
 1219         }
 1220 
 1221         if (!empty($this->page)) {
 1222             $params['page'] = $this->page;
 1223         }
 1224 
 1225         return $params;
 1226     }
 1227 
 1228     /**
 1229      * Returns return url
 1230      *
 1231      * @param string $default default url when params not set
 1232      * @param array  $extras Extra URL parameters
 1233      *
 1234      * @return string url
 1235      */
 1236     public function get_return_url($default, $extras=null) {
 1237         global $CFG;
 1238 
 1239         if (empty($this->type) or empty($this->plugin)) {
 1240             return $default;
 1241         }
 1242 
 1243         $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php';
 1244         $glue = '?';
 1245 
 1246         if (!empty($this->courseid)) {
 1247             $url .= $glue.'id='.$this->courseid;
 1248             $glue = '&amp;';
 1249         }
 1250 
 1251         if (!empty($this->userid)) {
 1252             $url .= $glue.'userid='.$this->userid;
 1253             $glue = '&amp;';
 1254         }
 1255 
 1256         if (!empty($this->groupid)) {
 1257             $url .= $glue.'group='.$this->groupid;
 1258             $glue = '&amp;';
 1259         }
 1260 
 1261         if (!empty($this->page)) {
 1262             $url .= $glue.'page='.$this->page;
 1263             $glue = '&amp;';
 1264         }
 1265 
 1266         if (!empty($extras)) {
 1267             foreach ($extras as $key=>$value) {
 1268                 $url .= $glue.$key.'='.$value;
 1269                 $glue = '&amp;';
 1270             }
 1271         }
 1272 
 1273         return $url;
 1274     }
 1275 
 1276     /**
 1277      * Returns string with hidden return tracking form elements.
 1278      * @return string
 1279      */
 1280     public function get_form_fields() {
 1281         if (empty($this->type)) {
 1282             return '';
 1283         }
 1284 
 1285         $result  = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />';
 1286 
 1287         if (!empty($this->plugin)) {
 1288             $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />';
 1289         }
 1290 
 1291         if (!empty($this->courseid)) {
 1292             $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />';
 1293         }
 1294 
 1295         if (!empty($this->userid)) {
 1296             $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />';
 1297         }
 1298 
 1299         if (!empty($this->groupid)) {
 1300             $result .= '<input type="hidden" name="gpr_groupid" value="'.$this->groupid.'" />';
 1301         }
 1302 
 1303         if (!empty($this->page)) {
 1304             $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />';
 1305         }
 1306         return $result;
 1307     }
 1308 
 1309     /**
 1310      * Add hidden elements into mform
 1311      *
 1312      * @param object &$mform moodle form object
 1313      *
 1314      * @return void
 1315      */
 1316     public function add_mform_elements(&$mform) {
 1317         if (empty($this->type)) {
 1318             return;
 1319         }
 1320 
 1321         $mform->addElement('hidden', 'gpr_type', $this->type);
 1322         $mform->setType('gpr_type', PARAM_SAFEDIR);
 1323 
 1324         if (!empty($this->plugin)) {
 1325             $mform->addElement('hidden', 'gpr_plugin', $this->plugin);
 1326             $mform->setType('gpr_plugin', PARAM_PLUGIN);
 1327         }
 1328 
 1329         if (!empty($this->courseid)) {
 1330             $mform->addElement('hidden', 'gpr_courseid', $this->courseid);
 1331             $mform->setType('gpr_courseid', PARAM_INT);
 1332         }
 1333 
 1334         if (!empty($this->userid)) {
 1335             $mform->addElement('hidden', 'gpr_userid', $this->userid);
 1336             $mform->setType('gpr_userid', PARAM_INT);
 1337         }
 1338 
 1339         if (!empty($this->groupid)) {
 1340             $mform->addElement('hidden', 'gpr_groupid', $this->groupid);
 1341             $mform->setType('gpr_groupid', PARAM_INT);
 1342         }
 1343 
 1344         if (!empty($this->page)) {
 1345             $mform->addElement('hidden', 'gpr_page', $this->page);
 1346             $mform->setType('gpr_page', PARAM_INT);
 1347         }
 1348     }
 1349 
 1350     /**
 1351      * Add return tracking params into url
 1352      *
 1353      * @param moodle_url $url A URL
 1354      *
 1355      * @return string $url with return tracking params
 1356      */
 1357     public function add_url_params(moodle_url $url) {
 1358         if (empty($this->type)) {
 1359             return $url;
 1360         }
 1361 
 1362         $url->param('gpr_type', $this->type);
 1363 
 1364         if (!empty($this->plugin)) {
 1365             $url->param('gpr_plugin', $this->plugin);
 1366         }
 1367 
 1368         if (!empty($this->courseid)) {
 1369             $url->param('gpr_courseid' ,$this->courseid);
 1370         }
 1371 
 1372         if (!empty($this->userid)) {
 1373             $url->param('gpr_userid', $this->userid);
 1374         }
 1375 
 1376         if (!empty($this->groupid)) {
 1377             $url->param('gpr_groupid', $this->groupid);
 1378         }
 1379 
 1380         if (!empty($this->page)) {
 1381             $url->param('gpr_page', $this->page);
 1382         }
 1383 
 1384         return $url;
 1385     }
 1386 }
 1387 
 1388 /**
 1389  * Function central to gradebook for building and printing the navigation (breadcrumb trail).
 1390  *
 1391  * @param string $path The path of the calling script (using __FILE__?)
 1392  * @param string $pagename The language string to use as the last part of the navigation (non-link)
 1393  * @param mixed  $id Either a plain integer (assuming the key is 'id') or
 1394  *                   an array of keys and values (e.g courseid => $courseid, itemid...)
 1395  *
 1396  * @return string
 1397  */
 1398 function grade_build_nav($path, $pagename=null, $id=null) {
 1399     global $CFG, $COURSE, $PAGE;
 1400 
 1401     $strgrades = get_string('grades', 'grades');
 1402 
 1403     // Parse the path and build navlinks from its elements
 1404     $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash
 1405     $path = substr($path, $dirroot_length);
 1406     $path = str_replace('\\', '/', $path);
 1407 
 1408     $path_elements = explode('/', $path);
 1409 
 1410     $path_elements_count = count($path_elements);
 1411 
 1412     // First link is always 'grade'
 1413     $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id)));
 1414 
 1415     $link = null;
 1416     $numberofelements = 3;
 1417 
 1418     // Prepare URL params string
 1419     $linkparams = array();
 1420     if (!is_null($id)) {
 1421         if (is_array($id)) {
 1422             foreach ($id as $idkey => $idvalue) {
 1423                 $linkparams[$idkey] = $idvalue;
 1424             }
 1425         } else {
 1426             $linkparams['id'] = $id;
 1427         }
 1428     }
 1429 
 1430     $navlink4 = null;
 1431 
 1432     // Remove file extensions from filenames
 1433     foreach ($path_elements as $key => $filename) {
 1434         $path_elements[$key] = str_replace('.php', '', $filename);
 1435     }
 1436 
 1437     // Second level links
 1438     switch ($path_elements[1]) {
 1439         case 'edit': // No link
 1440             if ($path_elements[3] != 'index.php') {
 1441                 $numberofelements = 4;
 1442             }
 1443             break;
 1444         case 'import': // No link
 1445             break;
 1446         case 'export': // No link
 1447             break;
 1448         case 'report':
 1449             // $id is required for this link. Do not print it if $id isn't given
 1450             if (!is_null($id)) {
 1451                 $link = new moodle_url('/grade/report/index.php', $linkparams);
 1452             }
 1453 
 1454             if ($path_elements[2] == 'grader') {
 1455                 $numberofelements = 4;
 1456             }
 1457             break;
 1458 
 1459         default:
 1460             // If this element isn't among the ones already listed above, it isn't supported, throw an error.
 1461             debugging("grade_build_nav() doesn't support ". $path_elements[1] .
 1462                     " as the second path element after 'grade'.");
 1463             return false;
 1464     }
 1465     $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link);
 1466 
 1467     // Third level links
 1468     if (empty($pagename)) {
 1469         $pagename = get_string($path_elements[2], 'grades');
 1470     }
 1471 
 1472     switch ($numberofelements) {
 1473         case 3:
 1474             $PAGE->navbar->add($pagename, $link);
 1475             break;
 1476         case 4:
 1477             if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') {
 1478                 $PAGE->navbar->add(get_string('pluginname', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams));
 1479             }
 1480             $PAGE->navbar->add($pagename);
 1481             break;
 1482     }
 1483 
 1484     return '';
 1485 }
 1486 
 1487 /**
 1488  * General structure representing grade items in course
 1489  *
 1490  * @package core_grades
 1491  * @copyright 2009 Nicolas Connault
 1492  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 1493  */
 1494 class grade_structure {
 1495     public $context;
 1496 
 1497     public $courseid;
 1498 
 1499     /**
 1500     * Reference to modinfo for current course (for performance, to save
 1501     * retrieving it from courseid every time). Not actually set except for
 1502     * the grade_tree type.
 1503     * @var course_modinfo
 1504     */
 1505     public $modinfo;
 1506 
 1507     /**
 1508      * 1D array of grade items only
 1509      */
 1510     public $items;
 1511 
 1512     /**
 1513      * Returns icon of element
 1514      *
 1515      * @param array &$element An array representing an element in the grade_tree
 1516      * @param bool  $spacerifnone return spacer if no icon found
 1517      *
 1518      * @return string icon or spacer
 1519      */
 1520     public function get_element_icon(&$element, $spacerifnone=false) {
 1521         global $CFG, $OUTPUT;
 1522         require_once $CFG->libdir.'/filelib.php';
 1523 
 1524         $outputstr = '';
 1525 
 1526         // Object holding pix_icon information before instantiation.
 1527         $icon = new stdClass();
 1528         $icon->attributes = array(
 1529             'class' => 'icon itemicon'
 1530         );
 1531         $icon->component = 'moodle';
 1532 
 1533         $none = true;
 1534         switch ($element['type']) {
 1535             case 'item':
 1536             case 'courseitem':
 1537             case 'categoryitem':
 1538                 $none = false;
 1539 
 1540                 $is_course   = $element['object']->is_course_item();
 1541                 $is_category = $element['object']->is_category_item();
 1542                 $is_scale    = $element['object']->gradetype == GRADE_TYPE_SCALE;
 1543                 $is_value    = $element['object']->gradetype == GRADE_TYPE_VALUE;
 1544                 $is_outcome  = !empty($element['object']->outcomeid);
 1545 
 1546                 if ($element['object']->is_calculated()) {
 1547                     $icon->pix = 'i/calc';
 1548                     $icon->title = s(get_string('calculatedgrade', 'grades'));
 1549 
 1550                 } else if (($is_course or $is_category) and ($is_scale or $is_value)) {
 1551                     if ($category = $element['object']->get_item_category()) {
 1552                         $aggrstrings = grade_helper::get_aggregation_strings();
 1553                         $stragg = $aggrstrings[$category->aggregation];
 1554 
 1555                         $icon->pix = 'i/calc';
 1556                         $icon->title = s($stragg);
 1557 
 1558                         switch ($category->aggregation) {
 1559                             case GRADE_AGGREGATE_MEAN:
 1560                             case GRADE_AGGREGATE_MEDIAN:
 1561                             case GRADE_AGGREGATE_WEIGHTED_MEAN:
 1562                             case GRADE_AGGREGATE_WEIGHTED_MEAN2:
 1563                             case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
 1564                                 $icon->pix = 'i/agg_mean';
 1565                                 break;
 1566                             case GRADE_AGGREGATE_SUM:
 1567                                 $icon->pix = 'i/agg_sum';
 1568                                 break;
 1569                         }
 1570                     }
 1571 
 1572                 } else if ($element['object']->itemtype == 'mod') {
 1573                     // Prevent outcomes displaying the same icon as the activity they are attached to.
 1574                     if ($is_outcome) {
 1575                         $icon->pix = 'i/outcomes';
 1576                         $icon->title = s(get_string('outcome', 'grades'));
 1577                     } else {
 1578                         $modinfo = get_fast_modinfo($element['object']->courseid);
 1579                         $module = $element['object']->itemmodule;
 1580                         $instanceid = $element['object']->iteminstance;
 1581                         if (isset($modinfo->instances[$module][$instanceid])) {
 1582                             $icon->url = $modinfo->instances[$module][$instanceid]->get_icon_url();
 1583                         } else {
 1584                             $icon->pix = 'icon';
 1585                             $icon->component = $element['object']->itemmodule;
 1586                         }
 1587                         $icon->title = s(get_string('modulename', $element['object']->itemmodule));
 1588                     }
 1589                 } else if ($element['object']->itemtype == 'manual') {
 1590                     if ($element['object']->is_outcome_item()) {
 1591                         $icon->pix = 'i/outcomes';
 1592                         $icon->title = s(get_string('outcome', 'grades'));
 1593                     } else {
 1594                         $icon->pix = 'i/manual_item';
 1595                         $icon->title = s(get_string('manualitem', 'grades'));
 1596                     }
 1597                 }
 1598                 break;
 1599 
 1600             case 'category':
 1601                 $none = false;
 1602                 $icon->pix = 'i/folder';
 1603                 $icon->title = s(get_string('category', 'grades'));
 1604                 break;
 1605         }
 1606 
 1607         if ($none) {
 1608             if ($spacerifnone) {
 1609                 $outputstr = $OUTPUT->spacer() . ' ';
 1610             }
 1611         } else if (isset($icon->url)) {
 1612             $outputstr = html_writer::img($icon->url, $icon->title, $icon->attributes);
 1613         } else {
 1614             $outputstr = $OUTPUT->pix_icon($icon->pix, $icon->title, $icon->component, $icon->attributes);
 1615         }
 1616 
 1617         return $outputstr;
 1618     }
 1619 
 1620     /**
 1621      * Returns name of element optionally with icon and link
 1622      *
 1623      * @param array &$element An array representing an element in the grade_tree
 1624      * @param bool  $withlink Whether or not this header has a link
 1625      * @param bool  $icon Whether or not to display an icon with this header
 1626      * @param bool  $spacerifnone return spacer if no icon found
 1627      * @param bool  $withdescription Show description if defined by this item.
 1628      * @param bool  $fulltotal If the item is a category total, returns $categoryname."total"
 1629      *                         instead of "Category total" or "Course total"
 1630      *
 1631      * @return string header
 1632      */
 1633     public function get_element_header(&$element, $withlink = false, $icon = true, $spacerifnone = false,
 1634         $withdescription = false, $fulltotal = false) {
 1635         $header = '';
 1636 
 1637         if ($icon) {
 1638             $header .= $this->get_element_icon($element, $spacerifnone);
 1639         }
 1640 
 1641         $title = $element['object']->get_name($fulltotal);
 1642         $header .= $title;
 1643 
 1644         if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and
 1645             $element['type'] != 'courseitem') {
 1646             return $header;
 1647         }
 1648 
 1649         if ($withlink && $url = $this->get_activity_link($element)) {
 1650             $a = new stdClass();
 1651             $a->name = get_string('modulename', $element['object']->itemmodule);
 1652             $a->title = $title;
 1653             $title = get_string('linktoactivity', 'grades', $a);
 1654 
 1655             $header = html_writer::link($url, $header, array('title' => $title, 'class' => 'gradeitemheader'));
 1656         } else {
 1657             $header = html_writer::span($header, 'gradeitemheader', array('title' => $title, 'tabindex' => '0'));
 1658         }
 1659 
 1660         if ($withdescription) {
 1661             $desc = $element['object']->get_description();
 1662             if (!empty($desc)) {
 1663                 $header .= '<div class="gradeitemdescription">' . s($desc) . '</div><div class="gradeitemdescriptionfiller"></div>';
 1664             }
 1665         }
 1666 
 1667         return $header;
 1668     }
 1669 
 1670     private function get_activity_link($element) {
 1671         global $CFG;
 1672         /** @var array static cache of the grade.php file existence flags */
 1673         static $hasgradephp = array();
 1674 
 1675         $itemtype = $element['object']->itemtype;
 1676         $itemmodule = $element['object']->itemmodule;
 1677         $iteminstance = $element['object']->iteminstance;
 1678         $itemnumber = $element['object']->itemnumber;
 1679 
 1680         // Links only for module items that have valid instance, module and are
 1681         // called from grade_tree with valid modinfo
 1682         if ($itemtype != 'mod' || !$iteminstance || !$itemmodule || !$this->modinfo) {
 1683             return null;
 1684         }
 1685 
 1686         // Get $cm efficiently and with visibility information using modinfo
 1687         $instances = $this->modinfo->get_instances();
 1688         if (empty($instances[$itemmodule][$iteminstance])) {
 1689             return null;
 1690         }
 1691         $cm = $instances[$itemmodule][$iteminstance];
 1692 
 1693         // Do not add link if activity is not visible to the current user
 1694         if (!$cm->uservisible) {
 1695             return null;
 1696         }
 1697 
 1698         if (!array_key_exists($itemmodule, $hasgradephp)) {
 1699             if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) {
 1700                 $hasgradephp[$itemmodule] = true;
 1701             } else {
 1702                 $hasgradephp[$itemmodule] = false;
 1703             }
 1704         }
 1705 
 1706         // If module has grade.php, link to that, otherwise view.php
 1707         if ($hasgradephp[$itemmodule]) {
 1708             $args = array('id' => $cm->id, 'itemnumber' => $itemnumber);
 1709             if (isset($element['userid'])) {
 1710                 $args['userid'] = $element['userid'];
 1711             }
 1712             return new moodle_url('/mod/' . $itemmodule . '/grade.php', $args);
 1713         } else {
 1714             return new moodle_url('/mod/' . $itemmodule . '/view.php', array('id' => $cm->id));
 1715         }
 1716     }
 1717 
 1718     /**
 1719      * Returns URL of a page that is supposed to contain detailed grade analysis
 1720      *
 1721      * At the moment, only activity modules are supported. The method generates link
 1722      * to the module's file grade.php with the parameters id (cmid), itemid, itemnumber,
 1723      * gradeid and userid. If the grade.php does not exist, null is returned.
 1724      *
 1725      * @return moodle_url|null URL or null if unable to construct it
 1726      */
 1727     public function get_grade_analysis_url(grade_grade $grade) {
 1728         global $CFG;
 1729         /** @var array static cache of the grade.php file existence flags */
 1730         static $hasgradephp = array();
 1731 
 1732         if (empty($grade->grade_item) or !($grade->grade_item instanceof grade_item)) {
 1733             throw new coding_exception('Passed grade without the associated grade item');
 1734         }
 1735         $item = $grade->grade_item;
 1736 
 1737         if (!$item->is_external_item()) {
 1738             // at the moment, only activity modules are supported
 1739             return null;
 1740         }
 1741         if ($item->itemtype !== 'mod') {
 1742             throw new coding_exception('Unknown external itemtype: '.$item->itemtype);
 1743         }
 1744         if (empty($item->iteminstance) or empty($item->itemmodule) or empty($this->modinfo)) {
 1745             return null;
 1746         }
 1747 
 1748         if (!array_key_exists($item->itemmodule, $hasgradephp)) {
 1749             if (file_exists($CFG->dirroot . '/mod/' . $item->itemmodule . '/grade.php')) {
 1750                 $hasgradephp[$item->itemmodule] = true;
 1751             } else {
 1752                 $hasgradephp[$item->itemmodule] = false;
 1753             }
 1754         }
 1755 
 1756         if (!$hasgradephp[$item->itemmodule]) {
 1757             return null;
 1758         }
 1759 
 1760         $instances = $this->modinfo->get_instances();
 1761         if (empty($instances[$item->itemmodule][$item->iteminstance])) {
 1762             return null;
 1763         }
 1764         $cm = $instances[$item->itemmodule][$item->iteminstance];
 1765         if (!$cm->uservisible) {
 1766             return null;
 1767         }
 1768 
 1769         $url = new moodle_url('/mod/'.$item->itemmodule.'/grade.php', array(
 1770             'id'         => $cm->id,
 1771             'itemid'     => $item->id,
 1772             'itemnumber' => $item->itemnumber,
 1773             'gradeid'    => $grade->id,
 1774             'userid'     => $grade->userid,
 1775         ));
 1776 
 1777         return $url;
 1778     }
 1779 
 1780     /**
 1781      * Returns an action icon leading to the grade analysis page
 1782      *
 1783      * @param grade_grade $grade
 1784      * @return string
 1785      */
 1786     public function get_grade_analysis_icon(grade_grade $grade) {
 1787         global $OUTPUT;
 1788 
 1789         $url = $this->get_grade_analysis_url($grade);
 1790         if (is_null($url)) {
 1791             return '';
 1792         }
 1793 
 1794         return $OUTPUT->action_icon($url, new pix_icon('t/preview',
 1795             get_string('gradeanalysis', 'core_grades')));
 1796     }
 1797 
 1798     /**
 1799      * Returns the grade eid - the grade may not exist yet.
 1800      *
 1801      * @param grade_grade $grade_grade A grade_grade object
 1802      *
 1803      * @return string eid
 1804      */
 1805     public function get_grade_eid($grade_grade) {
 1806         if (empty($grade_grade->id)) {
 1807             return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid;
 1808         } else {
 1809             return 'g'.$grade_grade->id;
 1810         }
 1811     }
 1812 
 1813     /**
 1814      * Returns the grade_item eid
 1815      * @param grade_item $grade_item A grade_item object
 1816      * @return string eid
 1817      */
 1818     public function get_item_eid($grade_item) {
 1819         return 'ig'.$grade_item->id;
 1820     }
 1821 
 1822     /**
 1823      * Given a grade_tree element, returns an array of parameters
 1824      * used to build an icon for that element.
 1825      *
 1826      * @param array $element An array representing an element in the grade_tree
 1827      *
 1828      * @return array
 1829      */
 1830     public function get_params_for_iconstr($element) {
 1831         $strparams = new stdClass();
 1832         $strparams->category = '';
 1833         $strparams->itemname = '';
 1834         $strparams->itemmodule = '';
 1835 
 1836         if (!method_exists($element['object'], 'get_name')) {
 1837             return $strparams;
 1838         }
 1839 
 1840         $strparams->itemname = html_to_text($element['object']->get_name());
 1841 
 1842         // If element name is categorytotal, get the name of the parent category
 1843         if ($strparams->itemname == get_string('categorytotal', 'grades')) {
 1844             $parent = $element['object']->get_parent_category();
 1845             $strparams->category = $parent->get_name() . ' ';
 1846         } else {
 1847             $strparams->category = '';
 1848         }
 1849 
 1850         $strparams->itemmodule = null;
 1851         if (isset($element['object']->itemmodule)) {
 1852             $strparams->itemmodule = $element['object']->itemmodule;
 1853         }
 1854         return $strparams;
 1855     }
 1856 
 1857     /**
 1858      * Return a reset icon for the given element.
 1859      *
 1860      * @param array  $element An array representing an element in the grade_tree
 1861      * @param object $gpr A grade_plugin_return object
 1862      * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
 1863      * @return string|action_menu_link
 1864      */
 1865     public function get_reset_icon($element, $gpr, $returnactionmenulink = false) {
 1866         global $CFG, $OUTPUT;
 1867 
 1868         // Limit to category items set to use the natural weights aggregation method, and users
 1869         // with the capability to manage grades.
 1870         if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM ||
 1871                 !has_capability('moodle/grade:manage', $this->context)) {
 1872             return $returnactionmenulink ? null : '';
 1873         }
 1874 
 1875         $str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element));
 1876         $url = new moodle_url('/grade/edit/tree/action.php', array(
 1877             'id' => $this->courseid,
 1878             'action' => 'resetweights',
 1879             'eid' => $element['eid'],
 1880             'sesskey' => sesskey(),
 1881         ));
 1882 
 1883         if ($returnactionmenulink) {
 1884             return new action_menu_link_secondary($gpr->add_url_params($url), new pix_icon('t/reset', $str),
 1885                 get_string('resetweightsshort', 'grades'));
 1886         } else {
 1887             return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/reset', $str));
 1888         }
 1889     }
 1890 
 1891     /**
 1892      * Return edit icon for give element
 1893      *
 1894      * @param array  $element An array representing an element in the grade_tree
 1895      * @param object $gpr A grade_plugin_return object
 1896      * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
 1897      * @return string|action_menu_link
 1898      */
 1899     public function get_edit_icon($element, $gpr, $returnactionmenulink = false) {
 1900         global $CFG, $OUTPUT;
 1901 
 1902         if (!has_capability('moodle/grade:manage', $this->context)) {
 1903             if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
 1904                 // oki - let them override grade
 1905             } else {
 1906                 return $returnactionmenulink ? null : '';
 1907             }
 1908         }
 1909 
 1910         static $strfeedback   = null;
 1911         static $streditgrade = null;
 1912         if (is_null($streditgrade)) {
 1913             $streditgrade = get_string('editgrade', 'grades');
 1914             $strfeedback  = get_string('feedback');
 1915         }
 1916 
 1917         $strparams = $this->get_params_for_iconstr($element);
 1918 
 1919         $object = $element['object'];
 1920 
 1921         switch ($element['type']) {
 1922             case 'item':
 1923             case 'categoryitem':
 1924             case 'courseitem':
 1925                 $stredit = get_string('editverbose', 'grades', $strparams);
 1926                 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) {
 1927                     $url = new moodle_url('/grade/edit/tree/item.php',
 1928                             array('courseid' => $this->courseid, 'id' => $object->id));
 1929                 } else {
 1930                     $url = new moodle_url('/grade/edit/tree/outcomeitem.php',
 1931                             array('courseid' => $this->courseid, 'id' => $object->id));
 1932                 }
 1933                 break;
 1934 
 1935             case 'category':
 1936                 $stredit = get_string('editverbose', 'grades', $strparams);
 1937                 $url = new moodle_url('/grade/edit/tree/category.php',
 1938                         array('courseid' => $this->courseid, 'id' => $object->id));
 1939                 break;
 1940 
 1941             case 'grade':
 1942                 $stredit = $streditgrade;
 1943                 if (empty($object->id)) {
 1944                     $url = new moodle_url('/grade/edit/tree/grade.php',
 1945                             array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid));
 1946                 } else {
 1947                     $url = new moodle_url('/grade/edit/tree/grade.php',
 1948                             array('courseid' => $this->courseid, 'id' => $object->id));
 1949                 }
 1950                 if (!empty($object->feedback)) {
 1951                     $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat)));
 1952                 }
 1953                 break;
 1954 
 1955             default:
 1956                 $url = null;
 1957         }
 1958 
 1959         if ($url) {
 1960             if ($returnactionmenulink) {
 1961                 return new action_menu_link_secondary($gpr->add_url_params($url),
 1962                     new pix_icon('t/edit', $stredit),
 1963                     get_string('editsettings'));
 1964             } else {
 1965                 return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit));
 1966             }
 1967 
 1968         } else {
 1969             return $returnactionmenulink ? null : '';
 1970         }
 1971     }
 1972 
 1973     /**
 1974      * Return hiding icon for give element
 1975      *
 1976      * @param array  $element An array representing an element in the grade_tree
 1977      * @param object $gpr A grade_plugin_return object
 1978      * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
 1979      * @return string|action_menu_link
 1980      */
 1981     public function get_hiding_icon($element, $gpr, $returnactionmenulink = false) {
 1982         global $CFG, $OUTPUT;
 1983 
 1984         if (!$element['object']->can_control_visibility()) {
 1985             return $returnactionmenulink ? null : '';
 1986         }
 1987 
 1988         if (!has_capability('moodle/grade:manage', $this->context) and
 1989             !has_capability('moodle/grade:hide', $this->context)) {
 1990             return $returnactionmenulink ? null : '';
 1991         }
 1992 
 1993         $strparams = $this->get_params_for_iconstr($element);
 1994         $strshow = get_string('showverbose', 'grades', $strparams);
 1995         $strhide = get_string('hideverbose', 'grades', $strparams);
 1996 
 1997         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
 1998         $url = $gpr->add_url_params($url);
 1999 
 2000         if ($element['object']->is_hidden()) {
 2001             $type = 'show';
 2002             $tooltip = $strshow;
 2003 
 2004             // Change the icon and add a tooltip showing the date
 2005             if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) {
 2006                 $type = 'hiddenuntil';
 2007                 $tooltip = get_string('hiddenuntildate', 'grades',
 2008                         userdate($element['object']->get_hidden()));
 2009             }
 2010 
 2011             $url->param('action', 'show');
 2012 
 2013             if ($returnactionmenulink) {
 2014                 $hideicon = new action_menu_link_secondary($url, new pix_icon('t/'.$type, $tooltip), get_string('show'));
 2015             } else {
 2016                 $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon')));
 2017             }
 2018 
 2019         } else {
 2020             $url->param('action', 'hide');
 2021             if ($returnactionmenulink) {
 2022                 $hideicon = new action_menu_link_secondary($url, new pix_icon('t/hide', $strhide), get_string('hide'));
 2023             } else {
 2024                 $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide));
 2025             }
 2026         }
 2027 
 2028         return $hideicon;
 2029     }
 2030 
 2031     /**
 2032      * Return locking icon for given element
 2033      *
 2034      * @param array  $element An array representing an element in the grade_tree
 2035      * @param object $gpr A grade_plugin_return object
 2036      *
 2037      * @return string
 2038      */
 2039     public function get_locking_icon($element, $gpr) {
 2040         global $CFG, $OUTPUT;
 2041 
 2042         $strparams = $this->get_params_for_iconstr($element);
 2043         $strunlock = get_string('unlockverbose', 'grades', $strparams);
 2044         $strlock = get_string('lockverbose', 'grades', $strparams);
 2045 
 2046         $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
 2047         $url = $gpr->add_url_params($url);
 2048 
 2049         // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon
 2050         if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) {
 2051             $strparamobj = new stdClass();
 2052             $strparamobj->itemname = $element['object']->grade_item->itemname;
 2053             $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj);
 2054 
 2055             $action = html_writer::tag('span', $OUTPUT->pix_icon('t/locked', $strnonunlockable),
 2056                     array('class' => 'action-icon'));
 2057 
 2058         } else if ($element['object']->is_locked()) {
 2059             $type = 'unlock';
 2060             $tooltip = $strunlock;
 2061 
 2062             // Change the icon and add a tooltip showing the date
 2063             if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) {
 2064                 $type = 'locktime';
 2065                 $tooltip = get_string('locktimedate', 'grades',
 2066                         userdate($element['object']->get_locktime()));
 2067             }
 2068 
 2069             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) {
 2070                 $action = '';
 2071             } else {
 2072                 $url->param('action', 'unlock');
 2073                 $action = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strunlock, 'class'=>'smallicon')));
 2074             }
 2075 
 2076         } else {
 2077             if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) {
 2078                 $action = '';
 2079             } else {
 2080                 $url->param('action', 'lock');
 2081                 $action = $OUTPUT->action_icon($url, new pix_icon('t/lock', $strlock));
 2082             }
 2083         }
 2084 
 2085         return $action;
 2086     }
 2087 
 2088     /**
 2089      * Return calculation icon for given element
 2090      *
 2091      * @param array  $element An array representing an element in the grade_tree
 2092      * @param object $gpr A grade_plugin_return object
 2093      * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
 2094      * @return string|action_menu_link
 2095      */
 2096     public function get_calculation_icon($element, $gpr, $returnactionmenulink = false) {
 2097         global $CFG, $OUTPUT;
 2098         if (!has_capability('moodle/grade:manage', $this->context)) {
 2099             return $returnactionmenulink ? null : '';
 2100         }
 2101 
 2102         $type   = $element['type'];
 2103         $object = $element['object'];
 2104 
 2105         if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
 2106             $strparams = $this->get_params_for_iconstr($element);
 2107             $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams);
 2108 
 2109             $is_scale = $object->gradetype == GRADE_TYPE_SCALE;
 2110             $is_value = $object->gradetype == GRADE_TYPE_VALUE;
 2111 
 2112             // show calculation icon only when calculation possible
 2113             if (!$object->is_external_item() and ($is_scale or $is_value)) {
 2114                 if ($object->is_calculated()) {
 2115                     $icon = 't/calc';
 2116                 } else {
 2117                     $icon = 't/calc_off';
 2118                 }
 2119 
 2120                 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id));
 2121                 $url = $gpr->add_url_params($url);
 2122                 if ($returnactionmenulink) {
 2123                     return new action_menu_link_secondary($url,
 2124                         new pix_icon($icon, $streditcalculation),
 2125                         get_string('editcalculation', 'grades'));
 2126                 } else {
 2127                     return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation));
 2128                 }
 2129             }
 2130         }
 2131 
 2132         return $returnactionmenulink ? null : '';
 2133     }
 2134 }
 2135 
 2136 /**
 2137  * Flat structure similar to grade tree.
 2138  *
 2139  * @uses grade_structure
 2140  * @package core_grades
 2141  * @copyright 2009 Nicolas Connault
 2142  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 2143  */
 2144 class grade_seq extends grade_structure {
 2145 
 2146     /**
 2147      * 1D array of elements
 2148      */
 2149     public $elements;
 2150 
 2151     /**
 2152      * Constructor, retrieves and stores array of all grade_category and grade_item
 2153      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
 2154      *
 2155      * @param int  $courseid The course id
 2156      * @param bool $category_grade_last category grade item is the last child
 2157      * @param bool $nooutcomes Whether or not outcomes should be included
 2158      */
 2159     public function __construct($courseid, $category_grade_last=false, $nooutcomes=false) {
 2160         global $USER, $CFG;
 2161 
 2162         $this->courseid   = $courseid;
 2163         $this->context    = context_course::instance($courseid);
 2164 
 2165         // get course grade tree
 2166         $top_element = grade_category::fetch_course_tree($courseid, true);
 2167 
 2168         $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes);
 2169 
 2170         foreach ($this->elements as $key=>$unused) {
 2171             $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object'];
 2172         }
 2173     }
 2174 
 2175     /**
 2176      * Old syntax of class constructor. Deprecated in PHP7.
 2177      *
 2178      * @deprecated since Moodle 3.1
 2179      */
 2180     public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) {
 2181         debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 2182         self::__construct($courseid, $category_grade_last, $nooutcomes);
 2183     }
 2184 
 2185     /**
 2186      * Static recursive helper - makes the grade_item for category the last children
 2187      *
 2188      * @param array &$element The seed of the recursion
 2189      * @param bool $category_grade_last category grade item is the last child
 2190      * @param bool $nooutcomes Whether or not outcomes should be included
 2191      *
 2192      * @return array
 2193      */
 2194     public function flatten(&$element, $category_grade_last, $nooutcomes) {
 2195         if (empty($element['children'])) {
 2196             return array();
 2197         }
 2198         $children = array();
 2199 
 2200         foreach ($element['children'] as $sortorder=>$unused) {
 2201             if ($nooutcomes and $element['type'] != 'category' and
 2202                 $element['children'][$sortorder]['object']->is_outcome_item()) {
 2203                 continue;
 2204             }
 2205             $children[] = $element['children'][$sortorder];
 2206         }
 2207         unset($element['children']);
 2208 
 2209         if ($category_grade_last and count($children) > 1 and
 2210             (
 2211                 $children[0]['type'] === 'courseitem' or
 2212                 $children[0]['type'] === 'categoryitem'
 2213             )
 2214         ) {
 2215             $cat_item = array_shift($children);
 2216             array_push($children, $cat_item);
 2217         }
 2218 
 2219         $result = array();
 2220         foreach ($children as $child) {
 2221             if ($child['type'] == 'category') {
 2222                 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes);
 2223             } else {
 2224                 $child['eid'] = 'i'.$child['object']->id;
 2225                 $result[$child['object']->id] = $child;
 2226             }
 2227         }
 2228 
 2229         return $result;
 2230     }
 2231 
 2232     /**
 2233      * Parses the array in search of a given eid and returns a element object with
 2234      * information about the element it has found.
 2235      *
 2236      * @param int $eid Gradetree Element ID
 2237      *
 2238      * @return object element
 2239      */
 2240     public function locate_element($eid) {
 2241         // it is a grade - construct a new object
 2242         if (strpos($eid, 'n') === 0) {
 2243             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
 2244                 return null;
 2245             }
 2246 
 2247             $itemid = $matches[1];
 2248             $userid = $matches[2];
 2249 
 2250             //extra security check - the grade item must be in this tree
 2251             if (!$item_el = $this->locate_element('ig'.$itemid)) {
 2252                 return null;
 2253             }
 2254 
 2255             // $gradea->id may be null - means does not exist yet
 2256             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
 2257 
 2258             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
 2259             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
 2260 
 2261         } else if (strpos($eid, 'g') === 0) {
 2262             $id = (int) substr($eid, 1);
 2263             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
 2264                 return null;
 2265             }
 2266             //extra security check - the grade item must be in this tree
 2267             if (!$item_el = $this->locate_element('ig'.$grade->itemid)) {
 2268                 return null;
 2269             }
 2270             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
 2271             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
 2272         }
 2273 
 2274         // it is a category or item
 2275         foreach ($this->elements as $element) {
 2276             if ($element['eid'] == $eid) {
 2277                 return $element;
 2278             }
 2279         }
 2280 
 2281         return null;
 2282     }
 2283 }
 2284 
 2285 /**
 2286  * This class represents a complete tree of categories, grade_items and final grades,
 2287  * organises as an array primarily, but which can also be converted to other formats.
 2288  * It has simple method calls with complex implementations, allowing for easy insertion,
 2289  * deletion and moving of items and categories within the tree.
 2290  *
 2291  * @uses grade_structure
 2292  * @package core_grades
 2293  * @copyright 2009 Nicolas Connault
 2294  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 2295  */
 2296 class grade_tree extends grade_structure {
 2297 
 2298     /**
 2299      * The basic representation of the tree as a hierarchical, 3-tiered array.
 2300      * @var object $top_element
 2301      */
 2302     public $top_element;
 2303 
 2304     /**
 2305      * 2D array of grade items and categories
 2306      * @var array $levels
 2307      */
 2308     public $levels;
 2309 
 2310     /**
 2311      * Grade items
 2312      * @var array $items
 2313      */
 2314     public $items;
 2315 
 2316     /**
 2317      * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item
 2318      * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
 2319      *
 2320      * @param int   $courseid The Course ID
 2321      * @param bool  $fillers include fillers and colspans, make the levels var "rectangular"
 2322      * @param bool  $category_grade_last category grade item is the last child
 2323      * @param array $collapsed array of collapsed categories
 2324      * @param bool  $nooutcomes Whether or not outcomes should be included
 2325      */
 2326     public function __construct($courseid, $fillers=true, $category_grade_last=false,
 2327                                $collapsed=null, $nooutcomes=false) {
 2328         global $USER, $CFG, $COURSE, $DB;
 2329 
 2330         $this->courseid   = $courseid;
 2331         $this->levels     = array();
 2332         $this->context    = context_course::instance($courseid);
 2333 
 2334         if (!empty($COURSE->id) && $COURSE->id == $this->courseid) {
 2335             $course = $COURSE;
 2336         } else {
 2337             $course = $DB->get_record('course', array('id' => $this->courseid));
 2338         }
 2339         $this->modinfo = get_fast_modinfo($course);
 2340 
 2341         // get course grade tree
 2342         $this->top_element = grade_category::fetch_course_tree($courseid, true);
 2343 
 2344         // collapse the categories if requested
 2345         if (!empty($collapsed)) {
 2346             grade_tree::category_collapse($this->top_element, $collapsed);
 2347         }
 2348 
 2349         // no otucomes if requested
 2350         if (!empty($nooutcomes)) {
 2351             grade_tree::no_outcomes($this->top_element);
 2352         }
 2353 
 2354         // move category item to last position in category
 2355         if ($category_grade_last) {
 2356             grade_tree::category_grade_last($this->top_element);
 2357         }
 2358 
 2359         if ($fillers) {
 2360             // inject fake categories == fillers
 2361             grade_tree::inject_fillers($this->top_element, 0);
 2362             // add colspans to categories and fillers
 2363             grade_tree::inject_colspans($this->top_element);
 2364         }
 2365 
 2366         grade_tree::fill_levels($this->levels, $this->top_element, 0);
 2367 
 2368     }
 2369 
 2370     /**
 2371      * Old syntax of class constructor. Deprecated in PHP7.
 2372      *
 2373      * @deprecated since Moodle 3.1
 2374      */
 2375     public function grade_tree($courseid, $fillers=true, $category_grade_last=false,
 2376                                $collapsed=null, $nooutcomes=false) {
 2377         debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
 2378         self::__construct($courseid, $fillers, $category_grade_last, $collapsed, $nooutcomes);
 2379     }
 2380 
 2381     /**
 2382      * Static recursive helper - removes items from collapsed categories
 2383      *
 2384      * @param array &$element The seed of the recursion
 2385      * @param array $collapsed array of collapsed categories
 2386      *
 2387      * @return void
 2388      */
 2389     public function category_collapse(&$element, $collapsed) {
 2390         if ($element['type'] != 'category') {
 2391             return;
 2392         }
 2393         if (empty($element['children']) or count($element['children']) < 2) {
 2394             return;
 2395         }
 2396 
 2397         if (in_array($element['object']->id, $collapsed['aggregatesonly'])) {
 2398             $category_item = reset($element['children']); //keep only category item
 2399             $element['children'] = array(key($element['children'])=>$category_item);
 2400 
 2401         } else {
 2402             if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item
 2403                 reset($element['children']);
 2404                 $first_key = key($element['children']);
 2405                 unset($element['children'][$first_key]);
 2406             }
 2407             foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children
 2408                 grade_tree::category_collapse($element['children'][$sortorder], $collapsed);
 2409             }
 2410         }
 2411     }
 2412 
 2413     /**
 2414      * Static recursive helper - removes all outcomes
 2415      *
 2416      * @param array &$element The seed of the recursion
 2417      *
 2418      * @return void
 2419      */
 2420     public function no_outcomes(&$element) {
 2421         if ($element['type'] != 'category') {
 2422             return;
 2423         }
 2424         foreach ($element['children'] as $sortorder=>$child) {
 2425             if ($element['children'][$sortorder]['type'] == 'item'
 2426               and $element['children'][$sortorder]['object']->is_outcome_item()) {
 2427                 unset($element['children'][$sortorder]);
 2428 
 2429             } else if ($element['children'][$sortorder]['type'] == 'category') {
 2430                 grade_tree::no_outcomes($element['children'][$sortorder]);
 2431             }
 2432         }
 2433     }
 2434 
 2435     /**
 2436      * Static recursive helper - makes the grade_item for category the last children
 2437      *
 2438      * @param array &$element The seed of the recursion
 2439      *
 2440      * @return void
 2441      */
 2442     public function category_grade_last(&$element) {
 2443         if (empty($element['children'])) {
 2444             return;
 2445         }
 2446         if (count($element['children']) < 2) {
 2447             return;
 2448         }
 2449         $first_item = reset($element['children']);
 2450         if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') {
 2451             // the category item might have been already removed
 2452             $order = key($element['children']);
 2453             unset($element['children'][$order]);
 2454             $element['children'][$order] =& $first_item;
 2455         }
 2456         foreach ($element['children'] as $sortorder => $child) {
 2457             grade_tree::category_grade_last($element['children'][$sortorder]);
 2458         }
 2459     }
 2460 
 2461     /**
 2462      * Static recursive helper - fills the levels array, useful when accessing tree elements of one level
 2463      *
 2464      * @param array &$levels The levels of the grade tree through which to recurse
 2465      * @param array &$element The seed of the recursion
 2466      * @param int   $depth How deep are we?
 2467      * @return void
 2468      */
 2469     public function fill_levels(&$levels, &$element, $depth) {
 2470         if (!array_key_exists($depth, $levels)) {
 2471             $levels[$depth] = array();
 2472         }
 2473 
 2474         // prepare unique identifier
 2475         if ($element['type'] == 'category') {
 2476             $element['eid'] = 'cg'.$element['object']->id;
 2477         } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
 2478             $element['eid'] = 'ig'.$element['object']->id;
 2479             $this->items[$element['object']->id] =& $element['object'];
 2480         }
 2481 
 2482         $levels[$depth][] =& $element;
 2483         $depth++;
 2484         if (empty($element['children'])) {
 2485             return;
 2486         }
 2487         $prev = 0;
 2488         foreach ($element['children'] as $sortorder=>$child) {
 2489             grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth);
 2490             $element['children'][$sortorder]['prev'] = $prev;
 2491             $element['children'][$sortorder]['next'] = 0;
 2492             if ($prev) {
 2493                 $element['children'][$prev]['next'] = $sortorder;
 2494             }
 2495             $prev = $sortorder;
 2496         }
 2497     }
 2498 
 2499     /**
 2500      * Determines whether the grade tree item can be displayed.
 2501      * This is particularly targeted for grade categories that have no total (None) when rendering the grade tree.
 2502      * It checks if the grade tree item is of type 'category', and makes sure that the category, or at least one of children,
 2503      * can be output.
 2504      *
 2505      * @param array $element The grade category element.
 2506      * @return bool True if the grade tree item can be displayed. False, otherwise.
 2507      */
 2508     public static function can_output_item($element) {
 2509         $canoutput = true;
 2510 
 2511         if ($element['type'] === 'category') {
 2512             $object = $element['object'];
 2513             $category = grade_category::fetch(array('id' => $object->id));
 2514             // Category has total, we can output this.
 2515             if ($category->get_grade_item()->gradetype != GRADE_TYPE_NONE) {
 2516                 return true;
 2517             }
 2518 
 2519             // Category has no total and has no children, no need to output this.
 2520             if (empty($element['children'])) {
 2521                 return false;
 2522             }
 2523 
 2524             $canoutput = false;
 2525             // Loop over children and make sure at least one child can be output.
 2526             foreach ($element['children'] as $child) {
 2527                 $canoutput = self::can_output_item($child);
 2528                 if ($canoutput) {
 2529                     break;
 2530                 }
 2531             }
 2532         }
 2533 
 2534         return $canoutput;
 2535     }
 2536 
 2537     /**
 2538      * Static recursive helper - makes full tree (all leafes are at the same level)
 2539      *
 2540      * @param array &$element The seed of the recursion
 2541      * @param int   $depth How deep are we?
 2542      *
 2543      * @return int
 2544      */
 2545     public function inject_fillers(&$element, $depth) {
 2546         $depth++;
 2547 
 2548         if (empty($element['children'])) {
 2549             return $depth;
 2550         }
 2551         $chdepths = array();
 2552         $chids = array_keys($element['children']);
 2553         $last_child  = end($chids);
 2554         $first_child = reset($chids);
 2555 
 2556         foreach ($chids as $chid) {
 2557             $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth);
 2558         }
 2559         arsort($chdepths);
 2560 
 2561         $maxdepth = reset($chdepths);
 2562         foreach ($chdepths as $chid=>$chd) {
 2563             if ($chd == $maxdepth) {
 2564                 continue;
 2565             }
 2566             if (!self::can_output_item($element['children'][$chid])) {
 2567                 continue;
 2568             }
 2569             for ($i=0; $i < $maxdepth-$chd; $i++) {
 2570                 if ($chid == $first_child) {
 2571                     $type = 'fillerfirst';
 2572                 } else if ($chid == $last_child) {
 2573                     $type = 'fillerlast';
 2574                 } else {
 2575                     $type = 'filler';
 2576                 }
 2577                 $oldchild =& $element['children'][$chid];
 2578                 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type,
 2579                                                     'eid'=>'', 'depth'=>$element['object']->depth,
 2580                                                     'children'=>array($oldchild));
 2581             }
 2582         }
 2583 
 2584         return $maxdepth;
 2585     }
 2586 
 2587     /**
 2588      * Static recursive helper - add colspan information into categories
 2589      *
 2590      * @param array &$element The seed of the recursion
 2591      *
 2592      * @return int
 2593      */
 2594     public function inject_colspans(&$element) {
 2595         if (empty($element['children'])) {
 2596             return 1;
 2597         }
 2598         $count = 0;
 2599         foreach ($element['children'] as $key=>$child) {
 2600             if (!self::can_output_item($child)) {
 2601                 continue;
 2602             }
 2603             $count += grade_tree::inject_colspans($element['children'][$key]);
 2604         }
 2605         $element['colspan'] = $count;
 2606         return $count;
 2607     }
 2608 
 2609     /**
 2610      * Parses the array in search of a given eid and returns a element object with
 2611      * information about the element it has found.
 2612      * @param int $eid Gradetree Element ID
 2613      * @return object element
 2614      */
 2615     public function locate_element($eid) {
 2616         // it is a grade - construct a new object
 2617         if (strpos($eid, 'n') === 0) {
 2618             if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
 2619                 return null;
 2620             }
 2621 
 2622             $itemid = $matches[1];
 2623             $userid = $matches[2];
 2624 
 2625             //extra security check - the grade item must be in this tree
 2626             if (!$item_el = $this->locate_element('ig'.$itemid)) {
 2627                 return null;
 2628             }
 2629 
 2630             // $gradea->id may be null - means does not exist yet
 2631             $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
 2632 
 2633             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
 2634             return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
 2635 
 2636         } else if (strpos($eid, 'g') === 0) {
 2637             $id = (int) substr($eid, 1);
 2638             if (!$grade = grade_grade::fetch(array('id'=>$id))) {
 2639                 return null;
 2640             }
 2641             //extra security check - the grade item must be in this tree
 2642             if (!$item_el = $this->locate_element('ig'.$grade->itemid)) {
 2643                 return null;
 2644             }
 2645             $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
 2646             return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
 2647         }
 2648 
 2649         // it is a category or item
 2650         foreach ($this->levels as $row) {
 2651             foreach ($row as $element) {
 2652                 if ($element['type'] == 'filler') {
 2653                     continue;
 2654                 }
 2655                 if ($element['eid'] == $eid) {
 2656                     return $element;
 2657                 }
 2658             }
 2659         }
 2660 
 2661         return null;
 2662     }
 2663 
 2664     /**
 2665      * Returns a well-formed XML representation of the grade-tree using recursion.
 2666      *
 2667      * @param array  $root The current element in the recursion. If null, starts at the top of the tree.
 2668      * @param string $tabs The control character to use for tabs
 2669      *
 2670      * @return string $xml
 2671      */
 2672     public function exporttoxml($root=null, $tabs="\t") {
 2673         $xml = null;
 2674         $first = false;
 2675         if (is_null($root)) {
 2676             $root = $this->top_element;
 2677             $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
 2678             $xml .= "<gradetree>\n";
 2679             $first = true;
 2680         }
 2681 
 2682         $type = 'undefined';
 2683         if (strpos($root['object']->table, 'grade_categories') !== false) {
 2684             $type = 'category';
 2685         } else if (strpos($root['object']->table, 'grade_items') !== false) {
 2686             $type = 'item';
 2687         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
 2688             $type = 'outcome';
 2689         }
 2690 
 2691         $xml .= "$tabs<element type=\"$type\">\n";
 2692         foreach ($root['object'] as $var => $value) {
 2693             if (!is_object($value) && !is_array($value) && !empty($value)) {
 2694                 $xml .= "$tabs\t<$var>$value</$var>\n";
 2695             }
 2696         }
 2697 
 2698         if (!empty($root['children'])) {
 2699             $xml .= "$tabs\t<children>\n";
 2700             foreach ($root['children'] as $sortorder => $child) {
 2701                 $xml .= $this->exportToXML($child, $tabs."\t\t");
 2702             }
 2703             $xml .= "$tabs\t</children>\n";
 2704         }
 2705 
 2706         $xml .= "$tabs</element>\n";
 2707 
 2708         if ($first) {
 2709             $xml .= "</gradetree>";
 2710         }
 2711 
 2712         return $xml;
 2713     }
 2714 
 2715     /**
 2716      * Returns a JSON representation of the grade-tree using recursion.
 2717      *
 2718      * @param array $root The current element in the recursion. If null, starts at the top of the tree.
 2719      * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy
 2720      *
 2721      * @return string
 2722      */
 2723     public function exporttojson($root=null, $tabs="\t") {
 2724         $json = null;
 2725         $first = false;
 2726         if (is_null($root)) {
 2727             $root = $this->top_element;
 2728             $first = true;
 2729         }
 2730 
 2731         $name = '';
 2732 
 2733 
 2734         if (strpos($root['object']->table, 'grade_categories') !== false) {
 2735             $name = $root['object']->fullname;
 2736             if ($name == '?') {
 2737                 $name = $root['object']->get_name();
 2738             }
 2739         } else if (strpos($root['object']->table, 'grade_items') !== false) {
 2740             $name = $root['object']->itemname;
 2741         } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
 2742             $name = $root['object']->itemname;
 2743         }
 2744 
 2745         $json .= "$tabs {\n";
 2746         $json .= "$tabs\t \"type\": \"{$root['type']}\",\n";
 2747         $json .= "$tabs\t \"name\": \"$name\",\n";
 2748 
 2749         foreach ($root['object'] as $var => $value) {
 2750             if (!is_object($value) && !is_array($value) && !empty($value)) {
 2751                 $json .= "$tabs\t \"$var\": \"$value\",\n";
 2752             }
 2753         }
 2754 
 2755         $json = substr($json, 0, strrpos($json, ','));
 2756 
 2757         if (!empty($root['children'])) {
 2758             $json .= ",\n$tabs\t\"children\": [\n";
 2759             foreach ($root['children'] as $sortorder => $child) {
 2760                 $json .= $this->exportToJSON($child, $tabs."\t\t");
 2761             }
 2762             $json = substr($json, 0, strrpos($json, ','));
 2763             $json .= "\n$tabs\t]\n";
 2764         }
 2765 
 2766         if ($first) {
 2767             $json .= "\n}";
 2768         } else {
 2769             $json .= "\n$tabs},\n";
 2770         }
 2771 
 2772         return $json;
 2773     }
 2774 
 2775     /**
 2776      * Returns the array of levels
 2777      *
 2778      * @return array
 2779      */
 2780     public function get_levels() {
 2781         return $this->levels;
 2782     }
 2783 
 2784     /**
 2785      * Returns the array of grade items
 2786      *
 2787      * @return array
 2788      */
 2789     public function get_items() {
 2790         return $this->items;
 2791     }
 2792 
 2793     /**
 2794      * Returns a specific Grade Item
 2795      *
 2796      * @param int $itemid The ID of the grade_item object
 2797      *
 2798      * @return grade_item
 2799      */
 2800     public function get_item($itemid) {
 2801         if (array_key_exists($itemid, $this->items)) {
 2802             return $this->items[$itemid];
 2803         } else {
 2804             return false;
 2805         }
 2806     }
 2807 }
 2808 
 2809 /**
 2810  * Local shortcut function for creating an edit/delete button for a grade_* object.
 2811  * @param string $type 'edit' or 'delete'
 2812  * @param int $courseid The Course ID
 2813  * @param grade_* $object The grade_* object
 2814  * @return string html
 2815  */
 2816 function grade_button($type, $courseid, $object) {
 2817     global $CFG, $OUTPUT;
 2818     if (preg_match('/grade_(.*)/', get_class($object), $matches)) {
 2819         $objectidstring = $matches[1] . 'id';
 2820     } else {
 2821         throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!');
 2822     }
 2823 
 2824     $strdelete = get_string('delete');
 2825     $stredit   = get_string('edit');
 2826 
 2827     if ($type == 'delete') {
 2828         $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey()));
 2829     } else if ($type == 'edit') {
 2830         $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id));
 2831     }
 2832 
 2833     return $OUTPUT->action_icon($url, new pix_icon('t/'.$type, ${'str'.$type}, '', array('class' => 'iconsmall')));
 2834 
 2835 }
 2836 
 2837 /**
 2838  * This method adds settings to the settings block for the grade system and its
 2839  * plugins
 2840  *
 2841  * @global moodle_page $PAGE
 2842  */
 2843 function grade_extend_settings($plugininfo, $courseid) {
 2844     global $PAGE;
 2845 
 2846     $gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER);
 2847 
 2848     $strings = array_shift($plugininfo);
 2849 
 2850     if ($reports = grade_helper::get_plugins_reports($courseid)) {
 2851         foreach ($reports as $report) {
 2852             $gradenode->add($report->string, $report->link, navigation_node::TYPE_SETTING, null, $report->id, new pix_icon('i/report', ''));
 2853         }
 2854     }
 2855 
 2856     if ($settings = grade_helper::get_info_manage_settings($courseid)) {
 2857         $settingsnode = $gradenode->add($strings['settings'], null, navigation_node::TYPE_CONTAINER);
 2858         foreach ($settings as $setting) {
 2859             $settingsnode->add($setting->string, $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', ''));
 2860         }
 2861     }
 2862 
 2863     if ($imports = grade_helper::get_plugins_import($courseid)) {
 2864         $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER);
 2865         foreach ($imports as $import) {
 2866             $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/import', ''));
 2867         }
 2868     }
 2869 
 2870     if ($exports = grade_helper::get_plugins_export($courseid)) {
 2871         $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER);
 2872         foreach ($exports as $export) {
 2873             $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/export', ''));
 2874         }
 2875     }
 2876 
 2877     if ($letters = grade_helper::get_info_letters($courseid)) {
 2878         $letters = array_shift($letters);
 2879         $gradenode->add($strings['letter'], $letters->link, navigation_node::TYPE_SETTING, null, $letters->id, new pix_icon('i/settings', ''));
 2880     }
 2881 
 2882     if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
 2883         $outcomes = array_shift($outcomes);
 2884         $gradenode->add($strings['outcome'], $outcomes->link, navigation_node::TYPE_SETTING, null, $outcomes->id, new pix_icon('i/outcomes', ''));
 2885     }
 2886 
 2887     if ($scales = grade_helper::get_info_scales($courseid)) {
 2888         $gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', ''));
 2889     }
 2890 
 2891     if ($gradenode->contains_active_node()) {
 2892         // If the gradenode is active include the settings base node (gradeadministration) in
 2893         // the navbar, typcially this is ignored.
 2894         $PAGE->navbar->includesettingsbase = true;
 2895 
 2896         // If we can get the course admin node make sure it is closed by default
 2897         // as in this case the gradenode will be opened
 2898         if ($coursenode = $PAGE->settingsnav->get('courseadmin', navigation_node::TYPE_COURSE)){
 2899             $coursenode->make_inactive();
 2900             $coursenode->forceopen = false;
 2901         }
 2902     }
 2903 }
 2904 
 2905 /**
 2906  * Grade helper class
 2907  *
 2908  * This class provides several helpful functions that work irrespective of any
 2909  * current state.
 2910  *
 2911  * @copyright 2010 Sam Hemelryk
 2912  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 2913  */
 2914 abstract class grade_helper {
 2915     /**
 2916      * Cached manage settings info {@see get_info_settings}
 2917      * @var grade_plugin_info|false
 2918      */
 2919     protected static $managesetting = null;
 2920     /**
 2921      * Cached grade report plugins {@see get_plugins_reports}
 2922      * @var array|false
 2923      */
 2924     protected static $gradereports = null;
 2925     /**
 2926      * Cached grade report plugins preferences {@see get_info_scales}
 2927      * @var array|false
 2928      */
 2929     protected static $gradereportpreferences = null;
 2930     /**
 2931      * Cached scale info {@see get_info_scales}
 2932      * @var grade_plugin_info|false
 2933      */
 2934     protected static $scaleinfo = null;
 2935     /**
 2936      * Cached outcome info {@see get_info_outcomes}
 2937      * @var grade_plugin_info|false
 2938      */
 2939     protected static $outcomeinfo = null;
 2940     /**
 2941      * Cached leftter info {@see get_info_letters}
 2942      * @var grade_plugin_info|false
 2943      */
 2944     protected static $letterinfo = null;
 2945     /**
 2946      * Cached grade import plugins {@see get_plugins_import}
 2947      * @var array|false
 2948      */
 2949     protected static $importplugins = null;
 2950     /**
 2951      * Cached grade export plugins {@see get_plugins_export}
 2952      * @var array|false
 2953      */
 2954     protected static $exportplugins = null;
 2955     /**
 2956      * Cached grade plugin strings
 2957      * @var array
 2958      */
 2959     protected static $pluginstrings = null;
 2960     /**
 2961      * Cached grade aggregation strings
 2962      * @var array
 2963      */
 2964     protected static $aggregationstrings = null;
 2965 
 2966     /**
 2967      * Gets strings commonly used by the describe plugins
 2968      *
 2969      * report => get_string('view'),
 2970      * scale => get_string('scales'),
 2971      * outcome => get_string('outcomes', 'grades'),
 2972      * letter => get_string('letters', 'grades'),
 2973      * export => get_string('export', 'grades'),
 2974      * import => get_string('import'),
 2975      * settings => get_string('settings')
 2976      *
 2977      * @return array
 2978      */
 2979     public static function get_plugin_strings() {
 2980         if (self::$pluginstrings === null) {
 2981             self::$pluginstrings = array(
 2982                 'report' => get_string('view'),
 2983                 'scale' => get_string('scales'),
 2984                 'outcome' => get_string('outcomes', 'grades'),
 2985                 'letter' => get_string('letters', 'grades'),
 2986                 'export' => get_string('export', 'grades'),
 2987                 'import' => get_string('import'),
 2988                 'settings' => get_string('edittree', 'grades')
 2989             );
 2990         }
 2991         return self::$pluginstrings;
 2992     }
 2993 
 2994     /**
 2995      * Gets strings describing the available aggregation methods.
 2996      *
 2997      * @return array
 2998      */
 2999     public static function get_aggregation_strings() {
 3000         if (self::$aggregationstrings === null) {
 3001             self::$aggregationstrings = array(
 3002                 GRADE_AGGREGATE_MEAN             => get_string('aggregatemean', 'grades'),
 3003                 GRADE_AGGREGATE_WEIGHTED_MEAN    => get_string('aggregateweightedmean', 'grades'),
 3004                 GRADE_AGGREGATE_WEIGHTED_MEAN2   => get_string('aggregateweightedmean2', 'grades'),
 3005                 GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'),
 3006                 GRADE_AGGREGATE_MEDIAN           => get_string('aggregatemedian', 'grades'),
 3007                 GRADE_AGGREGATE_MIN              => get_string('aggregatemin', 'grades'),
 3008                 GRADE_AGGREGATE_MAX              => get_string('aggregatemax', 'grades'),
 3009                 GRADE_AGGREGATE_MODE             => get_string('aggregatemode', 'grades'),
 3010                 GRADE_AGGREGATE_SUM              => get_string('aggregatesum', 'grades')
 3011             );
 3012         }
 3013         return self::$aggregationstrings;
 3014     }
 3015 
 3016     /**
 3017      * Get grade_plugin_info object for managing settings if the user can
 3018      *
 3019      * @param int $courseid
 3020      * @return grade_plugin_info[]
 3021      */
 3022     public static function get_info_manage_settings($courseid) {
 3023         if (self::$managesetting !== null) {
 3024             return self::$managesetting;
 3025         }
 3026         $context = context_course::instance($courseid);
 3027         self::$managesetting = array();
 3028         if ($courseid != SITEID && has_capability('moodle/grade:manage', $context)) {
 3029             self::$managesetting['gradebooksetup'] = new grade_plugin_info('setup',
 3030                 new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid)),
 3031                 get_string('gradebooksetup', 'grades'));
 3032             self::$managesetting['coursesettings'] = new grade_plugin_info('coursesettings',
 3033                 new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)),
 3034                 get_string('coursegradesettings', 'grades'));
 3035         }
 3036         if (self::$gradereportpreferences === null) {
 3037             self::get_plugins_reports($courseid);
 3038         }
 3039         if (self::$gradereportpreferences) {
 3040             self::$managesetting = array_merge(self::$managesetting, self::$gradereportpreferences);
 3041         }
 3042         return self::$managesetting;
 3043     }
 3044     /**
 3045      * Returns an array of plugin reports as grade_plugin_info objects
 3046      *
 3047      * @param int $courseid
 3048      * @return array
 3049      */
 3050     public static function get_plugins_reports($courseid) {
 3051         global $SITE;
 3052 
 3053         if (self::$gradereports !== null) {
 3054             return self::$gradereports;
 3055         }
 3056         $context = context_course::instance($courseid);
 3057         $gradereports = array();
 3058         $gradepreferences = array();
 3059         foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
 3060             //some reports make no sense if we're not within a course
 3061             if ($courseid==$SITE->id && ($plugin=='grader' || $plugin=='user')) {
 3062                 continue;
 3063             }
 3064 
 3065             // Remove ones we can't see
 3066             if (!has_capability('gradereport/'.$plugin.':view', $context)) {
 3067                 continue;
 3068             }
 3069 
 3070             // Singleview doesn't doesn't accomodate for all cap combos yet, so this is hardcoded..
 3071             if ($plugin === 'singleview' && !has_all_capabilities(array('moodle/grade:viewall',
 3072                     'moodle/grade:edit'), $context)) {
 3073                 continue;
 3074             }
 3075 
 3076             $pluginstr = get_string('pluginname', 'gradereport_'.$plugin);
 3077             $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid));
 3078             $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
 3079 
 3080             // Add link to preferences tab if such a page exists
 3081             if (file_exists($plugindir.'/preferences.php')) {
 3082                 $url = new moodle_url('/grade/report/'.$plugin.'/preferences.php', array('id'=>$courseid));
 3083                 $gradepreferences[$plugin] = new grade_plugin_info($plugin, $url,
 3084                     get_string('preferences', 'grades') . ': ' . $pluginstr);
 3085             }
 3086         }
 3087         if (count($gradereports) == 0) {
 3088             $gradereports = false;
 3089             $gradepreferences = false;
 3090         } else if (count($gradepreferences) == 0) {
 3091             $gradepreferences = false;
 3092             asort($gradereports);
 3093         } else {
 3094             asort($gradereports);
 3095             asort($gradepreferences);
 3096         }
 3097         self::$gradereports = $gradereports;
 3098         self::$gradereportpreferences = $gradepreferences;
 3099         return self::$gradereports;
 3100     }
 3101 
 3102     /**
 3103      * Get information on scales
 3104      * @param int $courseid
 3105      * @return grade_plugin_info
 3106      */
 3107     public static function get_info_scales($courseid) {
 3108         if (self::$scaleinfo !== null) {
 3109             return self::$scaleinfo;
 3110         }
 3111         if (has_capability('moodle/course:managescales', context_course::instance($courseid))) {
 3112             $url = new moodle_url('/grade/edit/scale/index.php', array('id'=>$courseid));
 3113             self::$scaleinfo = new grade_plugin_info('scale', $url, get_string('view'));
 3114         } else {
 3115             self::$scaleinfo = false;
 3116         }
 3117         return self::$scaleinfo;
 3118     }
 3119     /**
 3120      * Get information on outcomes
 3121      * @param int $courseid
 3122      * @return grade_plugin_info
 3123      */
 3124     public static function get_info_outcomes($courseid) {
 3125         global $CFG, $SITE;
 3126 
 3127         if (self::$outcomeinfo !== null) {
 3128             return self::$outcomeinfo;
 3129         }
 3130         $context = context_course::instance($courseid);
 3131         $canmanage = has_capability('moodle/grade:manage', $context);
 3132         $canupdate = has_capability('moodle/course:update', $context);
 3133         if (!empty($CFG->enableoutcomes) && ($canmanage || $canupdate)) {
 3134             $outcomes = array();
 3135             if ($canupdate) {
 3136                 if ($courseid!=$SITE->id) {
 3137                     $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
 3138                     $outcomes['course'] = new grade_plugin_info('course', $url, get_string('outcomescourse', 'grades'));
 3139                 }
 3140                 $url = new moodle_url('/grade/edit/outcome/index.php', array('id'=>$courseid));
 3141                 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('editoutcomes', 'grades'));
 3142                 $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid'=>$courseid));
 3143                 $outcomes['import'] = new grade_plugin_info('import', $url, get_string('importoutcomes', 'grades'));
 3144             } else {
 3145                 if ($courseid!=$SITE->id) {
 3146                     $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
 3147                     $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('outcomescourse', 'grades'));
 3148                 }
 3149             }
 3150             self::$outcomeinfo = $outcomes;
 3151         } else {
 3152             self::$outcomeinfo = false;
 3153         }
 3154         return self::$outcomeinfo;
 3155     }
 3156     /**
 3157      * Get information on letters
 3158      * @param int $courseid
 3159      * @return array
 3160      */
 3161     public static function get_info_letters($courseid) {
 3162         global $SITE;
 3163         if (self::$letterinfo !== null) {
 3164             return self::$letterinfo;
 3165         }
 3166         $context = context_course::instance($courseid);
 3167         $canmanage = has_capability('moodle/grade:manage', $context);
 3168         $canmanageletters = has_capability('moodle/grade:manageletters', $context);
 3169         if ($canmanage || $canmanageletters) {
 3170             // Redirect to system context when report is accessed from admin settings MDL-31633
 3171             if ($context->instanceid == $SITE->id) {
 3172                 $param = array('edit' => 1);
 3173             } else {
 3174                 $param = array('edit' => 1,'id' => $context->id);
 3175             }
 3176             self::$letterinfo = array(
 3177                 'view' => new grade_plugin_info('view', new moodle_url('/grade/edit/letter/index.php', array('id'=>$context->id)), get_string('view')),
 3178                 'edit' => new grade_plugin_info('edit', new moodle_url('/grade/edit/letter/index.php', $param), get_string('edit'))
 3179             );
 3180         } else {
 3181             self::$letterinfo = false;
 3182         }
 3183         return self::$letterinfo;
 3184     }
 3185     /**
 3186      * Get information import plugins
 3187      * @param int $courseid
 3188      * @return array
 3189      */
 3190     public static function get_plugins_import($courseid) {
 3191         global $CFG;
 3192 
 3193         if (self::$importplugins !== null) {
 3194             return self::$importplugins;
 3195         }
 3196         $importplugins = array();
 3197         $context = context_course::instance($courseid);
 3198 
 3199         if (has_capability('moodle/grade:import', $context)) {
 3200             foreach (core_component::get_plugin_list('gradeimport') as $plugin => $plugindir) {
 3201                 if (!has_capability('gradeimport/'.$plugin.':view', $context)) {
 3202                     continue;
 3203                 }
 3204                 $pluginstr = get_string('pluginname', 'gradeimport_'.$plugin);
 3205                 $url = new moodle_url('/grade/import/'.$plugin.'/index.php', array('id'=>$courseid));
 3206                 $importplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
 3207             }
 3208 
 3209             // Show key manager if grade publishing is enabled and the user has xml publishing capability.
 3210             // XML is the only grade import plugin that has publishing feature.
 3211             if ($CFG->gradepublishing && has_capability('gradeimport/xml:publish', $context)) {
 3212                 $url = new moodle_url('/grade/import/keymanager.php', array('id'=>$courseid));
 3213                 $importplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
 3214             }
 3215         }
 3216 
 3217         if (count($importplugins) > 0) {
 3218             asort($importplugins);
 3219             self::$importplugins = $importplugins;
 3220         } else {
 3221             self::$importplugins = false;
 3222         }
 3223         return self::$importplugins;
 3224     }
 3225     /**
 3226      * Get information export plugins
 3227      * @param int $courseid
 3228      * @return array
 3229      */
 3230     public static function get_plugins_export($courseid) {
 3231         global $CFG;
 3232 
 3233         if (self::$exportplugins !== null) {
 3234             return self::$exportplugins;
 3235         }
 3236         $context = context_course::instance($courseid);
 3237         $exportplugins = array();
 3238         $canpublishgrades = 0;
 3239         if (has_capability('moodle/grade:export', $context)) {
 3240             foreach (core_component::get_plugin_list('gradeexport') as $plugin => $plugindir) {
 3241                 if (!has_capability('gradeexport/'.$plugin.':view', $context)) {
 3242                     continue;
 3243                 }
 3244                 // All the grade export plugins has grade publishing capabilities.
 3245                 if (has_capability('gradeexport/'.$plugin.':publish', $context)) {
 3246                     $canpublishgrades++;
 3247                 }
 3248 
 3249                 $pluginstr = get_string('pluginname', 'gradeexport_'.$plugin);
 3250                 $url = new moodle_url('/grade/export/'.$plugin.'/index.php', array('id'=>$courseid));
 3251                 $exportplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
 3252             }
 3253 
 3254             // Show key manager if grade publishing is enabled and the user has at least one grade publishing capability.
 3255             if ($CFG->gradepublishing && $canpublishgrades != 0) {
 3256                 $url = new moodle_url('/grade/export/keymanager.php', array('id'=>$courseid));
 3257                 $exportplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
 3258             }
 3259         }
 3260         if (count($exportplugins) > 0) {
 3261             asort($exportplugins);
 3262             self::$exportplugins = $exportplugins;
 3263         } else {
 3264             self::$exportplugins = false;
 3265         }
 3266         return self::$exportplugins;
 3267     }
 3268 
 3269     /**
 3270      * Returns the value of a field from a user record
 3271      *
 3272      * @param stdClass $user object
 3273      * @param stdClass $field object
 3274      * @return string value of the field
 3275      */
 3276     public static function get_user_field_value($user, $field) {
 3277         if (!empty($field->customid)) {
 3278             $fieldname = 'customfield_' . $field->customid;
 3279             if (!empty($user->{$fieldname}) || is_numeric($user->{$fieldname})) {
 3280                 $fieldvalue = $user->{$fieldname};
 3281             } else {
 3282                 $fieldvalue = $field->default;
 3283             }
 3284         } else {
 3285             $fieldvalue = $user->{$field->shortname};
 3286         }
 3287         return $fieldvalue;
 3288     }
 3289 
 3290     /**
 3291      * Returns an array of user profile fields to be included in export
 3292      *
 3293      * @param int $courseid
 3294      * @param bool $includecustomfields
 3295      * @return array An array of stdClass instances with customid, shortname, datatype, default and fullname fields
 3296      */
 3297     public static function get_user_profile_fields($courseid, $includecustomfields = false) {
 3298         global $CFG, $DB;
 3299 
 3300         // Gets the fields that have to be hidden
 3301         $hiddenfields = array_map('trim', explode(',', $CFG->hiddenuserfields));
 3302         $context = context_course::instance($courseid);
 3303         $canseehiddenfields = has_capability('moodle/course:viewhiddenuserfields', $context);
 3304         if ($canseehiddenfields) {
 3305             $hiddenfields = array();
 3306         }
 3307 
 3308         $fields = array();
 3309         require_once($CFG->dirroot.'/user/lib.php');                // Loads user_get_default_fields()
 3310         require_once($CFG->dirroot.'/user/profile/lib.php');        // Loads constants, such as PROFILE_VISIBLE_ALL
 3311         $userdefaultfields = user_get_default_fields();
 3312 
 3313         // Sets the list of profile fields
 3314         $userprofilefields = array_map('trim', explode(',', $CFG->grade_export_userprofilefields));
 3315         if (!empty($userprofilefields)) {
 3316             foreach ($userprofilefields as $field) {
 3317                 $field = trim($field);
 3318                 if (in_array($field, $hiddenfields) || !in_array($field, $userdefaultfields)) {
 3319                     continue;
 3320                 }
 3321                 $obj = new stdClass();
 3322                 $obj->customid  = 0;
 3323                 $obj->shortname = $field;
 3324                 $obj->fullname  = get_string($field);
 3325                 $fields[] = $obj;
 3326             }
 3327         }
 3328 
 3329         // Sets the list of custom profile fields
 3330         $customprofilefields = array_map('trim', explode(',', $CFG->grade_export_customprofilefields));
 3331         if ($includecustomfields && !empty($customprofilefields)) {
 3332             list($wherefields, $whereparams) = $DB->get_in_or_equal($customprofilefields);
 3333             $customfields = $DB->get_records_sql("SELECT f.*
 3334                                                     FROM {user_info_field} f
 3335                                                     JOIN {user_info_category} c ON f.categoryid=c.id
 3336                                                     WHERE f.shortname $wherefields
 3337                                                     ORDER BY c.sortorder ASC, f.sortorder ASC", $whereparams);
 3338 
 3339             foreach ($customfields as $field) {
 3340                 // Make sure we can display this custom field
 3341                 if (!in_array($field->shortname, $customprofilefields)) {
 3342                     continue;
 3343                 } else if (in_array($field->shortname, $hiddenfields)) {
 3344                     continue;
 3345                 } else if ($field->visible != PROFILE_VISIBLE_ALL && !$canseehiddenfields) {
 3346                     continue;
 3347                 }
 3348 
 3349                 $obj = new stdClass();
 3350                 $obj->customid  = $field->id;
 3351                 $obj->shortname = $field->shortname;
 3352                 $obj->fullname  = format_string($field->name);
 3353                 $obj->datatype  = $field->datatype;
 3354                 $obj->default   = $field->defaultdata;
 3355                 $fields[] = $obj;
 3356             }
 3357         }
 3358 
 3359         return $fields;
 3360     }
 3361 
 3362     /**
 3363      * This helper method gets a snapshot of all the weights for a course.
 3364      * It is used as a quick method to see if any wieghts have been automatically adjusted.
 3365      * @param int $courseid
 3366      * @return array of itemid -> aggregationcoef2
 3367      */
 3368     public static function fetch_all_natural_weights_for_course($courseid) {
 3369         global $DB;
 3370         $result = array();
 3371 
 3372         $records = $DB->get_records('grade_items', array('courseid'=>$courseid), 'id', 'id, aggregationcoef2');
 3373         foreach ($records as $record) {
 3374             $result[$record->id] = $record->aggregationcoef2;
 3375         }
 3376         return $result;
 3377     }
 3378 }
 3379