MDL-46569 gradereport_history: Use table_sql for the report

Part of MDL-46191
This patch includes:-
- Convertion of the code to use new renderable/renderer apis
- Rewrote the code to remove all custom sorting, etc and use table sql instead
- Usernames are now clickable
- Grade items are now clickable
- Grade history is now displayed even if the grade item is deleted
- We are now using table sql for downloads so export in multiple formats is supported
- Feedback is now properly formatted
- Source fields are not capitilised anymore
- Paging is not shown on top of filters anymore
This commit is contained in:
Ankit Agarwal
2014-08-22 10:33:23 +08:00
parent 91737ef389
commit 2864abd876
5 changed files with 468 additions and 49 deletions
+2 -6
View File
@@ -76,12 +76,8 @@ class filter_form extends \moodleform {
$mform->addElement('hidden', 'userfullnames');
$mform->setType('userfullnames', PARAM_TEXT);
$submitlabel = get_string('submit');
$buttonarray = array();
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', $submitlabel);
$buttonarray[] = &$mform->createElement('submit', 'exportbutton', get_string('export', 'grades'));
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
// Add a submit button.
$mform->addElement('submit', 'submitbutton', get_string('submit'));
}
/**
@@ -23,6 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_history\output;
defined('MOODLE_INTERNAL') || die;
/**
@@ -34,7 +36,7 @@ defined('MOODLE_INTERNAL') || die;
* @author Adam Olley <adam.olley@netspot.com.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class gradereport_history_renderer extends plugin_renderer_base {
class renderer extends \plugin_renderer_base {
/**
* Render for the select user button.
@@ -51,7 +53,7 @@ class gradereport_history_renderer extends plugin_renderer_base {
'title' => $button->tooltip);
if ($button->actions) {
$id = html_writer::random_id('single_button');
$id = \html_writer::random_id('single_button');
$attributes['id'] = $id;
foreach ($button->actions as $action) {
$this->add_action_handler($action, $id);
@@ -60,7 +62,7 @@ class gradereport_history_renderer extends plugin_renderer_base {
$button->initialise_js($this->page);
// First the input element.
$output = html_writer::empty_tag('input', $attributes);
$output = \html_writer::empty_tag('input', $attributes);
// Then hidden fields.
$params = $button->url->params();
@@ -68,11 +70,11 @@ class gradereport_history_renderer extends plugin_renderer_base {
$params['sesskey'] = sesskey();
}
foreach ($params as $var => $val) {
$output .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
$output .= \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => $var, 'value' => $val));
}
// Then div wrapper for xhtml strictness.
$output = html_writer::tag('div', $output);
$output = \html_writer::tag('div', $output);
// Now the form itself around it.
if ($button->method === 'get') {
@@ -86,10 +88,10 @@ class gradereport_history_renderer extends plugin_renderer_base {
$attributes = array('method' => $button->method,
'action' => $url,
'id' => $button->formid);
$output = html_writer::tag('div', $output, $attributes);
$output = \html_writer::tag('div', $output, $attributes);
// Finally one more wrapper with class.
return html_writer::tag('div', $output, array('class' => $button->class));
return \html_writer::tag('div', $output, array('class' => $button->class));
}
/**
@@ -122,4 +124,22 @@ class gradereport_history_renderer extends plugin_renderer_base {
return $name;
}
/**
* Get the html for the table.
*
* @param tablelog $tablelog table object.
*
* @return string table html
*/
protected function render_tablelog(tablelog $tablelog) {
$o = '';
ob_start();
$tablelog->out($tablelog->pagesize, false);
$o = ob_get_contents();
ob_end_clean();
return $o;
}
}
@@ -0,0 +1,422 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Renderable class for gradehistory report.
*
* @package gradereport_history
* @copyright 2014 onwards Ankit Agarwal <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace gradereport_history\output;
defined('MOODLE_INTERNAL') || die;
require_once($CFG->libdir . '/tablelib.php');
/**
* Renderable class for gradehistory report.
*
* @since Moodle 2.8
* @package gradereport_history
* @copyright 2014 onwards Ankit Agarwal <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tablelog extends \table_sql implements \renderable {
/**
* @var int course id.
*/
protected $courseid;
/**
* @var \context context of the page to be rendered.
*/
protected $context;
/**
* @var \stdClass A list of filters to be applied to the sql query.
*/
protected $filters;
/**
* @var array A list of grade items present in the course.
*/
protected $gradeitems = array();
/**
* @var \course_modinfo|null A list of cm instances in course.
*/
protected $cms;
/**
* Sets up the table_log parameters.
*
* @param string $uniqueid unique id of table.
* @param \context_course $context Context of the report.
* @param \moodle_url $url url of the page where this table would be displayed.
* @param array $filters options are:
* users : limit to specific users (default: none)
* gradeitem : limit to specific item (default: all)
* grader : limit to specific graders (default: all)
* datefrom : start of date range
* datetill : end of date range
* revisedonly : only show revised grades (default: false)
* format : page | csv | excel (default: page)
* @param string $download Represents download format, pass '' no download at this time.
* @param int $page The current page being displayed.
* @param int $perpage Number of rules to display per page.
*/
public function __construct($uniqueid, \context_course $context, $url, $filters = array(), $download = '', $page = 0,
$perpage = 100) {
parent::__construct($uniqueid);
$this->set_attribute('class', 'gradereport_history generaltable generalbox');
// Set protected properties.
$this->context = $context;
$this->courseid = $this->context->instanceid;
$this->pagesize = $perpage;
$this->page = $page;
$this->filters = (object)$filters;
$this->gradeitems = \grade_item::fetch_all(array('courseid' => $this->courseid));
$this->cms = get_fast_modinfo($this->courseid);
$this->useridfield = 'userid';
// Define columns in the table.
$this->define_table_columns();
// Define configs.
$this->define_table_configs($url);
// Set download satus.
$this->is_downloading($download);
}
/**
* Define table configs.
*
* @param \moodle_url $url url of the page where this table would be displayed.
*/
protected function define_table_configs(\moodle_url $url) {
// Set table url.
$urlparams = (array)$this->filters;
unset($urlparams['submitbutton']);
unset($urlparams['userfullnames']);
$url->params($urlparams);
$this->define_baseurl($url);
// Set table configs.
$this->collapsible(true);
$this->sortable(true);
$this->pageable(true);
$this->no_sorting('grader');
}
/**
* Setup the headers for the html table.
*/
protected function define_table_columns() {
$extrafields = get_extra_user_fields($this->context);
// Define headers and columns.
$cols = array(
'timemodified' => get_string('datetime', 'gradereport_history'),
'fullname' => get_string('name')
);
// Add headers for extra user fields.
foreach ($extrafields as $field) {
if (get_string_manager()->string_exists($field, 'moodle')) {
$cols[$field] = get_string($field);
} else {
$cols[$field] = $field;
}
}
// Add remaining headers.
$cols = array_merge($cols, array(
'itemname' => get_string('gradeitem', 'gradereport_history'),
'prevgrade' => get_string('gradeold', 'gradereport_history'),
'finalgrade' => get_string('gradenew', 'gradereport_history'),
'grader' => get_string('grader', 'gradereport_history'),
'source' => get_string('source', 'gradereport_history'),
'overridden' => get_string('overridden', 'gradereport_history'),
'locked' => get_string('locked', 'gradereport_history'),
'excluded' => get_string('excluded', 'gradereport_history'),
'feedback' => get_string('feedbacktext', 'gradereport_history')
)
);
$this->define_columns(array_keys($cols));
$this->define_headers(array_values($cols));
}
/**
* Method to display column timemodifed.
*
* @param \stdClass $history an entry of history record.
*
* @return string HTML to display
*/
public function col_timemodified(\stdClass $history) {
return userdate($history->timemodified, '%d/%m/%Y %H:%M');
}
/**
* Method to display column itemname.
*
* @param \stdClass $history an entry of history record.
*
* @return string HTML to display
*/
public function col_itemname(\stdClass $history) {
// Make sure grade item is still present and link it to the module if possible.
$itemid = $history->itemid;
if (!empty($this->gradeitems[$itemid])) {
if ($history->itemtype === 'mod' && !$this->is_downloading()) {
if (!empty($this->cms->instances[$history->itemmodule][$history->iteminstance])) {
$cm = $this->cms->instances[$history->itemmodule][$history->iteminstance];
$url = new \moodle_url('/mod/' . $history->itemmodule . '/view.php', array('id' => $cm->id));
return \html_writer::link($url, $this->gradeitems[$itemid]->get_name());
}
}
return $this->gradeitems[$itemid]->get_name();
}
return get_string('deleteditemid', 'gradereport_history', $history->itemid);
}
/**
* Method to display column grader.
*
* @param \stdClass $history an entry of history record.
*
* @return string HTML to display
*/
public function col_grader(\stdClass $history) {
$grader = new \stdClass();
$grader = username_load_fields_from_object($grader, $history, 'grader');
$name = fullname($grader);
if ($this->download) {
return $name;
}
$userid = $history->usermodified;
$profileurl = new \moodle_url('/user/view.php', array('id' => $userid, 'course' => $this->courseid));
return \html_writer::link($profileurl, $name);
}
/**
* Method to display column overridden.
*
* @param \stdClass $history an entry of history record.
*
* @return string HTML to display
*/
public function col_overridden(\stdClass $history) {
return $history->overridden ? get_string('yes', 'gradereport_history') : get_string('no', 'gradereport_history');
}
/**
* Method to display column locked.
*
* @param \stdClass $history an entry of history record.
*
* @return string HTML to display
*/
public function col_locked(\stdClass $history) {
return $history->locked ? get_string('yes', 'gradereport_history') : get_string('no', 'gradereport_history');
}
/**
* Method to display column excluded.
*
* @param \stdClass $history an entry of history record.
*
* @return string HTML to display
*/
public function col_excluded(\stdClass $history) {
return $history->excluded ? get_string('yes', 'gradereport_history') : get_string('no', 'gradereport_history');
}
/**
* Method to display column feedback.
*
* @param \stdClass $history an entry of history record.
*
* @return string HTML to display
*/
public function col_feedback(\stdClass $history) {
if ($this->is_downloading()) {
return $history->feedback;
} else {
return format_text($history->feedback, $history->feedbackformat, array('context' => $this->context));
}
}
/**
* Builds the sql and param list needed, based on the user selected filters.
*
* @return array containing sql to use and an array of params.
*/
protected function get_filters_sql_and_params() {
global $DB;
$coursecontext = $this->context;
$filter = 'gi.courseid = :courseid';
$params = array(
'courseid' => $coursecontext->instanceid,
);
if (!empty($this->filters->itemid)) {
$filter .= ' AND ggh.itemid = :itemid';
$params['itemid'] = $this->filters->itemid;
}
if (!empty($this->filters->userids)) {
$list = explode(',', $this->filters->userids);
list($insql, $plist) = $DB->get_in_or_equal($list, SQL_PARAMS_NAMED);
$filter .= " AND ggh.userid $insql";
$params += $plist;
}
if (!empty($this->filters->datefrom)) {
$filter .= " AND ggh.timemodified >= :datefrom";
$params += array('datefrom' => $this->filters->datefrom);
}
if (!empty($this->filters->datetill)) {
$filter .= " AND ggh.timemodified <= :datetill";
$params += array('datetill' => $this->filters->datetill);
}
if (!empty($this->filters->grader)) {
$filter .= " AND ggh.usermodified = :grader";
$params += array('grader' => $this->filters->grader);
}
if (!empty($this->filters->revisedonly)) {
$filter .= " AND (ggh.finalgrade != ggh2.finalgrade
OR (ggh2.finalgrade IS NULL AND ggh.finalgrade IS NOT NULL)
OR (ggh2.finalgrade IS NOT NULL AND ggh.finalgrade IS NULL))";
}
return array($filter, $params);
}
/**
* Builds the complete sql with all the joins to get the grade history data.
*
* @param bool $count setting this to true, returns an sql to get count only instead of the complete data records.
*
* @return array containing sql to use and an array of params.
*/
protected function get_sql_and_params($count = false) {
$fields = 'ggh.timemodified, ggh.itemid, ggh.userid, ggh.finalgrade, ggh.usermodified,
ggh.source, ggh.overridden, ggh.locked, ggh.excluded, ggh.feedback, ggh.feedbackformat,
gi.itemtype, gi.itemmodule, gi.iteminstance, gi.itemnumber, ';
// Add extra user fields that we need for the graded user.
$extrafields = get_extra_user_fields($this->context);
foreach ($extrafields as $field) {
$fields .= 'u.' . $field . ', ';
}
$gradeduserfields = get_all_user_name_fields(true, 'u');
$fields .= $gradeduserfields . ', ';
$groupby = $fields;
// Add extra user fields that we need for the grader user.
$fields .= get_all_user_name_fields(true, 'ug', '', 'grader');
$groupby .= get_all_user_name_fields(true, 'ug');
if (!$count) {
// Max removes duplicates. Aliased and conditional fields added here.
$select = 'MAX(ggh.id) AS id, ' . $fields . ',
ggh2.finalgrade AS prevgrade,
CASE WHEN gi.itemname IS NULL THEN gi.itemtype ELSE gi.itemname END AS itemname';
} else {
$select = 'COUNT(1)';
}
list($where, $params) = $this->get_filters_sql_and_params();
// Group by removes duplicates, non-aliased fields added here.
$groupby = 'GROUP BY ' . $groupby . ', ggh2.finalgrade, gi.itemname';
$sql = "SELECT $select
FROM {grade_grades_history} ggh
LEFT JOIN {grade_items} gi ON gi.id = ggh.itemid
LEFT JOIN {grade_grades_history} ggh2 ON ggh2.id = (SELECT MAX(h.id)
FROM {grade_grades_history} h
WHERE h.itemid = ggh.itemid
AND h.userid = ggh.userid
AND (h.id < ggh.id))
JOIN {user} u ON u.id = ggh.userid
JOIN {user} ug ON ug.id = ggh.usermodified";
$sql .= " WHERE $where";
$sql .= " $groupby";
// Add order by if needed.
if (!$count && $this->get_sql_sort()) {
$sql .= " ORDER BY " . $this->get_sql_sort();
}
if ($count) {
return array("SELECT COUNT(1) FROM ($sql) res", $params);
}
return array($sql, $params);
}
/**
* Query the reader. Store results in the object for use by build_table.
*
* @param int $pagesize size of page for paginated displayed table.
* @param bool $useinitialsbar do you want to use the initials bar.
*/
public function query_db($pagesize, $useinitialsbar = true) {
global $DB;
list($countsql, $countparams) = $this->get_sql_and_params(true);
list($sql, $params) = $this->get_sql_and_params();
$total = $DB->count_records_sql($countsql, $countparams);
$this->pagesize($pagesize, $total);
$histories = $DB->get_records_sql($sql, $params, $this->pagesize * $this->page, $this->pagesize);
foreach ($histories as $history) {
$this->rawdata[] = $history;
}
// Set initial bars.
if ($useinitialsbar) {
$this->initialbars($total > $pagesize);
}
}
/**
* Returns a list of selected users.
*
* @return array returns an array in the format $userid => $userid
*/
public function get_selected_users() {
global $DB;
$idlist = array();
if (!empty($this->filters->userids)) {
$idlist = explode(',', $this->filters->userids);
list($where, $params) = $DB->get_in_or_equal($idlist);
return $DB->get_records_select('user', "id $where", $params);
}
return $idlist;
}
}
+14 -35
View File
@@ -25,18 +25,15 @@
require_once(__DIR__ . '/../../../config.php');
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->dirroot.'/user/renderer.php');
require_once($CFG->dirroot.'/grade/lib.php');
require_once($CFG->dirroot.'/grade/report/history/lib.php');
require_once($CFG->libdir.'/csvlib.class.php');
$download = optional_param('download', '', PARAM_ALPHA);
$courseid = required_param('id', PARAM_INT); // Course id.
$page = optional_param('page', 0, PARAM_INT); // Active page.
$sortitemid = optional_param('sortitemid', 0, PARAM_ALPHANUM);
$export = optional_param('exportbutton', false, PARAM_BOOL);
$PAGE->set_pagelayout('report');
$PAGE->set_url(new moodle_url('/grade/report/history/index.php', array('id' => $courseid)));
$url = new moodle_url('/grade/report/history/index.php', array('id' => $courseid));
$PAGE->set_url($url);
$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
require_login($course);
@@ -45,9 +42,6 @@ $context = context_course::instance($course->id);
require_capability('gradereport/history:view', $context);
require_capability('moodle/grade:viewall', $context);
// Return tracking object.
$gpr = new grade_plugin_return(array('type' => 'report', 'plugin' => 'history', 'courseid' => $courseid, 'page' => $page));
// Last selected report session tracking.
if (!isset($USER->grade_last_report)) {
$USER->grade_last_report = array();
@@ -71,7 +65,6 @@ $graders = array(0 => get_string('allgraders', 'gradereport_history')) + $grader
$output = $PAGE->get_renderer('gradereport_history');
$button = grade_report_history::get_user_select_button($course->id);
$params = array('course' => $course, 'itemids' => $itemids, 'graders' => $graders, 'userbutton' => null);
$mform = new \gradereport_history\filter_form(null, $params);
$filters = array();
@@ -93,46 +86,32 @@ if ($data = $mform->get_data()) {
);
}
$report = new grade_report_history($courseid, $gpr, $context, $filters, $page, $sortitemid);
$report->load_users();
$historytable = $report->get_history_table();
$numrows = $report->numrows;
$table = new \gradereport_history\output\tablelog('gradereport_history', $context, $url, $filters, $download, $page);
$names = array();
foreach ($report->get_selected_users() as $key => $user) {
$names[$key] = $user->firstname.' '.$user->lastname;
foreach ($table->get_selected_users() as $key => $user) {
$names[$key] = fullname($user);
}
$filters['userfullnames'] = implode(',', $names);
// Now that we have the names, reinitialise the button so its able to control them.
$button = grade_report_history::get_user_select_button($course->id, $names);
$button = \gradereport_history\helper::get_user_select_button($course->id);
$userbutton = $output->render($button);
$params = array('course' => $course, 'itemids' => $itemids, 'graders' => $graders, 'userbutton' => $userbutton);
$mform = new \gradereport_history\filter_form(null, $params);
$mform->set_data($filters);
if ($export) {
$filename = $COURSE->shortname;
$data = $report->get_table_data();
csv_export_writer::download_array($filename, $data);
if ($table->is_downloading()) {
// Download file and exit.
echo $output->render($table);
die();
}
$reportname = $output->report_title($report->get_selected_users());
$reportname = $output->report_title($table->get_selected_users());
// Print header.
print_grade_page_head($COURSE->id, 'report', 'history', $reportname, false, '');
if (!empty($report->perpage) && $report->perpage < $report->numrows) {
echo $OUTPUT->paging_bar($numrows, $report->page, $report->perpage, $report->pbarurl);
}
$mform->display();
echo $historytable;
// Prints paging bar at bottom for large pages.
if (!empty($report->perpage) && $report->perpage < $report->numrows) {
echo $OUTPUT->paging_bar($numrows, $report->page, $report->perpage, $report->pbarurl);
}
// Render table.
echo $output->render($table);
echo $OUTPUT->footer();
@@ -28,6 +28,7 @@ $string['allgraders'] = 'All graders';
$string['datefrom'] = 'Date from';
$string['datetill'] = 'Date till';
$string['datetime'] = 'Date and time';
$string['deleteditemid'] = 'Delete item with id {$a}';
$string['deselect'] = 'De-select';
$string['excluded'] = 'Excluded from calculations';
$string['feedbacktext'] = 'Feedback text';
@@ -36,11 +37,11 @@ $string['gradeitem'] = 'Grade item';
$string['gradenew'] = 'Revised grade';
$string['gradeold'] = 'Original grade';
$string['grader'] = 'Grader';
$string['history:manage'] = 'Manage the grade history';
$string['history:view'] = 'View the grade history';
$string['historyperpage'] = 'History entries per page';
$string['historyperpage_help'] = 'This setting determines the number of history entries displayed per page in the history report.';
$string['locked'] = 'Locked';
$string['no'] = 'N';
$string['overridden'] = 'Overridden';
$string['pluginname'] = 'Grade history';
$string['preferences'] = 'Grade history preferences';
@@ -53,3 +54,4 @@ $string['selectedusers'] = 'Selected users';
$string['source'] = 'Source';
$string['useractivitygrade'] = '{$a} grade';
$string['useractivityfeedback'] = '{$a} feedback';
$string['yes'] = 'Y';