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