9e1c9ce7b1eff29c5f0c3bccb40282eed0db96dd
[moodle.git] / mod / assign / gradingtable.php
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/>.
17 /**
18  * This file contains the definition for the grading table which subclassses easy_table
19  *
20  * @package   mod_assign
21  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
27 require_once($CFG->libdir.'/tablelib.php');
28 require_once($CFG->libdir.'/gradelib.php');
29 require_once($CFG->dirroot.'/mod/assign/locallib.php');
31 /**
32  * Extends table_sql to provide a table of assignment submissions
33  *
34  * @package   mod_assign
35  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
36  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 class assign_grading_table extends table_sql implements renderable {
39     /** @var assign $assignment */
40     private $assignment = null;
41     /** @var int $perpage */
42     private $perpage = 10;
43     /** @var int $rownum (global index of current row in table) */
44     private $rownum = -1;
45     /** @var renderer_base for getting output */
46     private $output = null;
47     /** @var stdClass gradinginfo */
48     private $gradinginfo = null;
49     /** @var int $tablemaxrows */
50     private $tablemaxrows = 10000;
51     /** @var boolean $quickgrading */
52     private $quickgrading = false;
54     /**
55      * overridden constructor keeps a reference to the assignment class that is displaying this table
56      *
57      * @param assign $assignment The assignment class
58      * @param int $perpage how many per page
59      * @param string $filter The current filter
60      * @param int $rowoffset For showing a subsequent page of results
61      * @param bool $quickgrading Is this table wrapped in a quickgrading form?
62      */
63     function __construct(assign $assignment, $perpage, $filter, $rowoffset, $quickgrading) {
64         global $CFG, $PAGE, $DB;
65         parent::__construct('mod_assign_grading');
66         $this->assignment = $assignment;
67         $this->perpage = $perpage;
68         $this->quickgrading = $quickgrading;
69         $this->output = $PAGE->get_renderer('mod_assign');
71         $this->define_baseurl(new moodle_url($CFG->wwwroot . '/mod/assign/view.php', array('action'=>'grading', 'id'=>$assignment->get_course_module()->id)));
73         // do some business - then set the sql
75         $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
77         if ($rowoffset) {
78             $this->rownum = $rowoffset - 1;
79         }
81         $users = array_keys( $assignment->list_participants($currentgroup, true));
82         if (count($users) == 0) {
83             // insert a record that will never match to the sql is still valid.
84             $users[] = -1;
85         }
87         $params = array();
88         $params['assignmentid1'] = (int)$this->assignment->get_instance()->id;
89         $params['assignmentid2'] = (int)$this->assignment->get_instance()->id;
91         $fields = user_picture::fields('u') . ', u.id as userid, u.firstname as firstname, u.lastname as lastname, ';
92         $fields .= 's.status as status, s.id as submissionid, s.timecreated as firstsubmission, s.timemodified as timesubmitted, ';
93         $fields .= 'g.id as gradeid, g.grade as grade, g.timemodified as timemarked, g.timecreated as firstmarked, g.mailed as mailed, g.locked as locked';
94         $from = '{user} u LEFT JOIN {assign_submission} s ON u.id = s.userid AND s.assignment = :assignmentid1' .
95                         ' LEFT JOIN {assign_grades} g ON u.id = g.userid AND g.assignment = :assignmentid2';
97         $userparams = array();
98         $userindex = 0;
100         list($userwhere, $userparams) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'user');
101         $where = 'u.id ' . $userwhere;
102         $params = array_merge($params, $userparams);
104         if ($filter == ASSIGN_FILTER_SUBMITTED) {
105             $where .= ' AND s.timecreated > 0 ';
106         }
107         if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) {
108             $where .= ' AND (s.timemodified > g.timemodified OR g.timemodified IS NULL)';
109         }
110         if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) {
111             $userfilter = (int) array_pop(explode('=', $filter));
112             $where .= ' AND (u.id = :userid)';
113             $params['userid'] = $userfilter;
114         }
115         $this->set_sql($fields, $from, $where, $params);
117         $columns = array();
118         $headers = array();
120         // Select
121         $columns[] = 'select';
122         $headers[] = get_string('select') . '<div class="selectall"><input type="checkbox" name="selectall" title="' . get_string('selectall') . '"/></div>';
124         // Edit links
125         if (!$this->is_downloading()) {
126             $columns[] = 'edit';
127             $headers[] = get_string('edit');
128         }
130         // User picture
131         $columns[] = 'picture';
132         $headers[] = get_string('pictureofuser');
134         // Fullname
135         $columns[] = 'fullname';
136         $headers[] = get_string('fullname');
138         // Submission status
139         if ($assignment->is_any_submission_plugin_enabled()) {
140             $columns[] = 'status';
141             $headers[] = get_string('status');
142         }
145         // Grade
146         $columns[] = 'grade';
147         $headers[] = get_string('grade');
149         // Submission plugins
150         if ($assignment->is_any_submission_plugin_enabled()) {
151             $columns[] = 'timesubmitted';
152             $headers[] = get_string('lastmodifiedsubmission', 'assign');
154             foreach ($this->assignment->get_submission_plugins() as $plugin) {
155                 if ($plugin->is_visible() && $plugin->is_enabled()) {
156                     $columns[] = 'assignsubmission_' . $plugin->get_type();
157                     $headers[] = $plugin->get_name();
158                 }
159             }
160         }
162         // time marked
163         $columns[] = 'timemarked';
164         $headers[] = get_string('lastmodifiedgrade', 'assign');
166         // Feedback plugins
167         foreach ($this->assignment->get_feedback_plugins() as $plugin) {
168             if ($plugin->is_visible() && $plugin->is_enabled()) {
169                 $columns[] = 'assignfeedback_' . $plugin->get_type();
170                 $headers[] = $plugin->get_name();
171             }
172         }
174         // final grade
175         $columns[] = 'finalgrade';
176         $headers[] = get_string('finalgrade', 'grades');
178         // load the grading info for all users
179         $this->gradinginfo = grade_get_grades($this->assignment->get_course()->id, 'mod', 'assign', $this->assignment->get_instance()->id, $users);
181         if (!empty($CFG->enableoutcomes) && !empty($this->gradinginfo->outcomes)) {
182             $columns[] = 'outcomes';
183             $headers[] = get_string('outcomes', 'grades');
184         }
187         // set the columns
188         $this->define_columns($columns);
189         $this->define_headers($headers);
190         $this->no_sorting('finalgrade');
191         $this->no_sorting('edit');
192         $this->no_sorting('select');
193         $this->no_sorting('outcomes');
195         foreach ($this->assignment->get_submission_plugins() as $plugin) {
196             if ($plugin->is_visible() && $plugin->is_enabled()) {
197                 $this->no_sorting('assignsubmission_' . $plugin->get_type());
198             }
199         }
200         foreach ($this->assignment->get_feedback_plugins() as $plugin) {
201             if ($plugin->is_visible() && $plugin->is_enabled()) {
202                 $this->no_sorting('assignfeedback_' . $plugin->get_type());
203             }
204         }
206     }
208     /**
209      * Add the userid to the row class so it can be updated via ajax
210      *
211      * @param stdClass $row The row of data
212      * @return string The row class
213      */
214     function get_row_class($row) {
215         return 'user' . $row->userid;
216     }
218     /**
219      * Return the number of rows to display on a single page
220      *
221      * @return int The number of rows per page
222      */
223     function get_rows_per_page() {
224         return $this->perpage;
225     }
227     /**
228      * Display a grade with scales etc.
229      *
230      * @param string $grade
231      * @param boolean $editable
232      * @param int $userid The user id of the user this grade belongs to
233      * @param int $modified Timestamp showing when the grade was last modified
234      * @return string The formatted grade
235      */
236     function display_grade($grade, $editable, $userid, $modified) {
237         if ($this->is_downloading()) {
238             return $grade;
239         }
240         $o = $this->assignment->display_grade($grade, $editable, $userid, $modified);
241         return $o;
242     }
244     /**
245      * Format a list of outcomes
246      *
247      * @param stdClass $row
248      * @return string
249      */
250     function col_outcomes(stdClass $row) {
251         $outcomes = '';
252         foreach($this->gradinginfo->outcomes as $index=>$outcome) {
253             $options = make_grades_menu(-$outcome->scaleid);
255             $options[0] = get_string('nooutcome', 'grades');
256             if ($this->quickgrading && !($outcome->grades[$row->userid]->locked)) {
257                 $select = '<select name="outcome_' . $index . '_' . $row->userid . '" class="quickgrade">';
258                 foreach ($options as $optionindex => $optionvalue) {
259                     $selected = '';
260                     if ($outcome->grades[$row->userid]->grade == $optionindex) {
261                         $selected = 'selected="selected"';
262                     }
263                     $select .= '<option value="' . $optionindex . '"' . $selected . '>' . $optionvalue . '</option>';
264                 }
265                 $select .= '</select>';
266                 $outcomes .= $this->output->container($outcome->name . ': ' . $select, 'outcome');
267             } else {
268                 $outcomes .= $this->output->container($outcome->name . ': ' . $options[$outcome->grades[$row->userid]->grade], 'outcome');
269             }
270         }
272         return $outcomes;
273     }
276     /**
277      * Format a user picture for display (and update rownum as a sideeffect)
278      *
279      * @param stdClass $row
280      * @return string
281      */
282     function col_picture(stdClass $row) {
283         if ($row->picture) {
284             return $this->output->user_picture($row);
285         }
286         return '';
287     }
289     /**
290      * Format a user record for display (don't link to profile)
291      *
292      * @param stdClass $row
293      * @return string
294      */
295     function col_fullname($row) {
296         return fullname($row);
297     }
299     /**
300      * Insert a checkbox for selecting the current row for batch operations
301      *
302      * @param stdClass $row
303      * @return string
304      */
305     function col_select(stdClass $row) {
306         return '<input type="checkbox" name="selectedusers" value="' . $row->userid . '"/>';
307     }
309     /**
310      * Return a users grades from the listing of all grade data for this assignment
311      *
312      * @param int $userid
313      * @return mixed stdClass or false
314      */
315     private function get_gradebook_data_for_user($userid) {
316         if (isset($this->gradinginfo->items[0]) && $this->gradinginfo->items[0]->grades[$userid]) {
317             return $this->gradinginfo->items[0]->grades[$userid];
318         }
319         return false;
320     }
322     /**
323      * Format a column of data for display
324      *
325      * @param stdClass $row
326      * @return string
327      */
328     function col_grade(stdClass $row) {
329         $o = '';
331         $link = '';
332         $separator = '';
333         $grade = '';
335         if (!$this->is_downloading()) {
336             $icon = $this->output->pix_icon('gradefeedback', get_string('grade'), 'mod_assign');
337             $url = new moodle_url('/mod/assign/view.php',
338                                             array('id' => $this->assignment->get_course_module()->id,
339                                                   'rownum'=>$this->rownum,'action'=>'grade'));
340             $link = $this->output->action_link($url, $icon);
341             $separator = $this->output->spacer(array(), true);
342         }
343         $gradingdisabled = $this->assignment->grading_disabled($row->id);
344         $grade = $this->display_grade($row->grade, $this->quickgrading && !$gradingdisabled, $row->userid, $row->timemarked);
346         //return $grade . $separator . $link;
347         return $link . $separator . $grade;
348     }
350     /**
351      * Format a column of data for display
352      *
353      * @param stdClass $row
354      * @return string
355      */
356     function col_finalgrade(stdClass $row) {
357         $o = '';
359         $grade = $this->get_gradebook_data_for_user($row->userid);
360         if ($grade) {
361             $o = $this->display_grade($grade->grade, false, $row->userid, $row->timemarked);
362         }
364         return $o;
365     }
367     /**
368      * Format a column of data for display
369      *
370      * @param stdClass $row
371      * @return string
372      */
373     function col_timemarked(stdClass $row) {
374         $o = '-';
376         if ($row->timemarked) {
377             $o = userdate($row->timemarked);
378         }
380         return $o;
381     }
383     /**
384      * Format a column of data for display
385      *
386      * @param stdClass $row
387      * @return string
388      */
389     function col_timesubmitted(stdClass $row) {
390         $o = '-';
392         if ($row->timesubmitted) {
393             $o = userdate($row->timesubmitted);
394         }
396         return $o;
397     }
399     /**
400      * Format a column of data for display
401      *
402      * @param stdClass $row
403      * @return string
404      */
405     function col_status(stdClass $row) {
406         $o = '';
408         if ($this->assignment->is_any_submission_plugin_enabled()) {
410             $o .= $this->output->container(get_string('submissionstatus_' . $row->status, 'assign'), array('class'=>'submissionstatus' .$row->status));
411             if ($this->assignment->get_instance()->duedate && $row->timesubmitted > $this->assignment->get_instance()->duedate) {
412                 $o .= $this->output->container(get_string('submittedlateshort', 'assign', format_time($row->timesubmitted - $this->assignment->get_instance()->duedate)), 'latesubmission');
413             }
414             if ($row->locked) {
415                 $o .= $this->output->container(get_string('submissionslockedshort', 'assign'), 'lockedsubmission');
416             }
417             if ($row->grade !== NULL && $row->grade >= 0) {
418                 $o .= $this->output->container(get_string('graded', 'assign'), 'submissiongraded');
419             }
420         }
422         return $o;
423     }
425     /**
426      * Format a column of data for display
427      *
428      * @param stdClass $row
429      * @return string
430      */
431     function col_edit(stdClass $row) {
432         $edit = '';
433         if ($this->rownum < 0) {
434             $this->rownum = $this->currpage * $this->pagesize;
435         } else {
436             $this->rownum += 1;
437         }
439         $actions = array();
441         $url = new moodle_url('/mod/assign/view.php',
442                                             array('id' => $this->assignment->get_course_module()->id,
443                                                   'rownum'=>$this->rownum,'action'=>'grade'));
444         if (!$row->grade) {
445            $description = get_string('grade');
446         }else{
447            $description = get_string('updategrade','assign');
448         }
449         $actions[$url->out(false)] = $description;
451         if (!$row->status || $row->status == ASSIGN_SUBMISSION_STATUS_DRAFT || !$this->assignment->get_instance()->submissiondrafts) {
452             if (!$row->locked) {
453                 $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
454                                                                     'userid'=>$row->id,
455                                                                     'action'=>'lock',
456                                                                     'sesskey'=>sesskey(),
457                                                                     'page'=>$this->currpage));
458                 $description = get_string('preventsubmissionsshort', 'assign');
459                 $actions[$url->out(false)] = $description;
460             } else {
461                 $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
462                                                                     'userid'=>$row->id,
463                                                                     'action'=>'unlock',
464                                                                     'sesskey'=>sesskey(),
465                                                                     'page'=>$this->currpage));
466                 $description = get_string('allowsubmissionsshort', 'assign');
467                 $actions[$url->out(false)] = $description;
468             }
469         }
470         if ($row->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED && $this->assignment->get_instance()->submissiondrafts) {
471             $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
472                                                                 'userid'=>$row->id,
473                                                                 'action'=>'reverttodraft',
474                                                                 'sesskey'=>sesskey(),
475                                                                 'page'=>$this->currpage));
476             $description = get_string('reverttodraftshort', 'assign');
477             $actions[$url->out(false)] = $description;
478         }
480         $edit .= $this->output->container_start(array('yui3-menu', 'actionmenu'), 'actionselect' . $row->id);
481         $edit .= $this->output->container_start(array('yui3-menu-content'));
482         $edit .= html_writer::start_tag('ul');
483         $edit .= html_writer::start_tag('li', array('class'=>'menuicon'));
485         $menuicon = $this->output->pix_icon('i/menu', get_string('actions'));
486         $edit .= $this->output->action_link('#menu' . $row->id, $menuicon, null, array('class'=>'yui3-menu-label'));
487         $edit .= $this->output->container_start(array('yui3-menu', 'yui3-loading'), 'menu' . $row->id);
488         $edit .= $this->output->container_start(array('yui3-menu-content'));
489         $edit .= html_writer::start_tag('ul');
491         foreach ($actions as $url => $description) {
492             $edit .= html_writer::start_tag('li', array('class'=>'yui3-menuitem'));
494             $edit .= $this->output->action_link($url, $description, null, array('class'=>'yui3-menuitem-content'));
496             $edit .= html_writer::end_tag('li');
497         }
498         $edit .= html_writer::end_tag('ul');
499         $edit .= $this->output->container_end();
500         $edit .= $this->output->container_end();
501         $edit .= html_writer::end_tag('li');
502         $edit .= html_writer::end_tag('ul');
504         $edit .= $this->output->container_end();
505         $edit .= $this->output->container_end();
507         return $edit;
508     }
510     /**
511      * Write the plugin summary with an optional link to view the full feedback/submission.
512      *
513      * @param assign_plugin $plugin Submission plugin or feedback plugin
514      * @param stdClass $item Submission or grade
515      * @param string $returnaction The return action to pass to the view_submission page (the current page)
516      * @param string $returnparams The return params to pass to the view_submission page (the current page)
517      * @return string The summary with an optional link
518      */
519     private function format_plugin_summary_with_link(assign_plugin $plugin, stdClass $item, $returnaction, $returnparams) {
520         $link = '';
521         $showviewlink = false;
523         $summary = $plugin->view_summary($item, $showviewlink);
524         $separator = '';
525         if ($showviewlink) {
526             $icon = $this->output->pix_icon('t/preview', get_string('view' . substr($plugin->get_subtype(), strlen('assign')), 'mod_assign'));
527             $link = $this->output->action_link(
528                                 new moodle_url('/mod/assign/view.php',
529                                                array('id' => $this->assignment->get_course_module()->id,
530                                                      'sid'=>$item->id,
531                                                      'gid'=>$item->id,
532                                                      'plugin'=>$plugin->get_type(),
533                                                      'action'=>'viewplugin' . $plugin->get_subtype(),
534                                                      'returnaction'=>$returnaction,
535                                                      'returnparams'=>http_build_query($returnparams))),
536                                 $icon);
537             $separator = $this->output->spacer(array(), true);
538         }
540         return $link . $separator . $summary;
541     }
544     /**
545      * Format the submission and feedback columns
546      *
547      * @param string $colname The column name
548      * @param stdClass $row The submission row
549      * @return mixed string or NULL
550      */
551     function other_cols($colname, $row){
552         if (($pos = strpos($colname, 'assignsubmission_')) !== false) {
553             $plugin = $this->assignment->get_submission_plugin_by_type(substr($colname, strlen('assignsubmission_')));
555             if ($plugin->is_visible() && $plugin->is_enabled()) {
556                 if ($row->submissionid) {
557                     $submission = new stdClass();
558                     $submission->id = $row->submissionid;
559                     $submission->timecreated = $row->firstsubmission;
560                     $submission->timemodified = $row->timesubmitted;
561                     $submission->assignment = $this->assignment->get_instance()->id;
562                     $submission->userid = $row->userid;
563                     return $this->format_plugin_summary_with_link($plugin, $submission, 'grading', array());
564                 }
565             }
566             return '';
567         }
568         if (($pos = strpos($colname, 'feedback_')) !== false) {
569             $plugin = $this->assignment->get_feedback_plugin_by_type(substr($colname, strlen('assignfeedback_')));
570             if ($plugin->is_visible() && $plugin->is_enabled()) {
571                 $grade = null;
572                 if ($row->gradeid) {
573                     $grade = new stdClass();
574                     $grade->id = $row->gradeid;
575                     $grade->timecreated = $row->firstmarked;
576                     $grade->timemodified = $row->timemarked;
577                     $grade->assignment = $this->assignment->get_instance()->id;
578                     $grade->userid = $row->userid;
579                     $grade->grade = $row->grade;
580                     $grade->mailed = $row->mailed;
581                 }
582                 if ($this->quickgrading && $plugin->supports_quickgrading()) {
583                     return $plugin->get_quickgrading_html($row->userid, $grade);
584                 } else if ($grade) {
585                     return $this->format_plugin_summary_with_link($plugin, $grade, 'grading', array());
586                 }
587             }
588             return '';
589         }
590         return NULL;
591     }
593     /**
594      * Using the current filtering and sorting - load all rows and return a single column from them
595      *
596      * @param string $columnname The name of the raw column data
597      * @return array of data
598      */
599     function get_column_data($columnname) {
600         $this->setup();
601         $this->currpage = 0;
602         $this->query_db($this->tablemaxrows);
603         $result = array();
604         foreach ($this->rawdata as $row) {
605             $result[] = $row->$columnname;
606         }
607         return $result;
608     }
609     /**
610      * Using the current filtering and sorting - load a single row and return a single column from it
611      *
612      * @param int $rownumber The rownumber to load
613      * @param string $columnname The name of the raw column data
614      * @param bool $lastrow Set to true if this is the last row in the table
615      * @return mixed string or false
616      */
617     function get_cell_data($rownumber, $columnname, $lastrow) {
618         $this->setup();
619         $this->currpage = $rownumber;
620         $this->query_db(1);
621         if ($rownumber == $this->totalrows-1) {
622             $lastrow = true;
623         }
624         foreach ($this->rawdata as $row) {
625             return $row->$columnname;
626         }
627         return false;
628     }
630     /**
631      * Return things to the renderer
632      *
633      * @return string the assignment name
634      */
635     function get_assignment_name() {
636         return $this->assignment->get_instance()->name;
637     }
639     /**
640      * Return things to the renderer
641      *
642      * @return int the course module id
643      */
644     function get_course_module_id() {
645         return $this->assignment->get_course_module()->id;
646     }
648     /**
649      * Return things to the renderer
650      *
651      * @return int the course id
652      */
653     function get_course_id() {
654         return $this->assignment->get_course()->id;
655     }
657     /**
658      * Return things to the renderer
659      *
660      * @return stdClass The course context
661      */
662     function get_course_context() {
663         return $this->assignment->get_course_context();
664     }
666     /**
667      * Return things to the renderer
668      *
669      * @return bool Does this assignment accept submissions
670      */
671     function submissions_enabled() {
672         return $this->assignment->is_any_submission_plugin_enabled();
673     }
675     /**
676      * Return things to the renderer
677      *
678      * @return bool Can this user view all grades (the gradebook)
679      */
680     function can_view_all_grades() {
681         return has_capability('gradereport/grader:view', $this->assignment->get_course_context()) && has_capability('moodle/grade:viewall', $this->assignment->get_course_context());
682     }
684     /**
685      * Override the table show_hide_link to not show for select column
686      *
687      * @param string $column the column name, index into various names.
688      * @param int $index numerical index of the column.
689      * @return string HTML fragment.
690      */
691     protected function show_hide_link($column, $index) {
692         if ($index > 0) {
693             return parent::show_hide_link($column, $index);
694         }
695         return '';
696     }