MDL-32772: Change SQL for mod_assign gradingtable to use named query parameters
[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;
52     /**
53      * overridden constructor keeps a reference to the assignment class that is displaying this table
54      *
55      * @param assign $assignment The assignment class
56      * @param int $perpage how many per page
57      * @param string $filter The current filter
58      * @param int $rowoffset For showing a subsequent page of results
59      */
60     function __construct(assign $assignment, $perpage, $filter, $rowoffset=0) {
61         global $CFG, $PAGE, $DB;
62         parent::__construct('mod_assign_grading');
63         $this->assignment = $assignment;
64         $this->perpage = $perpage;
65         $this->output = $PAGE->get_renderer('mod_assign');
67         $this->define_baseurl(new moodle_url($CFG->wwwroot . '/mod/assign/view.php', array('action'=>'grading', 'id'=>$assignment->get_course_module()->id)));
69         // do some business - then set the sql
71         $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
73         if ($rowoffset) {
74             $this->rownum = $rowoffset - 1;
75         }
77         $users = array_keys( $assignment->list_participants($currentgroup, true));
78         if (count($users) == 0) {
79             // insert a record that will never match to the sql is still valid.
80             $users[] = -1;
81         }
83         $params = array();
84         $params['assignmentid1'] = (int)$this->assignment->get_instance()->id;
85         $params['assignmentid2'] = (int)$this->assignment->get_instance()->id;
87         $fields = user_picture::fields('u') . ', u.id as userid, u.firstname as firstname, u.lastname as lastname, ';
88         $fields .= 's.status as status, s.id as submissionid, s.timecreated as firstsubmission, s.timemodified as timesubmitted, ';
89         $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';
90         $from = '{user} u LEFT JOIN {assign_submission} s ON u.id = s.userid AND s.assignment = :assignmentid1' .
91                         ' LEFT JOIN {assign_grades} g ON u.id = g.userid AND g.assignment = :assignmentid2';
93         $userparams = array();
94         $userindex = 0;
96         list($userwhere, $userparams) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'user');
97         $where = 'u.id ' . $userwhere;
98         $params = array_merge($params, $userparams);
100         if ($filter == ASSIGN_FILTER_SUBMITTED) {
101             $where .= ' AND s.timecreated > 0 ';
102         }
103         if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) {
104             $where .= ' AND (s.timemodified > g.timemodified OR g.timemodified IS NULL)';
105         }
106         if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) {
107             $userfilter = (int) array_pop(explode('=', $filter));
108             $where .= ' AND (u.id = :userid)';
109             $params['userid'] = $userfilter;
110         }
111         $this->set_sql($fields, $from, $where, $params);
113         $columns = array();
114         $headers = array();
116         // Select
117         $columns[] = 'select';
118         $headers[] = get_string('select') . '<div class="selectall"><input type="checkbox" name="selectall" title="' . get_string('selectall') . '"/></div>';
120         // Edit links
121         if (!$this->is_downloading()) {
122             $columns[] = 'edit';
123             $headers[] = get_string('edit');
124         }
126         // User picture
127         $columns[] = 'picture';
128         $headers[] = get_string('pictureofuser');
130         // Fullname
131         $columns[] = 'fullname';
132         $headers[] = get_string('fullname');
134         // Submission status
135         if ($assignment->is_any_submission_plugin_enabled()) {
136             $columns[] = 'status';
137             $headers[] = get_string('status');
138         }
141         // Grade
142         $columns[] = 'grade';
143         $headers[] = get_string('grade');
145         // Submission plugins
146         if ($assignment->is_any_submission_plugin_enabled()) {
147             $columns[] = 'timesubmitted';
148             $headers[] = get_string('lastmodifiedsubmission', 'assign');
150             foreach ($this->assignment->get_submission_plugins() as $plugin) {
151                 if ($plugin->is_visible() && $plugin->is_enabled()) {
152                     $columns[] = 'assignsubmission_' . $plugin->get_type();
153                     $headers[] = $plugin->get_name();
154                 }
155             }
156         }
158         // time marked
159         $columns[] = 'timemarked';
160         $headers[] = get_string('lastmodifiedgrade', 'assign');
162         // Feedback plugins
163         foreach ($this->assignment->get_feedback_plugins() as $plugin) {
164             if ($plugin->is_visible() && $plugin->is_enabled()) {
165                 $columns[] = 'assignfeedback_' . $plugin->get_type();
166                 $headers[] = $plugin->get_name();
167             }
168         }
170         // final grade
171         $columns[] = 'finalgrade';
172         $headers[] = get_string('finalgrade', 'grades');
176         // set the columns
177         $this->define_columns($columns);
178         $this->define_headers($headers);
179         $this->no_sorting('finalgrade');
180         $this->no_sorting('edit');
181         $this->no_sorting('select');
182         foreach ($this->assignment->get_submission_plugins() as $plugin) {
183             if ($plugin->is_visible() && $plugin->is_enabled()) {
184                 $this->no_sorting('assignsubmission_' . $plugin->get_type());
185             }
186         }
187         foreach ($this->assignment->get_feedback_plugins() as $plugin) {
188             if ($plugin->is_visible() && $plugin->is_enabled()) {
189                 $this->no_sorting('assignfeedback_' . $plugin->get_type());
190             }
191         }
193         // load the grading info for all users
194         $this->gradinginfo = grade_get_grades($this->assignment->get_course()->id, 'mod', 'assign', $this->assignment->get_instance()->id, $users);
195     }
197     /**
198      * Add the userid to the row class so it can be updated via ajax
199      *
200      * @param stdClass $row The row of data
201      * @return string The row class
202      */
203     function get_row_class($row) {
204         return 'user' . $row->userid;
205     }
207     /**
208      * Return the number of rows to display on a single page
209      *
210      * @return int The number of rows per page
211      */
212     function get_rows_per_page() {
213         return $this->perpage;
214     }
216     /**
217      * Display a grade with scales etc.
218      *
219      * @param string $grade
220      * @return string The formatted grade
221      */
222     function display_grade($grade) {
223         if ($this->is_downloading()) {
224             return $grade;
225         }
226         $o = $this->assignment->display_grade($grade);
228         return $o;
229     }
231     /**
232      * Format a user picture for display (and update rownum as a sideeffect)
233      *
234      * @param stdClass $row
235      * @return string
236      */
237     function col_picture(stdClass $row) {
238         if ($row->picture) {
239             return $this->output->user_picture($row);
240         }
241         return '';
242     }
244     /**
245      * Format a user record for display (don't link to profile)
246      *
247      * @param stdClass $row
248      * @return string
249      */
250     function col_fullname($row) {
251         return fullname($row);
252     }
254     /**
255      * Insert a checkbox for selecting the current row for batch operations
256      *
257      * @param stdClass $row
258      * @return string
259      */
260     function col_select(stdClass $row) {
261         return '<input type="checkbox" name="selectedusers" value="' . $row->userid . '"/>';
262     }
264     /**
265      * Return a users grades from the listing of all grade data for this assignment
266      *
267      * @param int $userid
268      * @return mixed stdClass or false
269      */
270     private function get_gradebook_data_for_user($userid) {
271         if (isset($this->gradinginfo->items[0]) && $this->gradinginfo->items[0]->grades[$userid]) {
272             return $this->gradinginfo->items[0]->grades[$userid];
273         }
274         return false;
275     }
277     /**
278      * Format a column of data for display
279      *
280      * @param stdClass $row
281      * @return string
282      */
283     function col_grade(stdClass $row) {
284         $o = '';
286         $link = '';
287         $separator = '';
288         $grade = '';
290         if (!$this->is_downloading()) {
291             $icon = $this->output->pix_icon('gradefeedback', get_string('grade'), 'mod_assign');
292             $url = new moodle_url('/mod/assign/view.php',
293                                             array('id' => $this->assignment->get_course_module()->id,
294                                                   'rownum'=>$this->rownum,'action'=>'grade'));
295             $link = $this->output->action_link($url, $icon);
296             $separator = $this->output->spacer(array(), true);
297         }
300         if ($row->grade) {
301             $grade = $this->display_grade($row->grade);
302         } else {
303             $grade = '-';
304         }
307         //return $grade . $separator . $link;
308         return $link . $separator . $grade;
309     }
311     /**
312      * Format a column of data for display
313      *
314      * @param stdClass $row
315      * @return string
316      */
317     function col_finalgrade(stdClass $row) {
318         $o = '';
320         $grade = $this->get_gradebook_data_for_user($row->userid);
321         if ($grade) {
322             $o = $this->display_grade($grade->grade);
323         }
325         return $o;
326     }
328     /**
329      * Format a column of data for display
330      *
331      * @param stdClass $row
332      * @return string
333      */
334     function col_timemarked(stdClass $row) {
335         $o = '-';
337         if ($row->timemarked) {
338             $o = userdate($row->timemarked);
339         }
341         return $o;
342     }
344     /**
345      * Format a column of data for display
346      *
347      * @param stdClass $row
348      * @return string
349      */
350     function col_timesubmitted(stdClass $row) {
351         $o = '-';
353         if ($row->timesubmitted) {
354             $o = userdate($row->timesubmitted);
355         }
357         return $o;
358     }
360     /**
361      * Format a column of data for display
362      *
363      * @param stdClass $row
364      * @return string
365      */
366     function col_status(stdClass $row) {
367         $o = '';
369         if ($this->assignment->is_any_submission_plugin_enabled()) {
371             $o .= $this->output->container(get_string('submissionstatus_' . $row->status, 'assign'), array('class'=>'submissionstatus' .$row->status));
372             if ($this->assignment->get_instance()->duedate && $row->timesubmitted > $this->assignment->get_instance()->duedate) {
373                 $o .= $this->output->container(get_string('submittedlateshort', 'assign', format_time($row->timesubmitted - $this->assignment->get_instance()->duedate)), 'latesubmission');
374             }
375             if ($row->locked) {
376                 $o .= $this->output->container(get_string('submissionslockedshort', 'assign'), 'lockedsubmission');
377             }
378             if ($row->grade) {
379                 $o .= $this->output->container(get_string('graded', 'assign'), 'submissiongraded');
380             }
381         }
383         return $o;
384     }
386     /**
387      * Format a column of data for display
388      *
389      * @param stdClass $row
390      * @return string
391      */
392     function col_edit(stdClass $row) {
393         $edit = '';
394         if ($this->rownum < 0) {
395             $this->rownum = $this->currpage * $this->pagesize;
396         } else {
397             $this->rownum += 1;
398         }
400         $actions = array();
402         $url = new moodle_url('/mod/assign/view.php',
403                                             array('id' => $this->assignment->get_course_module()->id,
404                                                   'rownum'=>$this->rownum,'action'=>'grade'));
405         if (!$row->grade) {
406            $description = get_string('grade');
407         }else{
408            $description = get_string('updategrade','assign');
409         }
410         $actions[$url->out(false)] = $description;
412         if (!$row->status || $row->status == ASSIGN_SUBMISSION_STATUS_DRAFT || !$this->assignment->get_instance()->submissiondrafts) {
413             if (!$row->locked) {
414                 $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
415                                                                     'userid'=>$row->id,
416                                                                     'action'=>'lock',
417                                                                     'sesskey'=>sesskey(),
418                                                                     'page'=>$this->currpage));
419                 $description = get_string('preventsubmissionsshort', 'assign');
420                 $actions[$url->out(false)] = $description;
421             } else {
422                 $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
423                                                                     'userid'=>$row->id,
424                                                                     'action'=>'unlock',
425                                                                     'sesskey'=>sesskey(),
426                                                                     'page'=>$this->currpage));
427                 $description = get_string('allowsubmissionsshort', 'assign');
428                 $actions[$url->out(false)] = $description;
429             }
430         }
431         if ($row->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED && $this->assignment->get_instance()->submissiondrafts) {
432             $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
433                                                                 'userid'=>$row->id,
434                                                                 'action'=>'reverttodraft',
435                                                                 'sesskey'=>sesskey(),
436                                                                 'page'=>$this->currpage));
437             $description = get_string('reverttodraftshort', 'assign');
438             $actions[$url->out(false)] = $description;
439         }
441         $edit .= $this->output->container_start(array('yui3-menu', 'actionmenu'), 'actionselect' . $row->id);
442         $edit .= $this->output->container_start(array('yui3-menu-content'));
443         $edit .= html_writer::start_tag('ul');
444         $edit .= html_writer::start_tag('li', array('class'=>'menuicon'));
446         $menuicon = $this->output->pix_icon('i/menu', get_string('actions'));
447         $edit .= $this->output->action_link('#menu' . $row->id, $menuicon, null, array('class'=>'yui3-menu-label'));
448         $edit .= $this->output->container_start(array('yui3-menu', 'yui3-loading'), 'menu' . $row->id);
449         $edit .= $this->output->container_start(array('yui3-menu-content'));
450         $edit .= html_writer::start_tag('ul');
452         foreach ($actions as $url => $description) {
453             $edit .= html_writer::start_tag('li', array('class'=>'yui3-menuitem'));
455             $edit .= $this->output->action_link($url, $description, null, array('class'=>'yui3-menuitem-content'));
457             $edit .= html_writer::end_tag('li');
458         }
459         $edit .= html_writer::end_tag('ul');
460         $edit .= $this->output->container_end();
461         $edit .= $this->output->container_end();
462         $edit .= html_writer::end_tag('li');
463         $edit .= html_writer::end_tag('ul');
465         $edit .= $this->output->container_end();
466         $edit .= $this->output->container_end();
468         return $edit;
469     }
471     /**
472      * Write the plugin summary with an optional link to view the full feedback/submission.
473      *
474      * @param assign_plugin $plugin Submission plugin or feedback plugin
475      * @param stdClass $item Submission or grade
476      * @param string $returnaction The return action to pass to the view_submission page (the current page)
477      * @param string $returnparams The return params to pass to the view_submission page (the current page)
478      * @return string The summary with an optional link
479      */
480     private function format_plugin_summary_with_link(assign_plugin $plugin, stdClass $item, $returnaction, $returnparams) {
481         $link = '';
482         $showviewlink = false;
483         $summary = $plugin->view_summary($item, $showviewlink);
484         $separator = '';
485         if ($showviewlink) {
486             $icon = $this->output->pix_icon('t/preview', get_string('view' . substr($plugin->get_subtype(), strlen('assign')), 'mod_assign'));
487             $link = $this->output->action_link(
488                                 new moodle_url('/mod/assign/view.php',
489                                                array('id' => $this->assignment->get_course_module()->id,
490                                                      'sid'=>$item->id,
491                                                      'gid'=>$item->id,
492                                                      'plugin'=>$plugin->get_type(),
493                                                      'action'=>'viewplugin' . $plugin->get_subtype(),
494                                                      'returnaction'=>$returnaction,
495                                                      'returnparams'=>http_build_query($returnparams))),
496                                 $icon);
497             $separator = $this->output->spacer(array(), true);
498         }
500         return $link . $separator . $summary;
501     }
504     /**
505      * Format the submission and feedback columns
506      *
507      * @param string $colname The column name
508      * @param stdClass $row The submission row
509      * @return mixed string or NULL
510      */
511     function other_cols($colname, $row){
512         if (($pos = strpos($colname, 'assignsubmission_')) !== false) {
513             $plugin = $this->assignment->get_submission_plugin_by_type(substr($colname, strlen('assignsubmission_')));
514             if ($plugin->is_visible() && $plugin->is_enabled()) {
515                 if ($row->submissionid) {
516                     $submission = new stdClass();
517                     $submission->id = $row->submissionid;
518                     $submission->timecreated = $row->firstsubmission;
519                     $submission->timemodified = $row->timesubmitted;
520                     $submission->assignment = $this->assignment->get_instance()->id;
521                     $submission->userid = $row->userid;
523                     return $this->format_plugin_summary_with_link($plugin, $submission, 'grading', array());
524                 }
525             }
526             return '';
527         }
528         if (($pos = strpos($colname, 'feedback_')) !== false) {
529             $plugin = $this->assignment->get_feedback_plugin_by_type(substr($colname, strlen('assignfeedback_')));
530             if ($plugin->is_visible() && $plugin->is_enabled()) {
531                 if ($row->gradeid) {
532                     $grade = new stdClass();
533                     $grade->id = $row->gradeid;
534                     $grade->timecreated = $row->firstmarked;
535                     $grade->timemodified = $row->timemarked;
536                     $grade->assignment = $this->assignment->get_instance()->id;
537                     $grade->userid = $row->userid;
538                     $grade->grade = $row->grade;
539                     $grade->mailed = $row->mailed;
541                     return $this->format_plugin_summary_with_link($plugin, $grade, 'grading', array());
542                 }
543             }
544             return '';
545         }
546         return NULL;
547     }
549     /**
550      * Using the current filtering and sorting - load all rows and return a single column from them
551      *
552      * @param string $columnname The name of the raw column data
553      * @return array of data
554      */
555     function get_column_data($columnname) {
556         $this->setup();
557         $this->currpage = 0;
558         $this->query_db($this->tablemaxrows);
559         $result = array();
560         foreach ($this->rawdata as $row) {
561             $result[] = $row->$columnname;
562         }
563         return $result;
564     }
565     /**
566      * Using the current filtering and sorting - load a single row and return a single column from it
567      *
568      * @param int $rownumber The rownumber to load
569      * @param string $columnname The name of the raw column data
570      * @param bool $lastrow Set to true if this is the last row in the table
571      * @return mixed string or false
572      */
573     function get_cell_data($rownumber, $columnname, $lastrow) {
574         $this->setup();
575         $this->currpage = $rownumber;
576         $this->query_db(1);
577         if ($rownumber == $this->totalrows-1) {
578             $lastrow = true;
579         }
580         foreach ($this->rawdata as $row) {
581             return $row->$columnname;
582         }
583         return false;
584     }
586     /**
587      * Return things to the renderer
588      *
589      * @return string the assignment name
590      */
591     function get_assignment_name() {
592         return $this->assignment->get_instance()->name;
593     }
595     /**
596      * Return things to the renderer
597      *
598      * @return int the course module id
599      */
600     function get_course_module_id() {
601         return $this->assignment->get_course_module()->id;
602     }
604     /**
605      * Return things to the renderer
606      *
607      * @return int the course id
608      */
609     function get_course_id() {
610         return $this->assignment->get_course()->id;
611     }
613     /**
614      * Return things to the renderer
615      *
616      * @return stdClass The course context
617      */
618     function get_course_context() {
619         return $this->assignment->get_course_context();
620     }
622     /**
623      * Return things to the renderer
624      *
625      * @return bool Does this assignment accept submissions
626      */
627     function submissions_enabled() {
628         return $this->assignment->is_any_submission_plugin_enabled();
629     }
631     /**
632      * Return things to the renderer
633      *
634      * @return bool Can this user view all grades (the gradebook)
635      */
636     function can_view_all_grades() {
637         return has_capability('gradereport/grader:view', $this->assignment->get_course_context()) && has_capability('moodle/grade:viewall', $this->assignment->get_course_context());
638     }
640     /**
641      * Override the table show_hide_link to not show for select column
642      *
643      * @param string $column the column name, index into various names.
644      * @param int $index numerical index of the column.
645      * @return string HTML fragment.
646      */
647     protected function show_hide_link($column, $index) {
648         if ($index > 0) {
649             return parent::show_hide_link($column, $index);
650         }
651         return '';
652     }