diff --git a/grade/report/history/classes/filter_form.php b/grade/report/history/classes/filter_form.php index f3b394a0a36..5ee1382540c 100644 --- a/grade/report/history/classes/filter_form.php +++ b/grade/report/history/classes/filter_form.php @@ -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')); } /** diff --git a/grade/report/history/renderer.php b/grade/report/history/classes/output/renderer.php similarity index 81% rename from grade/report/history/renderer.php rename to grade/report/history/classes/output/renderer.php index f01ad98412b..58fc1691c08 100644 --- a/grade/report/history/renderer.php +++ b/grade/report/history/classes/output/renderer.php @@ -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 * @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; + } + } diff --git a/grade/report/history/classes/output/tablelog.php b/grade/report/history/classes/output/tablelog.php new file mode 100644 index 00000000000..86f142a6179 --- /dev/null +++ b/grade/report/history/classes/output/tablelog.php @@ -0,0 +1,422 @@ +. + +/** + * Renderable class for gradehistory report. + * + * @package gradereport_history + * @copyright 2014 onwards Ankit Agarwal + * @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 + * @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; + } + +} diff --git a/grade/report/history/index.php b/grade/report/history/index.php index 025c04a2e7c..806e3b1810e 100644 --- a/grade/report/history/index.php +++ b/grade/report/history/index.php @@ -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(); diff --git a/grade/report/history/lang/en/gradereport_history.php b/grade/report/history/lang/en/gradereport_history.php index 1d7b0d1f7ce..14565a4e28f 100644 --- a/grade/report/history/lang/en/gradereport_history.php +++ b/grade/report/history/lang/en/gradereport_history.php @@ -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';