MDL-34192 mod_assign: prevent ambiguous column use for Oracle.
[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, ';
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 (s.timemodified IS NOT NULL AND 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 (link to profile)
291      *
292      * @param stdClass $row
293      * @return string
294      */
295     function col_fullname($row) {
296         $courseid = $this->assignment->get_course()->id;
297         $link= new moodle_url('/user/view.php', array('id' =>$row->id, 'course'=>$courseid));
298         return $this->output->action_link($link, fullname($row));
299     }
301     /**
302      * Insert a checkbox for selecting the current row for batch operations
303      *
304      * @param stdClass $row
305      * @return string
306      */
307     function col_select(stdClass $row) {
308         return '<input type="checkbox" name="selectedusers" value="' . $row->userid . '"/>';
309     }
311     /**
312      * Return a users grades from the listing of all grade data for this assignment
313      *
314      * @param int $userid
315      * @return mixed stdClass or false
316      */
317     private function get_gradebook_data_for_user($userid) {
318         if (isset($this->gradinginfo->items[0]) && $this->gradinginfo->items[0]->grades[$userid]) {
319             return $this->gradinginfo->items[0]->grades[$userid];
320         }
321         return false;
322     }
324     /**
325      * Format a column of data for display
326      *
327      * @param stdClass $row
328      * @return string
329      */
330     function col_grade(stdClass $row) {
331         $o = '';
333         $link = '';
334         $separator = '';
335         $grade = '';
337         if (!$this->is_downloading()) {
338             $icon = $this->output->pix_icon('gradefeedback', get_string('grade'), 'mod_assign');
339             $url = new moodle_url('/mod/assign/view.php',
340                                             array('id' => $this->assignment->get_course_module()->id,
341                                                   'rownum'=>$this->rownum,'action'=>'grade'));
342             $link = $this->output->action_link($url, $icon);
343             $separator = $this->output->spacer(array(), true);
344         }
345         $gradingdisabled = $this->assignment->grading_disabled($row->id);
346         $grade = $this->display_grade($row->grade, $this->quickgrading && !$gradingdisabled, $row->userid, $row->timemarked);
348         //return $grade . $separator . $link;
349         return $link . $separator . $grade;
350     }
352     /**
353      * Format a column of data for display
354      *
355      * @param stdClass $row
356      * @return string
357      */
358     function col_finalgrade(stdClass $row) {
359         $o = '';
361         $grade = $this->get_gradebook_data_for_user($row->userid);
362         if ($grade) {
363             $o = $this->display_grade($grade->grade, false, $row->userid, $row->timemarked);
364         }
366         return $o;
367     }
369     /**
370      * Format a column of data for display
371      *
372      * @param stdClass $row
373      * @return string
374      */
375     function col_timemarked(stdClass $row) {
376         $o = '-';
378         if ($row->timemarked && $row->grade !== NULL && $row->grade >= 0) {
379             $o = userdate($row->timemarked);
380         }
382         return $o;
383     }
385     /**
386      * Format a column of data for display
387      *
388      * @param stdClass $row
389      * @return string
390      */
391     function col_timesubmitted(stdClass $row) {
392         $o = '-';
394         if ($row->timesubmitted) {
395             $o = userdate($row->timesubmitted);
396         }
398         return $o;
399     }
401     /**
402      * Format a column of data for display
403      *
404      * @param stdClass $row
405      * @return string
406      */
407     function col_status(stdClass $row) {
408         $o = '';
410         if ($this->assignment->is_any_submission_plugin_enabled()) {
412             $o .= $this->output->container(get_string('submissionstatus_' . $row->status, 'assign'), array('class'=>'submissionstatus' .$row->status));
413             if ($this->assignment->get_instance()->duedate && $row->timesubmitted > $this->assignment->get_instance()->duedate) {
414                 $o .= $this->output->container(get_string('submittedlateshort', 'assign', format_time($row->timesubmitted - $this->assignment->get_instance()->duedate)), 'latesubmission');
415             }
416             if ($row->locked) {
417                 $o .= $this->output->container(get_string('submissionslockedshort', 'assign'), 'lockedsubmission');
418             }
419             if ($row->grade !== NULL && $row->grade >= 0) {
420                 $o .= $this->output->container(get_string('graded', 'assign'), 'submissiongraded');
421             }
422         }
424         return $o;
425     }
427     /**
428      * Format a column of data for display
429      *
430      * @param stdClass $row
431      * @return string
432      */
433     function col_edit(stdClass $row) {
434         $edit = '';
435         if ($this->rownum < 0) {
436             $this->rownum = $this->currpage * $this->pagesize;
437         } else {
438             $this->rownum += 1;
439         }
441         $actions = array();
443         $url = new moodle_url('/mod/assign/view.php',
444                                             array('id' => $this->assignment->get_course_module()->id,
445                                                   'rownum'=>$this->rownum,'action'=>'grade'));
446         if (!$row->grade) {
447            $description = get_string('grade');
448         }else{
449            $description = get_string('updategrade','assign');
450         }
451         $actions[$url->out(false)] = $description;
453         if (!$row->status || $row->status == ASSIGN_SUBMISSION_STATUS_DRAFT || !$this->assignment->get_instance()->submissiondrafts) {
454             if (!$row->locked) {
455                 $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
456                                                                     'userid'=>$row->id,
457                                                                     'action'=>'lock',
458                                                                     'sesskey'=>sesskey(),
459                                                                     'page'=>$this->currpage));
460                 $description = get_string('preventsubmissionsshort', 'assign');
461                 $actions[$url->out(false)] = $description;
462             } else {
463                 $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
464                                                                     'userid'=>$row->id,
465                                                                     'action'=>'unlock',
466                                                                     'sesskey'=>sesskey(),
467                                                                     'page'=>$this->currpage));
468                 $description = get_string('allowsubmissionsshort', 'assign');
469                 $actions[$url->out(false)] = $description;
470             }
471         }
472         if ($row->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED && $this->assignment->get_instance()->submissiondrafts) {
473             $url = new moodle_url('/mod/assign/view.php', array('id' => $this->assignment->get_course_module()->id,
474                                                                 'userid'=>$row->id,
475                                                                 'action'=>'reverttodraft',
476                                                                 'sesskey'=>sesskey(),
477                                                                 'page'=>$this->currpage));
478             $description = get_string('reverttodraftshort', 'assign');
479             $actions[$url->out(false)] = $description;
480         }
482         $edit .= $this->output->container_start(array('yui3-menu', 'actionmenu'), 'actionselect' . $row->id);
483         $edit .= $this->output->container_start(array('yui3-menu-content'));
484         $edit .= html_writer::start_tag('ul');
485         $edit .= html_writer::start_tag('li', array('class'=>'menuicon'));
487         $menuicon = $this->output->pix_icon('i/menu', get_string('actions'));
488         $edit .= $this->output->action_link('#menu' . $row->id, $menuicon, null, array('class'=>'yui3-menu-label'));
489         $edit .= $this->output->container_start(array('yui3-menu', 'yui3-loading'), 'menu' . $row->id);
490         $edit .= $this->output->container_start(array('yui3-menu-content'));
491         $edit .= html_writer::start_tag('ul');
493         foreach ($actions as $url => $description) {
494             $edit .= html_writer::start_tag('li', array('class'=>'yui3-menuitem'));
496             $edit .= $this->output->action_link($url, $description, null, array('class'=>'yui3-menuitem-content'));
498             $edit .= html_writer::end_tag('li');
499         }
500         $edit .= html_writer::end_tag('ul');
501         $edit .= $this->output->container_end();
502         $edit .= $this->output->container_end();
503         $edit .= html_writer::end_tag('li');
504         $edit .= html_writer::end_tag('ul');
506         $edit .= $this->output->container_end();
507         $edit .= $this->output->container_end();
509         return $edit;
510     }
512     /**
513      * Write the plugin summary with an optional link to view the full feedback/submission.
514      *
515      * @param assign_plugin $plugin Submission plugin or feedback plugin
516      * @param stdClass $item Submission or grade
517      * @param string $returnaction The return action to pass to the view_submission page (the current page)
518      * @param string $returnparams The return params to pass to the view_submission page (the current page)
519      * @return string The summary with an optional link
520      */
521     private function format_plugin_summary_with_link(assign_plugin $plugin, stdClass $item, $returnaction, $returnparams) {
522         $link = '';
523         $showviewlink = false;
525         $summary = $plugin->view_summary($item, $showviewlink);
526         $separator = '';
527         if ($showviewlink) {
528             $icon = $this->output->pix_icon('t/preview', get_string('view' . substr($plugin->get_subtype(), strlen('assign')), 'mod_assign'));
529             $link = $this->output->action_link(
530                                 new moodle_url('/mod/assign/view.php',
531                                                array('id' => $this->assignment->get_course_module()->id,
532                                                      'sid'=>$item->id,
533                                                      'gid'=>$item->id,
534                                                      'plugin'=>$plugin->get_type(),
535                                                      'action'=>'viewplugin' . $plugin->get_subtype(),
536                                                      'returnaction'=>$returnaction,
537                                                      'returnparams'=>http_build_query($returnparams))),
538                                 $icon);
539             $separator = $this->output->spacer(array(), true);
540         }
542         return $link . $separator . $summary;
543     }
546     /**
547      * Format the submission and feedback columns
548      *
549      * @param string $colname The column name
550      * @param stdClass $row The submission row
551      * @return mixed string or NULL
552      */
553     function other_cols($colname, $row){
554         if (($pos = strpos($colname, 'assignsubmission_')) !== false) {
555             $plugin = $this->assignment->get_submission_plugin_by_type(substr($colname, strlen('assignsubmission_')));
557             if ($plugin->is_visible() && $plugin->is_enabled()) {
558                 if ($row->submissionid) {
559                     $submission = new stdClass();
560                     $submission->id = $row->submissionid;
561                     $submission->timecreated = $row->firstsubmission;
562                     $submission->timemodified = $row->timesubmitted;
563                     $submission->assignment = $this->assignment->get_instance()->id;
564                     $submission->userid = $row->userid;
565                     return $this->format_plugin_summary_with_link($plugin, $submission, 'grading', array());
566                 }
567             }
568             return '';
569         }
570         if (($pos = strpos($colname, 'feedback_')) !== false) {
571             $plugin = $this->assignment->get_feedback_plugin_by_type(substr($colname, strlen('assignfeedback_')));
572             if ($plugin->is_visible() && $plugin->is_enabled()) {
573                 $grade = null;
574                 if ($row->gradeid) {
575                     $grade = new stdClass();
576                     $grade->id = $row->gradeid;
577                     $grade->timecreated = $row->firstmarked;
578                     $grade->timemodified = $row->timemarked;
579                     $grade->assignment = $this->assignment->get_instance()->id;
580                     $grade->userid = $row->userid;
581                     $grade->grade = $row->grade;
582                     $grade->mailed = $row->mailed;
583                 }
584                 if ($this->quickgrading && $plugin->supports_quickgrading()) {
585                     return $plugin->get_quickgrading_html($row->userid, $grade);
586                 } else if ($grade) {
587                     return $this->format_plugin_summary_with_link($plugin, $grade, 'grading', array());
588                 }
589             }
590             return '';
591         }
592         return NULL;
593     }
595     /**
596      * Using the current filtering and sorting - load all rows and return a single column from them
597      *
598      * @param string $columnname The name of the raw column data
599      * @return array of data
600      */
601     function get_column_data($columnname) {
602         $this->setup();
603         $this->currpage = 0;
604         $this->query_db($this->tablemaxrows);
605         $result = array();
606         foreach ($this->rawdata as $row) {
607             $result[] = $row->$columnname;
608         }
609         return $result;
610     }
611     /**
612      * Using the current filtering and sorting - load a single row and return a single column from it
613      *
614      * @param int $rownumber The rownumber to load
615      * @param string $columnname The name of the raw column data
616      * @param bool $lastrow Set to true if this is the last row in the table
617      * @return mixed string or false
618      */
619     function get_cell_data($rownumber, $columnname, $lastrow) {
620         $this->setup();
621         $this->currpage = $rownumber;
622         $this->query_db(1);
623         if ($rownumber == $this->totalrows-1) {
624             $lastrow = true;
625         }
626         foreach ($this->rawdata as $row) {
627             return $row->$columnname;
628         }
629         return false;
630     }
632     /**
633      * Return things to the renderer
634      *
635      * @return string the assignment name
636      */
637     function get_assignment_name() {
638         return $this->assignment->get_instance()->name;
639     }
641     /**
642      * Return things to the renderer
643      *
644      * @return int the course module id
645      */
646     function get_course_module_id() {
647         return $this->assignment->get_course_module()->id;
648     }
650     /**
651      * Return things to the renderer
652      *
653      * @return int the course id
654      */
655     function get_course_id() {
656         return $this->assignment->get_course()->id;
657     }
659     /**
660      * Return things to the renderer
661      *
662      * @return stdClass The course context
663      */
664     function get_course_context() {
665         return $this->assignment->get_course_context();
666     }
668     /**
669      * Return things to the renderer
670      *
671      * @return bool Does this assignment accept submissions
672      */
673     function submissions_enabled() {
674         return $this->assignment->is_any_submission_plugin_enabled();
675     }
677     /**
678      * Return things to the renderer
679      *
680      * @return bool Can this user view all grades (the gradebook)
681      */
682     function can_view_all_grades() {
683         return has_capability('gradereport/grader:view', $this->assignment->get_course_context()) && has_capability('moodle/grade:viewall', $this->assignment->get_course_context());
684     }
686     /**
687      * Override the table show_hide_link to not show for select column
688      *
689      * @param string $column the column name, index into various names.
690      * @param int $index numerical index of the column.
691      * @return string HTML fragment.
692      */
693     protected function show_hide_link($column, $index) {
694         if ($index > 0) {
695             return parent::show_hide_link($column, $index);
696         }
697         return '';
698     }