|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00023 require_once($CFG->dirroot . '/grade/report/lib.php'); 00024 require_once($CFG->libdir.'/tablelib.php'); 00025 00031 class grade_report_grader extends grade_report { 00036 public $grades; 00037 00042 public $gradeserror = array(); 00043 00045 00050 public $sortitemid; 00051 00056 public $sortorder; 00057 00062 public $userselect; 00063 00068 public $userselectparams = array(); 00069 00074 public $collapsed; 00075 00080 public $rowcount = 0; 00081 00085 public $canviewhidden; 00086 00087 var $preferencespage=false; 00088 00094 protected $feedback_trunc_length = 50; 00095 00104 public function __construct($courseid, $gpr, $context, $page=null, $sortitemid=null) { 00105 global $CFG; 00106 parent::__construct($courseid, $gpr, $context, $page); 00107 00108 $this->canviewhidden = has_capability('moodle/grade:viewhidden', get_context_instance(CONTEXT_COURSE, $this->course->id)); 00109 00110 // load collapsed settings for this report 00111 if ($collapsed = get_user_preferences('grade_report_grader_collapsed_categories')) { 00112 $this->collapsed = unserialize($collapsed); 00113 } else { 00114 $this->collapsed = array('aggregatesonly' => array(), 'gradesonly' => array()); 00115 } 00116 00117 if (empty($CFG->enableoutcomes)) { 00118 $nooutcomes = false; 00119 } else { 00120 $nooutcomes = get_user_preferences('grade_report_shownooutcomes'); 00121 } 00122 00123 // if user report preference set or site report setting set use it, otherwise use course or site setting 00124 $switch = $this->get_pref('aggregationposition'); 00125 if ($switch == '') { 00126 $switch = grade_get_setting($this->courseid, 'aggregationposition', $CFG->grade_aggregationposition); 00127 } 00128 00129 // Grab the grade_tree for this course 00130 $this->gtree = new grade_tree($this->courseid, true, $switch, $this->collapsed, $nooutcomes); 00131 00132 $this->sortitemid = $sortitemid; 00133 00134 // base url for sorting by first/last name 00135 00136 $this->baseurl = new moodle_url('index.php', array('id' => $this->courseid)); 00137 00138 $studentsperpage = $this->get_pref('studentsperpage'); 00139 if (!empty($studentsperpage)) { 00140 $this->baseurl->params(array('perpage' => $studentsperpage, 'page' => $this->page)); 00141 } 00142 00143 $this->pbarurl = new moodle_url('/grade/report/grader/index.php', array('id' => $this->courseid, 'perpage' => $studentsperpage)); 00144 00145 $this->setup_groups(); 00146 00147 $this->setup_sortitemid(); 00148 } 00149 00156 public function process_data($data) { 00157 global $DB; 00158 $warnings = array(); 00159 00160 $separategroups = false; 00161 $mygroups = array(); 00162 if ($this->groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $this->context)) { 00163 $separategroups = true; 00164 $mygroups = groups_get_user_groups($this->course->id); 00165 $mygroups = $mygroups[0]; // ignore groupings 00166 // reorder the groups fro better perf below 00167 $current = array_search($this->currentgroup, $mygroups); 00168 if ($current !== false) { 00169 unset($mygroups[$current]); 00170 array_unshift($mygroups, $this->currentgroup); 00171 } 00172 } 00173 00174 // always initialize all arrays 00175 $queue = array(); 00176 foreach ($data as $varname => $postedvalue) { 00177 00178 $needsupdate = false; 00179 00180 // skip, not a grade nor feedback 00181 if (strpos($varname, 'grade') === 0) { 00182 $datatype = 'grade'; 00183 } else if (strpos($varname, 'feedback') === 0) { 00184 $datatype = 'feedback'; 00185 } else { 00186 continue; 00187 } 00188 00189 $gradeinfo = explode("_", $varname); 00190 $userid = clean_param($gradeinfo[1], PARAM_INT); 00191 $itemid = clean_param($gradeinfo[2], PARAM_INT); 00192 00193 $oldvalue = $data->{'old'.$varname}; 00194 00195 // was change requested? 00196 if ($oldvalue == $postedvalue) { // string comparison 00197 continue; 00198 } 00199 00200 if (!$gradeitem = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid))) { // we must verify course id here! 00201 print_error('invalidgradeitmeid'); 00202 } 00203 00204 // Pre-process grade 00205 if ($datatype == 'grade') { 00206 $feedback = false; 00207 $feedbackformat = false; 00208 if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { 00209 if ($postedvalue == -1) { // -1 means no grade 00210 $finalgrade = null; 00211 } else { 00212 $finalgrade = $postedvalue; 00213 } 00214 } else { 00215 $finalgrade = unformat_float($postedvalue); 00216 } 00217 00218 $errorstr = ''; 00219 // Warn if the grade is out of bounds. 00220 if (is_null($finalgrade)) { 00221 // ok 00222 } else { 00223 $bounded = $gradeitem->bounded_grade($finalgrade); 00224 if ($bounded > $finalgrade) { 00225 $errorstr = 'lessthanmin'; 00226 } else if ($bounded < $finalgrade) { 00227 $errorstr = 'morethanmax'; 00228 } 00229 } 00230 if ($errorstr) { 00231 $user = $DB->get_record('user', array('id' => $userid), 'id, firstname, lastname'); 00232 $gradestr = new stdClass(); 00233 $gradestr->username = fullname($user); 00234 $gradestr->itemname = $gradeitem->get_name(); 00235 $warnings[] = get_string($errorstr, 'grades', $gradestr); 00236 } 00237 00238 } else if ($datatype == 'feedback') { 00239 $finalgrade = false; 00240 $trimmed = trim($postedvalue); 00241 if (empty($trimmed)) { 00242 $feedback = NULL; 00243 } else { 00244 $feedback = $postedvalue; 00245 } 00246 } 00247 00248 // group access control 00249 if ($separategroups) { 00250 // note: we can not use $this->currentgroup because it would fail badly 00251 // when having two browser windows each with different group 00252 $sharinggroup = false; 00253 foreach($mygroups as $groupid) { 00254 if (groups_is_member($groupid, $userid)) { 00255 $sharinggroup = true; 00256 break; 00257 } 00258 } 00259 if (!$sharinggroup) { 00260 // either group membership changed or somebody is hacking grades of other group 00261 $warnings[] = get_string('errorsavegrade', 'grades'); 00262 continue; 00263 } 00264 } 00265 00266 $gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE); 00267 } 00268 00269 return $warnings; 00270 } 00271 00272 00278 private function setup_sortitemid() { 00279 00280 global $SESSION; 00281 00282 if ($this->sortitemid) { 00283 if (!isset($SESSION->gradeuserreport->sort)) { 00284 if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') { 00285 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; 00286 } else { 00287 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC'; 00288 } 00289 } else { 00290 // this is the first sort, i.e. by last name 00291 if (!isset($SESSION->gradeuserreport->sortitemid)) { 00292 if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') { 00293 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; 00294 } else { 00295 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC'; 00296 } 00297 } else if ($SESSION->gradeuserreport->sortitemid == $this->sortitemid) { 00298 // same as last sort 00299 if ($SESSION->gradeuserreport->sort == 'ASC') { 00300 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC'; 00301 } else { 00302 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; 00303 } 00304 } else { 00305 if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') { 00306 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; 00307 } else { 00308 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC'; 00309 } 00310 } 00311 } 00312 $SESSION->gradeuserreport->sortitemid = $this->sortitemid; 00313 } else { 00314 // not requesting sort, use last setting (for paging) 00315 00316 if (isset($SESSION->gradeuserreport->sortitemid)) { 00317 $this->sortitemid = $SESSION->gradeuserreport->sortitemid; 00318 }else{ 00319 $this->sortitemid = 'lastname'; 00320 } 00321 00322 if (isset($SESSION->gradeuserreport->sort)) { 00323 $this->sortorder = $SESSION->gradeuserreport->sort; 00324 } else { 00325 $this->sortorder = 'ASC'; 00326 } 00327 } 00328 } 00329 00333 public function load_users() { 00334 global $CFG, $DB; 00335 00336 //limit to users with a gradeable role 00337 list($gradebookrolessql, $gradebookrolesparams) = $DB->get_in_or_equal(explode(',', $this->gradebookroles), SQL_PARAMS_NAMED, 'grbr0'); 00338 00339 //limit to users with an active enrollment 00340 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context); 00341 00342 //fields we need from the user table 00343 $userfields = user_picture::fields('u'); 00344 $userfields .= get_extra_user_fields_sql($this->context); 00345 00346 $sortjoin = $sort = $params = null; 00347 00348 //if the user has clicked one of the sort asc/desc arrows 00349 if (is_numeric($this->sortitemid)) { 00350 $params = array_merge(array('gitemid'=>$this->sortitemid), $gradebookrolesparams, $this->groupwheresql_params, $enrolledparams); 00351 00352 $sortjoin = "LEFT JOIN {grade_grades} g ON g.userid = u.id AND g.itemid = $this->sortitemid"; 00353 $sort = "g.finalgrade $this->sortorder"; 00354 00355 } else { 00356 $sortjoin = ''; 00357 switch($this->sortitemid) { 00358 case 'lastname': 00359 $sort = "u.lastname $this->sortorder, u.firstname $this->sortorder"; 00360 break; 00361 case 'firstname': 00362 $sort = "u.firstname $this->sortorder, u.lastname $this->sortorder"; 00363 break; 00364 case 'idnumber': 00365 default: 00366 $sort = "u.idnumber $this->sortorder"; 00367 break; 00368 } 00369 00370 $params = array_merge($gradebookrolesparams, $this->groupwheresql_params, $enrolledparams); 00371 } 00372 00373 $sql = "SELECT $userfields 00374 FROM {user} u 00375 JOIN ($enrolledsql) je ON je.id = u.id 00376 $this->groupsql 00377 $sortjoin 00378 JOIN ( 00379 SELECT DISTINCT ra.userid 00380 FROM {role_assignments} ra 00381 WHERE ra.roleid IN ($this->gradebookroles) 00382 AND ra.contextid " . get_related_contexts_string($this->context) . " 00383 ) rainner ON rainner.userid = u.id 00384 AND u.deleted = 0 00385 $this->groupwheresql 00386 ORDER BY $sort"; 00387 00388 $this->users = $DB->get_records_sql($sql, $params, $this->get_pref('studentsperpage') * $this->page, $this->get_pref('studentsperpage')); 00389 00390 if (empty($this->users)) { 00391 $this->userselect = ''; 00392 $this->users = array(); 00393 $this->userselect_params = array(); 00394 } else { 00395 list($usql, $uparams) = $DB->get_in_or_equal(array_keys($this->users), SQL_PARAMS_NAMED, 'usid0'); 00396 $this->userselect = "AND g.userid $usql"; 00397 $this->userselect_params = $uparams; 00398 00399 //add a flag to each user indicating whether their enrolment is active 00400 $sql = "SELECT ue.userid 00401 FROM {user_enrolments} ue 00402 JOIN {enrol} e ON e.id = ue.enrolid 00403 WHERE ue.userid $usql 00404 AND ue.status = :uestatus 00405 AND e.status = :estatus 00406 AND e.courseid = :courseid 00407 GROUP BY ue.userid"; 00408 $coursecontext = get_course_context($this->context); 00409 $params = array_merge($uparams, array('estatus'=>ENROL_INSTANCE_ENABLED, 'uestatus'=>ENROL_USER_ACTIVE, 'courseid'=>$coursecontext->instanceid)); 00410 $useractiveenrolments = $DB->get_records_sql($sql, $params); 00411 00412 foreach ($this->users as $user) { 00413 $this->users[$user->id]->suspendedenrolment = !array_key_exists($user->id, $useractiveenrolments); 00414 } 00415 } 00416 00417 return $this->users; 00418 } 00419 00424 public function load_final_grades() { 00425 global $CFG, $DB; 00426 00427 // please note that we must fetch all grade_grades fields if we want to construct grade_grade object from it! 00428 $params = array_merge(array('courseid'=>$this->courseid), $this->userselect_params); 00429 $sql = "SELECT g.* 00430 FROM {grade_items} gi, 00431 {grade_grades} g 00432 WHERE g.itemid = gi.id AND gi.courseid = :courseid {$this->userselect}"; 00433 00434 $userids = array_keys($this->users); 00435 00436 00437 if ($grades = $DB->get_records_sql($sql, $params)) { 00438 foreach ($grades as $graderec) { 00439 if (in_array($graderec->userid, $userids) and array_key_exists($graderec->itemid, $this->gtree->get_items())) { // some items may not be present!! 00440 $this->grades[$graderec->userid][$graderec->itemid] = new grade_grade($graderec, false); 00441 $this->grades[$graderec->userid][$graderec->itemid]->grade_item =& $this->gtree->get_item($graderec->itemid); // db caching 00442 } 00443 } 00444 } 00445 00446 // prefil grades that do not exist yet 00447 foreach ($userids as $userid) { 00448 foreach ($this->gtree->get_items() as $itemid=>$unused) { 00449 if (!isset($this->grades[$userid][$itemid])) { 00450 $this->grades[$userid][$itemid] = new grade_grade(); 00451 $this->grades[$userid][$itemid]->itemid = $itemid; 00452 $this->grades[$userid][$itemid]->userid = $userid; 00453 $this->grades[$userid][$itemid]->grade_item =& $this->gtree->get_item($itemid); // db caching 00454 } 00455 } 00456 } 00457 } 00458 00463 public function get_toggles_html() { 00464 global $CFG, $USER, $COURSE, $OUTPUT; 00465 00466 $html = ''; 00467 if ($USER->gradeediting[$this->courseid]) { 00468 if (has_capability('moodle/grade:manage', $this->context) or has_capability('moodle/grade:hide', $this->context)) { 00469 $html .= $this->print_toggle('eyecons'); 00470 } 00471 if (has_capability('moodle/grade:manage', $this->context) 00472 or has_capability('moodle/grade:lock', $this->context) 00473 or has_capability('moodle/grade:unlock', $this->context)) { 00474 $html .= $this->print_toggle('locks'); 00475 } 00476 if (has_capability('moodle/grade:manage', $this->context)) { 00477 $html .= $this->print_toggle('quickfeedback'); 00478 } 00479 00480 if (has_capability('moodle/grade:manage', $this->context)) { 00481 $html .= $this->print_toggle('calculations'); 00482 } 00483 } 00484 00485 if ($this->canviewhidden) { 00486 $html .= $this->print_toggle('averages'); 00487 } 00488 00489 $html .= $this->print_toggle('ranges'); 00490 if (!empty($CFG->enableoutcomes)) { 00491 $html .= $this->print_toggle('nooutcomes'); 00492 } 00493 00494 return $OUTPUT->container($html, 'grade-report-toggles'); 00495 } 00496 00503 public function print_toggle($type) { 00504 global $CFG, $OUTPUT; 00505 00506 $icons = array('eyecons' => 't/hide', 00507 'calculations' => 't/calc', 00508 'locks' => 't/lock', 00509 'averages' => 't/mean', 00510 'quickfeedback' => 't/feedback', 00511 'nooutcomes' => 't/outcomes'); 00512 00513 $prefname = 'grade_report_show' . $type; 00514 00515 if (array_key_exists($prefname, $CFG)) { 00516 $showpref = get_user_preferences($prefname, $CFG->$prefname); 00517 } else { 00518 $showpref = get_user_preferences($prefname); 00519 } 00520 00521 $strshow = $this->get_lang_string('show' . $type, 'grades'); 00522 $strhide = $this->get_lang_string('hide' . $type, 'grades'); 00523 00524 $showhide = 'show'; 00525 $toggleaction = 1; 00526 00527 if ($showpref) { 00528 $showhide = 'hide'; 00529 $toggleaction = 0; 00530 } 00531 00532 if (array_key_exists($type, $icons)) { 00533 $imagename = $icons[$type]; 00534 } else { 00535 $imagename = "t/$type"; 00536 } 00537 00538 $string = ${'str' . $showhide}; 00539 00540 $url = new moodle_url($this->baseurl, array('toggle' => $toggleaction, 'toggle_type' => $type)); 00541 00542 $retval = $OUTPUT->container($OUTPUT->action_icon($url, new pix_icon($imagename, $string))); // TODO: this container looks wrong here 00543 00544 return $retval; 00545 } 00546 00554 public function get_left_rows() { 00555 global $CFG, $USER, $OUTPUT; 00556 00557 $rows = array(); 00558 00559 $showuserimage = $this->get_pref('showuserimage'); 00560 $fixedstudents = $this->is_fixed_students(); 00561 00562 $strfeedback = $this->get_lang_string("feedback"); 00563 $strgrade = $this->get_lang_string('grade'); 00564 00565 $extrafields = get_extra_user_fields($this->context); 00566 00567 $arrows = $this->get_sort_arrows($extrafields); 00568 00569 $colspan = 1; 00570 if (has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context)) { 00571 $colspan++; 00572 } 00573 $colspan += count($extrafields); 00574 00575 $levels = count($this->gtree->levels) - 1; 00576 00577 for ($i = 0; $i < $levels; $i++) { 00578 $fillercell = new html_table_cell(); 00579 $fillercell->attributes['class'] = 'fixedcolumn cell topleft'; 00580 $fillercell->text = ' '; 00581 $fillercell->colspan = $colspan; 00582 $row = new html_table_row(array($fillercell)); 00583 $rows[] = $row; 00584 } 00585 00586 $headerrow = new html_table_row(); 00587 $headerrow->attributes['class'] = 'heading'; 00588 00589 $studentheader = new html_table_cell(); 00590 $studentheader->attributes['class'] = 'header'; 00591 $studentheader->scope = 'col'; 00592 $studentheader->header = true; 00593 $studentheader->id = 'studentheader'; 00594 if (has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context)) { 00595 $studentheader->colspan = 2; 00596 } 00597 $studentheader->text = $arrows['studentname']; 00598 00599 $headerrow->cells[] = $studentheader; 00600 00601 foreach ($extrafields as $field) { 00602 $fieldheader = new html_table_cell(); 00603 $fieldheader->attributes['class'] = 'header userfield user' . $field; 00604 $fieldheader->scope = 'col'; 00605 $fieldheader->header = true; 00606 $fieldheader->text = $arrows[$field]; 00607 00608 $headerrow->cells[] = $fieldheader; 00609 } 00610 00611 $rows[] = $headerrow; 00612 00613 $rows = $this->get_left_icons_row($rows, $colspan); 00614 00615 $rowclasses = array('even', 'odd'); 00616 00617 $suspendedstring = null; 00618 foreach ($this->users as $userid => $user) { 00619 $userrow = new html_table_row(); 00620 $userrow->id = 'fixed_user_'.$userid; 00621 $userrow->attributes['class'] = 'r'.$this->rowcount++.' '.$rowclasses[$this->rowcount % 2]; 00622 00623 $usercell = new html_table_cell(); 00624 $usercell->attributes['class'] = 'user'; 00625 00626 $usercell->header = true; 00627 $usercell->scope = 'row'; 00628 00629 if ($showuserimage) { 00630 $usercell->text = $OUTPUT->user_picture($user); 00631 } 00632 00633 $usercell->text .= html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $this->course->id)), fullname($user)); 00634 00635 if (!empty($user->suspendedenrolment)) { 00636 $usercell->attributes['class'] .= ' usersuspended'; 00637 00638 //may be lots of suspended users so only get the string once 00639 if (empty($suspendedstring)) { 00640 $suspendedstring = get_string('userenrolmentsuspended', 'grades'); 00641 } 00642 $usercell->text .= html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('i/enrolmentsuspended'), 'title'=>$suspendedstring, 'alt'=>$suspendedstring, 'class'=>'usersuspendedicon')); 00643 } 00644 00645 $userrow->cells[] = $usercell; 00646 00647 if (has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context)) { 00648 $userreportcell = new html_table_cell(); 00649 $userreportcell->attributes['class'] = 'userreport'; 00650 $userreportcell->header = true; 00651 $a = new stdClass(); 00652 $a->user = fullname($user); 00653 $strgradesforuser = get_string('gradesforuser', 'grades', $a); 00654 $url = new moodle_url('/grade/report/'.$CFG->grade_profilereport.'/index.php', array('userid' => $user->id, 'id' => $this->course->id)); 00655 $userreportcell->text = $OUTPUT->action_icon($url, new pix_icon('t/grades', $strgradesforuser)); 00656 $userrow->cells[] = $userreportcell; 00657 } 00658 00659 foreach ($extrafields as $field) { 00660 $fieldcell = new html_table_cell(); 00661 $fieldcell->attributes['class'] = 'header userfield user' . $field; 00662 $fieldcell->header = true; 00663 $fieldcell->scope = 'row'; 00664 $fieldcell->text = $user->{$field}; 00665 $userrow->cells[] = $fieldcell; 00666 } 00667 00668 $rows[] = $userrow; 00669 } 00670 00671 $rows = $this->get_left_range_row($rows, $colspan); 00672 $rows = $this->get_left_avg_row($rows, $colspan, true); 00673 $rows = $this->get_left_avg_row($rows, $colspan); 00674 00675 return $rows; 00676 } 00677 00682 public function get_right_rows() { 00683 global $CFG, $USER, $OUTPUT, $DB, $PAGE; 00684 00685 $rows = array(); 00686 $this->rowcount = 0; 00687 $numrows = count($this->gtree->get_levels()); 00688 $numusers = count($this->users); 00689 $gradetabindex = 1; 00690 $columnstounset = array(); 00691 $strgrade = $this->get_lang_string('grade'); 00692 $strfeedback = $this->get_lang_string("feedback"); 00693 $arrows = $this->get_sort_arrows(); 00694 00695 $jsarguments = array( 00696 'id' => '#fixed_column', 00697 'cfg' => array('ajaxenabled'=>false), 00698 'items' => array(), 00699 'users' => array(), 00700 'feedback' => array() 00701 ); 00702 $jsscales = array(); 00703 00704 foreach ($this->gtree->get_levels() as $key=>$row) { 00705 if ($key == 0) { 00706 // do not display course grade category 00707 // continue; 00708 } 00709 00710 $headingrow = new html_table_row(); 00711 $headingrow->attributes['class'] = 'heading_name_row'; 00712 00713 foreach ($row as $columnkey => $element) { 00714 $sortlink = clone($this->baseurl); 00715 if (isset($element['object']->id)) { 00716 $sortlink->param('sortitemid', $element['object']->id); 00717 } 00718 00719 $eid = $element['eid']; 00720 $object = $element['object']; 00721 $type = $element['type']; 00722 $categorystate = @$element['categorystate']; 00723 00724 if (!empty($element['colspan'])) { 00725 $colspan = $element['colspan']; 00726 } else { 00727 $colspan = 1; 00728 } 00729 00730 if (!empty($element['depth'])) { 00731 $catlevel = 'catlevel'.$element['depth']; 00732 } else { 00733 $catlevel = ''; 00734 } 00735 00736 // Element is a filler 00737 if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') { 00738 $fillercell = new html_table_cell(); 00739 $fillercell->attributes['class'] = $type . ' ' . $catlevel; 00740 $fillercell->colspan = $colspan; 00741 $fillercell->text = ' '; 00742 $fillercell->header = true; 00743 $fillercell->scope = 'col'; 00744 $headingrow->cells[] = $fillercell; 00745 } 00746 // Element is a category 00747 else if ($type == 'category') { 00748 $categorycell = new html_table_cell(); 00749 $categorycell->attributes['class'] = 'category ' . $catlevel; 00750 $categorycell->colspan = $colspan; 00751 $categorycell->text = shorten_text($element['object']->get_name()); 00752 $categorycell->text .= $this->get_collapsing_icon($element); 00753 $categorycell->header = true; 00754 $categorycell->scope = 'col'; 00755 00756 // Print icons 00757 if ($USER->gradeediting[$this->courseid]) { 00758 $categorycell->text .= $this->get_icons($element); 00759 } 00760 00761 $headingrow->cells[] = $categorycell; 00762 } 00763 // Element is a grade_item 00764 else { 00765 //$itemmodule = $element['object']->itemmodule; 00766 //$iteminstance = $element['object']->iteminstance; 00767 00768 if ($element['object']->id == $this->sortitemid) { 00769 if ($this->sortorder == 'ASC') { 00770 $arrow = $this->get_sort_arrow('up', $sortlink); 00771 } else { 00772 $arrow = $this->get_sort_arrow('down', $sortlink); 00773 } 00774 } else { 00775 $arrow = $this->get_sort_arrow('move', $sortlink); 00776 } 00777 00778 $headerlink = $this->gtree->get_element_header($element, true, $this->get_pref('showactivityicons'), false); 00779 00780 $itemcell = new html_table_cell(); 00781 $itemcell->attributes['class'] = $type . ' ' . $catlevel . 'highlightable'; 00782 00783 if ($element['object']->is_hidden()) { 00784 $itemcell->attributes['class'] .= ' hidden'; 00785 } 00786 00787 $itemcell->colspan = $colspan; 00788 $itemcell->text = shorten_text($headerlink) . $arrow; 00789 $itemcell->header = true; 00790 $itemcell->scope = 'col'; 00791 00792 $headingrow->cells[] = $itemcell; 00793 } 00794 } 00795 $rows[] = $headingrow; 00796 } 00797 00798 $rows = $this->get_right_icons_row($rows); 00799 00800 // Preload scale objects for items with a scaleid and initialize tab indices 00801 $scaleslist = array(); 00802 $tabindices = array(); 00803 00804 foreach ($this->gtree->get_items() as $itemid=>$item) { 00805 $scale = null; 00806 if (!empty($item->scaleid)) { 00807 $scaleslist[] = $item->scaleid; 00808 $jsarguments['items'][$itemid] = array('id'=>$itemid, 'name'=>$item->get_name(true), 'type'=>'scale', 'scale'=>$item->scaleid, 'decimals'=>$item->get_decimals()); 00809 } else { 00810 $jsarguments['items'][$itemid] = array('id'=>$itemid, 'name'=>$item->get_name(true), 'type'=>'value', 'scale'=>false, 'decimals'=>$item->get_decimals()); 00811 } 00812 $tabindices[$item->id]['grade'] = $gradetabindex; 00813 $tabindices[$item->id]['feedback'] = $gradetabindex + $numusers; 00814 $gradetabindex += $numusers * 2; 00815 } 00816 $scalesarray = array(); 00817 00818 if (!empty($scaleslist)) { 00819 $scalesarray = $DB->get_records_list('scale', 'id', $scaleslist); 00820 } 00821 $jsscales = $scalesarray; 00822 00823 $rowclasses = array('even', 'odd'); 00824 00825 foreach ($this->users as $userid => $user) { 00826 00827 if ($this->canviewhidden) { 00828 $altered = array(); 00829 $unknown = array(); 00830 } else { 00831 $hidingaffected = grade_grade::get_hiding_affected($this->grades[$userid], $this->gtree->get_items()); 00832 $altered = $hidingaffected['altered']; 00833 $unknown = $hidingaffected['unknown']; 00834 unset($hidingaffected); 00835 } 00836 00837 00838 $itemrow = new html_table_row(); 00839 $itemrow->id = 'user_'.$userid; 00840 $itemrow->attributes['class'] = $rowclasses[$this->rowcount % 2]; 00841 00842 $jsarguments['users'][$userid] = fullname($user); 00843 00844 foreach ($this->gtree->items as $itemid=>$unused) { 00845 $item =& $this->gtree->items[$itemid]; 00846 $grade = $this->grades[$userid][$item->id]; 00847 00848 $itemcell = new html_table_cell(); 00849 00850 $itemcell->id = 'u'.$userid.'i'.$itemid; 00851 00852 // Get the decimal points preference for this item 00853 $decimalpoints = $item->get_decimals(); 00854 00855 if (in_array($itemid, $unknown)) { 00856 $gradeval = null; 00857 } else if (array_key_exists($itemid, $altered)) { 00858 $gradeval = $altered[$itemid]; 00859 } else { 00860 $gradeval = $grade->finalgrade; 00861 } 00862 00863 // MDL-11274 00864 // Hide grades in the grader report if the current grader doesn't have 'moodle/grade:viewhidden' 00865 if (!$this->canviewhidden and $grade->is_hidden()) { 00866 if (!empty($CFG->grade_hiddenasdate) and $grade->get_datesubmitted() and !$item->is_category_item() and !$item->is_course_item()) { 00867 // the problem here is that we do not have the time when grade value was modified, 'timemodified' is general modification date for grade_grades records 00868 $itemcell->text = html_writer::tag('span', userdate($grade->get_datesubmitted(),get_string('strftimedatetimeshort')), array('class'=>'datesubmitted')); 00869 } else { 00870 $itemcell->text = '-'; 00871 } 00872 $itemrow->cells[] = $itemcell; 00873 continue; 00874 } 00875 00876 // emulate grade element 00877 $eid = $this->gtree->get_grade_eid($grade); 00878 $element = array('eid'=>$eid, 'object'=>$grade, 'type'=>'grade'); 00879 00880 $itemcell->attributes['class'] .= ' grade'; 00881 if ($item->is_category_item()) { 00882 $itemcell->attributes['class'] .= ' cat'; 00883 } 00884 if ($item->is_course_item()) { 00885 $itemcell->attributes['class'] .= ' course'; 00886 } 00887 if ($grade->is_overridden()) { 00888 $itemcell->attributes['class'] .= ' overridden'; 00889 } 00890 00891 if ($grade->is_excluded()) { 00892 // $itemcell->attributes['class'] .= ' excluded'; 00893 } 00894 00895 if (!empty($grade->feedback)) { 00896 //should we be truncating feedback? ie $short_feedback = shorten_text($feedback, $this->feedback_trunc_length); 00897 $jsarguments['feedback'][] = array('user'=>$userid, 'item'=>$itemid, 'content'=>wordwrap(trim(format_string($grade->feedback, $grade->feedbackformat)), 34, '<br/ >')); 00898 } 00899 00900 if ($grade->is_excluded()) { 00901 $itemcell->text .= html_writer::tag('span', get_string('excluded', 'grades'), array('class'=>'excludedfloater')); 00902 } 00903 00904 // Do not show any icons if no grade (no record in DB to match) 00905 if (!$item->needsupdate and $USER->gradeediting[$this->courseid]) { 00906 $itemcell->text .= $this->get_icons($element); 00907 } 00908 00909 $hidden = ''; 00910 if ($grade->is_hidden()) { 00911 $hidden = ' hidden '; 00912 } 00913 00914 $gradepass = ' gradefail '; 00915 if ($grade->is_passed($item)) { 00916 $gradepass = ' gradepass '; 00917 } elseif (is_null($grade->is_passed($item))) { 00918 $gradepass = ''; 00919 } 00920 00921 // if in editing mode, we need to print either a text box 00922 // or a drop down (for scales) 00923 // grades in item of type grade category or course are not directly editable 00924 if ($item->needsupdate) { 00925 $itemcell->text .= html_writer::tag('span', get_string('error'), array('class'=>"gradingerror$hidden")); 00926 00927 } else if ($USER->gradeediting[$this->courseid]) { 00928 00929 if ($item->scaleid && !empty($scalesarray[$item->scaleid])) { 00930 $scale = $scalesarray[$item->scaleid]; 00931 $gradeval = (int)$gradeval; // scales use only integers 00932 $scales = explode(",", $scale->scale); 00933 // reindex because scale is off 1 00934 00935 // MDL-12104 some previous scales might have taken up part of the array 00936 // so this needs to be reset 00937 $scaleopt = array(); 00938 $i = 0; 00939 foreach ($scales as $scaleoption) { 00940 $i++; 00941 $scaleopt[$i] = $scaleoption; 00942 } 00943 00944 if ($this->get_pref('quickgrading') and $grade->is_editable()) { 00945 $oldval = empty($gradeval) ? -1 : $gradeval; 00946 if (empty($item->outcomeid)) { 00947 $nogradestr = $this->get_lang_string('nograde'); 00948 } else { 00949 $nogradestr = $this->get_lang_string('nooutcome', 'grades'); 00950 } 00951 $itemcell->text .= '<input type="hidden" id="oldgrade_'.$userid.'_'.$item->id.'" name="oldgrade_'.$userid.'_'.$item->id.'" value="'.$oldval.'"/>'; 00952 $attributes = array('tabindex' => $tabindices[$item->id]['grade'], 'id'=>'grade_'.$userid.'_'.$item->id); 00953 $itemcell->text .= html_writer::select($scaleopt, 'grade_'.$userid.'_'.$item->id, $gradeval, array(-1=>$nogradestr), $attributes);; 00954 } elseif(!empty($scale)) { 00955 $scales = explode(",", $scale->scale); 00956 00957 // invalid grade if gradeval < 1 00958 if ($gradeval < 1) { 00959 $itemcell->text .= html_writer::tag('span', '-', array('class'=>"gradevalue$hidden$gradepass")); 00960 } else { 00961 $gradeval = $grade->grade_item->bounded_grade($gradeval); //just in case somebody changes scale 00962 $itemcell->text .= html_writer::tag('span', $scales[$gradeval-1], array('class'=>"gradevalue$hidden$gradepass")); 00963 } 00964 } else { 00965 // no such scale, throw error? 00966 } 00967 00968 } else if ($item->gradetype != GRADE_TYPE_TEXT) { // Value type 00969 if ($this->get_pref('quickgrading') and $grade->is_editable()) { 00970 $value = format_float($gradeval, $decimalpoints); 00971 $itemcell->text .= '<input type="hidden" id="oldgrade_'.$userid.'_'.$item->id.'" name="oldgrade_'.$userid.'_'.$item->id.'" value="'.$value.'" />'; 00972 $itemcell->text .= '<input size="6" tabindex="' . $tabindices[$item->id]['grade'] 00973 . '" type="text" class="text" title="'. $strgrade .'" name="grade_' 00974 .$userid.'_' .$item->id.'" id="grade_'.$userid.'_'.$item->id.'" value="'.$value.'" />'; 00975 } else { 00976 $itemcell->text .= html_writer::tag('span', format_float($gradeval, $decimalpoints), array('class'=>"gradevalue$hidden$gradepass")); 00977 } 00978 } 00979 00980 00981 // If quickfeedback is on, print an input element 00982 if ($this->get_pref('showquickfeedback') and $grade->is_editable()) { 00983 00984 $itemcell->text .= '<input type="hidden" id="oldfeedback_'.$userid.'_'.$item->id.'" name="oldfeedback_'.$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '" />'; 00985 $itemcell->text .= '<input class="quickfeedback" tabindex="' . $tabindices[$item->id]['feedback'].'" id="feedback_'.$userid.'_'.$item->id 00986 . '" size="6" title="' . $strfeedback . '" type="text" name="feedback_'.$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '" />'; 00987 } 00988 00989 } else { // Not editing 00990 $gradedisplaytype = $item->get_displaytype(); 00991 00992 if ($item->scaleid && !empty($scalesarray[$item->scaleid])) { 00993 $itemcell->attributes['class'] .= ' grade_type_scale'; 00994 } else if ($item->gradetype != GRADE_TYPE_TEXT) { 00995 $itemcell->attributes['class'] .= ' grade_type_text'; 00996 } 00997 00998 if ($this->get_pref('enableajax')) { 00999 $itemcell->attributes['class'] .= ' clickable'; 01000 } 01001 01002 if ($item->needsupdate) { 01003 $itemcell->text .= html_writer::tag('span', get_string('error'), array('class'=>"gradingerror$hidden$gradepass")); 01004 } else { 01005 $itemcell->text .= html_writer::tag('span', grade_format_gradevalue($gradeval, $item, true, $gradedisplaytype, null), array('class'=>"gradevalue$hidden$gradepass")); 01006 if ($this->get_pref('showanalysisicon')) { 01007 $itemcell->text .= $this->gtree->get_grade_analysis_icon($grade); 01008 } 01009 } 01010 } 01011 01012 if (!empty($this->gradeserror[$item->id][$userid])) { 01013 $itemcell->text .= $this->gradeserror[$item->id][$userid]; 01014 } 01015 01016 $itemrow->cells[] = $itemcell; 01017 } 01018 $rows[] = $itemrow; 01019 } 01020 01021 if ($this->get_pref('enableajax')) { 01022 $jsarguments['cfg']['ajaxenabled'] = true; 01023 $jsarguments['cfg']['scales'] = array(); 01024 foreach ($jsscales as $scale) { 01025 $jsarguments['cfg']['scales'][$scale->id] = explode(',',$scale->scale); 01026 } 01027 $jsarguments['cfg']['feedbacktrunclength'] = $this->feedback_trunc_length; 01028 01029 //feedbacks are now being stored in $jsarguments['feedback'] in get_right_rows() 01030 //$jsarguments['cfg']['feedback'] = $this->feedbacks; 01031 } 01032 $jsarguments['cfg']['isediting'] = (bool)$USER->gradeediting[$this->courseid]; 01033 $jsarguments['cfg']['courseid'] = $this->courseid; 01034 $jsarguments['cfg']['studentsperpage'] = $this->get_pref('studentsperpage'); 01035 $jsarguments['cfg']['showquickfeedback'] = (bool)$this->get_pref('showquickfeedback'); 01036 01037 $module = array( 01038 'name' => 'gradereport_grader', 01039 'fullpath' => '/grade/report/grader/module.js', 01040 'requires' => array('base', 'dom', 'event', 'event-mouseenter', 'event-key', 'io-base', 'json-parse', 'overlay') 01041 ); 01042 $PAGE->requires->js_init_call('M.gradereport_grader.init_report', $jsarguments, false, $module); 01043 $PAGE->requires->strings_for_js(array('addfeedback','feedback', 'grade'), 'grades'); 01044 $PAGE->requires->strings_for_js(array('ajaxchoosescale','ajaxclicktoclose','ajaxerror','ajaxfailedupdate', 'ajaxfieldchanged'), 'gradereport_grader'); 01045 01046 $rows = $this->get_right_range_row($rows); 01047 $rows = $this->get_right_avg_row($rows, true); 01048 $rows = $this->get_right_avg_row($rows); 01049 01050 return $rows; 01051 } 01052 01059 public function get_grade_table() { 01060 global $OUTPUT; 01061 $fixedstudents = $this->is_fixed_students(); 01062 01063 $leftrows = $this->get_left_rows(); 01064 $rightrows = $this->get_right_rows(); 01065 01066 $html = ''; 01067 01068 01069 if ($fixedstudents) { 01070 $fixedcolumntable = new html_table(); 01071 $fixedcolumntable->id = 'fixed_column'; 01072 $fixedcolumntable->data = $leftrows; 01073 $html .= $OUTPUT->container(html_writer::table($fixedcolumntable), 'left_scroller'); 01074 01075 $righttable = new html_table(); 01076 $righttable->id = 'user-grades'; 01077 $righttable->data = $rightrows; 01078 01079 $html .= $OUTPUT->container(html_writer::table($righttable), 'right_scroller'); 01080 } else { 01081 $fulltable = new html_table(); 01082 $fulltable->attributes['class'] = 'gradestable flexible boxaligncenter generaltable'; 01083 $fulltable->id = 'user-grades'; 01084 01085 // Extract rows from each side (left and right) and collate them into one row each 01086 foreach ($leftrows as $key => $row) { 01087 $row->cells = array_merge($row->cells, $rightrows[$key]->cells); 01088 $fulltable->data[] = $row; 01089 } 01090 $html .= html_writer::table($fulltable); 01091 } 01092 return $OUTPUT->container($html, 'gradeparent'); 01093 } 01094 01102 public function get_left_icons_row($rows=array(), $colspan=1) { 01103 global $USER; 01104 01105 if ($USER->gradeediting[$this->courseid]) { 01106 $controlsrow = new html_table_row(); 01107 $controlsrow->attributes['class'] = 'controls'; 01108 $controlscell = new html_table_cell(); 01109 $controlscell->attributes['class'] = 'header controls'; 01110 $controlscell->colspan = $colspan; 01111 $controlscell->text = $this->get_lang_string('controls','grades'); 01112 01113 $controlsrow->cells[] = $controlscell; 01114 $rows[] = $controlsrow; 01115 } 01116 return $rows; 01117 } 01118 01125 public function get_left_range_row($rows=array(), $colspan=1) { 01126 global $CFG, $USER; 01127 01128 if ($this->get_pref('showranges')) { 01129 $rangerow = new html_table_row(); 01130 $rangerow->attributes['class'] = 'range r'.$this->rowcount++; 01131 $rangecell = new html_table_cell(); 01132 $rangecell->attributes['class'] = 'header range'; 01133 $rangecell->colspan = $colspan; 01134 $rangecell->header = true; 01135 $rangecell->scope = 'row'; 01136 $rangecell->text = $this->get_lang_string('range','grades'); 01137 $rangerow->cells[] = $rangecell; 01138 $rows[] = $rangerow; 01139 } 01140 01141 return $rows; 01142 } 01143 01151 public function get_left_avg_row($rows=array(), $colspan=1, $groupavg=false) { 01152 if (!$this->canviewhidden) { 01153 // totals might be affected by hiding, if user can not see hidden grades the aggregations might be altered 01154 // better not show them at all if user can not see all hideen grades 01155 return $rows; 01156 } 01157 01158 $showaverages = $this->get_pref('showaverages'); 01159 $showaveragesgroup = $this->currentgroup && $showaverages; 01160 $straveragegroup = get_string('groupavg', 'grades'); 01161 01162 if ($groupavg) { 01163 if ($showaveragesgroup) { 01164 $groupavgrow = new html_table_row(); 01165 $groupavgrow->attributes['class'] = 'groupavg r'.$this->rowcount++; 01166 $groupavgcell = new html_table_cell(); 01167 $groupavgcell->attributes['class'] = 'header range'; 01168 $groupavgcell->colspan = $colspan; 01169 $groupavgcell->header = true; 01170 $groupavgcell->scope = 'row'; 01171 $groupavgcell->text = $straveragegroup; 01172 $groupavgrow->cells[] = $groupavgcell; 01173 $rows[] = $groupavgrow; 01174 } 01175 } else { 01176 $straverage = get_string('overallaverage', 'grades'); 01177 01178 if ($showaverages) { 01179 $avgrow = new html_table_row(); 01180 $avgrow->attributes['class'] = 'avg r'.$this->rowcount++; 01181 $avgcell = new html_table_cell(); 01182 $avgcell->attributes['class'] = 'header range'; 01183 $avgcell->colspan = $colspan; 01184 $avgcell->header = true; 01185 $avgcell->scope = 'row'; 01186 $avgcell->text = $straverage; 01187 $avgrow->cells[] = $avgcell; 01188 $rows[] = $avgrow; 01189 } 01190 } 01191 01192 return $rows; 01193 } 01194 01200 public function get_right_icons_row($rows=array()) { 01201 global $USER; 01202 if ($USER->gradeediting[$this->courseid]) { 01203 $iconsrow = new html_table_row(); 01204 $iconsrow->attributes['class'] = 'controls'; 01205 01206 foreach ($this->gtree->items as $itemid=>$unused) { 01207 // emulate grade element 01208 $item =& $this->gtree->get_item($itemid); 01209 01210 $eid = $this->gtree->get_item_eid($item); 01211 $element = $this->gtree->locate_element($eid); 01212 $itemcell = new html_table_cell(); 01213 $itemcell->attributes['class'] = 'controls icons'; 01214 $itemcell->text = $this->get_icons($element); 01215 $iconsrow->cells[] = $itemcell; 01216 } 01217 $rows[] = $iconsrow; 01218 } 01219 return $rows; 01220 } 01221 01227 public function get_right_range_row($rows=array()) { 01228 global $OUTPUT; 01229 01230 if ($this->get_pref('showranges')) { 01231 $rangesdisplaytype = $this->get_pref('rangesdisplaytype'); 01232 $rangesdecimalpoints = $this->get_pref('rangesdecimalpoints'); 01233 $rangerow = new html_table_row(); 01234 $rangerow->attributes['class'] = 'heading range'; 01235 01236 foreach ($this->gtree->items as $itemid=>$unused) { 01237 $item =& $this->gtree->items[$itemid]; 01238 $itemcell = new html_table_cell(); 01239 $itemcell->header = true; 01240 $itemcell->attributes['class'] .= ' header range'; 01241 01242 $hidden = ''; 01243 if ($item->is_hidden()) { 01244 $hidden = ' hidden '; 01245 } 01246 01247 $formattedrange = $item->get_formatted_range($rangesdisplaytype, $rangesdecimalpoints); 01248 01249 $itemcell->text = $OUTPUT->container($formattedrange, 'rangevalues'.$hidden); 01250 $rangerow->cells[] = $itemcell; 01251 } 01252 $rows[] = $rangerow; 01253 } 01254 return $rows; 01255 } 01256 01263 public function get_right_avg_row($rows=array(), $grouponly=false) { 01264 global $CFG, $USER, $DB, $OUTPUT; 01265 01266 if (!$this->canviewhidden) { 01267 // totals might be affected by hiding, if user can not see hidden grades the aggregations might be altered 01268 // better not show them at all if user can not see all hidden grades 01269 return $rows; 01270 } 01271 01272 $showaverages = $this->get_pref('showaverages'); 01273 $showaveragesgroup = $this->currentgroup && $showaverages; 01274 01275 $averagesdisplaytype = $this->get_pref('averagesdisplaytype'); 01276 $averagesdecimalpoints = $this->get_pref('averagesdecimalpoints'); 01277 $meanselection = $this->get_pref('meanselection'); 01278 $shownumberofgrades = $this->get_pref('shownumberofgrades'); 01279 01280 $avghtml = ''; 01281 $avgcssclass = 'avg'; 01282 01283 if ($grouponly) { 01284 $straverage = get_string('groupavg', 'grades'); 01285 $showaverages = $this->currentgroup && $this->get_pref('showaverages'); 01286 $groupsql = $this->groupsql; 01287 $groupwheresql = $this->groupwheresql; 01288 $groupwheresqlparams = $this->groupwheresql_params; 01289 $avgcssclass = 'groupavg'; 01290 } else { 01291 $straverage = get_string('overallaverage', 'grades'); 01292 $showaverages = $this->get_pref('showaverages'); 01293 $groupsql = ""; 01294 $groupwheresql = ""; 01295 $groupwheresqlparams = array(); 01296 } 01297 01298 if ($shownumberofgrades) { 01299 $straverage .= ' (' . get_string('submissions', 'grades') . ') '; 01300 } 01301 01302 $totalcount = $this->get_numusers($grouponly); 01303 01304 //limit to users with a gradeable role 01305 list($gradebookrolessql, $gradebookrolesparams) = $DB->get_in_or_equal(explode(',', $this->gradebookroles), SQL_PARAMS_NAMED, 'grbr0'); 01306 01307 //limit to users with an active enrollment 01308 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context); 01309 01310 if ($showaverages) { 01311 $params = array_merge(array('courseid'=>$this->courseid), $gradebookrolesparams, $enrolledparams, $groupwheresqlparams); 01312 01313 // find sums of all grade items in course 01314 $sql = "SELECT g.itemid, SUM(g.finalgrade) AS sum 01315 FROM {grade_items} gi 01316 JOIN {grade_grades} g ON g.itemid = gi.id 01317 JOIN {user} u ON u.id = g.userid 01318 JOIN ($enrolledsql) je ON je.id = u.id 01319 JOIN ( 01320 SELECT DISTINCT ra.userid 01321 FROM {role_assignments} ra 01322 WHERE ra.roleid $gradebookrolessql 01323 AND ra.contextid " . get_related_contexts_string($this->context) . " 01324 ) rainner ON rainner.userid = u.id 01325 $groupsql 01326 WHERE gi.courseid = :courseid 01327 AND u.deleted = 0 01328 AND g.finalgrade IS NOT NULL 01329 $groupwheresql 01330 GROUP BY g.itemid"; 01331 $sumarray = array(); 01332 if ($sums = $DB->get_records_sql($sql, $params)) { 01333 foreach ($sums as $itemid => $csum) { 01334 $sumarray[$itemid] = $csum->sum; 01335 } 01336 } 01337 01338 // MDL-10875 Empty grades must be evaluated as grademin, NOT always 0 01339 // This query returns a count of ungraded grades (NULL finalgrade OR no matching record in grade_grades table) 01340 $sql = "SELECT gi.id, COUNT(DISTINCT u.id) AS count 01341 FROM {grade_items} gi 01342 CROSS JOIN {user} u 01343 JOIN ($enrolledsql) je 01344 ON je.id = u.id 01345 JOIN {role_assignments} ra 01346 ON ra.userid = u.id 01347 LEFT OUTER JOIN {grade_grades} g 01348 ON (g.itemid = gi.id AND g.userid = u.id AND g.finalgrade IS NOT NULL) 01349 $groupsql 01350 WHERE gi.courseid = :courseid 01351 AND ra.roleid $gradebookrolessql 01352 AND ra.contextid ".get_related_contexts_string($this->context)." 01353 AND u.deleted = 0 01354 AND g.id IS NULL 01355 $groupwheresql 01356 GROUP BY gi.id"; 01357 01358 $ungradedcounts = $DB->get_records_sql($sql, $params); 01359 01360 $avgrow = new html_table_row(); 01361 $avgrow->attributes['class'] = 'avg'; 01362 01363 foreach ($this->gtree->items as $itemid=>$unused) { 01364 $item =& $this->gtree->items[$itemid]; 01365 01366 if ($item->needsupdate) { 01367 $avgcell = new html_table_cell(); 01368 $avgcell->text = $OUTPUT->container(get_string('error'), 'gradingerror'); 01369 $avgrow->cells[] = $avgcell; 01370 continue; 01371 } 01372 01373 if (!isset($sumarray[$item->id])) { 01374 $sumarray[$item->id] = 0; 01375 } 01376 01377 if (empty($ungradedcounts[$itemid])) { 01378 $ungradedcount = 0; 01379 } else { 01380 $ungradedcount = $ungradedcounts[$itemid]->count; 01381 } 01382 01383 if ($meanselection == GRADE_REPORT_MEAN_GRADED) { 01384 $meancount = $totalcount - $ungradedcount; 01385 } else { // Bump up the sum by the number of ungraded items * grademin 01386 $sumarray[$item->id] += $ungradedcount * $item->grademin; 01387 $meancount = $totalcount; 01388 } 01389 01390 $decimalpoints = $item->get_decimals(); 01391 01392 // Determine which display type to use for this average 01393 if ($USER->gradeediting[$this->courseid]) { 01394 $displaytype = GRADE_DISPLAY_TYPE_REAL; 01395 01396 } else if ($averagesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT) { // no ==0 here, please resave the report and user preferences 01397 $displaytype = $item->get_displaytype(); 01398 01399 } else { 01400 $displaytype = $averagesdisplaytype; 01401 } 01402 01403 // Override grade_item setting if a display preference (not inherit) was set for the averages 01404 if ($averagesdecimalpoints == GRADE_REPORT_PREFERENCE_INHERIT) { 01405 $decimalpoints = $item->get_decimals(); 01406 01407 } else { 01408 $decimalpoints = $averagesdecimalpoints; 01409 } 01410 01411 if (!isset($sumarray[$item->id]) || $meancount == 0) { 01412 $avgcell = new html_table_cell(); 01413 $avgcell->text = '-'; 01414 $avgrow->cells[] = $avgcell; 01415 01416 } else { 01417 $sum = $sumarray[$item->id]; 01418 $avgradeval = $sum/$meancount; 01419 $gradehtml = grade_format_gradevalue($avgradeval, $item, true, $displaytype, $decimalpoints); 01420 01421 $numberofgrades = ''; 01422 if ($shownumberofgrades) { 01423 $numberofgrades = " ($meancount)"; 01424 } 01425 01426 $avgcell = new html_table_cell(); 01427 $avgcell->text = $gradehtml.$numberofgrades; 01428 $avgrow->cells[] = $avgcell; 01429 } 01430 } 01431 $rows[] = $avgrow; 01432 } 01433 return $rows; 01434 } 01435 01444 protected function get_icons($element) { 01445 global $CFG, $USER, $OUTPUT; 01446 01447 if (!$USER->gradeediting[$this->courseid]) { 01448 return '<div class="grade_icons" />'; 01449 } 01450 01451 // Init all icons 01452 $editicon = ''; 01453 01454 if ($element['type'] != 'categoryitem' && $element['type'] != 'courseitem') { 01455 $editicon = $this->gtree->get_edit_icon($element, $this->gpr); 01456 } 01457 01458 $editcalculationicon = ''; 01459 $showhideicon = ''; 01460 $lockunlockicon = ''; 01461 01462 if (has_capability('moodle/grade:manage', $this->context)) { 01463 if ($this->get_pref('showcalculations')) { 01464 $editcalculationicon = $this->gtree->get_calculation_icon($element, $this->gpr); 01465 } 01466 01467 if ($this->get_pref('showeyecons')) { 01468 $showhideicon = $this->gtree->get_hiding_icon($element, $this->gpr); 01469 } 01470 01471 if ($this->get_pref('showlocks')) { 01472 $lockunlockicon = $this->gtree->get_locking_icon($element, $this->gpr); 01473 } 01474 01475 } 01476 01477 $gradeanalysisicon = ''; 01478 if ($this->get_pref('showanalysisicon') && $element['type'] == 'grade') { 01479 $gradeanalysisicon .= $this->gtree->get_grade_analysis_icon($element['object']); 01480 } 01481 01482 return $OUTPUT->container($editicon.$editcalculationicon.$showhideicon.$lockunlockicon.$gradeanalysisicon, 'grade_icons'); 01483 } 01484 01490 protected function get_collapsing_icon($element) { 01491 global $OUTPUT; 01492 01493 $icon = ''; 01494 // If object is a category, display expand/contract icon 01495 if ($element['type'] == 'category') { 01496 // Load language strings 01497 $strswitchminus = $this->get_lang_string('aggregatesonly', 'grades'); 01498 $strswitchplus = $this->get_lang_string('gradesonly', 'grades'); 01499 $strswitchwhole = $this->get_lang_string('fullmode', 'grades'); 01500 01501 $url = new moodle_url($this->gpr->get_return_url(null, array('target'=>$element['eid'], 'sesskey'=>sesskey()))); 01502 01503 if (in_array($element['object']->id, $this->collapsed['aggregatesonly'])) { 01504 $url->param('action', 'switch_plus'); 01505 $icon = $OUTPUT->action_icon($url, new pix_icon('t/switch_plus', $strswitchplus)); 01506 01507 } else if (in_array($element['object']->id, $this->collapsed['gradesonly'])) { 01508 $url->param('action', 'switch_whole'); 01509 $icon = $OUTPUT->action_icon($url, new pix_icon('t/switch_whole', $strswitchwhole)); 01510 01511 } else { 01512 $url->param('action', 'switch_minus'); 01513 $icon = $OUTPUT->action_icon($url, new pix_icon('t/switch_minus', $strswitchminus)); 01514 } 01515 } 01516 return $icon; 01517 } 01518 01525 public function process_action($target, $action) { 01526 // TODO: this code should be in some grade_tree static method 01527 $targettype = substr($target, 0, 1); 01528 $targetid = substr($target, 1); 01529 // TODO: end 01530 01531 if ($collapsed = get_user_preferences('grade_report_grader_collapsed_categories')) { 01532 $collapsed = unserialize($collapsed); 01533 } else { 01534 $collapsed = array('aggregatesonly' => array(), 'gradesonly' => array()); 01535 } 01536 01537 switch ($action) { 01538 case 'switch_minus': // Add category to array of aggregatesonly 01539 if (!in_array($targetid, $collapsed['aggregatesonly'])) { 01540 $collapsed['aggregatesonly'][] = $targetid; 01541 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed)); 01542 } 01543 break; 01544 01545 case 'switch_plus': // Remove category from array of aggregatesonly, and add it to array of gradesonly 01546 $key = array_search($targetid, $collapsed['aggregatesonly']); 01547 if ($key !== false) { 01548 unset($collapsed['aggregatesonly'][$key]); 01549 } 01550 if (!in_array($targetid, $collapsed['gradesonly'])) { 01551 $collapsed['gradesonly'][] = $targetid; 01552 } 01553 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed)); 01554 break; 01555 case 'switch_whole': // Remove the category from the array of collapsed cats 01556 $key = array_search($targetid, $collapsed['gradesonly']); 01557 if ($key !== false) { 01558 unset($collapsed['gradesonly'][$key]); 01559 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed)); 01560 } 01561 01562 break; 01563 default: 01564 break; 01565 } 01566 01567 return true; 01568 } 01569 01576 public function is_fixed_students() { 01577 global $USER, $CFG; 01578 return empty($USER->screenreader) && $CFG->grade_report_fixedstudents && 01579 (check_browser_version('MSIE', '7.0') || 01580 check_browser_version('Firefox', '2.0') || 01581 check_browser_version('Gecko', '2006010100') || 01582 check_browser_version('Camino', '1.0') || 01583 check_browser_version('Opera', '6.0') || 01584 check_browser_version('Chrome', '6') || 01585 check_browser_version('Safari', '300')); 01586 } 01587 01596 public function get_sort_arrows(array $extrafields = array()) { 01597 global $OUTPUT; 01598 $arrows = array(); 01599 01600 $strsortasc = $this->get_lang_string('sortasc', 'grades'); 01601 $strsortdesc = $this->get_lang_string('sortdesc', 'grades'); 01602 $strfirstname = $this->get_lang_string('firstname'); 01603 $strlastname = $this->get_lang_string('lastname'); 01604 01605 $firstlink = html_writer::link(new moodle_url($this->baseurl, array('sortitemid'=>'firstname')), $strfirstname); 01606 $lastlink = html_writer::link(new moodle_url($this->baseurl, array('sortitemid'=>'lastname')), $strlastname); 01607 01608 $arrows['studentname'] = $lastlink; 01609 01610 if ($this->sortitemid === 'lastname') { 01611 if ($this->sortorder == 'ASC') { 01612 $arrows['studentname'] .= print_arrow('up', $strsortasc, true); 01613 } else { 01614 $arrows['studentname'] .= print_arrow('down', $strsortdesc, true); 01615 } 01616 } 01617 01618 $arrows['studentname'] .= ' ' . $firstlink; 01619 01620 if ($this->sortitemid === 'firstname') { 01621 if ($this->sortorder == 'ASC') { 01622 $arrows['studentname'] .= print_arrow('up', $strsortasc, true); 01623 } else { 01624 $arrows['studentname'] .= print_arrow('down', $strsortdesc, true); 01625 } 01626 } 01627 01628 foreach ($extrafields as $field) { 01629 $fieldlink = html_writer::link(new moodle_url($this->baseurl, 01630 array('sortitemid'=>$field)), get_user_field_name($field)); 01631 $arrows[$field] = $fieldlink; 01632 01633 if ($field == $this->sortitemid) { 01634 if ($this->sortorder == 'ASC') { 01635 $arrows[$field] .= print_arrow('up', $strsortasc, true); 01636 } else { 01637 $arrows[$field] .= print_arrow('down', $strsortdesc, true); 01638 } 01639 } 01640 } 01641 01642 return $arrows; 01643 } 01644 } 01645