Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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 * Renderable class for gradehistory report. 19 * 20 * @package gradereport_history 21 * @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace gradereport_history\output; 26 27 defined('MOODLE_INTERNAL') || die; 28 29 require_once($CFG->libdir . '/tablelib.php'); 30 31 /** 32 * Renderable class for gradehistory report. 33 * 34 * @since Moodle 2.8 35 * @package gradereport_history 36 * @copyright 2014 onwards Ankit Agarwal <ankit.agrr@gmail.com> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class tablelog extends \table_sql implements \renderable { 40 41 /** 42 * @var int course id. 43 */ 44 protected $courseid; 45 46 /** 47 * @var \context context of the page to be rendered. 48 */ 49 protected $context; 50 51 /** 52 * @var \stdClass A list of filters to be applied to the sql query. 53 */ 54 protected $filters; 55 56 /** 57 * @var array A list of grade items present in the course. 58 */ 59 protected $gradeitems = array(); 60 61 /** 62 * @var \course_modinfo|null A list of cm instances in course. 63 */ 64 protected $cms; 65 66 /** 67 * @var int The default number of decimal points to use in this course 68 * when a grade item does not itself define the number of decimal points. 69 */ 70 protected $defaultdecimalpoints; 71 72 /** 73 * Sets up the table_log parameters. 74 * 75 * @param string $uniqueid unique id of table. 76 * @param \context_course $context Context of the report. 77 * @param \moodle_url $url url of the page where this table would be displayed. 78 * @param array $filters options are: 79 * userids : limit to specific users (default: none) 80 * itemid : limit to specific grade item (default: all) 81 * grader : limit to specific graders (default: all) 82 * datefrom : start of date range 83 * datetill : end of date range 84 * revisedonly : only show revised grades (default: false) 85 * format : page | csv | excel (default: page) 86 * @param string $download Represents download format, pass '' no download at this time. 87 * @param int $page The current page being displayed. 88 * @param int $perpage Number of rules to display per page. 89 */ 90 public function __construct($uniqueid, \context_course $context, $url, $filters = array(), $download = '', $page = 0, 91 $perpage = 100) { 92 global $CFG; 93 parent::__construct($uniqueid); 94 95 $this->set_attribute('class', 'gradereport_history generaltable generalbox'); 96 97 // Set protected properties. 98 $this->context = $context; 99 $this->courseid = $this->context->instanceid; 100 $this->pagesize = $perpage; 101 $this->page = $page; 102 $this->filters = (object)$filters; 103 $this->gradeitems = \grade_item::fetch_all(array('courseid' => $this->courseid)); 104 $this->cms = get_fast_modinfo($this->courseid); 105 $this->useridfield = 'userid'; 106 $this->defaultdecimalpoints = grade_get_setting($this->courseid, 'decimalpoints', $CFG->grade_decimalpoints); 107 108 // Define columns in the table. 109 $this->define_table_columns(); 110 111 // Define configs. 112 $this->define_table_configs($url); 113 114 // Set download status. 115 $this->is_downloading($download, get_string('exportfilename', 'gradereport_history')); 116 } 117 118 /** 119 * Define table configs. 120 * 121 * @param \moodle_url $url url of the page where this table would be displayed. 122 */ 123 protected function define_table_configs(\moodle_url $url) { 124 125 // Set table url. 126 $urlparams = (array)$this->filters; 127 unset($urlparams['submitbutton']); 128 unset($urlparams['userfullnames']); 129 $url->params($urlparams); 130 $this->define_baseurl($url); 131 132 // Set table configs. 133 $this->collapsible(true); 134 $this->sortable(true, 'timemodified', SORT_DESC); 135 $this->pageable(true); 136 $this->no_sorting('grader'); 137 } 138 139 /** 140 * Setup the headers for the html table. 141 */ 142 protected function define_table_columns() { 143 $extrafields = get_extra_user_fields($this->context); 144 145 // Define headers and columns. 146 $cols = array( 147 'timemodified' => get_string('datetime', 'gradereport_history'), 148 'fullname' => get_string('name') 149 ); 150 151 // Add headers for extra user fields. 152 foreach ($extrafields as $field) { 153 if (get_string_manager()->string_exists($field, 'moodle')) { 154 $cols[$field] = get_string($field); 155 } else { 156 $cols[$field] = $field; 157 } 158 } 159 160 // Add remaining headers. 161 $cols = array_merge($cols, array( 162 'itemname' => get_string('gradeitem', 'grades'), 163 'prevgrade' => get_string('gradeold', 'gradereport_history'), 164 'finalgrade' => get_string('gradenew', 'gradereport_history'), 165 'grader' => get_string('grader', 'gradereport_history'), 166 'source' => get_string('source', 'gradereport_history'), 167 'overridden' => get_string('overridden', 'grades'), 168 'locked' => get_string('locked', 'grades'), 169 'excluded' => get_string('excluded', 'gradereport_history'), 170 'feedback' => get_string('feedbacktext', 'gradereport_history') 171 ) 172 ); 173 174 $this->define_columns(array_keys($cols)); 175 $this->define_headers(array_values($cols)); 176 } 177 178 /** 179 * Method to display the final grade. 180 * 181 * @param \stdClass $history an entry of history record. 182 * 183 * @return string HTML to display 184 */ 185 public function col_finalgrade(\stdClass $history) { 186 if (!empty($this->gradeitems[$history->itemid])) { 187 $decimalpoints = $this->gradeitems[$history->itemid]->get_decimals(); 188 } else { 189 $decimalpoints = $this->defaultdecimalpoints; 190 } 191 192 return format_float($history->finalgrade, $decimalpoints); 193 } 194 195 /** 196 * Method to display the previous grade. 197 * 198 * @param \stdClass $history an entry of history record. 199 * 200 * @return string HTML to display 201 */ 202 public function col_prevgrade(\stdClass $history) { 203 if (!empty($this->gradeitems[$history->itemid])) { 204 $decimalpoints = $this->gradeitems[$history->itemid]->get_decimals(); 205 } else { 206 $decimalpoints = $this->defaultdecimalpoints; 207 } 208 209 return format_float($history->prevgrade, $decimalpoints); 210 } 211 212 /** 213 * Method to display column timemodifed. 214 * 215 * @param \stdClass $history an entry of history record. 216 * 217 * @return string HTML to display 218 */ 219 public function col_timemodified(\stdClass $history) { 220 return userdate($history->timemodified); 221 } 222 223 /** 224 * Method to display column itemname. 225 * 226 * @param \stdClass $history an entry of history record. 227 * 228 * @return string HTML to display 229 */ 230 public function col_itemname(\stdClass $history) { 231 // Make sure grade item is still present and link it to the module if possible. 232 $itemid = $history->itemid; 233 if (!empty($this->gradeitems[$itemid])) { 234 if ($history->itemtype === 'mod' && !$this->is_downloading()) { 235 if (!empty($this->cms->instances[$history->itemmodule][$history->iteminstance])) { 236 $cm = $this->cms->instances[$history->itemmodule][$history->iteminstance]; 237 $url = new \moodle_url('/mod/' . $history->itemmodule . '/view.php', array('id' => $cm->id)); 238 return \html_writer::link($url, $this->gradeitems[$itemid]->get_name()); 239 } 240 } 241 return $this->gradeitems[$itemid]->get_name(); 242 } 243 return get_string('deleteditemid', 'gradereport_history', $history->itemid); 244 } 245 246 /** 247 * Method to display column grader. 248 * 249 * @param \stdClass $history an entry of history record. 250 * 251 * @return string HTML to display 252 */ 253 public function col_grader(\stdClass $history) { 254 if (empty($history->usermodified)) { 255 // Not every row has a valid usermodified. 256 return ''; 257 } 258 259 $grader = new \stdClass(); 260 $grader = username_load_fields_from_object($grader, $history, 'grader'); 261 $name = fullname($grader); 262 263 if ($this->download) { 264 return $name; 265 } 266 267 $userid = $history->usermodified; 268 $profileurl = new \moodle_url('/user/view.php', array('id' => $userid, 'course' => $this->courseid)); 269 270 return \html_writer::link($profileurl, $name); 271 } 272 273 /** 274 * Method to display column overridden. 275 * 276 * @param \stdClass $history an entry of history record. 277 * 278 * @return string HTML to display 279 */ 280 public function col_overridden(\stdClass $history) { 281 return $history->overridden ? get_string('yes') : get_string('no'); 282 } 283 284 /** 285 * Method to display column locked. 286 * 287 * @param \stdClass $history an entry of history record. 288 * 289 * @return string HTML to display 290 */ 291 public function col_locked(\stdClass $history) { 292 return $history->locked ? get_string('yes') : get_string('no'); 293 } 294 295 /** 296 * Method to display column excluded. 297 * 298 * @param \stdClass $history an entry of history record. 299 * 300 * @return string HTML to display 301 */ 302 public function col_excluded(\stdClass $history) { 303 return $history->excluded ? get_string('yes') : get_string('no'); 304 } 305 306 /** 307 * Method to display column feedback. 308 * 309 * @param \stdClass $history an entry of history record. 310 * 311 * @return string HTML to display 312 */ 313 public function col_feedback(\stdClass $history) { 314 if ($this->is_downloading()) { 315 return $history->feedback; 316 } else { 317 // We need the activity context, not the course context. 318 $gradeitem = $this->gradeitems[$history->itemid]; 319 $context = $gradeitem->get_context(); 320 321 $feedback = file_rewrite_pluginfile_urls( 322 $history->feedback, 323 'pluginfile.php', 324 $context->id, 325 GRADE_FILE_COMPONENT, 326 GRADE_HISTORY_FEEDBACK_FILEAREA, 327 $history->id 328 ); 329 330 return format_text($feedback, $history->feedbackformat, array('context' => $context)); 331 } 332 } 333 334 /** 335 * Builds the sql and param list needed, based on the user selected filters. 336 * 337 * @return array containing sql to use and an array of params. 338 */ 339 protected function get_filters_sql_and_params() { 340 global $DB, $USER; 341 342 $coursecontext = $this->context; 343 $filter = 'gi.courseid = :courseid'; 344 $params = array( 345 'courseid' => $coursecontext->instanceid, 346 ); 347 348 if (!empty($this->filters->itemid)) { 349 $filter .= ' AND ggh.itemid = :itemid'; 350 $params['itemid'] = $this->filters->itemid; 351 } 352 if (!empty($this->filters->userids)) { 353 $list = explode(',', $this->filters->userids); 354 list($insql, $plist) = $DB->get_in_or_equal($list, SQL_PARAMS_NAMED); 355 $filter .= " AND ggh.userid $insql"; 356 $params += $plist; 357 } 358 if (!empty($this->filters->datefrom)) { 359 $filter .= " AND ggh.timemodified >= :datefrom"; 360 $params += array('datefrom' => $this->filters->datefrom); 361 } 362 if (!empty($this->filters->datetill)) { 363 $filter .= " AND ggh.timemodified <= :datetill"; 364 $params += array('datetill' => $this->filters->datetill); 365 } 366 if (!empty($this->filters->grader)) { 367 $filter .= " AND ggh.usermodified = :grader"; 368 $params += array('grader' => $this->filters->grader); 369 } 370 371 // If the course is separate group mode and the current user is not allowed to see all groups make sure 372 // that we display only users from the same groups as current user. 373 $groupmode = get_course($coursecontext->instanceid)->groupmode; 374 if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $coursecontext)) { 375 $groupids = array_column(groups_get_all_groups($coursecontext->instanceid, $USER->id, 0, 'g.id'), 'id'); 376 list($gsql, $gparams) = $DB->get_in_or_equal($groupids, SQL_PARAMS_NAMED, 'gmuparam', true, 0); 377 $filter .= " AND EXISTS (SELECT 1 FROM {groups_members} gmu WHERE gmu.userid=ggh.userid AND gmu.groupid $gsql)"; 378 $params += $gparams; 379 } 380 381 return array($filter, $params); 382 } 383 384 /** 385 * Builds the complete sql with all the joins to get the grade history data. 386 * 387 * @param bool $count setting this to true, returns an sql to get count only instead of the complete data records. 388 * 389 * @return array containing sql to use and an array of params. 390 */ 391 protected function get_sql_and_params($count = false) { 392 $fields = 'ggh.id, ggh.timemodified, ggh.itemid, ggh.userid, ggh.finalgrade, ggh.usermodified, 393 ggh.source, ggh.overridden, ggh.locked, ggh.excluded, ggh.feedback, ggh.feedbackformat, 394 gi.itemtype, gi.itemmodule, gi.iteminstance, gi.itemnumber, '; 395 396 // Add extra user fields that we need for the graded user. 397 $extrafields = get_extra_user_fields($this->context); 398 foreach ($extrafields as $field) { 399 $fields .= 'u.' . $field . ', '; 400 } 401 $gradeduserfields = get_all_user_name_fields(true, 'u'); 402 $fields .= $gradeduserfields . ', '; 403 $groupby = $fields; 404 405 // Add extra user fields that we need for the grader user. 406 $fields .= get_all_user_name_fields(true, 'ug', '', 'grader'); 407 $groupby .= get_all_user_name_fields(true, 'ug'); 408 409 // Filtering on revised grades only. 410 $revisedonly = !empty($this->filters->revisedonly); 411 412 if ($count && !$revisedonly) { 413 // We can only directly use count when not using the filter revised only. 414 $select = "COUNT(1)"; 415 } else { 416 // Fetching the previous grade. We use MAX() to ensure that we only get one result if 417 // more than one histories happened at the same second. 418 $prevgrade = "SELECT MAX(finalgrade) 419 FROM {grade_grades_history} h 420 WHERE h.itemid = ggh.itemid 421 AND h.userid = ggh.userid 422 AND h.timemodified < ggh.timemodified 423 AND NOT EXISTS ( 424 SELECT 1 425 FROM {grade_grades_history} h2 426 WHERE h2.itemid = ggh.itemid 427 AND h2.userid = ggh.userid 428 AND h2.timemodified < ggh.timemodified 429 AND h.timemodified < h2.timemodified)"; 430 431 $select = "$fields, ($prevgrade) AS prevgrade, 432 CASE WHEN gi.itemname IS NULL THEN gi.itemtype ELSE gi.itemname END AS itemname"; 433 } 434 435 list($where, $params) = $this->get_filters_sql_and_params(); 436 437 $sql = "SELECT $select 438 FROM {grade_grades_history} ggh 439 JOIN {grade_items} gi ON gi.id = ggh.itemid 440 JOIN {user} u ON u.id = ggh.userid 441 LEFT JOIN {user} ug ON ug.id = ggh.usermodified 442 WHERE $where"; 443 444 // As prevgrade is a dynamic field, we need to wrap the query. This is the only filtering 445 // that should be defined outside the method self::get_filters_sql_and_params(). 446 if ($revisedonly) { 447 $allorcount = $count ? 'COUNT(1)' : '*'; 448 $sql = "SELECT $allorcount FROM ($sql) pg 449 WHERE pg.finalgrade != pg.prevgrade 450 OR (pg.prevgrade IS NULL AND pg.finalgrade IS NOT NULL) 451 OR (pg.prevgrade IS NOT NULL AND pg.finalgrade IS NULL)"; 452 } 453 454 // Add order by if needed. 455 if (!$count && $sqlsort = $this->get_sql_sort()) { 456 $sql .= " ORDER BY " . $sqlsort; 457 } 458 459 return array($sql, $params); 460 } 461 462 /** 463 * Get the SQL fragment to sort by. 464 * 465 * This is overridden to sort by timemodified and ID by default. Many items happen at the same time 466 * and a second sorting by ID is valuable to distinguish the order in which the history happened. 467 * 468 * @return string SQL fragment. 469 */ 470 public function get_sql_sort() { 471 $columns = $this->get_sort_columns(); 472 if (count($columns) == 1 && isset($columns['timemodified']) && $columns['timemodified'] == SORT_DESC) { 473 // Add the 'id' column when we are using the default sorting. 474 $columns['id'] = SORT_DESC; 475 return self::construct_order_by($columns); 476 } 477 return parent::get_sql_sort(); 478 } 479 480 /** 481 * Query the reader. Store results in the object for use by build_table. 482 * 483 * @param int $pagesize size of page for paginated displayed table. 484 * @param bool $useinitialsbar do you want to use the initials bar. 485 */ 486 public function query_db($pagesize, $useinitialsbar = true) { 487 global $DB; 488 489 list($countsql, $countparams) = $this->get_sql_and_params(true); 490 list($sql, $params) = $this->get_sql_and_params(); 491 $total = $DB->count_records_sql($countsql, $countparams); 492 $this->pagesize($pagesize, $total); 493 if ($this->is_downloading()) { 494 $histories = $DB->get_records_sql($sql, $params); 495 } else { 496 $histories = $DB->get_records_sql($sql, $params, $this->pagesize * $this->page, $this->pagesize); 497 } 498 foreach ($histories as $history) { 499 $this->rawdata[] = $history; 500 } 501 // Set initial bars. 502 if ($useinitialsbar) { 503 $this->initialbars($total > $pagesize); 504 } 505 } 506 507 /** 508 * Returns a list of selected users. 509 * 510 * @return array returns an array in the format $userid => $userid 511 */ 512 public function get_selected_users() { 513 global $DB; 514 $idlist = array(); 515 if (!empty($this->filters->userids)) { 516 517 $idlist = explode(',', $this->filters->userids); 518 list($where, $params) = $DB->get_in_or_equal($idlist); 519 return $DB->get_records_select('user', "id $where", $params); 520 521 } 522 return $idlist; 523 } 524 525 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body