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