Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [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. 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 null $unused This parameter has been deprecated since 4.3 and should not be used anymore. 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, string|bool $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, $unused = null) { 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 if ($unused !== null) { 895 debugging('Deprecated argument passed to ' . __FUNCTION__, DEBUG_DEVELOPER); 896 } 897 898 // Put a warning on all gradebook pages if the course has modules currently scheduled for background deletion. 899 require_once($CFG->dirroot . '/course/lib.php'); 900 if (course_modules_pending_deletion($courseid, true)) { 901 \core\notification::add(get_string('gradesmoduledeletionpendingwarning', 'grades'), 902 \core\output\notification::NOTIFY_WARNING); 903 } 904 905 if ($active_type === 'preferences') { 906 // In Moodle 2.8 report preferences were moved under 'settings'. Allow backward compatibility for 3rd party grade reports. 907 $active_type = 'settings'; 908 } 909 910 $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin); 911 912 // Determine the string of the active plugin. 913 $stractive_type = $plugin_info['strings'][$active_type]; 914 $stractiveplugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading; 915 916 if ($active_type == 'report') { 917 $PAGE->set_pagelayout('report'); 918 } else { 919 $PAGE->set_pagelayout('admin'); 920 } 921 $coursecontext = context_course::instance($courseid); 922 // Title will be constituted by information starting from the unique identifying information for the page. 923 if ($heading) { 924 // If heading is supplied, use this for the page title. 925 $uniquetitle = $heading; 926 } else if (in_array($active_type, ['report', 'settings'])) { 927 // For grade reports or settings pages of grade plugins, use the plugin name for the unique title. 928 $uniquetitle = $stractiveplugin; 929 // But if editing mode is turned on, check if the report plugin has an editing mode title string and use it if present. 930 if ($PAGE->user_is_editing() && $active_type === 'report') { 931 $strcomponent = "gradereport_{$active_plugin}"; 932 if (get_string_manager()->string_exists('editingmode_title', $strcomponent)) { 933 $uniquetitle = get_string('editingmode_title', $strcomponent); 934 } 935 } 936 } else { 937 $uniquetitle = $stractive_type . ': ' . $stractiveplugin; 938 } 939 $titlecomponents = [ 940 $uniquetitle, 941 $coursecontext->get_context_name(false), 942 ]; 943 $PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $titlecomponents)); 944 $PAGE->set_heading($PAGE->course->fullname); 945 $PAGE->set_secondary_active_tab('grades'); 946 947 if ($buttons instanceof single_button) { 948 $buttons = $OUTPUT->render($buttons); 949 } 950 $PAGE->set_button($buttons); 951 if ($courseid != SITEID) { 952 grade_extend_settings($plugin_info, $courseid); 953 } 954 955 // Set the current report as active in the breadcrumbs. 956 if ($active_plugin !== null && $reportnav = $PAGE->settingsnav->find($active_plugin, navigation_node::TYPE_SETTING)) { 957 $reportnav->make_active(); 958 } 959 960 $returnval = $OUTPUT->header(); 961 962 if (!$return) { 963 echo $returnval; 964 } 965 966 if ($shownavigation) { 967 $renderer = $PAGE->get_renderer('core_grades'); 968 // If the navigation action bar is not explicitly defined, use the general (default) action bar. 969 if (!$actionbar) { 970 $actionbar = new general_action_bar($PAGE->context, $PAGE->url, $active_type, $active_plugin); 971 } 972 973 if ($return) { 974 $returnval .= $renderer->render_action_bar($actionbar); 975 } else { 976 echo $renderer->render_action_bar($actionbar); 977 } 978 } 979 980 $output = ''; 981 // Add a help dialogue box if provided. 982 if (isset($headerhelpidentifier) && !empty($heading)) { 983 $output = $OUTPUT->heading_with_help($heading, $headerhelpidentifier, $headerhelpcomponent); 984 } else if (isset($user)) { 985 $renderer = $PAGE->get_renderer('core_grades'); 986 // If the user is viewing their own grade report, no need to show the "Message" 987 // and "Add to contact" buttons in the user heading. 988 $showuserbuttons = $user->id != $USER->id; 989 $output = $renderer->user_heading($user, $courseid, $showuserbuttons); 990 } else if (!empty($heading)) { 991 $output = $OUTPUT->heading($heading); 992 } 993 994 if ($return) { 995 $returnval .= $output; 996 } else { 997 echo $output; 998 } 999 1000 $returnval .= print_natural_aggregation_upgrade_notice($courseid, $coursecontext, $PAGE->url, $return); 1001 1002 if ($return) { 1003 return $returnval; 1004 } 1005 } 1006 1007 /** 1008 * Utility class used for return tracking when using edit and other forms in grade plugins 1009 * 1010 * @package core_grades 1011 * @copyright 2009 Nicolas Connault 1012 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1013 */ 1014 class grade_plugin_return { 1015 /** 1016 * Type of grade plugin (e.g. 'edit', 'report') 1017 * 1018 * @var string 1019 */ 1020 public $type; 1021 /** 1022 * Name of grade plugin (e.g. 'grader', 'overview') 1023 * 1024 * @var string 1025 */ 1026 public $plugin; 1027 /** 1028 * Course id being viewed 1029 * 1030 * @var int 1031 */ 1032 public $courseid; 1033 /** 1034 * Id of user whose information is being viewed/edited 1035 * 1036 * @var int 1037 */ 1038 public $userid; 1039 /** 1040 * Id of group for which information is being viewed/edited 1041 * 1042 * @var int 1043 */ 1044 public $groupid; 1045 /** 1046 * Current page # within output 1047 * 1048 * @var int 1049 */ 1050 public $page; 1051 /** 1052 * Search string 1053 * 1054 * @var string 1055 */ 1056 public $search; 1057 1058 /** 1059 * Constructor 1060 * 1061 * @param array $params - associative array with return parameters, if not supplied parameter are taken from _GET or _POST 1062 */ 1063 public function __construct($params = []) { 1064 $this->type = optional_param('gpr_type', null, PARAM_SAFEDIR); 1065 $this->plugin = optional_param('gpr_plugin', null, PARAM_PLUGIN); 1066 $this->courseid = optional_param('gpr_courseid', null, PARAM_INT); 1067 $this->userid = optional_param('gpr_userid', null, PARAM_INT); 1068 $this->groupid = optional_param('gpr_groupid', null, PARAM_INT); 1069 $this->page = optional_param('gpr_page', null, PARAM_INT); 1070 $this->search = optional_param('gpr_search', '', PARAM_NOTAGS); 1071 1072 foreach ($params as $key => $value) { 1073 if (property_exists($this, $key)) { 1074 $this->$key = $value; 1075 } 1076 } 1077 // Allow course object rather than id to be used to specify course 1078 // - avoid unnecessary use of get_course. 1079 if (array_key_exists('course', $params)) { 1080 $course = $params['course']; 1081 $this->courseid = $course->id; 1082 } else { 1083 $course = null; 1084 } 1085 // If group has been explicitly set in constructor parameters, 1086 // we should respect that. 1087 if (!array_key_exists('groupid', $params)) { 1088 // Otherwise, 'group' in request parameters is a request for a change. 1089 // In that case, or if we have no group at all, we should get groupid from 1090 // groups_get_course_group, which will do some housekeeping as well as 1091 // give us the correct value. 1092 $changegroup = optional_param('group', -1, PARAM_INT); 1093 if ($changegroup !== -1 or (empty($this->groupid) and !empty($this->courseid))) { 1094 if ($course === null) { 1095 $course = get_course($this->courseid); 1096 } 1097 $this->groupid = groups_get_course_group($course, true); 1098 } 1099 } 1100 } 1101 1102 /** 1103 * Old syntax of class constructor. Deprecated in PHP7. 1104 * 1105 * @deprecated since Moodle 3.1 1106 */ 1107 public function grade_plugin_return($params = null) { 1108 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 1109 self::__construct($params); 1110 } 1111 1112 /** 1113 * Returns return parameters as options array suitable for buttons. 1114 * @return array options 1115 */ 1116 public function get_options() { 1117 if (empty($this->type)) { 1118 return array(); 1119 } 1120 1121 $params = array(); 1122 1123 if (!empty($this->plugin)) { 1124 $params['plugin'] = $this->plugin; 1125 } 1126 1127 if (!empty($this->courseid)) { 1128 $params['id'] = $this->courseid; 1129 } 1130 1131 if (!empty($this->userid)) { 1132 $params['userid'] = $this->userid; 1133 } 1134 1135 if (!empty($this->groupid)) { 1136 $params['group'] = $this->groupid; 1137 } 1138 1139 if (!empty($this->page)) { 1140 $params['page'] = $this->page; 1141 } 1142 1143 return $params; 1144 } 1145 1146 /** 1147 * Returns return url 1148 * 1149 * @param string $default default url when params not set 1150 * @param array $extras Extra URL parameters 1151 * 1152 * @return string url 1153 */ 1154 public function get_return_url($default, $extras=null) { 1155 global $CFG; 1156 1157 if (empty($this->type) or empty($this->plugin)) { 1158 return $default; 1159 } 1160 1161 $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php'; 1162 $glue = '?'; 1163 1164 if (!empty($this->courseid)) { 1165 $url .= $glue.'id='.$this->courseid; 1166 $glue = '&'; 1167 } 1168 1169 if (!empty($this->userid)) { 1170 $url .= $glue.'userid='.$this->userid; 1171 $glue = '&'; 1172 } 1173 1174 if (!empty($this->groupid)) { 1175 $url .= $glue.'group='.$this->groupid; 1176 $glue = '&'; 1177 } 1178 1179 if (!empty($this->page)) { 1180 $url .= $glue.'page='.$this->page; 1181 $glue = '&'; 1182 } 1183 1184 if (!empty($extras)) { 1185 foreach ($extras as $key=>$value) { 1186 $url .= $glue.$key.'='.$value; 1187 $glue = '&'; 1188 } 1189 } 1190 1191 return $url; 1192 } 1193 1194 /** 1195 * Returns string with hidden return tracking form elements. 1196 * @return string 1197 */ 1198 public function get_form_fields() { 1199 if (empty($this->type)) { 1200 return ''; 1201 } 1202 1203 $result = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />'; 1204 1205 if (!empty($this->plugin)) { 1206 $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />'; 1207 } 1208 1209 if (!empty($this->courseid)) { 1210 $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />'; 1211 } 1212 1213 if (!empty($this->userid)) { 1214 $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />'; 1215 } 1216 1217 if (!empty($this->groupid)) { 1218 $result .= '<input type="hidden" name="gpr_groupid" value="'.$this->groupid.'" />'; 1219 } 1220 1221 if (!empty($this->page)) { 1222 $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />'; 1223 } 1224 1225 if (!empty($this->search)) { 1226 $result .= html_writer::empty_tag('input', 1227 ['type' => 'hidden', 'name' => 'gpr_search', 'value' => $this->search]); 1228 } 1229 1230 return $result; 1231 } 1232 1233 /** 1234 * Add hidden elements into mform 1235 * 1236 * @param object &$mform moodle form object 1237 * 1238 * @return void 1239 */ 1240 public function add_mform_elements(&$mform) { 1241 if (empty($this->type)) { 1242 return; 1243 } 1244 1245 $mform->addElement('hidden', 'gpr_type', $this->type); 1246 $mform->setType('gpr_type', PARAM_SAFEDIR); 1247 1248 if (!empty($this->plugin)) { 1249 $mform->addElement('hidden', 'gpr_plugin', $this->plugin); 1250 $mform->setType('gpr_plugin', PARAM_PLUGIN); 1251 } 1252 1253 if (!empty($this->courseid)) { 1254 $mform->addElement('hidden', 'gpr_courseid', $this->courseid); 1255 $mform->setType('gpr_courseid', PARAM_INT); 1256 } 1257 1258 if (!empty($this->userid)) { 1259 $mform->addElement('hidden', 'gpr_userid', $this->userid); 1260 $mform->setType('gpr_userid', PARAM_INT); 1261 } 1262 1263 if (!empty($this->groupid)) { 1264 $mform->addElement('hidden', 'gpr_groupid', $this->groupid); 1265 $mform->setType('gpr_groupid', PARAM_INT); 1266 } 1267 1268 if (!empty($this->page)) { 1269 $mform->addElement('hidden', 'gpr_page', $this->page); 1270 $mform->setType('gpr_page', PARAM_INT); 1271 } 1272 } 1273 1274 /** 1275 * Add return tracking params into url 1276 * 1277 * @param moodle_url $url A URL 1278 * @return moodle_url with return tracking params 1279 */ 1280 public function add_url_params(moodle_url $url): moodle_url { 1281 if (empty($this->type)) { 1282 return $url; 1283 } 1284 1285 $url->param('gpr_type', $this->type); 1286 1287 if (!empty($this->plugin)) { 1288 $url->param('gpr_plugin', $this->plugin); 1289 } 1290 1291 if (!empty($this->courseid)) { 1292 $url->param('gpr_courseid' ,$this->courseid); 1293 } 1294 1295 if (!empty($this->userid)) { 1296 $url->param('gpr_userid', $this->userid); 1297 } 1298 1299 if (!empty($this->groupid)) { 1300 $url->param('gpr_groupid', $this->groupid); 1301 } 1302 1303 if (!empty($this->page)) { 1304 $url->param('gpr_page', $this->page); 1305 } 1306 1307 return $url; 1308 } 1309 } 1310 1311 /** 1312 * Function central to gradebook for building and printing the navigation (breadcrumb trail). 1313 * 1314 * @param string $path The path of the calling script (using __FILE__?) 1315 * @param string $pagename The language string to use as the last part of the navigation (non-link) 1316 * @param mixed $id Either a plain integer (assuming the key is 'id') or 1317 * an array of keys and values (e.g courseid => $courseid, itemid...) 1318 * 1319 * @return string 1320 */ 1321 function grade_build_nav($path, $pagename=null, $id=null) { 1322 global $CFG, $COURSE, $PAGE; 1323 1324 $strgrades = get_string('grades', 'grades'); 1325 1326 // Parse the path and build navlinks from its elements 1327 $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash 1328 $path = substr($path, $dirroot_length); 1329 $path = str_replace('\\', '/', $path); 1330 1331 $path_elements = explode('/', $path); 1332 1333 $path_elements_count = count($path_elements); 1334 1335 // First link is always 'grade' 1336 $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id))); 1337 1338 $link = null; 1339 $numberofelements = 3; 1340 1341 // Prepare URL params string 1342 $linkparams = array(); 1343 if (!is_null($id)) { 1344 if (is_array($id)) { 1345 foreach ($id as $idkey => $idvalue) { 1346 $linkparams[$idkey] = $idvalue; 1347 } 1348 } else { 1349 $linkparams['id'] = $id; 1350 } 1351 } 1352 1353 $navlink4 = null; 1354 1355 // Remove file extensions from filenames 1356 foreach ($path_elements as $key => $filename) { 1357 $path_elements[$key] = str_replace('.php', '', $filename); 1358 } 1359 1360 // Second level links 1361 switch ($path_elements[1]) { 1362 case 'edit': // No link 1363 if ($path_elements[3] != 'index.php') { 1364 $numberofelements = 4; 1365 } 1366 break; 1367 case 'import': // No link 1368 break; 1369 case 'export': // No link 1370 break; 1371 case 'report': 1372 // $id is required for this link. Do not print it if $id isn't given 1373 if (!is_null($id)) { 1374 $link = new moodle_url('/grade/report/index.php', $linkparams); 1375 } 1376 1377 if ($path_elements[2] == 'grader') { 1378 $numberofelements = 4; 1379 } 1380 break; 1381 1382 default: 1383 // If this element isn't among the ones already listed above, it isn't supported, throw an error. 1384 debugging("grade_build_nav() doesn't support ". $path_elements[1] . 1385 " as the second path element after 'grade'."); 1386 return false; 1387 } 1388 $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link); 1389 1390 // Third level links 1391 if (empty($pagename)) { 1392 $pagename = get_string($path_elements[2], 'grades'); 1393 } 1394 1395 switch ($numberofelements) { 1396 case 3: 1397 $PAGE->navbar->add($pagename, $link); 1398 break; 1399 case 4: 1400 if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') { 1401 $PAGE->navbar->add(get_string('pluginname', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams)); 1402 } 1403 $PAGE->navbar->add($pagename); 1404 break; 1405 } 1406 1407 return ''; 1408 } 1409 1410 /** 1411 * General structure representing grade items in course 1412 * 1413 * @package core_grades 1414 * @copyright 2009 Nicolas Connault 1415 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1416 */ 1417 class grade_structure { 1418 public $context; 1419 1420 public $courseid; 1421 1422 /** 1423 * Reference to modinfo for current course (for performance, to save 1424 * retrieving it from courseid every time). Not actually set except for 1425 * the grade_tree type. 1426 * @var course_modinfo 1427 */ 1428 public $modinfo; 1429 1430 /** 1431 * 1D array of grade items only 1432 */ 1433 public $items; 1434 1435 /** 1436 * Returns icon of element 1437 * 1438 * @param array &$element An array representing an element in the grade_tree 1439 * @param bool $spacerifnone return spacer if no icon found 1440 * 1441 * @return string icon or spacer 1442 */ 1443 public function get_element_icon(&$element, $spacerifnone=false) { 1444 global $CFG, $OUTPUT; 1445 require_once $CFG->libdir.'/filelib.php'; 1446 1447 $outputstr = ''; 1448 1449 // Object holding pix_icon information before instantiation. 1450 $icon = new stdClass(); 1451 $icon->attributes = array( 1452 'class' => 'icon itemicon' 1453 ); 1454 $icon->component = 'moodle'; 1455 1456 $none = true; 1457 switch ($element['type']) { 1458 case 'item': 1459 case 'courseitem': 1460 case 'categoryitem': 1461 $none = false; 1462 1463 $is_course = $element['object']->is_course_item(); 1464 $is_category = $element['object']->is_category_item(); 1465 $is_scale = $element['object']->gradetype == GRADE_TYPE_SCALE; 1466 $is_value = $element['object']->gradetype == GRADE_TYPE_VALUE; 1467 $is_outcome = !empty($element['object']->outcomeid); 1468 1469 if ($element['object']->is_calculated()) { 1470 $icon->pix = 'i/calc'; 1471 $icon->title = s(get_string('calculatedgrade', 'grades')); 1472 1473 } else if (($is_course or $is_category) and ($is_scale or $is_value)) { 1474 if ($category = $element['object']->get_item_category()) { 1475 $aggrstrings = grade_helper::get_aggregation_strings(); 1476 $stragg = $aggrstrings[$category->aggregation]; 1477 1478 $icon->pix = 'i/calc'; 1479 $icon->title = s($stragg); 1480 1481 switch ($category->aggregation) { 1482 case GRADE_AGGREGATE_MEAN: 1483 case GRADE_AGGREGATE_MEDIAN: 1484 case GRADE_AGGREGATE_WEIGHTED_MEAN: 1485 case GRADE_AGGREGATE_WEIGHTED_MEAN2: 1486 case GRADE_AGGREGATE_EXTRACREDIT_MEAN: 1487 $icon->pix = 'i/agg_mean'; 1488 break; 1489 case GRADE_AGGREGATE_SUM: 1490 $icon->pix = 'i/agg_sum'; 1491 break; 1492 } 1493 } 1494 1495 } else if ($element['object']->itemtype == 'mod') { 1496 // Prevent outcomes displaying the same icon as the activity they are attached to. 1497 if ($is_outcome) { 1498 $icon->pix = 'i/outcomes'; 1499 $icon->title = s(get_string('outcome', 'grades')); 1500 } else { 1501 $modinfo = get_fast_modinfo($element['object']->courseid); 1502 $module = $element['object']->itemmodule; 1503 $instanceid = $element['object']->iteminstance; 1504 if (isset($modinfo->instances[$module][$instanceid])) { 1505 $icon->url = $modinfo->instances[$module][$instanceid]->get_icon_url(); 1506 } else { 1507 $icon->pix = 'monologo'; 1508 $icon->component = $element['object']->itemmodule; 1509 } 1510 $icon->title = s(get_string('modulename', $element['object']->itemmodule)); 1511 } 1512 } else if ($element['object']->itemtype == 'manual') { 1513 if ($element['object']->is_outcome_item()) { 1514 $icon->pix = 'i/outcomes'; 1515 $icon->title = s(get_string('outcome', 'grades')); 1516 } else { 1517 $icon->pix = 'i/manual_item'; 1518 $icon->title = s(get_string('manualitem', 'grades')); 1519 } 1520 } 1521 break; 1522 1523 case 'category': 1524 $none = false; 1525 $icon->pix = 'i/folder'; 1526 $icon->title = s(get_string('category', 'grades')); 1527 break; 1528 } 1529 1530 if ($none) { 1531 if ($spacerifnone) { 1532 $outputstr = $OUTPUT->spacer() . ' '; 1533 } 1534 } else if (isset($icon->url)) { 1535 $outputstr = html_writer::img($icon->url, $icon->title, $icon->attributes); 1536 } else { 1537 $outputstr = $OUTPUT->pix_icon($icon->pix, $icon->title, $icon->component, $icon->attributes); 1538 } 1539 1540 return $outputstr; 1541 } 1542 1543 /** 1544 * Returns the string that describes the type of the element. 1545 * 1546 * @param array $element An array representing an element in the grade_tree 1547 * @return string The string that describes the type of the grade element 1548 */ 1549 public function get_element_type_string(array $element): string { 1550 // If the element is a grade category. 1551 if ($element['type'] == 'category') { 1552 return get_string('category', 'grades'); 1553 } 1554 // If the element is a grade item. 1555 if (in_array($element['type'], ['item', 'courseitem', 'categoryitem'])) { 1556 // If calculated grade item. 1557 if ($element['object']->is_calculated()) { 1558 return get_string('calculatedgrade', 'grades'); 1559 } 1560 // If aggregated type grade item. 1561 if ($element['object']->is_aggregate_item()) { 1562 return get_string('aggregation', 'core_grades'); 1563 } 1564 // If external grade item (module, plugin, etc.). 1565 if ($element['object']->is_external_item()) { 1566 // If outcome grade item. 1567 if ($element['object']->is_outcome_item()) { 1568 return get_string('outcome', 'grades'); 1569 } 1570 return get_string('modulename', $element['object']->itemmodule); 1571 } 1572 // If manual grade item. 1573 if ($element['object']->itemtype == 'manual') { 1574 // If outcome grade item. 1575 if ($element['object']->is_outcome_item()) { 1576 return get_string('outcome', 'grades'); 1577 } 1578 return get_string('manualitem', 'grades'); 1579 } 1580 } 1581 1582 return ''; 1583 } 1584 1585 /** 1586 * Returns name of element optionally with icon and link 1587 * 1588 * @param array &$element An array representing an element in the grade_tree 1589 * @param bool $withlink Whether or not this header has a link 1590 * @param bool $icon Whether or not to display an icon with this header 1591 * @param bool $spacerifnone return spacer if no icon found 1592 * @param bool $withdescription Show description if defined by this item. 1593 * @param bool $fulltotal If the item is a category total, returns $categoryname."total" 1594 * instead of "Category total" or "Course total" 1595 * @param moodle_url|null $sortlink Link to sort column. 1596 * 1597 * @return string header 1598 */ 1599 public function get_element_header(array &$element, bool $withlink = false, bool $icon = true, 1600 bool $spacerifnone = false, bool $withdescription = false, bool $fulltotal = false, 1601 ?moodle_url $sortlink = null) { 1602 $header = ''; 1603 1604 if ($icon) { 1605 $header .= $this->get_element_icon($element, $spacerifnone); 1606 } 1607 1608 $title = $element['object']->get_name($fulltotal); 1609 $titleunescaped = $element['object']->get_name($fulltotal, false); 1610 $header .= $title; 1611 1612 if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and 1613 $element['type'] != 'courseitem') { 1614 return $header; 1615 } 1616 1617 if ($sortlink) { 1618 $url = $sortlink; 1619 $header = html_writer::link($url, $header, [ 1620 'title' => $titleunescaped, 1621 'class' => 'gradeitemheader ' 1622 ]); 1623 } else { 1624 if ($withlink && $url = $this->get_activity_link($element)) { 1625 $a = new stdClass(); 1626 $a->name = get_string('modulename', $element['object']->itemmodule); 1627 $a->title = $titleunescaped; 1628 $title = get_string('linktoactivity', 'grades', $a); 1629 $header = html_writer::link($url, $header, [ 1630 'title' => $title, 1631 'class' => 'gradeitemheader ', 1632 ]); 1633 } else { 1634 $header = html_writer::span($header, 'gradeitemheader ', [ 1635 'title' => $titleunescaped, 1636 'tabindex' => '0' 1637 ]); 1638 } 1639 } 1640 1641 if ($withdescription) { 1642 $desc = $element['object']->get_description(); 1643 if (!empty($desc)) { 1644 $header .= '<div class="gradeitemdescription">' . s($desc) . '</div><div class="gradeitemdescriptionfiller"></div>'; 1645 } 1646 } 1647 1648 return $header; 1649 } 1650 1651 private function get_activity_link($element) { 1652 global $CFG; 1653 /** @var array static cache of the grade.php file existence flags */ 1654 static $hasgradephp = array(); 1655 1656 $itemtype = $element['object']->itemtype; 1657 $itemmodule = $element['object']->itemmodule; 1658 $iteminstance = $element['object']->iteminstance; 1659 $itemnumber = $element['object']->itemnumber; 1660 1661 // Links only for module items that have valid instance, module and are 1662 // called from grade_tree with valid modinfo 1663 if ($itemtype != 'mod' || !$iteminstance || !$itemmodule || !$this->modinfo) { 1664 return null; 1665 } 1666 1667 // Get $cm efficiently and with visibility information using modinfo 1668 $instances = $this->modinfo->get_instances(); 1669 if (empty($instances[$itemmodule][$iteminstance])) { 1670 return null; 1671 } 1672 $cm = $instances[$itemmodule][$iteminstance]; 1673 1674 // Do not add link if activity is not visible to the current user 1675 if (!$cm->uservisible) { 1676 return null; 1677 } 1678 1679 if (!array_key_exists($itemmodule, $hasgradephp)) { 1680 if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) { 1681 $hasgradephp[$itemmodule] = true; 1682 } else { 1683 $hasgradephp[$itemmodule] = false; 1684 } 1685 } 1686 1687 // If module has grade.php, link to that, otherwise view.php 1688 if ($hasgradephp[$itemmodule]) { 1689 $args = array('id' => $cm->id, 'itemnumber' => $itemnumber); 1690 if (isset($element['userid'])) { 1691 $args['userid'] = $element['userid']; 1692 } 1693 return new moodle_url('/mod/' . $itemmodule . '/grade.php', $args); 1694 } else { 1695 return new moodle_url('/mod/' . $itemmodule . '/view.php', array('id' => $cm->id)); 1696 } 1697 } 1698 1699 /** 1700 * Returns URL of a page that is supposed to contain detailed grade analysis 1701 * 1702 * At the moment, only activity modules are supported. The method generates link 1703 * to the module's file grade.php with the parameters id (cmid), itemid, itemnumber, 1704 * gradeid and userid. If the grade.php does not exist, null is returned. 1705 * 1706 * @return moodle_url|null URL or null if unable to construct it 1707 */ 1708 public function get_grade_analysis_url(grade_grade $grade) { 1709 global $CFG; 1710 /** @var array static cache of the grade.php file existence flags */ 1711 static $hasgradephp = array(); 1712 1713 if (empty($grade->grade_item) or !($grade->grade_item instanceof grade_item)) { 1714 throw new coding_exception('Passed grade without the associated grade item'); 1715 } 1716 $item = $grade->grade_item; 1717 1718 if (!$item->is_external_item()) { 1719 // at the moment, only activity modules are supported 1720 return null; 1721 } 1722 if ($item->itemtype !== 'mod') { 1723 throw new coding_exception('Unknown external itemtype: '.$item->itemtype); 1724 } 1725 if (empty($item->iteminstance) or empty($item->itemmodule) or empty($this->modinfo)) { 1726 return null; 1727 } 1728 1729 if (!array_key_exists($item->itemmodule, $hasgradephp)) { 1730 if (file_exists($CFG->dirroot . '/mod/' . $item->itemmodule . '/grade.php')) { 1731 $hasgradephp[$item->itemmodule] = true; 1732 } else { 1733 $hasgradephp[$item->itemmodule] = false; 1734 } 1735 } 1736 1737 if (!$hasgradephp[$item->itemmodule]) { 1738 return null; 1739 } 1740 1741 $instances = $this->modinfo->get_instances(); 1742 if (empty($instances[$item->itemmodule][$item->iteminstance])) { 1743 return null; 1744 } 1745 $cm = $instances[$item->itemmodule][$item->iteminstance]; 1746 if (!$cm->uservisible) { 1747 return null; 1748 } 1749 1750 $url = new moodle_url('/mod/'.$item->itemmodule.'/grade.php', array( 1751 'id' => $cm->id, 1752 'itemid' => $item->id, 1753 'itemnumber' => $item->itemnumber, 1754 'gradeid' => $grade->id, 1755 'userid' => $grade->userid, 1756 )); 1757 1758 return $url; 1759 } 1760 1761 /** 1762 * Returns an action icon leading to the grade analysis page 1763 * 1764 * @param grade_grade $grade 1765 * @return string 1766 * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. 1767 * @todo MDL-77307 This will be deleted in Moodle 4.6. 1768 */ 1769 public function get_grade_analysis_icon(grade_grade $grade) { 1770 global $OUTPUT; 1771 debugging('The function get_grade_analysis_icon() is deprecated, please do not use it anymore.', 1772 DEBUG_DEVELOPER); 1773 1774 $url = $this->get_grade_analysis_url($grade); 1775 if (is_null($url)) { 1776 return ''; 1777 } 1778 1779 $title = get_string('gradeanalysis', 'core_grades'); 1780 return $OUTPUT->action_icon($url, new pix_icon('t/preview', ''), null, 1781 ['title' => $title, 'aria-label' => $title]); 1782 } 1783 1784 /** 1785 * Returns a link leading to the grade analysis page 1786 * 1787 * @param grade_grade $grade 1788 * @return string|null 1789 */ 1790 public function get_grade_analysis_link(grade_grade $grade): ?string { 1791 $url = $this->get_grade_analysis_url($grade); 1792 if (is_null($url)) { 1793 return null; 1794 } 1795 1796 $gradeanalysisstring = get_string('gradeanalysis', 'grades'); 1797 return html_writer::link($url, $gradeanalysisstring, 1798 ['class' => 'dropdown-item', 'aria-label' => $gradeanalysisstring, 'role' => 'menuitem']); 1799 } 1800 1801 /** 1802 * Returns an action menu for the grade. 1803 * 1804 * @param grade_grade $grade A grade_grade object 1805 * @return string 1806 */ 1807 public function get_grade_action_menu(grade_grade $grade) : string { 1808 global $OUTPUT; 1809 1810 $menuitems = []; 1811 1812 $url = $this->get_grade_analysis_url($grade); 1813 if ($url) { 1814 $title = get_string('gradeanalysis', 'core_grades'); 1815 $menuitems[] = new action_menu_link_secondary($url, null, $title); 1816 } 1817 1818 if ($menuitems) { 1819 $menu = new action_menu($menuitems); 1820 $icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions')); 1821 $extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center'; 1822 $menu->set_menu_trigger($icon, $extraclasses); 1823 $menu->set_menu_left(); 1824 1825 return $OUTPUT->render($menu); 1826 } else { 1827 return ''; 1828 } 1829 } 1830 1831 /** 1832 * Returns the grade eid - the grade may not exist yet. 1833 * 1834 * @param grade_grade $grade_grade A grade_grade object 1835 * 1836 * @return string eid 1837 */ 1838 public function get_grade_eid($grade_grade) { 1839 if (empty($grade_grade->id)) { 1840 return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid; 1841 } else { 1842 return 'g'.$grade_grade->id; 1843 } 1844 } 1845 1846 /** 1847 * Returns the grade_item eid 1848 * @param grade_item $grade_item A grade_item object 1849 * @return string eid 1850 */ 1851 public function get_item_eid($grade_item) { 1852 return 'ig'.$grade_item->id; 1853 } 1854 1855 /** 1856 * Given a grade_tree element, returns an array of parameters 1857 * used to build an icon for that element. 1858 * 1859 * @param array $element An array representing an element in the grade_tree 1860 * 1861 * @return array 1862 */ 1863 public function get_params_for_iconstr($element) { 1864 $strparams = new stdClass(); 1865 $strparams->category = ''; 1866 $strparams->itemname = ''; 1867 $strparams->itemmodule = ''; 1868 1869 if (!method_exists($element['object'], 'get_name')) { 1870 return $strparams; 1871 } 1872 1873 $strparams->itemname = html_to_text($element['object']->get_name()); 1874 1875 // If element name is categorytotal, get the name of the parent category 1876 if ($strparams->itemname == get_string('categorytotal', 'grades')) { 1877 $parent = $element['object']->get_parent_category(); 1878 $strparams->category = $parent->get_name() . ' '; 1879 } else { 1880 $strparams->category = ''; 1881 } 1882 1883 $strparams->itemmodule = null; 1884 if (isset($element['object']->itemmodule)) { 1885 $strparams->itemmodule = $element['object']->itemmodule; 1886 } 1887 return $strparams; 1888 } 1889 1890 /** 1891 * Return a reset icon for the given element. 1892 * 1893 * @param array $element An array representing an element in the grade_tree 1894 * @param object $gpr A grade_plugin_return object 1895 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 1896 * @return string|action_menu_link 1897 * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. 1898 * @todo MDL-77307 This will be deleted in Moodle 4.6. 1899 */ 1900 public function get_reset_icon($element, $gpr, $returnactionmenulink = false) { 1901 global $CFG, $OUTPUT; 1902 debugging('The function get_reset_icon() is deprecated, please do not use it anymore.', 1903 DEBUG_DEVELOPER); 1904 1905 // Limit to category items set to use the natural weights aggregation method, and users 1906 // with the capability to manage grades. 1907 if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM || 1908 !has_capability('moodle/grade:manage', $this->context)) { 1909 return $returnactionmenulink ? null : ''; 1910 } 1911 1912 $str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element)); 1913 $url = new moodle_url('/grade/edit/tree/action.php', array( 1914 'id' => $this->courseid, 1915 'action' => 'resetweights', 1916 'eid' => $element['eid'], 1917 'sesskey' => sesskey(), 1918 )); 1919 1920 if ($returnactionmenulink) { 1921 return new action_menu_link_secondary($gpr->add_url_params($url), new pix_icon('t/reset', $str), 1922 get_string('resetweightsshort', 'grades')); 1923 } else { 1924 return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/reset', $str)); 1925 } 1926 } 1927 1928 /** 1929 * Returns a link to reset weights for the given element. 1930 * 1931 * @param array $element An array representing an element in the grade_tree 1932 * @param object $gpr A grade_plugin_return object 1933 * @return string|null 1934 */ 1935 public function get_reset_weights_link(array $element, object $gpr): ?string { 1936 1937 // Limit to category items set to use the natural weights aggregation method, and users 1938 // with the capability to manage grades. 1939 if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM || 1940 !has_capability('moodle/grade:manage', $this->context)) { 1941 return null; 1942 } 1943 1944 $title = get_string('resetweightsshort', 'grades'); 1945 $str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element)); 1946 $url = new moodle_url('/grade/edit/tree/action.php', [ 1947 'id' => $this->courseid, 1948 'action' => 'resetweights', 1949 'eid' => $element['eid'], 1950 'sesskey' => sesskey(), 1951 ]); 1952 $gpr->add_url_params($url); 1953 return html_writer::link($url, $title, 1954 ['class' => 'dropdown-item', 'aria-label' => $str, 'role' => 'menuitem']); 1955 } 1956 1957 /** 1958 * Returns a link to delete a given element. 1959 * 1960 * @param array $element An array representing an element in the grade_tree 1961 * @param object $gpr A grade_plugin_return object 1962 * @return string|null 1963 */ 1964 public function get_delete_link(array $element, object $gpr): ?string { 1965 if ($element['type'] == 'item' || ($element['type'] == 'category' && $element['depth'] > 1)) { 1966 if (grade_edit_tree::element_deletable($element)) { 1967 $deleteconfirmationurl = new moodle_url('index.php', [ 1968 'id' => $this->courseid, 1969 'action' => 'delete', 1970 'confirm' => 1, 1971 'eid' => $element['eid'], 1972 'sesskey' => sesskey(), 1973 ]); 1974 $gpr->add_url_params($deleteconfirmationurl); 1975 $title = get_string('delete'); 1976 return html_writer::link( 1977 '', 1978 $title, 1979 [ 1980 'class' => 'dropdown-item', 1981 'aria-label' => $title, 1982 'role' => 'menuitem', 1983 'data-modal' => 'confirmation', 1984 'data-modal-title-str' => json_encode(['confirm', 'core']), 1985 'data-modal-content-str' => json_encode([ 1986 'deletecheck', 1987 '', 1988 $element['object']->get_name() 1989 ]), 1990 'data-modal-yes-button-str' => json_encode(['delete', 'core']), 1991 'data-modal-destination' => $deleteconfirmationurl->out(false), 1992 ] 1993 ); 1994 } 1995 } 1996 return null; 1997 } 1998 1999 /** 2000 * Returns a link to duplicate a given element. 2001 * 2002 * @param array $element An array representing an element in the grade_tree 2003 * @param object $gpr A grade_plugin_return object 2004 * @return string|null 2005 */ 2006 public function get_duplicate_link(array $element, object $gpr): ?string { 2007 if ($element['type'] == 'item' || ($element['type'] == 'category' && $element['depth'] > 1)) { 2008 if (grade_edit_tree::element_duplicatable($element)) { 2009 $duplicateparams = []; 2010 $duplicateparams['id'] = $this->courseid; 2011 $duplicateparams['action'] = 'duplicate'; 2012 $duplicateparams['eid'] = $element['eid']; 2013 $duplicateparams['sesskey'] = sesskey(); 2014 $url = new moodle_url('index.php', $duplicateparams); 2015 $title = get_string('duplicate'); 2016 $gpr->add_url_params($url); 2017 return html_writer::link($url, $title, 2018 ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); 2019 } 2020 } 2021 return null; 2022 } 2023 2024 /** 2025 * Return edit icon for give element 2026 * 2027 * @param array $element An array representing an element in the grade_tree 2028 * @param object $gpr A grade_plugin_return object 2029 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 2030 * @return string|action_menu_link 2031 * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. 2032 * @todo MDL-77307 This will be deleted in Moodle 4.6. 2033 */ 2034 public function get_edit_icon($element, $gpr, $returnactionmenulink = false) { 2035 global $CFG, $OUTPUT; 2036 2037 debugging('The function get_edit_icon() is deprecated, please do not use it anymore.', 2038 DEBUG_DEVELOPER); 2039 2040 if (!has_capability('moodle/grade:manage', $this->context)) { 2041 if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) { 2042 // oki - let them override grade 2043 } else { 2044 return $returnactionmenulink ? null : ''; 2045 } 2046 } 2047 2048 static $strfeedback = null; 2049 static $streditgrade = null; 2050 if (is_null($streditgrade)) { 2051 $streditgrade = get_string('editgrade', 'grades'); 2052 $strfeedback = get_string('feedback'); 2053 } 2054 2055 $strparams = $this->get_params_for_iconstr($element); 2056 2057 $object = $element['object']; 2058 2059 switch ($element['type']) { 2060 case 'item': 2061 case 'categoryitem': 2062 case 'courseitem': 2063 $stredit = get_string('editverbose', 'grades', $strparams); 2064 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) { 2065 $url = new moodle_url('/grade/edit/tree/item.php', 2066 array('courseid' => $this->courseid, 'id' => $object->id)); 2067 } else { 2068 $url = new moodle_url('/grade/edit/tree/outcomeitem.php', 2069 array('courseid' => $this->courseid, 'id' => $object->id)); 2070 } 2071 break; 2072 2073 case 'category': 2074 $stredit = get_string('editverbose', 'grades', $strparams); 2075 $url = new moodle_url('/grade/edit/tree/category.php', 2076 array('courseid' => $this->courseid, 'id' => $object->id)); 2077 break; 2078 2079 case 'grade': 2080 $stredit = $streditgrade; 2081 if (empty($object->id)) { 2082 $url = new moodle_url('/grade/edit/tree/grade.php', 2083 array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid)); 2084 } else { 2085 $url = new moodle_url('/grade/edit/tree/grade.php', 2086 array('courseid' => $this->courseid, 'id' => $object->id)); 2087 } 2088 if (!empty($object->feedback)) { 2089 $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat))); 2090 } 2091 break; 2092 2093 default: 2094 $url = null; 2095 } 2096 2097 if ($url) { 2098 if ($returnactionmenulink) { 2099 return new action_menu_link_secondary($gpr->add_url_params($url), 2100 new pix_icon('t/edit', $stredit), 2101 get_string('editsettings')); 2102 } else { 2103 return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit)); 2104 } 2105 2106 } else { 2107 return $returnactionmenulink ? null : ''; 2108 } 2109 } 2110 2111 /** 2112 * Returns a link leading to the edit grade/grade item/category page 2113 * 2114 * @param array $element An array representing an element in the grade_tree 2115 * @param object $gpr A grade_plugin_return object 2116 * @return string|null 2117 */ 2118 public function get_edit_link(array $element, object $gpr): ?string { 2119 global $CFG; 2120 2121 $url = null; 2122 $title = ''; 2123 if ((!has_capability('moodle/grade:manage', $this->context) && 2124 (!($element['type'] == 'grade') || !has_capability('moodle/grade:edit', $this->context)))) { 2125 return null; 2126 } 2127 2128 $object = $element['object']; 2129 2130 if ($element['type'] == 'grade') { 2131 if (empty($object->id)) { 2132 $url = new moodle_url('/grade/edit/tree/grade.php', 2133 ['courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid]); 2134 } else { 2135 $url = new moodle_url('/grade/edit/tree/grade.php', 2136 ['courseid' => $this->courseid, 'id' => $object->id]); 2137 } 2138 $url = $gpr->add_url_params($url); 2139 $title = get_string('editgrade', 'grades'); 2140 } else if (($element['type'] == 'item') || ($element['type'] == 'categoryitem') || 2141 ($element['type'] == 'courseitem')) { 2142 $url = new moodle_url('#'); 2143 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) { 2144 return html_writer::link($url, get_string('itemsedit', 'grades'), [ 2145 'class' => 'dropdown-item', 2146 'aria-label' => get_string('itemsedit', 'grades'), 2147 'role' => 'menuitem', 2148 'data-gprplugin' => $gpr->plugin, 2149 'data-courseid' => $this->courseid, 2150 'data-itemid' => $object->id, 'data-trigger' => 'add-item-form' 2151 ]); 2152 } else if (count(grade_outcome::fetch_all_available($this->courseid)) > 0) { 2153 return html_writer::link($url, get_string('itemsedit', 'grades'), [ 2154 'class' => 'dropdown-item', 2155 get_string('itemsedit', 'grades'), 2156 'role' => 'menuitem', 2157 'data-gprplugin' => $gpr->plugin, 2158 'data-courseid' => $this->courseid, 2159 'data-itemid' => $object->id, 'data-trigger' => 'add-outcome-form' 2160 ]); 2161 } 2162 } else if ($element['type'] == 'category') { 2163 $url = new moodle_url('#'); 2164 $title = get_string('categoryedit', 'grades'); 2165 return html_writer::link($url, $title, [ 2166 'class' => 'dropdown-item', 2167 'aria-label' => $title, 2168 'role' => 'menuitem', 2169 'data-gprplugin' => $gpr->plugin, 2170 'data-courseid' => $this->courseid, 2171 'data-category' => $object->id, 2172 'data-trigger' => 'add-category-form' 2173 ]); 2174 } 2175 return html_writer::link($url, $title, 2176 ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); 2177 } 2178 2179 /** 2180 * Returns link to the advanced grading page 2181 * 2182 * @param array $element An array representing an element in the grade_tree 2183 * @param object $gpr A grade_plugin_return object 2184 * @return string|null 2185 */ 2186 public function get_advanced_grading_link(array $element, object $gpr): ?string { 2187 global $CFG; 2188 2189 /** @var array static cache of the grade.php file existence flags */ 2190 static $hasgradephp = []; 2191 2192 $itemtype = $element['object']->itemtype; 2193 $itemmodule = $element['object']->itemmodule; 2194 $iteminstance = $element['object']->iteminstance; 2195 $itemnumber = $element['object']->itemnumber; 2196 2197 // Links only for module items that have valid instance, module and are 2198 // called from grade_tree with valid modinfo. 2199 if ($itemtype == 'mod' && $iteminstance && $itemmodule && $this->modinfo) { 2200 2201 // Get $cm efficiently and with visibility information using modinfo. 2202 $instances = $this->modinfo->get_instances(); 2203 if (!empty($instances[$itemmodule][$iteminstance])) { 2204 $cm = $instances[$itemmodule][$iteminstance]; 2205 2206 // Do not add link if activity is not visible to the current user. 2207 if ($cm->uservisible) { 2208 if (!array_key_exists($itemmodule, $hasgradephp)) { 2209 if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) { 2210 $hasgradephp[$itemmodule] = true; 2211 } else { 2212 $hasgradephp[$itemmodule] = false; 2213 } 2214 } 2215 2216 // If module has grade.php, add link to that. 2217 if ($hasgradephp[$itemmodule]) { 2218 $args = array('id' => $cm->id, 'itemnumber' => $itemnumber); 2219 if (isset($element['userid'])) { 2220 $args['userid'] = $element['userid']; 2221 } 2222 2223 $url = new moodle_url('/mod/' . $itemmodule . '/grade.php', $args); 2224 $title = get_string('advancedgrading', 'gradereport_grader', $itemmodule); 2225 $gpr->add_url_params($url); 2226 return html_writer::link($url, $title, 2227 ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); 2228 } 2229 } 2230 } 2231 } 2232 2233 return null; 2234 } 2235 2236 /** 2237 * Return hiding icon for give element 2238 * 2239 * @param array $element An array representing an element in the grade_tree 2240 * @param object $gpr A grade_plugin_return object 2241 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 2242 * @return string|action_menu_link 2243 * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. 2244 * @todo MDL-77307 This will be deleted in Moodle 4.6. 2245 */ 2246 public function get_hiding_icon($element, $gpr, $returnactionmenulink = false) { 2247 global $CFG, $OUTPUT; 2248 debugging('The function get_hiding_icon() is deprecated, please do not use it anymore.', 2249 DEBUG_DEVELOPER); 2250 2251 if (!$element['object']->can_control_visibility()) { 2252 return $returnactionmenulink ? null : ''; 2253 } 2254 2255 if (!has_capability('moodle/grade:manage', $this->context) and 2256 !has_capability('moodle/grade:hide', $this->context)) { 2257 return $returnactionmenulink ? null : ''; 2258 } 2259 2260 $strparams = $this->get_params_for_iconstr($element); 2261 $strshow = get_string('showverbose', 'grades', $strparams); 2262 $strhide = get_string('hideverbose', 'grades', $strparams); 2263 2264 $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid'])); 2265 $url = $gpr->add_url_params($url); 2266 2267 if ($element['object']->is_hidden()) { 2268 $type = 'show'; 2269 $tooltip = $strshow; 2270 2271 // Change the icon and add a tooltip showing the date 2272 if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) { 2273 $type = 'hiddenuntil'; 2274 $tooltip = get_string('hiddenuntildate', 'grades', 2275 userdate($element['object']->get_hidden())); 2276 } 2277 2278 $url->param('action', 'show'); 2279 2280 if ($returnactionmenulink) { 2281 $hideicon = new action_menu_link_secondary($url, new pix_icon('t/'.$type, $tooltip), get_string('show')); 2282 } else { 2283 $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon'))); 2284 } 2285 2286 } else { 2287 $url->param('action', 'hide'); 2288 if ($returnactionmenulink) { 2289 $hideicon = new action_menu_link_secondary($url, new pix_icon('t/hide', $strhide), get_string('hide')); 2290 } else { 2291 $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide)); 2292 } 2293 } 2294 2295 return $hideicon; 2296 } 2297 2298 /** 2299 * Returns a link with url to hide/unhide grade/grade item/grade category 2300 * 2301 * @param array $element An array representing an element in the grade_tree 2302 * @param object $gpr A grade_plugin_return object 2303 * @return string|null 2304 */ 2305 public function get_hiding_link(array $element, object $gpr): ?string { 2306 if (!$element['object']->can_control_visibility() || !has_capability('moodle/grade:manage', $this->context) || 2307 !has_capability('moodle/grade:hide', $this->context)) { 2308 return null; 2309 } 2310 2311 $url = new moodle_url('/grade/edit/tree/action.php', 2312 ['id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']]); 2313 $url = $gpr->add_url_params($url); 2314 2315 if ($element['object']->is_hidden()) { 2316 $url->param('action', 'show'); 2317 $title = get_string('show'); 2318 } else { 2319 $url->param('action', 'hide'); 2320 $title = get_string('hide'); 2321 } 2322 2323 $url = html_writer::link($url, $title, 2324 ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); 2325 2326 if ($element['type'] == 'grade') { 2327 $item = $element['object']->grade_item; 2328 if ($item->hidden) { 2329 $strparamobj = new stdClass(); 2330 $strparamobj->itemname = $item->get_name(true, true); 2331 $strnonunhideable = get_string('nonunhideableverbose', 'grades', $strparamobj); 2332 $url = html_writer::span($title, 'text-muted dropdown-item', 2333 ['title' => $strnonunhideable, 'aria-label' => $title, 'role' => 'menuitem']); 2334 } 2335 } 2336 2337 return $url; 2338 } 2339 2340 /** 2341 * Return locking icon for given element 2342 * 2343 * @param array $element An array representing an element in the grade_tree 2344 * @param object $gpr A grade_plugin_return object 2345 * 2346 * @return string 2347 * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. 2348 * @todo MDL-77307 This will be deleted in Moodle 4.6. 2349 */ 2350 public function get_locking_icon($element, $gpr) { 2351 global $CFG, $OUTPUT; 2352 debugging('The function get_locking_icon() is deprecated, please do not use it anymore.', 2353 DEBUG_DEVELOPER); 2354 2355 $strparams = $this->get_params_for_iconstr($element); 2356 $strunlock = get_string('unlockverbose', 'grades', $strparams); 2357 $strlock = get_string('lockverbose', 'grades', $strparams); 2358 2359 $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid'])); 2360 $url = $gpr->add_url_params($url); 2361 2362 // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon 2363 if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) { 2364 $strparamobj = new stdClass(); 2365 $strparamobj->itemname = $element['object']->grade_item->itemname; 2366 $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj); 2367 2368 $action = html_writer::tag('span', $OUTPUT->pix_icon('t/locked', $strnonunlockable), 2369 array('class' => 'action-icon')); 2370 2371 } else if ($element['object']->is_locked()) { 2372 $type = 'unlock'; 2373 $tooltip = $strunlock; 2374 2375 // Change the icon and add a tooltip showing the date 2376 if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) { 2377 $type = 'locktime'; 2378 $tooltip = get_string('locktimedate', 'grades', 2379 userdate($element['object']->get_locktime())); 2380 } 2381 2382 if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) { 2383 $action = ''; 2384 } else { 2385 $url->param('action', 'unlock'); 2386 $action = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strunlock, 'class'=>'smallicon'))); 2387 } 2388 2389 } else { 2390 if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) { 2391 $action = ''; 2392 } else { 2393 $url->param('action', 'lock'); 2394 $action = $OUTPUT->action_icon($url, new pix_icon('t/lock', $strlock)); 2395 } 2396 } 2397 2398 return $action; 2399 } 2400 2401 /** 2402 * Returns link to lock/unlock grade/grade item/grade category 2403 * 2404 * @param array $element An array representing an element in the grade_tree 2405 * @param object $gpr A grade_plugin_return object 2406 * 2407 * @return string|null 2408 */ 2409 public function get_locking_link(array $element, object $gpr): ?string { 2410 2411 if (has_capability('moodle/grade:manage', $this->context) && isset($element['object'])) { 2412 $title = ''; 2413 $url = new moodle_url('/grade/edit/tree/action.php', 2414 ['id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']]); 2415 $url = $gpr->add_url_params($url); 2416 2417 if ($element['type'] == 'category') { 2418 // Grade categories themselves cannot be locked. We lock/unlock their grade items. 2419 $children = $element['object']->get_children(true); 2420 $alllocked = true; 2421 foreach ($children as $child) { 2422 if (!$child['object']->is_locked()) { 2423 $alllocked = false; 2424 break; 2425 } 2426 } 2427 if ($alllocked && has_capability('moodle/grade:unlock', $this->context)) { 2428 $title = get_string('unlock', 'grades'); 2429 $url->param('action', 'unlock'); 2430 } else if (!$alllocked && has_capability('moodle/grade:lock', $this->context)) { 2431 $title = get_string('lock', 'grades'); 2432 $url->param('action', 'lock'); 2433 } else { 2434 return null; 2435 } 2436 } else if (($element['type'] == 'grade') && ($element['object']->grade_item->is_locked())) { 2437 // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon. 2438 $strparamobj = new stdClass(); 2439 $strparamobj->itemname = $element['object']->grade_item->get_name(true, true); 2440 $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj); 2441 $title = get_string('unlock', 'grades'); 2442 return html_writer::span($title, 'text-muted dropdown-item', ['title' => $strnonunlockable, 2443 'aria-label' => $title, 'role' => 'menuitem']); 2444 } else if ($element['object']->is_locked()) { 2445 if (has_capability('moodle/grade:unlock', $this->context)) { 2446 $title = get_string('unlock', 'grades'); 2447 $url->param('action', 'unlock'); 2448 } else { 2449 return null; 2450 } 2451 } else { 2452 if (has_capability('moodle/grade:lock', $this->context)) { 2453 $title = get_string('lock', 'grades'); 2454 $url->param('action', 'lock'); 2455 } else { 2456 return null; 2457 } 2458 } 2459 2460 return html_writer::link($url, $title, 2461 ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); 2462 } else { 2463 return null; 2464 } 2465 } 2466 2467 /** 2468 * Return calculation icon for given element 2469 * 2470 * @param array $element An array representing an element in the grade_tree 2471 * @param object $gpr A grade_plugin_return object 2472 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 2473 * @return string|action_menu_link 2474 * @deprecated since Moodle 4.2 - The row is not shown anymore - we have actions menu. 2475 * @todo MDL-77307 This will be deleted in Moodle 4.6. 2476 */ 2477 public function get_calculation_icon($element, $gpr, $returnactionmenulink = false) { 2478 global $CFG, $OUTPUT; 2479 debugging('The function get_calculation_icon() is deprecated, please do not use it anymore.', 2480 DEBUG_DEVELOPER); 2481 2482 if (!has_capability('moodle/grade:manage', $this->context)) { 2483 return $returnactionmenulink ? null : ''; 2484 } 2485 2486 $type = $element['type']; 2487 $object = $element['object']; 2488 2489 if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') { 2490 $strparams = $this->get_params_for_iconstr($element); 2491 $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams); 2492 2493 $is_scale = $object->gradetype == GRADE_TYPE_SCALE; 2494 $is_value = $object->gradetype == GRADE_TYPE_VALUE; 2495 2496 // show calculation icon only when calculation possible 2497 if (!$object->is_external_item() and ($is_scale or $is_value)) { 2498 if ($object->is_calculated()) { 2499 $icon = 't/calc'; 2500 } else { 2501 $icon = 't/calc_off'; 2502 } 2503 2504 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id)); 2505 $url = $gpr->add_url_params($url); 2506 if ($returnactionmenulink) { 2507 return new action_menu_link_secondary($url, 2508 new pix_icon($icon, $streditcalculation), 2509 get_string('editcalculation', 'grades')); 2510 } else { 2511 return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation)); 2512 } 2513 } 2514 } 2515 2516 return $returnactionmenulink ? null : ''; 2517 } 2518 2519 /** 2520 * Returns link to edit calculation for a grade item. 2521 * 2522 * @param array $element An array representing an element in the grade_tree 2523 * @param object $gpr A grade_plugin_return object 2524 * 2525 * @return string|null 2526 */ 2527 public function get_edit_calculation_link(array $element, object $gpr): ?string { 2528 2529 if (has_capability('moodle/grade:manage', $this->context) && isset($element['object'])) { 2530 $object = $element['object']; 2531 $isscale = $object->gradetype == GRADE_TYPE_SCALE; 2532 $isvalue = $object->gradetype == GRADE_TYPE_VALUE; 2533 2534 // Show calculation icon only when calculation possible. 2535 if (!$object->is_external_item() && ($isscale || $isvalue)) { 2536 $editcalculationstring = get_string('editcalculation', 'grades'); 2537 $url = new moodle_url('/grade/edit/tree/calculation.php', 2538 ['courseid' => $this->courseid, 'id' => $object->id]); 2539 $url = $gpr->add_url_params($url); 2540 return html_writer::link($url, $editcalculationstring, 2541 ['class' => 'dropdown-item', 'aria-label' => $editcalculationstring, 'role' => 'menuitem']); 2542 } 2543 } 2544 return null; 2545 } 2546 2547 /** 2548 * Sets status icons for the grade. 2549 * 2550 * @param array $element array with grade item info 2551 * @return string|null status icons container HTML 2552 */ 2553 public function set_grade_status_icons(array $element): ?string { 2554 global $OUTPUT; 2555 2556 $context = [ 2557 'hidden' => $element['object']->is_hidden(), 2558 ]; 2559 2560 if ($element['object'] instanceof grade_grade) { 2561 $grade = $element['object']; 2562 $context['overridden'] = $grade->is_overridden(); 2563 $context['excluded'] = $grade->is_excluded(); 2564 $context['feedback'] = !empty($grade->feedback) && $grade->load_grade_item()->gradetype != GRADE_TYPE_TEXT; 2565 } 2566 2567 $context['classes'] = 'grade_icons data-collapse_gradeicons'; 2568 2569 if ($element['object'] instanceof grade_category) { 2570 $context['classes'] = 'category_grade_icons'; 2571 2572 $children = $element['object']->get_children(true); 2573 $alllocked = true; 2574 foreach ($children as $child) { 2575 if (!$child['object']->is_locked()) { 2576 $alllocked = false; 2577 break; 2578 } 2579 } 2580 if ($alllocked) { 2581 $context['locked'] = true; 2582 } 2583 } else { 2584 $context['locked'] = $element['object']->is_locked(); 2585 } 2586 2587 // Don't even attempt rendering if there is no status to show. 2588 if (in_array(true, $context)) { 2589 return $OUTPUT->render_from_template('core_grades/status_icons', $context); 2590 } else { 2591 return null; 2592 } 2593 } 2594 2595 /** 2596 * Returns an action menu for the grade. 2597 * 2598 * @param array $element Array with cell info. 2599 * @param string $mode Mode - gradeitem or user 2600 * @param grade_plugin_return $gpr 2601 * @param moodle_url|null $baseurl 2602 * @return string 2603 */ 2604 public function get_cell_action_menu(array $element, string $mode, grade_plugin_return $gpr, 2605 ?moodle_url $baseurl = null): string { 2606 global $OUTPUT, $USER; 2607 2608 $context = new stdClass(); 2609 2610 if ($mode == 'gradeitem' || $mode == 'setup') { 2611 $editable = true; 2612 2613 if ($element['type'] == 'grade') { 2614 $context->datatype = 'grade'; 2615 2616 $item = $element['object']->grade_item; 2617 if ($item->is_course_item() || $item->is_category_item()) { 2618 $editable = (bool)get_config('moodle', 'grade_overridecat');; 2619 } 2620 2621 if (!empty($USER->editing)) { 2622 if ($editable) { 2623 $context->editurl = $this->get_edit_link($element, $gpr); 2624 } 2625 $context->hideurl = $this->get_hiding_link($element, $gpr); 2626 $context->lockurl = $this->get_locking_link($element, $gpr); 2627 } 2628 2629 $context->gradeanalysisurl = $this->get_grade_analysis_link($element['object']); 2630 } else if (($element['type'] == 'item') || ($element['type'] == 'categoryitem') || 2631 ($element['type'] == 'courseitem') || ($element['type'] == 'userfield')) { 2632 2633 $context->datatype = 'item'; 2634 2635 if ($element['type'] == 'item') { 2636 if ($mode == 'setup') { 2637 $context->deleteurl = $this->get_delete_link($element, $gpr); 2638 $context->duplicateurl = $this->get_duplicate_link($element, $gpr); 2639 } else { 2640 $context = 2641 grade_report::get_additional_context($this->context, $this->courseid, 2642 $element, $gpr, $mode, $context, true); 2643 $context->advancedgradingurl = $this->get_advanced_grading_link($element, $gpr); 2644 } 2645 $context->divider1 = true; 2646 } 2647 2648 if (($element['type'] == 'item') || 2649 (($element['type'] == 'userfield') && ($element['name'] !== 'fullname'))) { 2650 $context->divider2 = true; 2651 } 2652 2653 if (!empty($USER->editing) || $mode == 'setup') { 2654 if (($element['type'] == 'userfield') && ($element['name'] !== 'fullname')) { 2655 $context->divider2 = true; 2656 } else if (($mode !== 'setup') && ($element['type'] !== 'userfield')) { 2657 $context->divider1 = true; 2658 $context->divider2 = true; 2659 } 2660 2661 if ($element['type'] == 'item') { 2662 $context->editurl = $this->get_edit_link($element, $gpr); 2663 } 2664 2665 $context->editcalculationurl = 2666 $this->get_edit_calculation_link($element, $gpr); 2667 2668 if (isset($element['object'])) { 2669 $object = $element['object']; 2670 if ($object->itemmodule !== 'quiz') { 2671 $context->hideurl = $this->get_hiding_link($element, $gpr); 2672 } 2673 } 2674 $context->lockurl = $this->get_locking_link($element, $gpr); 2675 } 2676 2677 // Sorting item. 2678 if ($baseurl) { 2679 $sortlink = clone($baseurl); 2680 if (isset($element['object']->id)) { 2681 $sortlink->param('sortitemid', $element['object']->id); 2682 } else if ($element['type'] == 'userfield') { 2683 $context->datatype = $element['name']; 2684 $sortlink->param('sortitemid', $element['name']); 2685 } 2686 2687 if (($element['type'] == 'userfield') && ($element['name'] == 'fullname')) { 2688 $sortlink->param('sortitemid', 'firstname'); 2689 $context->ascendingfirstnameurl = $this->get_sorting_link($sortlink, $gpr); 2690 $context->descendingfirstnameurl = $this->get_sorting_link($sortlink, $gpr, 'desc'); 2691 2692 $sortlink->param('sortitemid', 'lastname'); 2693 $context->ascendinglastnameurl = $this->get_sorting_link($sortlink, $gpr); 2694 $context->descendinglastnameurl = $this->get_sorting_link($sortlink, $gpr, 'desc'); 2695 } else { 2696 $context->ascendingurl = $this->get_sorting_link($sortlink, $gpr); 2697 $context->descendingurl = $this->get_sorting_link($sortlink, $gpr, 'desc'); 2698 } 2699 } 2700 if ($mode !== 'setup') { 2701 $context = grade_report::get_additional_context($this->context, $this->courseid, 2702 $element, $gpr, $mode, $context); 2703 } 2704 } else if ($element['type'] == 'category') { 2705 $context->datatype = 'category'; 2706 if ($mode !== 'setup') { 2707 $mode = 'category'; 2708 $context = grade_report::get_additional_context($this->context, $this->courseid, 2709 $element, $gpr, $mode, $context); 2710 } else { 2711 $context->deleteurl = $this->get_delete_link($element, $gpr); 2712 $context->resetweightsurl = $this->get_reset_weights_link($element, $gpr); 2713 } 2714 2715 if (!empty($USER->editing) || $mode == 'setup') { 2716 if ($mode !== 'setup') { 2717 $context->divider1 = true; 2718 } 2719 $context->editurl = $this->get_edit_link($element, $gpr); 2720 $context->hideurl = $this->get_hiding_link($element, $gpr); 2721 $context->lockurl = $this->get_locking_link($element, $gpr); 2722 } 2723 } 2724 2725 if (isset($element['object'])) { 2726 $context->dataid = $element['object']->id; 2727 } else if ($element['type'] == 'userfield') { 2728 $context->dataid = $element['name']; 2729 } 2730 2731 if ($element['type'] != 'text' && !empty($element['object']->feedback)) { 2732 $viewfeedbackstring = get_string('viewfeedback', 'grades'); 2733 $context->viewfeedbackurl = html_writer::link('#', $viewfeedbackstring, ['class' => 'dropdown-item', 2734 'aria-label' => $viewfeedbackstring, 'role' => 'menuitem', 'data-action' => 'feedback', 2735 'data-courseid' => $this->courseid]); 2736 } 2737 } else if ($mode == 'user') { 2738 $context->datatype = 'user'; 2739 $context = grade_report::get_additional_context($this->context, $this->courseid, $element, $gpr, $mode, $context, true); 2740 $context->dataid = $element['userid']; 2741 } 2742 2743 // Omit the second divider if there is nothing between it and the first divider. 2744 if (!isset($context->ascendingfirstnameurl) && !isset($context->ascendingurl)) { 2745 $context->divider2 = false; 2746 } 2747 2748 if ($mode == 'setup') { 2749 $context->databoundary = 'window'; 2750 } 2751 2752 if (!empty($USER->editing) || isset($context->gradeanalysisurl) || isset($context->gradesonlyurl) 2753 || isset($context->aggregatesonlyurl) || isset($context->fullmodeurl) || isset($context->reporturl0) 2754 || isset($context->ascendingfirstnameurl) || isset($context->ascendingurl) 2755 || isset($context->viewfeedbackurl) || ($mode == 'setup')) { 2756 return $OUTPUT->render_from_template('core_grades/cellmenu', $context); 2757 } 2758 return ''; 2759 } 2760 2761 /** 2762 * Returns link to sort grade item column 2763 * 2764 * @param moodle_url $sortlink A base link for sorting 2765 * @param object $gpr A grade_plugin_return object 2766 * @param string $direction Direction od sorting 2767 * @return string 2768 */ 2769 public function get_sorting_link(moodle_url $sortlink, object $gpr, string $direction = 'asc'): string { 2770 2771 if ($direction == 'asc') { 2772 $title = get_string('asc'); 2773 } else { 2774 $title = get_string('desc'); 2775 } 2776 2777 $sortlink->param('sort', $direction); 2778 $gpr->add_url_params($sortlink); 2779 return html_writer::link($sortlink, $title, 2780 ['class' => 'dropdown-item', 'aria-label' => $title, 'role' => 'menuitem']); 2781 } 2782 2783 } 2784 2785 /** 2786 * Flat structure similar to grade tree. 2787 * 2788 * @uses grade_structure 2789 * @package core_grades 2790 * @copyright 2009 Nicolas Connault 2791 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2792 */ 2793 class grade_seq extends grade_structure { 2794 2795 /** 2796 * 1D array of elements 2797 */ 2798 public $elements; 2799 2800 /** 2801 * Constructor, retrieves and stores array of all grade_category and grade_item 2802 * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed. 2803 * 2804 * @param int $courseid The course id 2805 * @param bool $category_grade_last category grade item is the last child 2806 * @param bool $nooutcomes Whether or not outcomes should be included 2807 */ 2808 public function __construct($courseid, $category_grade_last=false, $nooutcomes=false) { 2809 global $USER, $CFG; 2810 2811 $this->courseid = $courseid; 2812 $this->context = context_course::instance($courseid); 2813 2814 // get course grade tree 2815 $top_element = grade_category::fetch_course_tree($courseid, true); 2816 2817 $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes); 2818 2819 foreach ($this->elements as $key=>$unused) { 2820 $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object']; 2821 } 2822 } 2823 2824 /** 2825 * Old syntax of class constructor. Deprecated in PHP7. 2826 * 2827 * @deprecated since Moodle 3.1 2828 */ 2829 public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) { 2830 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 2831 self::__construct($courseid, $category_grade_last, $nooutcomes); 2832 } 2833 2834 /** 2835 * Static recursive helper - makes the grade_item for category the last children 2836 * 2837 * @param array &$element The seed of the recursion 2838 * @param bool $category_grade_last category grade item is the last child 2839 * @param bool $nooutcomes Whether or not outcomes should be included 2840 * 2841 * @return array 2842 */ 2843 public function flatten(&$element, $category_grade_last, $nooutcomes) { 2844 if (empty($element['children'])) { 2845 return array(); 2846 } 2847 $children = array(); 2848 2849 foreach ($element['children'] as $sortorder=>$unused) { 2850 if ($nooutcomes and $element['type'] != 'category' and 2851 $element['children'][$sortorder]['object']->is_outcome_item()) { 2852 continue; 2853 } 2854 $children[] = $element['children'][$sortorder]; 2855 } 2856 unset($element['children']); 2857 2858 if ($category_grade_last and count($children) > 1 and 2859 ( 2860 $children[0]['type'] === 'courseitem' or 2861 $children[0]['type'] === 'categoryitem' 2862 ) 2863 ) { 2864 $cat_item = array_shift($children); 2865 array_push($children, $cat_item); 2866 } 2867 2868 $result = array(); 2869 foreach ($children as $child) { 2870 if ($child['type'] == 'category') { 2871 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes); 2872 } else { 2873 $child['eid'] = 'i'.$child['object']->id; 2874 $result[$child['object']->id] = $child; 2875 } 2876 } 2877 2878 return $result; 2879 } 2880 2881 /** 2882 * Parses the array in search of a given eid and returns a element object with 2883 * information about the element it has found. 2884 * 2885 * @param int $eid Gradetree Element ID 2886 * 2887 * @return object element 2888 */ 2889 public function locate_element($eid) { 2890 // it is a grade - construct a new object 2891 if (strpos($eid, 'n') === 0) { 2892 if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) { 2893 return null; 2894 } 2895 2896 $itemid = $matches[1]; 2897 $userid = $matches[2]; 2898 2899 //extra security check - the grade item must be in this tree 2900 if (!$item_el = $this->locate_element('ig'.$itemid)) { 2901 return null; 2902 } 2903 2904 // $gradea->id may be null - means does not exist yet 2905 $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid)); 2906 2907 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 2908 return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade'); 2909 2910 } else if (strpos($eid, 'g') === 0) { 2911 $id = (int) substr($eid, 1); 2912 if (!$grade = grade_grade::fetch(array('id'=>$id))) { 2913 return null; 2914 } 2915 //extra security check - the grade item must be in this tree 2916 if (!$item_el = $this->locate_element('ig'.$grade->itemid)) { 2917 return null; 2918 } 2919 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 2920 return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade'); 2921 } 2922 2923 // it is a category or item 2924 foreach ($this->elements as $element) { 2925 if ($element['eid'] == $eid) { 2926 return $element; 2927 } 2928 } 2929 2930 return null; 2931 } 2932 } 2933 2934 /** 2935 * This class represents a complete tree of categories, grade_items and final grades, 2936 * organises as an array primarily, but which can also be converted to other formats. 2937 * It has simple method calls with complex implementations, allowing for easy insertion, 2938 * deletion and moving of items and categories within the tree. 2939 * 2940 * @uses grade_structure 2941 * @package core_grades 2942 * @copyright 2009 Nicolas Connault 2943 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2944 */ 2945 class grade_tree extends grade_structure { 2946 2947 /** 2948 * The basic representation of the tree as a hierarchical, 3-tiered array. 2949 * @var object $top_element 2950 */ 2951 public $top_element; 2952 2953 /** 2954 * 2D array of grade items and categories 2955 * @var array $levels 2956 */ 2957 public $levels; 2958 2959 /** 2960 * Grade items 2961 * @var array $items 2962 */ 2963 public $items; 2964 2965 /** 2966 * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item 2967 * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed. 2968 * 2969 * @param int $courseid The Course ID 2970 * @param bool $fillers include fillers and colspans, make the levels var "rectangular" 2971 * @param bool $category_grade_last category grade item is the last child 2972 * @param array $collapsed array of collapsed categories 2973 * @param bool $nooutcomes Whether or not outcomes should be included 2974 */ 2975 public function __construct($courseid, $fillers=true, $category_grade_last=false, 2976 $collapsed=null, $nooutcomes=false) { 2977 global $USER, $CFG, $COURSE, $DB; 2978 2979 $this->courseid = $courseid; 2980 $this->levels = array(); 2981 $this->context = context_course::instance($courseid); 2982 2983 if (!empty($COURSE->id) && $COURSE->id == $this->courseid) { 2984 $course = $COURSE; 2985 } else { 2986 $course = $DB->get_record('course', array('id' => $this->courseid)); 2987 } 2988 $this->modinfo = get_fast_modinfo($course); 2989 2990 // get course grade tree 2991 $this->top_element = grade_category::fetch_course_tree($courseid, true); 2992 2993 // collapse the categories if requested 2994 if (!empty($collapsed)) { 2995 grade_tree::category_collapse($this->top_element, $collapsed); 2996 } 2997 2998 // no otucomes if requested 2999 if (!empty($nooutcomes)) { 3000 grade_tree::no_outcomes($this->top_element); 3001 } 3002 3003 // move category item to last position in category 3004 if ($category_grade_last) { 3005 grade_tree::category_grade_last($this->top_element); 3006 } 3007 3008 if ($fillers) { 3009 // inject fake categories == fillers 3010 grade_tree::inject_fillers($this->top_element, 0); 3011 // add colspans to categories and fillers 3012 grade_tree::inject_colspans($this->top_element); 3013 } 3014 3015 grade_tree::fill_levels($this->levels, $this->top_element, 0); 3016 3017 } 3018 3019 /** 3020 * Old syntax of class constructor. Deprecated in PHP7. 3021 * 3022 * @deprecated since Moodle 3.1 3023 */ 3024 public function grade_tree($courseid, $fillers=true, $category_grade_last=false, 3025 $collapsed=null, $nooutcomes=false) { 3026 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 3027 self::__construct($courseid, $fillers, $category_grade_last, $collapsed, $nooutcomes); 3028 } 3029 3030 /** 3031 * Static recursive helper - removes items from collapsed categories 3032 * 3033 * @param array &$element The seed of the recursion 3034 * @param array $collapsed array of collapsed categories 3035 * 3036 * @return void 3037 */ 3038 public function category_collapse(&$element, $collapsed) { 3039 if ($element['type'] != 'category') { 3040 return; 3041 } 3042 if (empty($element['children']) or count($element['children']) < 2) { 3043 return; 3044 } 3045 3046 if (in_array($element['object']->id, $collapsed['aggregatesonly'])) { 3047 $category_item = reset($element['children']); //keep only category item 3048 $element['children'] = array(key($element['children'])=>$category_item); 3049 3050 } else { 3051 if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item 3052 reset($element['children']); 3053 $first_key = key($element['children']); 3054 unset($element['children'][$first_key]); 3055 } 3056 foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children 3057 grade_tree::category_collapse($element['children'][$sortorder], $collapsed); 3058 } 3059 } 3060 } 3061 3062 /** 3063 * Static recursive helper - removes all outcomes 3064 * 3065 * @param array &$element The seed of the recursion 3066 * 3067 * @return void 3068 */ 3069 public function no_outcomes(&$element) { 3070 if ($element['type'] != 'category') { 3071 return; 3072 } 3073 foreach ($element['children'] as $sortorder=>$child) { 3074 if ($element['children'][$sortorder]['type'] == 'item' 3075 and $element['children'][$sortorder]['object']->is_outcome_item()) { 3076 unset($element['children'][$sortorder]); 3077 3078 } else if ($element['children'][$sortorder]['type'] == 'category') { 3079 grade_tree::no_outcomes($element['children'][$sortorder]); 3080 } 3081 } 3082 } 3083 3084 /** 3085 * Static recursive helper - makes the grade_item for category the last children 3086 * 3087 * @param array &$element The seed of the recursion 3088 * 3089 * @return void 3090 */ 3091 public function category_grade_last(&$element) { 3092 if (empty($element['children'])) { 3093 return; 3094 } 3095 if (count($element['children']) < 2) { 3096 return; 3097 } 3098 $first_item = reset($element['children']); 3099 if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') { 3100 // the category item might have been already removed 3101 $order = key($element['children']); 3102 unset($element['children'][$order]); 3103 $element['children'][$order] =& $first_item; 3104 } 3105 foreach ($element['children'] as $sortorder => $child) { 3106 grade_tree::category_grade_last($element['children'][$sortorder]); 3107 } 3108 } 3109 3110 /** 3111 * Static recursive helper - fills the levels array, useful when accessing tree elements of one level 3112 * 3113 * @param array &$levels The levels of the grade tree through which to recurse 3114 * @param array &$element The seed of the recursion 3115 * @param int $depth How deep are we? 3116 * @return void 3117 */ 3118 public function fill_levels(&$levels, &$element, $depth) { 3119 if (!array_key_exists($depth, $levels)) { 3120 $levels[$depth] = array(); 3121 } 3122 3123 // prepare unique identifier 3124 if ($element['type'] == 'category') { 3125 $element['eid'] = 'cg'.$element['object']->id; 3126 } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) { 3127 $element['eid'] = 'ig'.$element['object']->id; 3128 $this->items[$element['object']->id] =& $element['object']; 3129 } 3130 3131 $levels[$depth][] =& $element; 3132 $depth++; 3133 if (empty($element['children'])) { 3134 return; 3135 } 3136 $prev = 0; 3137 foreach ($element['children'] as $sortorder=>$child) { 3138 grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth); 3139 $element['children'][$sortorder]['prev'] = $prev; 3140 $element['children'][$sortorder]['next'] = 0; 3141 if ($prev) { 3142 $element['children'][$prev]['next'] = $sortorder; 3143 } 3144 $prev = $sortorder; 3145 } 3146 } 3147 3148 /** 3149 * Determines whether the grade tree item can be displayed. 3150 * This is particularly targeted for grade categories that have no total (None) when rendering the grade tree. 3151 * It checks if the grade tree item is of type 'category', and makes sure that the category, or at least one of children, 3152 * can be output. 3153 * 3154 * @param array $element The grade category element. 3155 * @return bool True if the grade tree item can be displayed. False, otherwise. 3156 */ 3157 public static function can_output_item($element) { 3158 $canoutput = true; 3159 3160 if ($element['type'] === 'category') { 3161 $object = $element['object']; 3162 $category = grade_category::fetch(array('id' => $object->id)); 3163 // Category has total, we can output this. 3164 if ($category->get_grade_item()->gradetype != GRADE_TYPE_NONE) { 3165 return true; 3166 } 3167 3168 // Category has no total and has no children, no need to output this. 3169 if (empty($element['children'])) { 3170 return false; 3171 } 3172 3173 $canoutput = false; 3174 // Loop over children and make sure at least one child can be output. 3175 foreach ($element['children'] as $child) { 3176 $canoutput = self::can_output_item($child); 3177 if ($canoutput) { 3178 break; 3179 } 3180 } 3181 } 3182 3183 return $canoutput; 3184 } 3185 3186 /** 3187 * Static recursive helper - makes full tree (all leafes are at the same level) 3188 * 3189 * @param array &$element The seed of the recursion 3190 * @param int $depth How deep are we? 3191 * 3192 * @return int 3193 */ 3194 public function inject_fillers(&$element, $depth) { 3195 $depth++; 3196 3197 if (empty($element['children'])) { 3198 return $depth; 3199 } 3200 $chdepths = array(); 3201 $chids = array_keys($element['children']); 3202 $last_child = end($chids); 3203 $first_child = reset($chids); 3204 3205 foreach ($chids as $chid) { 3206 $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth); 3207 } 3208 arsort($chdepths); 3209 3210 $maxdepth = reset($chdepths); 3211 foreach ($chdepths as $chid=>$chd) { 3212 if ($chd == $maxdepth) { 3213 continue; 3214 } 3215 if (!self::can_output_item($element['children'][$chid])) { 3216 continue; 3217 } 3218 for ($i=0; $i < $maxdepth-$chd; $i++) { 3219 if ($chid == $first_child) { 3220 $type = 'fillerfirst'; 3221 } else if ($chid == $last_child) { 3222 $type = 'fillerlast'; 3223 } else { 3224 $type = 'filler'; 3225 } 3226 $oldchild =& $element['children'][$chid]; 3227 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type, 3228 'eid'=>'', 'depth'=>$element['object']->depth, 3229 'children'=>array($oldchild)); 3230 } 3231 } 3232 3233 return $maxdepth; 3234 } 3235 3236 /** 3237 * Static recursive helper - add colspan information into categories 3238 * 3239 * @param array &$element The seed of the recursion 3240 * 3241 * @return int 3242 */ 3243 public function inject_colspans(&$element) { 3244 if (empty($element['children'])) { 3245 return 1; 3246 } 3247 $count = 0; 3248 foreach ($element['children'] as $key=>$child) { 3249 if (!self::can_output_item($child)) { 3250 continue; 3251 } 3252 $count += grade_tree::inject_colspans($element['children'][$key]); 3253 } 3254 $element['colspan'] = $count; 3255 return $count; 3256 } 3257 3258 /** 3259 * Parses the array in search of a given eid and returns a element object with 3260 * information about the element it has found. 3261 * @param int $eid Gradetree Element ID 3262 * @return object element 3263 */ 3264 public function locate_element($eid) { 3265 // it is a grade - construct a new object 3266 if (strpos($eid, 'n') === 0) { 3267 if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) { 3268 return null; 3269 } 3270 3271 $itemid = $matches[1]; 3272 $userid = $matches[2]; 3273 3274 //extra security check - the grade item must be in this tree 3275 if (!$item_el = $this->locate_element('ig'.$itemid)) { 3276 return null; 3277 } 3278 3279 // $gradea->id may be null - means does not exist yet 3280 $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid)); 3281 3282 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 3283 return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade'); 3284 3285 } else if (strpos($eid, 'g') === 0) { 3286 $id = (int) substr($eid, 1); 3287 if (!$grade = grade_grade::fetch(array('id'=>$id))) { 3288 return null; 3289 } 3290 //extra security check - the grade item must be in this tree 3291 if (!$item_el = $this->locate_element('ig'.$grade->itemid)) { 3292 return null; 3293 } 3294 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 3295 return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade'); 3296 } 3297 3298 // it is a category or item 3299 foreach ($this->levels as $row) { 3300 foreach ($row as $element) { 3301 if ($element['type'] == 'filler') { 3302 continue; 3303 } 3304 if ($element['eid'] == $eid) { 3305 return $element; 3306 } 3307 } 3308 } 3309 3310 return null; 3311 } 3312 3313 /** 3314 * Returns a well-formed XML representation of the grade-tree using recursion. 3315 * 3316 * @param array $root The current element in the recursion. If null, starts at the top of the tree. 3317 * @param string $tabs The control character to use for tabs 3318 * 3319 * @return string $xml 3320 */ 3321 public function exporttoxml($root=null, $tabs="\t") { 3322 $xml = null; 3323 $first = false; 3324 if (is_null($root)) { 3325 $root = $this->top_element; 3326 $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n"; 3327 $xml .= "<gradetree>\n"; 3328 $first = true; 3329 } 3330 3331 $type = 'undefined'; 3332 if (strpos($root['object']->table, 'grade_categories') !== false) { 3333 $type = 'category'; 3334 } else if (strpos($root['object']->table, 'grade_items') !== false) { 3335 $type = 'item'; 3336 } else if (strpos($root['object']->table, 'grade_outcomes') !== false) { 3337 $type = 'outcome'; 3338 } 3339 3340 $xml .= "$tabs<element type=\"$type\">\n"; 3341 foreach ($root['object'] as $var => $value) { 3342 if (!is_object($value) && !is_array($value) && !empty($value)) { 3343 $xml .= "$tabs\t<$var>$value</$var>\n"; 3344 } 3345 } 3346 3347 if (!empty($root['children'])) { 3348 $xml .= "$tabs\t<children>\n"; 3349 foreach ($root['children'] as $sortorder => $child) { 3350 $xml .= $this->exportToXML($child, $tabs."\t\t"); 3351 } 3352 $xml .= "$tabs\t</children>\n"; 3353 } 3354 3355 $xml .= "$tabs</element>\n"; 3356 3357 if ($first) { 3358 $xml .= "</gradetree>"; 3359 } 3360 3361 return $xml; 3362 } 3363 3364 /** 3365 * Returns a JSON representation of the grade-tree using recursion. 3366 * 3367 * @param array $root The current element in the recursion. If null, starts at the top of the tree. 3368 * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy 3369 * 3370 * @return string 3371 */ 3372 public function exporttojson($root=null, $tabs="\t") { 3373 $json = null; 3374 $first = false; 3375 if (is_null($root)) { 3376 $root = $this->top_element; 3377 $first = true; 3378 } 3379 3380 $name = ''; 3381 3382 3383 if (strpos($root['object']->table, 'grade_categories') !== false) { 3384 $name = $root['object']->fullname; 3385 if ($name == '?') { 3386 $name = $root['object']->get_name(); 3387 } 3388 } else if (strpos($root['object']->table, 'grade_items') !== false) { 3389 $name = $root['object']->itemname; 3390 } else if (strpos($root['object']->table, 'grade_outcomes') !== false) { 3391 $name = $root['object']->itemname; 3392 } 3393 3394 $json .= "$tabs {\n"; 3395 $json .= "$tabs\t \"type\": \"{$root['type']}\",\n"; 3396 $json .= "$tabs\t \"name\": \"$name\",\n"; 3397 3398 foreach ($root['object'] as $var => $value) { 3399 if (!is_object($value) && !is_array($value) && !empty($value)) { 3400 $json .= "$tabs\t \"$var\": \"$value\",\n"; 3401 } 3402 } 3403 3404 $json = substr($json, 0, strrpos($json, ',')); 3405 3406 if (!empty($root['children'])) { 3407 $json .= ",\n$tabs\t\"children\": [\n"; 3408 foreach ($root['children'] as $sortorder => $child) { 3409 $json .= $this->exportToJSON($child, $tabs."\t\t"); 3410 } 3411 $json = substr($json, 0, strrpos($json, ',')); 3412 $json .= "\n$tabs\t]\n"; 3413 } 3414 3415 if ($first) { 3416 $json .= "\n}"; 3417 } else { 3418 $json .= "\n$tabs},\n"; 3419 } 3420 3421 return $json; 3422 } 3423 3424 /** 3425 * Returns the array of levels 3426 * 3427 * @return array 3428 */ 3429 public function get_levels() { 3430 return $this->levels; 3431 } 3432 3433 /** 3434 * Returns the array of grade items 3435 * 3436 * @return array 3437 */ 3438 public function get_items() { 3439 return $this->items; 3440 } 3441 3442 /** 3443 * Returns a specific Grade Item 3444 * 3445 * @param int $itemid The ID of the grade_item object 3446 * 3447 * @return grade_item 3448 */ 3449 public function get_item($itemid) { 3450 if (array_key_exists($itemid, $this->items)) { 3451 return $this->items[$itemid]; 3452 } else { 3453 return false; 3454 } 3455 } 3456 } 3457 3458 /** 3459 * Local shortcut function for creating an edit/delete button for a grade_* object. 3460 * @param string $type 'edit' or 'delete' 3461 * @param int $courseid The Course ID 3462 * @param grade_* $object The grade_* object 3463 * @return string html 3464 */ 3465 function grade_button($type, $courseid, $object) { 3466 global $CFG, $OUTPUT; 3467 if (preg_match('/grade_(.*)/', get_class($object), $matches)) { 3468 $objectidstring = $matches[1] . 'id'; 3469 } else { 3470 throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!'); 3471 } 3472 3473 $strdelete = get_string('delete'); 3474 $stredit = get_string('edit'); 3475 3476 if ($type == 'delete') { 3477 $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey())); 3478 } else if ($type == 'edit') { 3479 $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id)); 3480 } 3481 3482 return $OUTPUT->action_icon($url, new pix_icon('t/'.$type, ${'str'.$type}, '', array('class' => 'iconsmall'))); 3483 3484 } 3485 3486 /** 3487 * This method adds settings to the settings block for the grade system and its 3488 * plugins 3489 * 3490 * @global moodle_page $PAGE 3491 */ 3492 function grade_extend_settings($plugininfo, $courseid) { 3493 global $PAGE; 3494 3495 $gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER, 3496 null, 'gradeadmin'); 3497 3498 $strings = array_shift($plugininfo); 3499 3500 if ($reports = grade_helper::get_plugins_reports($courseid)) { 3501 foreach ($reports as $report) { 3502 $gradenode->add($report->string, $report->link, navigation_node::TYPE_SETTING, null, $report->id, new pix_icon('i/report', '')); 3503 } 3504 } 3505 3506 if ($settings = grade_helper::get_info_manage_settings($courseid)) { 3507 $settingsnode = $gradenode->add($strings['settings'], null, navigation_node::TYPE_CONTAINER); 3508 foreach ($settings as $setting) { 3509 $settingsnode->add($setting->string, $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', '')); 3510 } 3511 } 3512 3513 if ($imports = grade_helper::get_plugins_import($courseid)) { 3514 $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER); 3515 foreach ($imports as $import) { 3516 $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/import', '')); 3517 } 3518 } 3519 3520 if ($exports = grade_helper::get_plugins_export($courseid)) { 3521 $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER); 3522 foreach ($exports as $export) { 3523 $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/export', '')); 3524 } 3525 } 3526 3527 if ($letters = grade_helper::get_info_letters($courseid)) { 3528 $letters = array_shift($letters); 3529 $gradenode->add($strings['letter'], $letters->link, navigation_node::TYPE_SETTING, null, $letters->id, new pix_icon('i/settings', '')); 3530 } 3531 3532 if ($outcomes = grade_helper::get_info_outcomes($courseid)) { 3533 $outcomes = array_shift($outcomes); 3534 $gradenode->add($strings['outcome'], $outcomes->link, navigation_node::TYPE_SETTING, null, $outcomes->id, new pix_icon('i/outcomes', '')); 3535 } 3536 3537 if ($scales = grade_helper::get_info_scales($courseid)) { 3538 $gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', '')); 3539 } 3540 3541 if ($gradenode->contains_active_node()) { 3542 // If the gradenode is active include the settings base node (gradeadministration) in 3543 // the navbar, typcially this is ignored. 3544 $PAGE->navbar->includesettingsbase = true; 3545 3546 // If we can get the course admin node make sure it is closed by default 3547 // as in this case the gradenode will be opened 3548 if ($coursenode = $PAGE->settingsnav->get('courseadmin', navigation_node::TYPE_COURSE)){ 3549 $coursenode->make_inactive(); 3550 $coursenode->forceopen = false; 3551 } 3552 } 3553 } 3554 3555 /** 3556 * Grade helper class 3557 * 3558 * This class provides several helpful functions that work irrespective of any 3559 * current state. 3560 * 3561 * @copyright 2010 Sam Hemelryk 3562 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 3563 */ 3564 abstract class grade_helper { 3565 /** 3566 * Cached manage settings info {@see get_info_settings} 3567 * @var grade_plugin_info|false 3568 */ 3569 protected static $managesetting = null; 3570 /** 3571 * Cached grade report plugins {@see get_plugins_reports} 3572 * @var array|false 3573 */ 3574 protected static $gradereports = null; 3575 /** 3576 * Cached grade report plugins preferences {@see get_info_scales} 3577 * @var array|false 3578 */ 3579 protected static $gradereportpreferences = null; 3580 /** 3581 * Cached scale info {@see get_info_scales} 3582 * @var grade_plugin_info|false 3583 */ 3584 protected static $scaleinfo = null; 3585 /** 3586 * Cached outcome info {@see get_info_outcomes} 3587 * @var grade_plugin_info|false 3588 */ 3589 protected static $outcomeinfo = null; 3590 /** 3591 * Cached leftter info {@see get_info_letters} 3592 * @var grade_plugin_info|false 3593 */ 3594 protected static $letterinfo = null; 3595 /** 3596 * Cached grade import plugins {@see get_plugins_import} 3597 * @var array|false 3598 */ 3599 protected static $importplugins = null; 3600 /** 3601 * Cached grade export plugins {@see get_plugins_export} 3602 * @var array|false 3603 */ 3604 protected static $exportplugins = null; 3605 /** 3606 * Cached grade plugin strings 3607 * @var array 3608 */ 3609 protected static $pluginstrings = null; 3610 /** 3611 * Cached grade aggregation strings 3612 * @var array 3613 */ 3614 protected static $aggregationstrings = null; 3615 3616 /** 3617 * Cached grade tree plugin strings 3618 * @var array 3619 */ 3620 protected static $langstrings = []; 3621 3622 /** 3623 * First checks the cached language strings, then returns match if found, or uses get_string() 3624 * to get it from the DB, caches it then returns it. 3625 * 3626 * @deprecated since 4.3 3627 * @todo MDL-78780 This will be deleted in Moodle 4.7. 3628 * @param string $strcode 3629 * @param string|null $section Optional language section 3630 * @return string 3631 */ 3632 public static function get_lang_string(string $strcode, ?string $section = null): string { 3633 debugging('grade_helper::get_lang_string() is deprecated, please use' . 3634 ' get_string() instead.', DEBUG_DEVELOPER); 3635 3636 if (empty(self::$langstrings[$strcode])) { 3637 self::$langstrings[$strcode] = get_string($strcode, $section); 3638 } 3639 return self::$langstrings[$strcode]; 3640 } 3641 3642 /** 3643 * Gets strings commonly used by the describe plugins 3644 * 3645 * report => get_string('view'), 3646 * scale => get_string('scales'), 3647 * outcome => get_string('outcomes', 'grades'), 3648 * letter => get_string('letters', 'grades'), 3649 * export => get_string('export', 'grades'), 3650 * import => get_string('import'), 3651 * settings => get_string('settings') 3652 * 3653 * @return array 3654 */ 3655 public static function get_plugin_strings() { 3656 if (self::$pluginstrings === null) { 3657 self::$pluginstrings = array( 3658 'report' => get_string('view'), 3659 'scale' => get_string('scales'), 3660 'outcome' => get_string('outcomes', 'grades'), 3661 'letter' => get_string('letters', 'grades'), 3662 'export' => get_string('export', 'grades'), 3663 'import' => get_string('import'), 3664 'settings' => get_string('edittree', 'grades') 3665 ); 3666 } 3667 return self::$pluginstrings; 3668 } 3669 3670 /** 3671 * Gets strings describing the available aggregation methods. 3672 * 3673 * @return array 3674 */ 3675 public static function get_aggregation_strings() { 3676 if (self::$aggregationstrings === null) { 3677 self::$aggregationstrings = array( 3678 GRADE_AGGREGATE_MEAN => get_string('aggregatemean', 'grades'), 3679 GRADE_AGGREGATE_WEIGHTED_MEAN => get_string('aggregateweightedmean', 'grades'), 3680 GRADE_AGGREGATE_WEIGHTED_MEAN2 => get_string('aggregateweightedmean2', 'grades'), 3681 GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'), 3682 GRADE_AGGREGATE_MEDIAN => get_string('aggregatemedian', 'grades'), 3683 GRADE_AGGREGATE_MIN => get_string('aggregatemin', 'grades'), 3684 GRADE_AGGREGATE_MAX => get_string('aggregatemax', 'grades'), 3685 GRADE_AGGREGATE_MODE => get_string('aggregatemode', 'grades'), 3686 GRADE_AGGREGATE_SUM => get_string('aggregatesum', 'grades') 3687 ); 3688 } 3689 return self::$aggregationstrings; 3690 } 3691 3692 /** 3693 * Get grade_plugin_info object for managing settings if the user can 3694 * 3695 * @param int $courseid 3696 * @return grade_plugin_info[] 3697 */ 3698 public static function get_info_manage_settings($courseid) { 3699 if (self::$managesetting !== null) { 3700 return self::$managesetting; 3701 } 3702 $context = context_course::instance($courseid); 3703 self::$managesetting = array(); 3704 if ($courseid != SITEID && has_capability('moodle/grade:manage', $context)) { 3705 self::$managesetting['gradebooksetup'] = new grade_plugin_info('setup', 3706 new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid)), 3707 get_string('gradebooksetup', 'grades')); 3708 self::$managesetting['coursesettings'] = new grade_plugin_info('coursesettings', 3709 new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)), 3710 get_string('coursegradesettings', 'grades')); 3711 } 3712 if (self::$gradereportpreferences === null) { 3713 self::get_plugins_reports($courseid); 3714 } 3715 if (self::$gradereportpreferences) { 3716 self::$managesetting = array_merge(self::$managesetting, self::$gradereportpreferences); 3717 } 3718 return self::$managesetting; 3719 } 3720 /** 3721 * Returns an array of plugin reports as grade_plugin_info objects 3722 * 3723 * @param int $courseid 3724 * @return array 3725 */ 3726 public static function get_plugins_reports($courseid) { 3727 global $SITE, $CFG; 3728 3729 if (self::$gradereports !== null) { 3730 return self::$gradereports; 3731 } 3732 $context = context_course::instance($courseid); 3733 $gradereports = array(); 3734 $gradepreferences = array(); 3735 foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) { 3736 //some reports make no sense if we're not within a course 3737 if ($courseid==$SITE->id && ($plugin=='grader' || $plugin=='user')) { 3738 continue; 3739 } 3740 3741 // Remove outcomes report if outcomes not enabled. 3742 if ($plugin === 'outcomes' && empty($CFG->enableoutcomes)) { 3743 continue; 3744 } 3745 3746 // Remove ones we can't see 3747 if (!has_capability('gradereport/'.$plugin.':view', $context)) { 3748 continue; 3749 } 3750 3751 // Singleview doesn't doesn't accomodate for all cap combos yet, so this is hardcoded.. 3752 if ($plugin === 'singleview' && !has_all_capabilities(array('moodle/grade:viewall', 3753 'moodle/grade:edit'), $context)) { 3754 continue; 3755 } 3756 3757 $pluginstr = get_string('pluginname', 'gradereport_'.$plugin); 3758 $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid)); 3759 $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); 3760 3761 // Add link to preferences tab if such a page exists 3762 if (file_exists($plugindir.'/preferences.php')) { 3763 $url = new moodle_url('/grade/report/'.$plugin.'/preferences.php', array('id' => $courseid)); 3764 $gradepreferences[$plugin] = new grade_plugin_info($plugin, $url, 3765 get_string('preferences', 'grades') . ': ' . $pluginstr); 3766 } 3767 } 3768 if (count($gradereports) == 0) { 3769 $gradereports = false; 3770 $gradepreferences = false; 3771 } else if (count($gradepreferences) == 0) { 3772 $gradepreferences = false; 3773 asort($gradereports); 3774 } else { 3775 asort($gradereports); 3776 asort($gradepreferences); 3777 } 3778 self::$gradereports = $gradereports; 3779 self::$gradereportpreferences = $gradepreferences; 3780 return self::$gradereports; 3781 } 3782 3783 /** 3784 * Get information on scales 3785 * @param int $courseid 3786 * @return grade_plugin_info 3787 */ 3788 public static function get_info_scales($courseid) { 3789 if (self::$scaleinfo !== null) { 3790 return self::$scaleinfo; 3791 } 3792 if (has_capability('moodle/course:managescales', context_course::instance($courseid))) { 3793 $url = new moodle_url('/grade/edit/scale/index.php', array('id'=>$courseid)); 3794 self::$scaleinfo = new grade_plugin_info('scale', $url, get_string('view')); 3795 } else { 3796 self::$scaleinfo = false; 3797 } 3798 return self::$scaleinfo; 3799 } 3800 /** 3801 * Get information on outcomes 3802 * @param int $courseid 3803 * @return grade_plugin_info[]|false 3804 */ 3805 public static function get_info_outcomes($courseid) { 3806 global $CFG, $SITE; 3807 3808 if (self::$outcomeinfo !== null) { 3809 return self::$outcomeinfo; 3810 } 3811 $context = context_course::instance($courseid); 3812 $canmanage = has_capability('moodle/grade:manage', $context); 3813 $canupdate = has_capability('moodle/course:update', $context); 3814 if (!empty($CFG->enableoutcomes) && ($canmanage || $canupdate)) { 3815 $outcomes = array(); 3816 if ($canupdate) { 3817 if ($courseid!=$SITE->id) { 3818 $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid)); 3819 $outcomes['course'] = new grade_plugin_info('course', $url, get_string('outcomescourse', 'grades')); 3820 } 3821 $url = new moodle_url('/grade/edit/outcome/index.php', array('id'=>$courseid)); 3822 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('editoutcomes', 'grades')); 3823 $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid'=>$courseid)); 3824 $outcomes['import'] = new grade_plugin_info('import', $url, get_string('importoutcomes', 'grades')); 3825 } else { 3826 if ($courseid!=$SITE->id) { 3827 $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid)); 3828 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('outcomescourse', 'grades')); 3829 } 3830 } 3831 self::$outcomeinfo = $outcomes; 3832 } else { 3833 self::$outcomeinfo = false; 3834 } 3835 return self::$outcomeinfo; 3836 } 3837 /** 3838 * Get information on letters 3839 * @param int $courseid 3840 * @return array 3841 */ 3842 public static function get_info_letters($courseid) { 3843 global $SITE; 3844 if (self::$letterinfo !== null) { 3845 return self::$letterinfo; 3846 } 3847 $context = context_course::instance($courseid); 3848 $canmanage = has_capability('moodle/grade:manage', $context); 3849 $canmanageletters = has_capability('moodle/grade:manageletters', $context); 3850 if ($canmanage || $canmanageletters) { 3851 // Redirect to system context when report is accessed from admin settings MDL-31633 3852 if ($context->instanceid == $SITE->id) { 3853 $param = array('edit' => 1); 3854 } else { 3855 $param = array('edit' => 1,'id' => $context->id); 3856 } 3857 self::$letterinfo = array( 3858 'view' => new grade_plugin_info('view', new moodle_url('/grade/edit/letter/index.php', array('id'=>$context->id)), get_string('view')), 3859 'edit' => new grade_plugin_info('edit', new moodle_url('/grade/edit/letter/index.php', $param), get_string('edit')) 3860 ); 3861 } else { 3862 self::$letterinfo = false; 3863 } 3864 return self::$letterinfo; 3865 } 3866 /** 3867 * Get information import plugins 3868 * @param int $courseid 3869 * @return array 3870 */ 3871 public static function get_plugins_import($courseid) { 3872 global $CFG; 3873 3874 if (self::$importplugins !== null) { 3875 return self::$importplugins; 3876 } 3877 $importplugins = array(); 3878 $context = context_course::instance($courseid); 3879 3880 if (has_capability('moodle/grade:import', $context)) { 3881 foreach (core_component::get_plugin_list('gradeimport') as $plugin => $plugindir) { 3882 if (!has_capability('gradeimport/'.$plugin.':view', $context)) { 3883 continue; 3884 } 3885 $pluginstr = get_string('pluginname', 'gradeimport_'.$plugin); 3886 $url = new moodle_url('/grade/import/'.$plugin.'/index.php', array('id'=>$courseid)); 3887 $importplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); 3888 } 3889 3890 // Show key manager if grade publishing is enabled and the user has xml publishing capability. 3891 // XML is the only grade import plugin that has publishing feature. 3892 if ($CFG->gradepublishing && has_capability('gradeimport/xml:publish', $context)) { 3893 $url = new moodle_url('/grade/import/keymanager.php', array('id'=>$courseid)); 3894 $importplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades')); 3895 } 3896 } 3897 3898 if (count($importplugins) > 0) { 3899 asort($importplugins); 3900 self::$importplugins = $importplugins; 3901 } else { 3902 self::$importplugins = false; 3903 } 3904 return self::$importplugins; 3905 } 3906 /** 3907 * Get information export plugins 3908 * @param int $courseid 3909 * @return array 3910 */ 3911 public static function get_plugins_export($courseid) { 3912 global $CFG; 3913 3914 if (self::$exportplugins !== null) { 3915 return self::$exportplugins; 3916 } 3917 $context = context_course::instance($courseid); 3918 $exportplugins = array(); 3919 $canpublishgrades = 0; 3920 if (has_capability('moodle/grade:export', $context)) { 3921 foreach (core_component::get_plugin_list('gradeexport') as $plugin => $plugindir) { 3922 if (!has_capability('gradeexport/'.$plugin.':view', $context)) { 3923 continue; 3924 } 3925 // All the grade export plugins has grade publishing capabilities. 3926 if (has_capability('gradeexport/'.$plugin.':publish', $context)) { 3927 $canpublishgrades++; 3928 } 3929 3930 $pluginstr = get_string('pluginname', 'gradeexport_'.$plugin); 3931 $url = new moodle_url('/grade/export/'.$plugin.'/index.php', array('id'=>$courseid)); 3932 $exportplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); 3933 } 3934 3935 // Show key manager if grade publishing is enabled and the user has at least one grade publishing capability. 3936 if ($CFG->gradepublishing && $canpublishgrades != 0) { 3937 $url = new moodle_url('/grade/export/keymanager.php', array('id'=>$courseid)); 3938 $exportplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades')); 3939 } 3940 } 3941 if (count($exportplugins) > 0) { 3942 asort($exportplugins); 3943 self::$exportplugins = $exportplugins; 3944 } else { 3945 self::$exportplugins = false; 3946 } 3947 return self::$exportplugins; 3948 } 3949 3950 /** 3951 * Returns the value of a field from a user record 3952 * 3953 * @param stdClass $user object 3954 * @param stdClass $field object 3955 * @return string value of the field 3956 */ 3957 public static function get_user_field_value($user, $field) { 3958 if (!empty($field->customid)) { 3959 $fieldname = 'customfield_' . $field->customid; 3960 if (!empty($user->{$fieldname}) || is_numeric($user->{$fieldname})) { 3961 $fieldvalue = $user->{$fieldname}; 3962 } else { 3963 $fieldvalue = $field->default; 3964 } 3965 } else { 3966 $fieldvalue = $user->{$field->shortname}; 3967 } 3968 return $fieldvalue; 3969 } 3970 3971 /** 3972 * Returns an array of user profile fields to be included in export 3973 * 3974 * @param int $courseid 3975 * @param bool $includecustomfields 3976 * @return array An array of stdClass instances with customid, shortname, datatype, default and fullname fields 3977 */ 3978 public static function get_user_profile_fields($courseid, $includecustomfields = false) { 3979 global $CFG, $DB; 3980 3981 // Gets the fields that have to be hidden 3982 $hiddenfields = array_map('trim', explode(',', $CFG->hiddenuserfields)); 3983 $context = context_course::instance($courseid); 3984 $canseehiddenfields = has_capability('moodle/course:viewhiddenuserfields', $context); 3985 if ($canseehiddenfields) { 3986 $hiddenfields = array(); 3987 } 3988 3989 $fields = array(); 3990 require_once($CFG->dirroot.'/user/lib.php'); // Loads user_get_default_fields() 3991 require_once($CFG->dirroot.'/user/profile/lib.php'); // Loads constants, such as PROFILE_VISIBLE_ALL 3992 $userdefaultfields = user_get_default_fields(); 3993 3994 // Sets the list of profile fields 3995 $userprofilefields = array_map('trim', explode(',', $CFG->grade_export_userprofilefields)); 3996 if (!empty($userprofilefields)) { 3997 foreach ($userprofilefields as $field) { 3998 $field = trim($field); 3999 if (in_array($field, $hiddenfields) || !in_array($field, $userdefaultfields)) { 4000 continue; 4001 } 4002 $obj = new stdClass(); 4003 $obj->customid = 0; 4004 $obj->shortname = $field; 4005 $obj->fullname = get_string($field); 4006 $fields[] = $obj; 4007 } 4008 } 4009 4010 // Sets the list of custom profile fields 4011 $customprofilefields = array_map('trim', explode(',', $CFG->grade_export_customprofilefields)); 4012 if ($includecustomfields && !empty($customprofilefields)) { 4013 $customfields = profile_get_user_fields_with_data(0); 4014 4015 foreach ($customfields as $fieldobj) { 4016 $field = (object)$fieldobj->get_field_config_for_external(); 4017 // Make sure we can display this custom field 4018 if (!in_array($field->shortname, $customprofilefields)) { 4019 continue; 4020 } else if (in_array($field->shortname, $hiddenfields)) { 4021 continue; 4022 } else if ($field->visible != PROFILE_VISIBLE_ALL && !$canseehiddenfields) { 4023 continue; 4024 } 4025 4026 $obj = new stdClass(); 4027 $obj->customid = $field->id; 4028 $obj->shortname = $field->shortname; 4029 $obj->fullname = format_string($field->name); 4030 $obj->datatype = $field->datatype; 4031 $obj->default = $field->defaultdata; 4032 $fields[] = $obj; 4033 } 4034 } 4035 4036 return $fields; 4037 } 4038 4039 /** 4040 * This helper method gets a snapshot of all the weights for a course. 4041 * It is used as a quick method to see if any wieghts have been automatically adjusted. 4042 * @param int $courseid 4043 * @return array of itemid -> aggregationcoef2 4044 */ 4045 public static function fetch_all_natural_weights_for_course($courseid) { 4046 global $DB; 4047 $result = array(); 4048 4049 $records = $DB->get_records('grade_items', array('courseid'=>$courseid), 'id', 'id, aggregationcoef2'); 4050 foreach ($records as $record) { 4051 $result[$record->id] = $record->aggregationcoef2; 4052 } 4053 return $result; 4054 } 4055 4056 /** 4057 * Resets all static caches. 4058 * 4059 * @return void 4060 */ 4061 public static function reset_caches() { 4062 self::$managesetting = null; 4063 self::$gradereports = null; 4064 self::$gradereportpreferences = null; 4065 self::$scaleinfo = null; 4066 self::$outcomeinfo = null; 4067 self::$letterinfo = null; 4068 self::$importplugins = null; 4069 self::$exportplugins = null; 4070 self::$pluginstrings = null; 4071 self::$aggregationstrings = null; 4072 } 4073 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body