Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
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
 */
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
require_once($CFG->libdir.'/tablelib.php');
28
require_once($CFG->libdir.'/gradelib.php');
29
require_once($CFG->dirroot.'/mod/assign/locallib.php');
30
 
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;
1441 ariadna 43
    /** @var int[] $pagingoptions Available pagination options */
44
    private $pagingoptions = [10, 20, 50, 100];
1 efrain 45
    /** @var int $rownum (global index of current row in table) */
46
    private $rownum = -1;
47
    /** @var renderer_base for getting output */
48
    private $output = null;
49
    /** @var stdClass gradinginfo */
50
    private $gradinginfo = null;
51
    /** @var int $tablemaxrows */
52
    private $tablemaxrows = 10000;
53
    /** @var boolean $quickgrading */
54
    private $quickgrading = false;
55
    /** @var boolean $hasgrantextension - Only do the capability check once for the entire table */
56
    private $hasgrantextension = false;
57
    /** @var boolean $hasgrade - Only do the capability check once for the entire table */
58
    private $hasgrade = false;
59
    /** @var array $groupsubmissions - A static cache of group submissions */
60
    private $groupsubmissions = array();
61
    /** @var array $submissiongroups - A static cache of submission groups */
62
    private $submissiongroups = array();
63
    /** @var array $plugincache - A cache of plugin lookups to match a column name to a plugin efficiently */
64
    private $plugincache = array();
65
    /** @var array $scale - A list of the keys and descriptions for the custom scale */
66
    private $scale = null;
67
    /** @var bool true if the user has this capability. Otherwise false. */
68
    private $hasviewblind;
69
 
70
    /**
71
     * overridden constructor keeps a reference to the assignment class that is displaying this table
72
     *
73
     * @param assign $assignment The assignment class
74
     * @param int $perpage how many per page
75
     * @param string $filter The current filter
76
     * @param int $rowoffset For showing a subsequent page of results
77
     * @param bool $quickgrading Is this table wrapped in a quickgrading form?
78
     * @param string $downloadfilename
79
     */
80
    public function __construct(assign $assignment,
81
                                $perpage,
82
                                $filter,
83
                                $rowoffset,
84
                                $quickgrading,
85
                                $downloadfilename = null) {
86
        global $CFG, $PAGE, $DB, $USER;
87
 
88
        parent::__construct('mod_assign_grading-' . $assignment->get_context()->id);
89
 
90
        $this->is_persistent(true);
1441 ariadna 91
        $this->set_attribute('id', 'submissions');
1 efrain 92
        $this->assignment = $assignment;
93
 
94
        // Check permissions up front.
95
        $this->hasgrantextension = has_capability('mod/assign:grantextension',
96
                                                  $this->assignment->get_context());
97
        $this->hasgrade = $this->assignment->can_grade();
98
 
99
        // Check if we have the elevated view capablities to see the blind details.
100
        $this->hasviewblind = has_capability('mod/assign:viewblinddetails',
101
                $this->assignment->get_context());
102
 
103
        $this->perpage = $perpage;
104
        $this->quickgrading = $quickgrading && $this->hasgrade;
105
        $this->output = $PAGE->get_renderer('mod_assign');
106
 
107
        $urlparams = array('action' => 'grading', 'id' => $assignment->get_course_module()->id);
108
        $url = new moodle_url($CFG->wwwroot . '/mod/assign/view.php', $urlparams);
109
        $this->define_baseurl($url);
110
 
111
        // Do some business - then set the sql.
112
        $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
113
 
114
        if ($rowoffset) {
115
            $this->rownum = $rowoffset - 1;
116
        }
117
 
1441 ariadna 118
        $userid = optional_param('userid', null, PARAM_INT);
119
        $groupid = groups_get_course_group($assignment->get_course(), true);
120
        // If the user ID is set, it indicates that a user has been selected. In this case, override the user search
121
        // string with the full name of the selected user.
122
        $usersearch = $userid ? fullname(\core_user::get_user($userid)) : optional_param('search', '', PARAM_NOTAGS);
123
        $assignment->set_usersearch($userid, $groupid, $usersearch);
1 efrain 124
        $users = array_keys( $assignment->list_participants($currentgroup, true));
125
        if (count($users) == 0) {
126
            // Insert a record that will never match to the sql is still valid.
127
            $users[] = -1;
128
        }
129
 
130
        $params = array();
131
        $params['assignmentid1'] = (int)$this->assignment->get_instance()->id;
132
        $params['assignmentid2'] = (int)$this->assignment->get_instance()->id;
133
        $params['assignmentid3'] = (int)$this->assignment->get_instance()->id;
134
        $params['newstatus'] = ASSIGN_SUBMISSION_STATUS_NEW;
135
 
136
        // TODO Does not support custom user profile fields (MDL-70456).
137
        $userfieldsapi = \core_user\fields::for_identity($this->assignment->get_context(), false)->with_userpic();
138
        $userfields = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
139
        $extrauserfields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]);
140
        $fields = $userfields . ', ';
141
        $fields .= 'u.id as userid, ';
142
        $fields .= 's.status as status, ';
143
        $fields .= 's.id as submissionid, ';
144
        $fields .= 's.timecreated as firstsubmission, ';
145
        $fields .= "CASE WHEN status <> :newstatus THEN s.timemodified ELSE NULL END as timesubmitted, ";
146
        $fields .= 's.attemptnumber as attemptnumber, ';
147
        $fields .= 'g.id as gradeid, ';
148
        $fields .= 'g.grade as grade, ';
149
        $fields .= 'g.timemodified as timemarked, ';
150
        $fields .= 'g.timecreated as firstmarked, ';
151
        $fields .= 'uf.mailed as mailed, ';
152
        $fields .= 'uf.locked as locked, ';
153
        $fields .= 'uf.extensionduedate as extensionduedate, ';
154
        $fields .= 'uf.workflowstate as workflowstate, ';
155
        $fields .= 'uf.allocatedmarker as allocatedmarker';
156
 
157
        $from = '{user} u
158
                         LEFT JOIN {assign_submission} s
159
                                ON u.id = s.userid
160
                               AND s.assignment = :assignmentid1
161
                               AND s.latest = 1 ';
162
 
163
        // For group assignments, there can be a grade with no submission.
164
        $from .= ' LEFT JOIN {assign_grades} g
165
                            ON g.assignment = :assignmentid2
166
                           AND u.id = g.userid
167
                           AND (g.attemptnumber = s.attemptnumber OR s.attemptnumber IS NULL) ';
168
 
169
        $from .= 'LEFT JOIN {assign_user_flags} uf
170
                         ON u.id = uf.userid
171
                        AND uf.assignment = :assignmentid3 ';
172
 
173
        if ($this->assignment->get_course()->relativedatesmode) {
174
            $params['courseid1'] = $this->assignment->get_course()->id;
175
            $from .= ' LEFT JOIN (
176
            SELECT ue1.userid as enroluserid,
177
              CASE WHEN MIN(ue1.timestart - c2.startdate) < 0 THEN 0 ELSE MIN(ue1.timestart - c2.startdate) END as enrolstartoffset
178
              FROM {enrol} e1
179
              JOIN {user_enrolments} ue1
180
                ON (ue1.enrolid = e1.id AND ue1.status = 0)
181
              JOIN {course} c2
182
                ON c2.id = e1.courseid
183
             WHERE e1.courseid = :courseid1 AND e1.status = 0
184
             GROUP BY ue1.userid
185
            ) enroloffset
186
            ON (enroloffset.enroluserid = u.id) ';
187
        }
188
 
189
        $hasoverrides = $this->assignment->has_overrides();
190
        $inrelativedatesmode = $this->assignment->get_course()->relativedatesmode;
191
 
192
        if ($hasoverrides) {
193
            $params['assignmentid5'] = (int)$this->assignment->get_instance()->id;
194
            $params['assignmentid6'] = (int)$this->assignment->get_instance()->id;
195
            $params['assignmentid7'] = (int)$this->assignment->get_instance()->id;
196
            $params['assignmentid8'] = (int)$this->assignment->get_instance()->id;
197
            $params['assignmentid9'] = (int)$this->assignment->get_instance()->id;
198
 
199
            list($userwhere1, $userparams1) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'priorityuser');
200
            list($userwhere2, $userparams2) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'effectiveuser');
201
 
202
            $userwhere1 = "WHERE u.id {$userwhere1}";
203
            $userwhere2 = "WHERE u.id {$userwhere2}";
204
            $params = array_merge($params, $userparams1);
205
            $params = array_merge($params, $userparams2);
206
 
207
            $fields .= ', priority.priority, ';
208
            $fields .= 'effective.allowsubmissionsfromdate, ';
209
 
210
            if ($inrelativedatesmode) {
211
                // If the priority is less than the 9999999 constant value it means it's an override
212
                // and we should use that value directly. Otherwise we need to apply the uesr's course
213
                // start date offset.
214
                $fields .= 'CASE WHEN priority.priority < 9999999 THEN effective.duedate ELSE' .
215
                           ' effective.duedate + enroloffset.enrolstartoffset END as duedate, ';
216
            } else {
217
                $fields .= 'effective.duedate, ';
218
            }
219
 
220
            $fields .= 'effective.cutoffdate ';
221
 
222
            $from .= ' LEFT JOIN (
223
               SELECT merged.userid, min(merged.priority) priority FROM (
224
                  ( SELECT u.id as userid, 9999999 AS priority
225
                      FROM {user} u '.$userwhere1.'
226
                  )
227
                  UNION
228
                  ( SELECT uo.userid, 0 AS priority
229
                      FROM {assign_overrides} uo
230
                     WHERE uo.assignid = :assignmentid5
231
                  )
232
                  UNION
233
                  ( SELECT gm.userid, go.sortorder AS priority
234
                      FROM {assign_overrides} go
235
                      JOIN {groups} g ON g.id = go.groupid
236
                      JOIN {groups_members} gm ON gm.groupid = g.id
237
                     WHERE go.assignid = :assignmentid6
238
                  )
239
                ) merged
240
                GROUP BY merged.userid
241
              ) priority ON priority.userid = u.id
242
 
243
            JOIN (
244
              (SELECT 9999999 AS priority,
245
                      u.id AS userid,
246
                      a.allowsubmissionsfromdate,
247
                      a.duedate,
248
                      a.cutoffdate
249
                 FROM {user} u
250
                 JOIN {assign} a ON a.id = :assignmentid7
251
                 '.$userwhere2.'
252
              )
253
              UNION
254
              (SELECT 0 AS priority,
255
                      uo.userid,
256
                      uo.allowsubmissionsfromdate,
257
                      uo.duedate,
258
                      uo.cutoffdate
259
                 FROM {assign_overrides} uo
260
                WHERE uo.assignid = :assignmentid8
261
              )
262
              UNION
263
              (SELECT go.sortorder AS priority,
264
                      gm.userid,
265
                      go.allowsubmissionsfromdate,
266
                      go.duedate,
267
                      go.cutoffdate
268
                 FROM {assign_overrides} go
269
                 JOIN {groups} g ON g.id = go.groupid
270
                 JOIN {groups_members} gm ON gm.groupid = g.id
271
                WHERE go.assignid = :assignmentid9
272
              )
273
 
274
            ) effective ON effective.priority = priority.priority AND effective.userid = priority.userid ';
275
        } else if ($inrelativedatesmode) {
276
            // In relative dates mode and when we don't have overrides, include the
277
            // duedate, cutoffdate and allowsubmissionsfrom date anyway as this information is useful and can vary.
278
            $params['assignmentid5'] = (int)$this->assignment->get_instance()->id;
279
            $fields .= ', a.duedate + enroloffset.enrolstartoffset as duedate, ';
280
            $fields .= 'a.allowsubmissionsfromdate, ';
281
            $fields .= 'a.cutoffdate ';
282
            $from .= 'JOIN {assign} a ON a.id = :assignmentid5 ';
283
        }
284
 
285
        if (!empty($this->assignment->get_instance()->blindmarking)) {
286
            $from .= 'LEFT JOIN {assign_user_mapping} um
287
                             ON u.id = um.userid
288
                            AND um.assignment = :assignmentidblind ';
289
            $params['assignmentidblind'] = (int)$this->assignment->get_instance()->id;
290
            $fields .= ', um.id as recordid ';
291
        }
292
 
293
        $userparams3 = array();
294
        $userindex = 0;
295
 
296
        list($userwhere3, $userparams3) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED, 'user');
297
        $where = 'u.id ' . $userwhere3;
298
        $params = array_merge($params, $userparams3);
299
 
300
        // The filters do not make sense when there are no submissions, so do not apply them.
301
        if ($this->assignment->is_any_submission_plugin_enabled()) {
302
            if ($filter == ASSIGN_FILTER_SUBMITTED) {
303
                $where .= ' AND (s.timemodified IS NOT NULL AND
304
                                 s.status = :submitted) ';
305
                $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
306
 
307
            } else if ($filter == ASSIGN_FILTER_NOT_SUBMITTED) {
308
                $where .= ' AND (s.timemodified IS NULL OR s.status <> :submitted) ';
309
                $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
310
            } else if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) {
311
                $where .= ' AND (s.timemodified IS NOT NULL AND
312
                                 s.status = :submitted AND
313
                                 (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL';
314
 
315
                // Assignment grade is set to the negative grade scale id when scales are used.
316
                if ($this->assignment->get_instance()->grade < 0) {
317
                    // Scale grades are set to -1 when not graded.
318
                    $where .= ' OR g.grade = -1';
319
                }
320
 
321
                $where .= '))';
322
                $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
323
 
1441 ariadna 324
            } else if ($filter == ASSIGN_FILTER_GRADED) {
325
                $where .= ' AND (s.timemodified IS NOT NULL AND
326
                                 s.timemodified < g.timemodified AND g.grade IS NOT NULL)';
327
                $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
328
 
1 efrain 329
            } else if ($filter == ASSIGN_FILTER_GRANTED_EXTENSION) {
330
                $where .= ' AND uf.extensionduedate > 0 ';
331
 
332
            } else if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) {
333
                $userfilter = (int) array_pop(explode('=', $filter));
334
                $where .= ' AND (u.id = :userid)';
335
                $params['userid'] = $userfilter;
336
            } else if ($filter == ASSIGN_FILTER_DRAFT) {
337
                $where .= ' AND (s.timemodified IS NOT NULL AND
338
                                 s.status = :draft) ';
339
                $params['draft'] = ASSIGN_SUBMISSION_STATUS_DRAFT;
340
            }
341
        }
342
 
343
        if ($this->assignment->get_instance()->markingworkflow &&
344
            $this->assignment->get_instance()->markingallocation) {
345
            if (has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
346
                // Check to see if marker filter is set.
347
                $markerfilter = (int)get_user_preferences('assign_markerfilter', '');
348
                if (!empty($markerfilter)) {
349
                    if ($markerfilter == ASSIGN_MARKER_FILTER_NO_MARKER) {
350
                        $where .= ' AND (uf.allocatedmarker IS NULL OR uf.allocatedmarker = 0)';
351
                    } else {
352
                        $where .= ' AND uf.allocatedmarker = :markerid';
353
                        $params['markerid'] = $markerfilter;
354
                    }
355
                }
356
            }
357
        }
358
 
359
        if ($this->assignment->get_instance()->markingworkflow) {
360
            $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user();
361
            if (!empty($workflowstates)) {
362
                $workflowfilter = get_user_preferences('assign_workflowfilter', '');
363
                if ($workflowfilter == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED) {
364
                    $where .= ' AND (uf.workflowstate = :workflowstate OR uf.workflowstate IS NULL OR '.
365
                        $DB->sql_isempty('assign_user_flags', 'workflowstate', true, true).')';
366
                    $params['workflowstate'] = $workflowfilter;
367
                } else if (array_key_exists($workflowfilter, $workflowstates)) {
368
                    $where .= ' AND uf.workflowstate = :workflowstate';
369
                    $params['workflowstate'] = $workflowfilter;
370
                }
371
            }
372
        }
373
 
374
        $this->set_sql($fields, $from, $where, $params);
375
 
376
        if ($downloadfilename) {
377
            $this->is_downloading('csv', $downloadfilename);
378
        }
379
 
380
        $columns = array();
381
        $headers = array();
382
 
383
        // Select.
384
        if (!$this->is_downloading() && $this->hasgrade) {
385
            $columns[] = 'select';
1441 ariadna 386
            // The displayed text for the column header. Hidden to assistive technologies.
387
            $visibleheader = html_writer::span(get_string('select'), '', ['aria-hidden' => 'true']);
388
            // The actual accessible name for the column header which provides more context about the column's purpose to
389
            // screen reader users.
390
            $bulkactionsselection = html_writer::span(get_string('bulkactionsselection', 'assign'), 'visually-hidden');
391
 
392
            // The select all checkbox.
393
            $selectalllabel = html_writer::label(get_string('selectall'), 'selectall', false, ['class' => 'visually-hidden']);
394
            $selectallcheckbox = html_writer::empty_tag(
395
                'input',
396
                [
397
                    'type' => 'checkbox',
398
                    'id' => 'selectall',
399
                    'name' => 'selectall',
400
                ],
401
            );
402
            $headers[] = $visibleheader . $bulkactionsselection .
403
                html_writer::div($selectalllabel . $selectallcheckbox, 'selectall');
1 efrain 404
        }
405
 
406
        if ($this->hasviewblind || !$this->assignment->is_blind_marking()) {
1441 ariadna 407
            if ($this->is_downloading()) {
1 efrain 408
                $columns[] = 'recordid';
409
                $headers[] = get_string('recordid', 'assign');
410
            }
411
 
412
            // Fullname.
413
            $columns[] = 'fullname';
414
            $headers[] = get_string('fullname');
415
            // Participant # details if can view real identities.
416
            if ($this->assignment->is_blind_marking()) {
417
                if (!$this->is_downloading()) {
418
                    $columns[] = 'recordid';
419
                    $headers[] = get_string('recordid', 'assign');
420
                }
421
            }
422
 
423
            foreach ($extrauserfields as $extrafield) {
424
                $columns[] = $extrafield;
425
                $headers[] = \core_user\fields::get_display_name($extrafield);
426
            }
427
        } else {
428
            // Record ID.
429
            $columns[] = 'recordid';
430
            $headers[] = get_string('recordid', 'assign');
431
        }
432
 
433
        // Submission status.
434
        $columns[] = 'status';
435
        $headers[] = get_string('status', 'assign');
436
 
437
        if ($hasoverrides || $inrelativedatesmode) {
438
            // Allowsubmissionsfromdate.
439
            $columns[] = 'allowsubmissionsfromdate';
440
            $headers[] = get_string('allowsubmissionsfromdate', 'assign');
441
 
442
            // Duedate.
443
            $columns[] = 'duedate';
444
            $headers[] = get_string('duedate', 'assign');
445
 
446
            // Cutoffdate.
447
            $columns[] = 'cutoffdate';
448
            $headers[] = get_string('cutoffdate', 'assign');
449
        }
450
 
451
        // Team submission columns.
452
        if ($assignment->get_instance()->teamsubmission) {
453
            $columns[] = 'team';
454
            $headers[] = get_string('submissionteam', 'assign');
455
        }
456
        // Allocated marker.
457
        if ($this->assignment->get_instance()->markingworkflow &&
458
            $this->assignment->get_instance()->markingallocation &&
459
            has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
460
            // Add a column for the allocated marker.
461
            $columns[] = 'allocatedmarker';
462
            $headers[] = get_string('marker', 'assign');
463
        }
464
        // Grade.
465
        $columns[] = 'grade';
466
        $headers[] = get_string('gradenoun');
467
        if ($this->is_downloading()) {
468
            $gradetype = $this->assignment->get_instance()->grade;
469
            if ($gradetype > 0) {
470
                $columns[] = 'grademax';
471
                $headers[] = get_string('maxgrade', 'assign');
472
            } else if ($gradetype < 0) {
473
                // This is a custom scale.
474
                $columns[] = 'scale';
475
                $headers[] = get_string('scale', 'assign');
476
            }
477
 
478
            if ($this->assignment->get_instance()->markingworkflow) {
479
                // Add a column for the marking workflow state.
480
                $columns[] = 'workflowstate';
481
                $headers[] = get_string('markingworkflowstate', 'assign');
482
            }
483
            // Add a column to show if this grade can be changed.
484
            $columns[] = 'gradecanbechanged';
485
            $headers[] = get_string('gradecanbechanged', 'assign');
486
        }
487
 
488
        // Submission plugins.
489
        if ($assignment->is_any_submission_plugin_enabled()) {
490
            $columns[] = 'timesubmitted';
491
            $headers[] = get_string('lastmodifiedsubmission', 'assign');
492
 
493
            foreach ($this->assignment->get_submission_plugins() as $plugin) {
494
                if ($this->is_downloading()) {
495
                    if ($plugin->is_visible() && $plugin->is_enabled()) {
496
                        foreach ($plugin->get_editor_fields() as $field => $description) {
497
                            $index = 'plugin' . count($this->plugincache);
498
                            $this->plugincache[$index] = array($plugin, $field);
499
                            $columns[] = $index;
500
                            $headers[] = $plugin->get_name();
501
                        }
502
                    }
503
                } else {
504
                    if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
505
                        $index = 'plugin' . count($this->plugincache);
506
                        $this->plugincache[$index] = array($plugin);
507
                        $columns[] = $index;
508
                        $headers[] = $plugin->get_name();
509
                    }
510
                }
511
            }
512
        }
513
 
514
        // Time marked.
515
        $columns[] = 'timemarked';
516
        $headers[] = get_string('lastmodifiedgrade', 'assign');
517
 
518
        // Feedback plugins.
519
        foreach ($this->assignment->get_feedback_plugins() as $plugin) {
520
            if ($this->is_downloading()) {
521
                if ($plugin->is_visible() && $plugin->is_enabled()) {
522
                    foreach ($plugin->get_editor_fields() as $field => $description) {
523
                        $index = 'plugin' . count($this->plugincache);
524
                        $this->plugincache[$index] = array($plugin, $field);
525
                        $columns[] = $index;
526
                        $headers[] = $description;
527
                    }
528
                }
529
            } else if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
530
                $index = 'plugin' . count($this->plugincache);
531
                $this->plugincache[$index] = array($plugin);
532
                $columns[] = $index;
533
                $headers[] = $plugin->get_name();
534
            }
535
        }
536
 
537
        // Exclude 'Final grade' column in downloaded grading worksheets.
538
        if (!$this->is_downloading()) {
539
            // Final grade.
540
            $columns[] = 'finalgrade';
541
            $headers[] = get_string('finalgrade', 'grades');
542
        }
543
 
544
        // Load the grading info for all users.
545
        $this->gradinginfo = grade_get_grades($this->assignment->get_course()->id,
546
                                              'mod',
547
                                              'assign',
548
                                              $this->assignment->get_instance()->id,
549
                                              $users);
550
 
551
        if (!empty($CFG->enableoutcomes) && !empty($this->gradinginfo->outcomes)) {
552
            $columns[] = 'outcomes';
553
            $headers[] = get_string('outcomes', 'grades');
554
        }
555
 
556
        // Set the columns.
557
        $this->define_columns($columns);
558
        $this->define_headers($headers);
1441 ariadna 559
        $this->column_class('fullname', 'username');
560
        $this->column_class('status', 'status');
561
        $this->column_class('grade', 'grade');
1 efrain 562
        foreach ($extrauserfields as $extrafield) {
563
             $this->column_class($extrafield, $extrafield);
564
        }
565
        $this->no_sorting('recordid');
566
        $this->no_sorting('finalgrade');
567
        $this->no_sorting('userid');
568
        $this->no_sorting('select');
569
        $this->no_sorting('outcomes');
570
 
571
        if ($assignment->get_instance()->teamsubmission) {
572
            $this->no_sorting('team');
573
        }
574
 
575
        $plugincolumnindex = 0;
576
        foreach ($this->assignment->get_submission_plugins() as $plugin) {
577
            if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
578
                $submissionpluginindex = 'plugin' . $plugincolumnindex++;
579
                $this->no_sorting($submissionpluginindex);
580
            }
581
        }
582
        foreach ($this->assignment->get_feedback_plugins() as $plugin) {
583
            if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
584
                $feedbackpluginindex = 'plugin' . $plugincolumnindex++;
585
                $this->no_sorting($feedbackpluginindex);
586
            }
587
        }
588
 
589
        // When there is no data we still want the column headers printed in the csv file.
590
        if ($this->is_downloading()) {
591
            $this->start_output();
592
        }
593
    }
594
 
595
    /**
596
     * Before adding each row to the table make sure rownum is incremented.
597
     *
598
     * @param array $row row of data from db used to make one row of the table.
599
     * @return array one row for the table
600
     */
601
    public function format_row($row) {
602
        if ($this->rownum < 0) {
603
            $this->rownum = $this->currpage * $this->pagesize;
604
        } else {
605
            $this->rownum += 1;
606
        }
607
 
608
        return parent::format_row($row);
609
    }
610
 
611
    /**
612
     * Add a column with an ID that uniquely identifies this user in this assignment.
613
     *
614
     * @param stdClass $row
615
     * @return string
616
     */
617
    public function col_recordid(stdClass $row) {
618
        if (empty($row->recordid)) {
619
            $row->recordid = $this->assignment->get_uniqueid_for_user($row->userid);
620
        }
621
        return get_string('hiddenuser', 'assign') . $row->recordid;
622
    }
623
 
624
 
625
    /**
626
     * Add the userid to the row class so it can be updated via ajax.
627
     *
628
     * @param stdClass $row The row of data
629
     * @return string The row class
630
     */
631
    public function get_row_class($row) {
632
        return 'user' . $row->userid;
633
    }
634
 
635
    /**
636
     * Return the number of rows to display on a single page.
637
     *
638
     * @return int The number of rows per page
639
     */
640
    public function get_rows_per_page() {
641
        return $this->perpage;
642
    }
643
 
644
    /**
645
     * list current marking workflow state
646
     *
647
     * @param stdClass $row
648
     * @return string
649
     */
650
    public function col_workflowstatus(stdClass $row) {
651
        $o = '';
652
 
653
        $gradingdisabled = $this->assignment->grading_disabled($row->id, true, $this->gradinginfo);
654
        // The function in the assignment keeps a static cache of this list of states.
655
        $workflowstates = $this->assignment->get_marking_workflow_states_for_current_user();
656
        $workflowstate = $row->workflowstate;
657
        if (empty($workflowstate)) {
658
            $workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
659
        }
660
        if ($this->quickgrading && !$gradingdisabled) {
661
            $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
662
            $name = 'quickgrade_' . $row->id . '_workflowstate';
663
            if ($workflowstate !== ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED && !array_key_exists($workflowstate, $workflowstates)) {
664
                $allworkflowstates = $this->assignment->get_all_marking_workflow_states();
665
                $o .= html_writer::div($allworkflowstates[$workflowstate]);
666
            } else {
667
                $o .= html_writer::select($workflowstates, $name, $workflowstate, ['' => $notmarked]);
668
                // Check if this user is a marker that can't manage allocations and doesn't have the marker column added.
669
                if ($this->assignment->get_instance()->markingworkflow &&
670
                    $this->assignment->get_instance()->markingallocation &&
671
                    !has_capability('mod/assign:manageallocations', $this->assignment->get_context())) {
672
 
673
                    $name = 'quickgrade_' . $row->id . '_allocatedmarker';
674
                    $o .= html_writer::empty_tag('input', ['type' => 'hidden', 'name' => $name,
675
                            'value' => $row->allocatedmarker]);
676
                }
677
            }
678
        } else {
679
            $o .= $this->output->container(get_string('markingworkflowstate' . $workflowstate, 'assign'), $workflowstate);
680
        }
681
        return $o;
682
    }
683
 
684
    /**
685
     * For download only - list current marking workflow state
686
     *
687
     * @param stdClass $row - The row of data
688
     * @return string The current marking workflow state
689
     */
690
    public function col_workflowstate($row) {
691
        $state = $row->workflowstate;
692
        if (empty($state)) {
693
            $state = ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
694
        }
695
 
696
        return get_string('markingworkflowstate' . $state, 'assign');
697
    }
698
 
699
    /**
700
     * list current marker
701
     *
702
     * @param stdClass $row - The row of data
703
     * @return id the user->id of the marker.
704
     */
705
    public function col_allocatedmarker(stdClass $row) {
706
        static $markers = null;
707
        static $markerlist = array();
708
        if ($markers === null) {
709
            list($sort, $params) = users_order_by_sql('u');
710
            // Only enrolled users could be assigned as potential markers.
711
            $markers = get_enrolled_users($this->assignment->get_context(), 'mod/assign:grade', 0, 'u.*', $sort);
712
            $markerlist[0] = get_string('choosemarker', 'assign');
713
            $viewfullnames = has_capability('moodle/site:viewfullnames', $this->assignment->get_context());
714
            foreach ($markers as $marker) {
715
                $markerlist[$marker->id] = fullname($marker, $viewfullnames);
716
            }
717
        }
718
        if (empty($markerlist)) {
719
            // TODO: add some form of notification here that no markers are available.
720
            return '';
721
        }
722
        if ($this->is_downloading()) {
723
            if (isset($markers[$row->allocatedmarker])) {
724
                return fullname($markers[$row->allocatedmarker],
725
                        has_capability('moodle/site:viewfullnames', $this->assignment->get_context()));
726
            } else {
727
                return '';
728
            }
729
        }
730
 
731
        if ($this->quickgrading && has_capability('mod/assign:manageallocations', $this->assignment->get_context()) &&
732
            (empty($row->workflowstate) ||
733
             $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INMARKING ||
734
             $row->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED)) {
735
 
736
            $name = 'quickgrade_' . $row->id . '_allocatedmarker';
737
            return  html_writer::select($markerlist, $name, $row->allocatedmarker, false);
738
        } else if (!empty($row->allocatedmarker)) {
739
            $output = '';
740
            if ($this->quickgrading) { // Add hidden field for quickgrading page.
741
                $name = 'quickgrade_' . $row->id . '_allocatedmarker';
742
                $attributes = ['type' => 'hidden', 'name' => $name, 'value' => $row->allocatedmarker];
743
                $output .= html_writer::empty_tag('input', $attributes);
744
            }
745
            $output .= $markerlist[$row->allocatedmarker];
746
            return $output;
747
        }
748
    }
749
    /**
750
     * For download only - list all the valid options for this custom scale.
751
     *
752
     * @param stdClass $row - The row of data
753
     * @return string A list of valid options for the current scale
754
     */
755
    public function col_scale($row) {
756
        global $DB;
757
 
758
        if (empty($this->scale)) {
759
            $dbparams = array('id' => -($this->assignment->get_instance()->grade));
760
            $this->scale = $DB->get_record('scale', $dbparams);
761
        }
762
 
763
        if (!empty($this->scale->scale)) {
764
            return implode("\n", explode(',', $this->scale->scale));
765
        }
766
        return '';
767
    }
768
 
769
    /**
770
     * Display a grade with scales etc.
771
     *
772
     * @param string $grade
773
     * @param boolean $editable
774
     * @param int $userid The user id of the user this grade belongs to
775
     * @param int $modified Timestamp showing when the grade was last modified
1441 ariadna 776
     * @param float $deductedmark The deducted mark if penalty is applied
1 efrain 777
     * @return string The formatted grade
778
     */
1441 ariadna 779
    public function display_grade($grade, $editable, $userid, $modified, float $deductedmark = 0) {
1 efrain 780
        if ($this->is_downloading()) {
781
            if ($this->assignment->get_instance()->grade >= 0) {
782
                if ($grade == -1 || $grade === null) {
783
                    return '';
784
                }
785
                $gradeitem = $this->assignment->get_grade_item();
786
                return format_float($grade, $gradeitem->get_decimals());
787
            } else {
788
                // This is a custom scale.
789
                $scale = $this->assignment->display_grade($grade, false);
790
                if ($scale == '-') {
791
                    $scale = '';
792
                }
793
                return $scale;
794
            }
795
        }
1441 ariadna 796
        return $this->assignment->display_grade($grade, $editable, $userid, $modified, $deductedmark);
1 efrain 797
    }
798
 
799
    /**
800
     * Get the team info for this user.
801
     *
802
     * @param stdClass $row
803
     * @return string The team name
804
     */
805
    public function col_team(stdClass $row) {
806
        $submission = false;
807
        $group = false;
808
        $this->get_group_and_submission($row->id, $group, $submission, -1);
809
        if ($group) {
810
            return format_string($group->name, true, ['context' => $this->assignment->get_context()]);
811
        } else if ($this->assignment->get_instance()->preventsubmissionnotingroup) {
812
            $usergroups = $this->assignment->get_all_groups($row->id);
813
            if (count($usergroups) > 1) {
814
                return get_string('multipleteamsgrader', 'assign');
815
            } else {
816
                return get_string('noteamgrader', 'assign');
817
            }
818
        }
819
        return get_string('defaultteam', 'assign');
820
    }
821
 
822
    /**
823
     * Use a static cache to try and reduce DB calls.
824
     *
825
     * @param int $userid The user id for this submission
826
     * @param int $group The groupid (returned)
827
     * @param stdClass|false $submission The stdClass submission or false (returned)
828
     * @param int $attemptnumber Return a specific attempt number (-1 for latest)
829
     */
830
    protected function get_group_and_submission($userid, &$group, &$submission, $attemptnumber) {
831
        $group = false;
832
        if (isset($this->submissiongroups[$userid])) {
833
            $group = $this->submissiongroups[$userid];
834
        } else {
835
            $group = $this->assignment->get_submission_group($userid, false);
836
            $this->submissiongroups[$userid] = $group;
837
        }
838
 
839
        $groupid = 0;
840
        if ($group) {
841
            $groupid = $group->id;
842
        }
843
 
844
        // Static cache is keyed by groupid and attemptnumber.
845
        // We may need both the latest and previous attempt in the same page.
846
        if (isset($this->groupsubmissions[$groupid . ':' . $attemptnumber])) {
847
            $submission = $this->groupsubmissions[$groupid . ':' . $attemptnumber];
848
        } else {
849
            $submission = $this->assignment->get_group_submission($userid, $groupid, false, $attemptnumber);
850
            $this->groupsubmissions[$groupid . ':' . $attemptnumber] = $submission;
851
        }
852
    }
853
 
854
    /**
855
     * Format a list of outcomes.
856
     *
857
     * @param stdClass $row
858
     * @return string
859
     */
860
    public function col_outcomes(stdClass $row) {
861
        $outcomes = '';
862
        foreach ($this->gradinginfo->outcomes as $index => $outcome) {
863
            $options = make_grades_menu(-$outcome->scaleid);
864
 
865
            $options[0] = get_string('nooutcome', 'grades');
866
            if ($this->quickgrading && !($outcome->grades[$row->userid]->locked)) {
867
                $select = '<select name="outcome_' . $index . '_' . $row->userid . '" class="quickgrade">';
868
                foreach ($options as $optionindex => $optionvalue) {
869
                    $selected = '';
870
                    if ($outcome->grades[$row->userid]->grade == $optionindex) {
871
                        $selected = 'selected="selected"';
872
                    }
873
                    $select .= '<option value="' . $optionindex . '"' . $selected . '>' . $optionvalue . '</option>';
874
                }
875
                $select .= '</select>';
876
                $outcomes .= $this->output->container($outcome->name . ': ' . $select, 'outcome');
877
            } else {
878
                $name = $outcome->name . ': ' . $options[$outcome->grades[$row->userid]->grade];
879
                if ($this->is_downloading()) {
880
                    $outcomes .= $name;
881
                } else {
882
                    $outcomes .= $this->output->container($name, 'outcome');
883
                }
884
            }
885
        }
886
 
887
        return $outcomes;
888
    }
889
 
890
 
891
    /**
892
     * Format a user picture for display.
893
     *
894
     * @param stdClass $row
895
     * @return string
1441 ariadna 896
     * @deprecated since Moodle 4.5
897
     * @todo Final deprecation in Moodle 6.0. See MDL-82336.
1 efrain 898
     */
1441 ariadna 899
    #[\core\attribute\deprecated(
900
        replacement: null,
901
        since: '4.5',
902
        reason: 'Picture column is merged with fullname column'
903
    )]
1 efrain 904
    public function col_picture(stdClass $row) {
1441 ariadna 905
        \core\deprecation::emit_deprecation([$this, __FUNCTION__]);
1 efrain 906
        return $this->output->user_picture($row);
907
    }
908
 
909
    /**
910
     * Format a user record for display (link to profile).
911
     *
912
     * @param stdClass $row
913
     * @return string
914
     */
915
    public function col_fullname($row) {
916
        if (!$this->is_downloading()) {
917
            $courseid = $this->assignment->get_course()->id;
1441 ariadna 918
            $fullname = $this->output->render(\core_user::get_profile_picture($row, null,
919
                ['courseid' => $courseid, 'includefullname' => true]));
1 efrain 920
        } else {
921
            $fullname = $this->assignment->fullname($row);
922
        }
923
 
924
        if (!$this->assignment->is_active_user($row->id)) {
925
            $suspendedstring = get_string('userenrolmentsuspended', 'grades');
926
            $fullname .= ' ' . $this->output->pix_icon('i/enrolmentsuspended', $suspendedstring);
927
            $fullname = html_writer::tag('span', $fullname, array('class' => 'usersuspended'));
928
        }
929
        return $fullname;
930
    }
931
 
932
    /**
933
     * Insert a checkbox for selecting the current row for batch operations.
934
     *
935
     * @param stdClass $row
936
     * @return string
937
     */
938
    public function col_select(stdClass $row) {
939
        $selectcol = '<label class="accesshide" for="selectuser_' . $row->userid . '">';
940
        $selectcol .= get_string('selectuser', 'assign', $this->assignment->fullname($row));
941
        $selectcol .= '</label>';
942
        $selectcol .= '<input type="checkbox"
1441 ariadna 943
                              class="ignoredirty"
1 efrain 944
                              id="selectuser_' . $row->userid . '"
945
                              name="selectedusers"
946
                              value="' . $row->userid . '"/>';
947
        $selectcol .= '<input type="hidden"
948
                              name="grademodified_' . $row->userid . '"
949
                              value="' . $row->timemarked . '"/>';
950
        $selectcol .= '<input type="hidden"
951
                              name="gradeattempt_' . $row->userid . '"
952
                              value="' . $row->attemptnumber . '"/>';
953
        return $selectcol;
954
    }
955
 
956
    /**
957
     * Return a users grades from the listing of all grade data for this assignment.
958
     *
959
     * @param int $userid
960
     * @return mixed stdClass or false
961
     */
962
    private function get_gradebook_data_for_user($userid) {
963
        if (isset($this->gradinginfo->items[0]) && $this->gradinginfo->items[0]->grades[$userid]) {
964
            return $this->gradinginfo->items[0]->grades[$userid];
965
        }
966
        return false;
967
    }
968
 
969
    /**
970
     * Format a column of data for display.
971
     *
972
     * @param stdClass $row
973
     * @return string
974
     */
975
    public function col_gradecanbechanged(stdClass $row) {
976
        $gradingdisabled = $this->assignment->grading_disabled($row->id, true, $this->gradinginfo);
977
        if ($gradingdisabled) {
978
            return get_string('no');
979
        } else {
980
            return get_string('yes');
981
        }
982
    }
983
 
984
    /**
985
     * Format a column of data for display
986
     *
987
     * @param stdClass $row
988
     * @return string
989
     */
990
    public function col_grademax(stdClass $row) {
991
        if ($this->assignment->get_instance()->grade > 0) {
992
            $gradeitem = $this->assignment->get_grade_item();
993
            return format_float($this->assignment->get_instance()->grade, $gradeitem->get_decimals());
994
        } else {
995
            return '';
996
        }
997
    }
998
 
999
    /**
1000
     * Format a column of data for display.
1001
     *
1002
     * @param stdClass $row
1003
     * @return string
1004
     */
1441 ariadna 1005
    public function col_grade(stdClass $row): string {
1 efrain 1006
        $gradingdisabled = $this->assignment->grading_disabled($row->id, true, $this->gradinginfo);
1441 ariadna 1007
        $displaygrade = $this->display_grade($row->grade, $this->quickgrading && !$gradingdisabled, $row->userid, $row->timemarked);
1 efrain 1008
 
1009
        if (!$this->is_downloading() && $this->hasgrade) {
1441 ariadna 1010
            $urlparams = [
1011
                'id' => $this->assignment->get_course_module()->id,
1012
                'rownum' => 0,
1013
                'action' => 'grader',
1014
            ];
1 efrain 1015
 
1016
            if ($this->assignment->is_blind_marking()) {
1017
                if (empty($row->recordid)) {
1018
                    $row->recordid = $this->assignment->get_uniqueid_for_user($row->userid);
1019
                }
1020
                $urlparams['blindid'] = $row->recordid;
1021
            } else {
1022
                $urlparams['userid'] = $row->userid;
1023
            }
1024
            $url = new moodle_url('/mod/assign/view.php', $urlparams);
1025
 
1441 ariadna 1026
            // The container with the grade information.
1027
            $gradecontainer = $this->output->container($displaygrade, 'w-100');
1 efrain 1028
 
1441 ariadna 1029
            $menu = new action_menu();
1030
            $menu->set_owner_selector('.gradingtable-actionmenu');
1031
            $menu->set_boundary('window');
1032
            $menu->set_kebab_trigger(get_string('gradeactions', 'assign'));
1033
            $menu->set_additional_classes('ps-2 ms-auto');
1034
            // Prioritise the menu ahead of all other actions.
1035
            $menu->prioritise = true;
1036
            // Add the 'Grade' action item to the contextual menu.
1037
            $menu->add(new action_menu_link_secondary($url, null, get_string('gradeverb')));
1038
            // The contextual menu container.
1039
            $contextualmenucontainer = $this->output->container($this->output->render($menu), 'd-flex');
1040
 
1041
            return $this->output->container($gradecontainer . $contextualmenucontainer, ['class' => 'd-flex']);
1042
        }
1043
        // The table data is being downloaded, or the user cannot grade; therefore, only the formatted grade for display
1044
        // is returned.
1045
        return $displaygrade;
1 efrain 1046
    }
1047
 
1048
    /**
1049
     * Format a column of data for display.
1050
     *
1051
     * @param stdClass $row
1052
     * @return string
1053
     */
1054
    public function col_finalgrade(stdClass $row) {
1055
        $o = '';
1056
 
1057
        $grade = $this->get_gradebook_data_for_user($row->userid);
1058
        if ($grade) {
1441 ariadna 1059
            $o = $this->display_grade($grade->grade, false, $row->userid, $row->timemarked, $grade->deductedmark);
1 efrain 1060
        }
1061
 
1062
        return $o;
1063
    }
1064
 
1065
    /**
1066
     * Format a column of data for display.
1067
     *
1068
     * @param stdClass $row
1069
     * @return string
1070
     */
1071
    public function col_timemarked(stdClass $row) {
1072
        $o = '-';
1073
 
1074
        if ($row->timemarked && $row->grade !== null && $row->grade >= 0) {
1075
            $o = userdate($row->timemarked);
1076
        }
1077
        if ($row->timemarked && $this->is_downloading()) {
1078
            // Force it for downloads as it affects import.
1079
            $o = userdate($row->timemarked);
1080
        }
1081
 
1082
        return $o;
1083
    }
1084
 
1085
    /**
1086
     * Format a column of data for display.
1087
     *
1088
     * @param stdClass $row
1089
     * @return string
1090
     */
1091
    public function col_timesubmitted(stdClass $row) {
1092
        $o = '-';
1093
 
1094
        $group = false;
1095
        $submission = false;
1096
        $this->get_group_and_submission($row->id, $group, $submission, -1);
1097
        if ($submission && $submission->timemodified && $submission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
1098
            $o = userdate($submission->timemodified);
1099
        } else if ($row->timesubmitted && $row->status != ASSIGN_SUBMISSION_STATUS_NEW) {
1100
            $o = userdate($row->timesubmitted);
1101
        }
1102
 
1103
        return $o;
1104
    }
1105
 
1106
    /**
1107
     * Format a column of data for display
1108
     *
1109
     * @param stdClass $row
1110
     * @return string
1111
     */
1112
    public function col_status(stdClass $row) {
1441 ariadna 1113
        global $USER;
1114
 
1 efrain 1115
        $o = '';
1116
 
1117
        $instance = $this->assignment->get_instance($row->userid);
1118
        $timelimitenabled = get_config('assign', 'enabletimelimit');
1119
 
1120
        $due = $instance->duedate;
1121
        if ($row->extensionduedate) {
1122
            $due = $row->extensionduedate;
1123
        } else if (!empty($row->duedate)) {
1124
            // The override due date.
1125
            $due = $row->duedate;
1126
        }
1127
 
1128
        $group = false;
1129
        $submission = false;
1130
 
1131
        if ($instance->teamsubmission) {
1132
            $this->get_group_and_submission($row->id, $group, $submission, -1);
1133
        }
1134
 
1135
        if ($instance->teamsubmission && !$group && !$instance->preventsubmissionnotingroup) {
1136
            $group = true;
1137
        }
1138
 
1139
        if ($group && $submission) {
1140
            $timesubmitted = $submission->timemodified;
1141
            $status = $submission->status;
1142
        } else {
1143
            $timesubmitted = $row->timesubmitted;
1144
            $status = $row->status;
1145
        }
1146
 
1147
        $displaystatus = $status;
1148
        if ($displaystatus == 'new') {
1149
            $displaystatus = '';
1150
        }
1151
 
1441 ariadna 1152
        // Generate the output for the submission contextual (action) menu.
1153
        $actionmenu = '';
1154
        if (!$this->is_downloading() && $this->hasgrade) {
1155
 
1156
            $submissionsopen = $this->assignment->submissions_open(
1157
                userid: $row->id,
1158
                skipenrolled: true,
1159
                submission: $submission ? $submission : $row,
1160
                flags: $row,
1161
                gradinginfo: $this->gradinginfo
1162
            );
1163
            $caneditsubmission = $this->assignment->can_edit_submission($row->id, $USER->id);
1164
 
1165
            $baseactionurl = new moodle_url('/mod/assign/view.php', [
1166
                'id' => $this->assignment->get_course_module()->id,
1167
                'userid' => $row->id,
1168
                'sesskey' => sesskey(),
1169
                'page' => $this->currpage,
1170
            ]);
1171
 
1172
            $menu = new action_menu();
1173
            $menu->set_owner_selector('.gradingtable-actionmenu');
1174
            $menu->set_boundary('window');
1175
            $menu->set_kebab_trigger(get_string('submissionactions', 'assign'));
1176
            $menu->set_additional_classes('ps-2 ms-auto');
1177
            // Prioritise the menu ahead of all other actions.
1178
            $menu->prioritise = true;
1179
 
1180
            // Hide for offline assignments.
1181
            if ($this->assignment->is_any_submission_plugin_enabled()) {
1182
 
1183
                if ($submissionsopen && $USER->id != $row->id && $caneditsubmission) {
1184
                    // Edit submission action link.
1185
                    $baseactionurl->param('action', 'editsubmission');
1186
                    $description = get_string('editsubmission', 'assign');
1187
                    $menu->add(new action_menu_link_secondary($baseactionurl, null, $description));
1188
                }
1189
 
1190
                if (!$row->status || $row->status == ASSIGN_SUBMISSION_STATUS_DRAFT
1191
                        || !$this->assignment->get_instance()->submissiondrafts) {
1192
                    // Allow/prevent submission changes action link.
1193
                    $baseactionurl->param('action', $row->locked ? 'unlock' : 'lock');
1194
                    $description = $row->locked ? get_string('allowsubmissionsshort', 'assign') :
1195
                        get_string('preventsubmissionsshort', 'assign');
1196
                    $menu->add(new action_menu_link_secondary($baseactionurl, null, $description));
1197
                }
1198
            }
1199
 
1200
            if (($this->assignment->get_instance()->duedate || $this->assignment->get_instance()->cutoffdate) &&
1201
                    $this->hasgrantextension) {
1202
                // Grant extension action link.
1203
                $baseactionurl->param('action', 'grantextension');
1204
                $description = get_string('grantextension', 'assign');
1205
                $menu->add(new action_menu_link_secondary($baseactionurl, null, $description));
1206
            }
1207
 
1208
            if ($row->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED &&
1209
                    $this->assignment->get_instance()->submissiondrafts) {
1210
                // Revert submission to draft action link.
1211
                $baseactionurl->param('action', 'reverttodraft');
1212
                $description = get_string('reverttodraftshort', 'assign');
1213
                $menu->add(new action_menu_link_secondary($baseactionurl, null, $description));
1214
            }
1215
 
1216
            if ($row->status == ASSIGN_SUBMISSION_STATUS_DRAFT && $this->assignment->get_instance()->submissiondrafts &&
1217
                    $caneditsubmission && $submissionsopen && $row->id != $USER->id) {
1218
                // Submit for grading action link.
1219
                $baseactionurl->param('action', 'submitotherforgrading');
1220
                $description = get_string('submitforgrading', 'assign');
1221
                $menu->add(new action_menu_link_secondary($baseactionurl, null, $description));
1222
            }
1223
 
1224
            $ismanual = $this->assignment->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL;
1225
            $hassubmission = !empty($row->status);
1226
            $notreopened = $hassubmission && $row->status != ASSIGN_SUBMISSION_STATUS_REOPENED;
1227
            $isunlimited = $this->assignment->get_instance()->maxattempts == ASSIGN_UNLIMITED_ATTEMPTS;
1228
            $hasattempts = $isunlimited || $row->attemptnumber < $this->assignment->get_instance()->maxattempts - 1;
1229
 
1230
            if ($ismanual && $hassubmission && $notreopened && $hasattempts) {
1231
                // Allow another attempt action link.
1232
                $baseactionurl->param('action', 'addattempt');
1233
                $description = get_string('addattempt', 'assign');
1234
                $menu->add(new action_menu_link_secondary($baseactionurl, null, $description));
1235
            }
1236
 
1237
            if ($this->assignment->is_any_submission_plugin_enabled()) {
1238
                if ($USER->id != $row->id && $caneditsubmission && !empty($row->status)) {
1239
                    // Remove submission action link. This action link should be always placed as the last item
1240
                    // within the contextual menu.
1241
                    $baseactionurl->param('action', 'removesubmissionconfirm');
1242
                    $description = get_string('removesubmission', 'assign');
1243
                    $menu->add(new action_menu_link_secondary($baseactionurl, null, $description,
1244
                        ['class' => 'text-danger']));
1245
                }
1246
            }
1247
 
1248
            $actionmenu = $this->output->render($menu);
1249
        }
1250
 
1251
        $actionmenucontainer = $this->output->container($actionmenu, 'd-flex');
1252
 
1253
        // Generate the output for the submission information.
1254
        $submissioninfo = '';
1 efrain 1255
        if ($this->assignment->is_any_submission_plugin_enabled()) {
1256
 
1441 ariadna 1257
            $submissioninfo .= $this->output->container(get_string('submissionstatus_' . $displaystatus, 'assign'),
1258
                ['class' => 'submissionstatus' . $displaystatus]);
1259
 
1 efrain 1260
            if ($due && $timesubmitted > $due && $status != ASSIGN_SUBMISSION_STATUS_NEW) {
1261
                $usertime = format_time($timesubmitted - $due);
1262
                $latemessage = get_string('submittedlateshort',
1263
                                          'assign',
1264
                                          $usertime);
1441 ariadna 1265
                $submissioninfo .= $this->output->container($latemessage, 'latesubmission');
1 efrain 1266
            } else if ($timelimitenabled && $instance->timelimit && !empty($submission->timestarted)
1267
                && ($timesubmitted - $submission->timestarted > $instance->timelimit)
1268
                && $status != ASSIGN_SUBMISSION_STATUS_NEW) {
1269
                $usertime = format_time($timesubmitted - $submission->timestarted - $instance->timelimit);
1441 ariadna 1270
                $latemessage = get_string('submittedlateshort', 'assign', $usertime);
1271
                $submissioninfo .= $this->output->container($latemessage, 'latesubmission');
1 efrain 1272
            }
1273
            if ($row->locked) {
1274
                $lockedstr = get_string('submissionslockedshort', 'assign');
1441 ariadna 1275
                $submissioninfo .= $this->output->container($lockedstr, 'lockedsubmission');
1 efrain 1276
            }
1277
 
1278
            // Add status of "grading" if markflow is not enabled.
1279
            if (!$instance->markingworkflow) {
1280
                if ($row->grade !== null && $row->grade >= 0) {
1281
                    if ($row->timemarked < $row->timesubmitted) {
1441 ariadna 1282
                        $submissioninfo .= $this->output->container(get_string('gradedfollowupsubmit', 'assign'), 'gradingreminder');
1 efrain 1283
                    } else {
1441 ariadna 1284
                        $submissioninfo .= $this->output->container(get_string('graded', 'assign'), 'submissiongraded');
1 efrain 1285
                    }
1286
                } else if (!$timesubmitted || $status == ASSIGN_SUBMISSION_STATUS_NEW) {
1287
                    $now = time();
1288
                    if ($due && ($now > $due)) {
1289
                        $overduestr = get_string('overdue', 'assign', format_time($now - $due));
1441 ariadna 1290
                        $submissioninfo .= $this->output->container($overduestr, 'overduesubmission');
1 efrain 1291
                    }
1292
                }
1293
            }
1294
        }
1295
 
1296
        if ($instance->markingworkflow) {
1441 ariadna 1297
            $submissioninfo .= $this->col_workflowstatus($row);
1 efrain 1298
        }
1299
        if ($row->extensionduedate) {
1300
            $userdate = userdate($row->extensionduedate);
1301
            $extensionstr = get_string('userextensiondate', 'assign', $userdate);
1441 ariadna 1302
            $submissioninfo .= $this->output->container($extensionstr, 'extensiondate');
1 efrain 1303
        }
1441 ariadna 1304
        // The container with the submission information.
1305
        $submissoninfocontainer = $this->output->container($submissioninfo, 'submissioninfo w-100');
1 efrain 1306
 
1441 ariadna 1307
        $o .= $this->output->container($submissoninfocontainer . $actionmenucontainer, 'd-flex');
1308
 
1 efrain 1309
        if ($this->is_downloading()) {
1310
            $o = strip_tags(rtrim(str_replace('</div>', ' - ', $o), '- '));
1311
        }
1312
 
1313
        return $o;
1314
    }
1315
 
1316
    /**
1317
     * Format a column of data for display.
1318
     *
1319
     * @param stdClass $row
1320
     * @return string
1321
     */
1322
    public function col_allowsubmissionsfromdate(stdClass $row) {
1323
        $o = '';
1324
 
1325
        if ($row->allowsubmissionsfromdate) {
1326
            $userdate = userdate($row->allowsubmissionsfromdate);
1327
            $o = ($this->is_downloading()) ? $userdate : $this->output->container($userdate, 'allowsubmissionsfromdate');
1328
        }
1329
 
1330
        return $o;
1331
    }
1332
 
1333
    /**
1334
     * Format a column of data for display.
1335
     *
1336
     * @param stdClass $row
1337
     * @return string
1338
     */
1339
    public function col_duedate(stdClass $row) {
1340
        $o = '';
1341
 
1342
        if ($row->duedate) {
1343
            $userdate = userdate($row->duedate);
1344
            $o = ($this->is_downloading()) ? $userdate : $this->output->container($userdate, 'duedate');
1345
        }
1346
 
1347
        return $o;
1348
    }
1349
 
1350
    /**
1351
     * Format a column of data for display.
1352
     *
1353
     * @param stdClass $row
1354
     * @return string
1355
     */
1356
    public function col_cutoffdate(stdClass $row) {
1357
        $o = '';
1358
 
1359
        if ($row->cutoffdate) {
1360
            $userdate = userdate($row->cutoffdate);
1361
            $o = ($this->is_downloading()) ? $userdate : $this->output->container($userdate, 'cutoffdate');
1362
        }
1363
 
1364
        return $o;
1365
    }
1366
 
1367
    /**
1368
     * Format a column of data for display.
1369
     *
1370
     * @param stdClass $row
1371
     * @return string
1441 ariadna 1372
     * @deprecated since Moodle 4.5
1373
     * @todo Final deprecation in Moodle 6.0. See MDL-82336.
1 efrain 1374
     */
1441 ariadna 1375
    #[\core\attribute\deprecated(
1376
        replacement: null,
1377
        since: '4.5',
1378
        reason: 'Userid column is merged with status and grade columns'
1379
    )]
1 efrain 1380
    public function col_userid(stdClass $row) {
1381
        global $USER;
1382
 
1441 ariadna 1383
        \core\deprecation::emit_deprecation([$this, __FUNCTION__]);
1 efrain 1384
        $edit = '';
1385
 
1386
        $actions = array();
1387
 
1388
        $urlparams = array('id' => $this->assignment->get_course_module()->id,
1389
                               'rownum' => 0,
1390
                               'action' => 'grader');
1391
 
1392
        if ($this->assignment->is_blind_marking()) {
1393
            if (empty($row->recordid)) {
1394
                $row->recordid = $this->assignment->get_uniqueid_for_user($row->userid);
1395
            }
1396
            $urlparams['blindid'] = $row->recordid;
1397
        } else {
1398
            $urlparams['userid'] = $row->userid;
1399
        }
1400
        $url = new moodle_url('/mod/assign/view.php', $urlparams);
1401
        $noimage = null;
1402
 
1403
        if (!$row->grade) {
1404
            $description = get_string('gradeverb');
1405
        } else {
1406
            $description = get_string('updategrade', 'assign');
1407
        }
1408
        $actions['grade'] = new action_menu_link_secondary(
1409
            $url,
1410
            $noimage,
1411
            $description
1412
        );
1413
 
1414
        // Everything we need is in the row.
1415
        $submission = $row;
1416
        $flags = $row;
1417
        if ($this->assignment->get_instance()->teamsubmission) {
1418
            // Use the cache for this.
1419
            $submission = false;
1420
            $group = false;
1421
            $this->get_group_and_submission($row->id, $group, $submission, -1);
1422
        }
1423
 
1424
        $submissionsopen = $this->assignment->submissions_open($row->id,
1441 ariadna 1425
            true,
1426
            $submission,
1427
            $flags,
1428
            $this->gradinginfo);
1 efrain 1429
        $caneditsubmission = $this->assignment->can_edit_submission($row->id, $USER->id);
1430
 
1431
        // Hide for offline assignments.
1432
        if ($this->assignment->is_any_submission_plugin_enabled()) {
1433
            if (!$row->status ||
1441 ariadna 1434
                $row->status == ASSIGN_SUBMISSION_STATUS_DRAFT ||
1435
                !$this->assignment->get_instance()->submissiondrafts) {
1 efrain 1436
 
1437
                if (!$row->locked) {
1438
                    $urlparams = array('id' => $this->assignment->get_course_module()->id,
1441 ariadna 1439
                        'userid' => $row->id,
1440
                        'action' => 'lock',
1441
                        'sesskey' => sesskey(),
1442
                        'page' => $this->currpage);
1 efrain 1443
                    $url = new moodle_url('/mod/assign/view.php', $urlparams);
1444
 
1445
                    $description = get_string('preventsubmissionsshort', 'assign');
1446
                    $actions['lock'] = new action_menu_link_secondary(
1447
                        $url,
1448
                        $noimage,
1449
                        $description
1450
                    );
1451
                } else {
1452
                    $urlparams = array('id' => $this->assignment->get_course_module()->id,
1441 ariadna 1453
                        'userid' => $row->id,
1454
                        'action' => 'unlock',
1455
                        'sesskey' => sesskey(),
1456
                        'page' => $this->currpage);
1 efrain 1457
                    $url = new moodle_url('/mod/assign/view.php', $urlparams);
1458
                    $description = get_string('allowsubmissionsshort', 'assign');
1459
                    $actions['unlock'] = new action_menu_link_secondary(
1460
                        $url,
1461
                        $noimage,
1462
                        $description
1463
                    );
1464
                }
1465
            }
1466
 
1467
            if ($submissionsopen &&
1441 ariadna 1468
                $USER->id != $row->id &&
1469
                $caneditsubmission) {
1 efrain 1470
                $urlparams = array('id' => $this->assignment->get_course_module()->id,
1441 ariadna 1471
                    'userid' => $row->id,
1472
                    'action' => 'editsubmission',
1473
                    'sesskey' => sesskey(),
1474
                    'page' => $this->currpage);
1 efrain 1475
                $url = new moodle_url('/mod/assign/view.php', $urlparams);
1476
                $description = get_string('editsubmission', 'assign');
1477
                $actions['editsubmission'] = new action_menu_link_secondary(
1478
                    $url,
1479
                    $noimage,
1480
                    $description
1481
                );
1482
            }
1483
            if ($USER->id != $row->id &&
1441 ariadna 1484
                $caneditsubmission &&
1485
                !empty($row->status)) {
1 efrain 1486
                $urlparams = array('id' => $this->assignment->get_course_module()->id,
1441 ariadna 1487
                    'userid' => $row->id,
1488
                    'action' => 'removesubmissionconfirm',
1489
                    'sesskey' => sesskey(),
1490
                    'page' => $this->currpage);
1 efrain 1491
                $url = new moodle_url('/mod/assign/view.php', $urlparams);
1492
                $description = get_string('removesubmission', 'assign');
1493
                $actions['removesubmission'] = new action_menu_link_secondary(
1494
                    $url,
1495
                    $noimage,
1496
                    $description
1497
                );
1498
            }
1499
        }
1500
        if (($this->assignment->get_instance()->duedate ||
1501
                $this->assignment->get_instance()->cutoffdate) &&
1441 ariadna 1502
            $this->hasgrantextension) {
1503
            $urlparams = array('id' => $this->assignment->get_course_module()->id,
1504
                'userid' => $row->id,
1505
                'action' => 'grantextension',
1506
                'sesskey' => sesskey(),
1507
                'page' => $this->currpage);
1508
            $url = new moodle_url('/mod/assign/view.php', $urlparams);
1509
            $description = get_string('grantextension', 'assign');
1510
            $actions['grantextension'] = new action_menu_link_secondary(
1511
                $url,
1512
                $noimage,
1513
                $description
1514
            );
1 efrain 1515
        }
1516
        if ($row->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED &&
1441 ariadna 1517
            $this->assignment->get_instance()->submissiondrafts) {
1 efrain 1518
            $urlparams = array('id' => $this->assignment->get_course_module()->id,
1441 ariadna 1519
                'userid' => $row->id,
1520
                'action' => 'reverttodraft',
1521
                'sesskey' => sesskey(),
1522
                'page' => $this->currpage);
1 efrain 1523
            $url = new moodle_url('/mod/assign/view.php', $urlparams);
1524
            $description = get_string('reverttodraftshort', 'assign');
1525
            $actions['reverttodraft'] = new action_menu_link_secondary(
1526
                $url,
1527
                $noimage,
1528
                $description
1529
            );
1530
        }
1531
        if ($row->status == ASSIGN_SUBMISSION_STATUS_DRAFT &&
1441 ariadna 1532
            $this->assignment->get_instance()->submissiondrafts &&
1533
            $caneditsubmission &&
1534
            $submissionsopen &&
1535
            $row->id != $USER->id) {
1 efrain 1536
            $urlparams = array('id' => $this->assignment->get_course_module()->id,
1441 ariadna 1537
                'userid' => $row->id,
1538
                'action' => 'submitotherforgrading',
1539
                'sesskey' => sesskey(),
1540
                'page' => $this->currpage);
1 efrain 1541
            $url = new moodle_url('/mod/assign/view.php', $urlparams);
1542
            $description = get_string('submitforgrading', 'assign');
1543
            $actions['submitforgrading'] = new action_menu_link_secondary(
1544
                $url,
1545
                $noimage,
1546
                $description
1547
            );
1548
        }
1549
 
1550
        $ismanual = $this->assignment->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL;
1551
        $hassubmission = !empty($row->status);
1552
        $notreopened = $hassubmission && $row->status != ASSIGN_SUBMISSION_STATUS_REOPENED;
1553
        $isunlimited = $this->assignment->get_instance()->maxattempts == ASSIGN_UNLIMITED_ATTEMPTS;
1554
        $hasattempts = $isunlimited || $row->attemptnumber < $this->assignment->get_instance()->maxattempts - 1;
1555
 
1556
        if ($ismanual && $hassubmission && $notreopened && $hasattempts) {
1557
            $urlparams = array('id' => $this->assignment->get_course_module()->id,
1441 ariadna 1558
                'userid' => $row->id,
1559
                'action' => 'addattempt',
1560
                'sesskey' => sesskey(),
1561
                'page' => $this->currpage);
1 efrain 1562
            $url = new moodle_url('/mod/assign/view.php', $urlparams);
1563
            $description = get_string('addattempt', 'assign');
1564
            $actions['addattempt'] = new action_menu_link_secondary(
1565
                $url,
1566
                $noimage,
1567
                $description
1568
            );
1569
        }
1570
 
1571
        $menu = new action_menu();
1572
        $menu->set_owner_selector('.gradingtable-actionmenu');
1573
        $menu->set_boundary('window');
1574
        $menu->set_menu_trigger(get_string('edit'));
1575
        foreach ($actions as $action) {
1576
            $menu->add($action);
1577
        }
1578
 
1579
        // Prioritise the menu ahead of all other actions.
1580
        $menu->prioritise = true;
1581
 
1582
        $edit .= $this->output->render($menu);
1583
 
1584
        return $edit;
1585
    }
1586
 
1587
    /**
1588
     * Write the plugin summary with an optional link to view the full feedback/submission.
1589
     *
1590
     * @param assign_plugin $plugin Submission plugin or feedback plugin
1591
     * @param stdClass $item Submission or grade
1592
     * @param string $returnaction The return action to pass to the
1593
     *                             view_submission page (the current page)
1594
     * @param string $returnparams The return params to pass to the view_submission
1595
     *                             page (the current page)
1596
     * @return string The summary with an optional link
1597
     */
1598
    private function format_plugin_summary_with_link(assign_plugin $plugin,
1599
                                                     stdClass $item,
1600
                                                     $returnaction,
1601
                                                     $returnparams) {
1602
        $link = '';
1603
        $showviewlink = false;
1604
 
1605
        $summary = $plugin->view_summary($item, $showviewlink);
1606
        $separator = '';
1607
        if ($showviewlink) {
1608
            $viewstr = get_string('view' . substr($plugin->get_subtype(), strlen('assign')), 'assign');
1441 ariadna 1609
            $icon = $this->output->pix_icon('t/viewdetails', $viewstr);
1 efrain 1610
            $urlparams = array('id' => $this->assignment->get_course_module()->id,
1611
                                                     'sid' => $item->id,
1612
                                                     'gid' => $item->id,
1613
                                                     'plugin' => $plugin->get_type(),
1614
                                                     'action' => 'viewplugin' . $plugin->get_subtype(),
1615
                                                     'returnaction' => $returnaction,
1616
                                                     'returnparams' => http_build_query($returnparams));
1617
            $url = new moodle_url('/mod/assign/view.php', $urlparams);
1618
            $link = $this->output->action_link($url, $icon);
1619
            $separator = $this->output->spacer(array(), true);
1620
        }
1621
 
1622
        return $link . $separator . $summary;
1623
    }
1624
 
1625
 
1626
    /**
1627
     * Format the submission and feedback columns.
1628
     *
1629
     * @param string $colname The column name
1630
     * @param stdClass $row The submission row
1631
     * @return mixed string or NULL
1632
     */
1633
    public function other_cols($colname, $row) {
1634
        // For extra user fields the result is already in $row.
1635
        if (empty($this->plugincache[$colname])) {
1636
            return parent::other_cols($colname, $row);
1637
        }
1638
 
1639
        // This must be a plugin field.
1640
        $plugincache = $this->plugincache[$colname];
1641
 
1642
        $plugin = $plugincache[0];
1643
 
1644
        $field = null;
1645
        if (isset($plugincache[1])) {
1646
            $field = $plugincache[1];
1647
        }
1648
 
1649
        if ($plugin->is_visible() && $plugin->is_enabled()) {
1650
            if ($plugin->get_subtype() == 'assignsubmission') {
1651
                if ($this->assignment->get_instance()->teamsubmission) {
1652
                    $group = false;
1653
                    $submission = false;
1654
 
1655
                    $this->get_group_and_submission($row->id, $group, $submission, -1);
1656
                    if ($submission) {
1657
                        if ($submission->status == ASSIGN_SUBMISSION_STATUS_REOPENED) {
1658
                            // For a newly reopened submission - we want to show the previous submission in the table.
1659
                            $this->get_group_and_submission($row->id, $group, $submission, $submission->attemptnumber-1);
1660
                        }
1661
                        if (isset($field)) {
1662
                            return $plugin->get_editor_text($field, $submission->id);
1663
                        }
1664
                        return $this->format_plugin_summary_with_link($plugin,
1665
                                                                      $submission,
1666
                                                                      'grading',
1667
                                                                      array());
1668
                    }
1669
                } else if ($row->submissionid) {
1670
                    if ($row->status == ASSIGN_SUBMISSION_STATUS_REOPENED) {
1671
                        // For a newly reopened submission - we want to show the previous submission in the table.
1672
                        $submission = $this->assignment->get_user_submission($row->userid, false, $row->attemptnumber - 1);
1673
                    } else {
1674
                        $submission = new stdClass();
1675
                        $submission->id = $row->submissionid;
1676
                        $submission->timecreated = $row->firstsubmission;
1677
                        $submission->timemodified = $row->timesubmitted;
1678
                        $submission->assignment = $this->assignment->get_instance()->id;
1679
                        $submission->userid = $row->userid;
1680
                        $submission->attemptnumber = $row->attemptnumber;
1681
                    }
1682
                    // Field is used for only for import/export and refers the the fieldname for the text editor.
1683
                    if (isset($field)) {
1684
                        return $plugin->get_editor_text($field, $submission->id);
1685
                    }
1686
                    return $this->format_plugin_summary_with_link($plugin,
1687
                                                                  $submission,
1688
                                                                  'grading',
1689
                                                                  array());
1690
                }
1691
            } else {
1692
                $grade = null;
1693
                if (isset($field)) {
1694
                    return $plugin->get_editor_text($field, $row->gradeid);
1695
                }
1696
 
1697
                if ($row->gradeid) {
1698
                    $grade = new stdClass();
1699
                    $grade->id = $row->gradeid;
1700
                    $grade->timecreated = $row->firstmarked;
1701
                    $grade->timemodified = $row->timemarked;
1702
                    $grade->assignment = $this->assignment->get_instance()->id;
1703
                    $grade->userid = $row->userid;
1704
                    $grade->grade = $row->grade;
1705
                    $grade->mailed = $row->mailed;
1706
                    $grade->attemptnumber = $row->attemptnumber;
1707
                }
1708
                if ($this->quickgrading && $plugin->supports_quickgrading()) {
1709
                    return $plugin->get_quickgrading_html($row->userid, $grade);
1710
                } else if ($grade) {
1711
                    return $this->format_plugin_summary_with_link($plugin,
1712
                                                                  $grade,
1713
                                                                  'grading',
1714
                                                                  array());
1715
                }
1716
            }
1717
        }
1718
        return '';
1719
    }
1720
 
1721
    /**
1722
     * Using the current filtering and sorting - load all rows and return a single column from them.
1723
     *
1724
     * @param string $columnname The name of the raw column data
1725
     * @return array of data
1726
     */
1727
    public function get_column_data($columnname) {
1728
        $this->setup();
1729
        $this->currpage = 0;
1730
        $this->query_db($this->tablemaxrows);
1731
        $result = array();
1732
        foreach ($this->rawdata as $row) {
1733
            $result[] = $row->$columnname;
1734
        }
1735
        return $result;
1736
    }
1737
 
1738
    /**
1739
     * Return things to the renderer.
1740
     *
1741
     * @return string the assignment name
1742
     */
1743
    public function get_assignment_name() {
1744
        return $this->assignment->get_instance()->name;
1745
    }
1746
 
1747
    /**
1748
     * Return things to the renderer.
1749
     *
1750
     * @return int the course module id
1751
     */
1752
    public function get_course_module_id() {
1753
        return $this->assignment->get_course_module()->id;
1754
    }
1755
 
1756
    /**
1757
     * Return things to the renderer.
1758
     *
1759
     * @return int the course id
1760
     */
1761
    public function get_course_id() {
1762
        return $this->assignment->get_course()->id;
1763
    }
1764
 
1765
    /**
1766
     * Return things to the renderer.
1767
     *
1768
     * @return stdClass The course context
1769
     */
1770
    public function get_course_context() {
1771
        return $this->assignment->get_course_context();
1772
    }
1773
 
1774
    /**
1775
     * Return things to the renderer.
1776
     *
1777
     * @return bool Does this assignment accept submissions
1778
     */
1779
    public function submissions_enabled() {
1780
        return $this->assignment->is_any_submission_plugin_enabled();
1781
    }
1782
 
1783
    /**
1784
     * Return things to the renderer.
1785
     *
1786
     * @return bool Can this user view all grades (the gradebook)
1787
     */
1788
    public function can_view_all_grades() {
1789
        $context = $this->assignment->get_course_context();
1790
        return has_capability('gradereport/grader:view', $context) &&
1791
               has_capability('moodle/grade:viewall', $context);
1792
    }
1793
 
1794
    /**
1795
     * Always return a valid sort - even if the userid column is missing.
1796
     * @return array column name => SORT_... constant.
1797
     */
1798
    public function get_sort_columns() {
1799
        $result = parent::get_sort_columns();
1800
 
1801
        $assignment = $this->assignment->get_instance();
1802
        if (empty($assignment->blindmarking)) {
1803
            $result = array_merge($result, array('userid' => SORT_ASC));
1804
        } else {
1805
            $result = array_merge($result, [
1806
                    'COALESCE(s.timecreated, '  . time()        . ')'   => SORT_ASC,
1807
                    'COALESCE(s.id, '           . PHP_INT_MAX   . ')'   => SORT_ASC,
1808
                    'um.id'                                             => SORT_ASC,
1809
                ]);
1810
        }
1811
        return $result;
1812
    }
1813
 
1814
    /**
1815
     * Override the table show_hide_link to not show for select column.
1816
     *
1817
     * @param string $column the column name, index into various names.
1818
     * @param int $index numerical index of the column.
1819
     * @return string HTML fragment.
1820
     */
1821
    protected function show_hide_link($column, $index) {
1822
        if ($index > 0 || !$this->hasgrade) {
1823
            return parent::show_hide_link($column, $index);
1824
        }
1825
        return '';
1826
    }
1827
 
1828
    /**
1829
     * Overides setup to ensure it will only run a single time.
1830
     */
1831
    public function setup() {
1832
        // Check if the setup function has been called before, we should not run it twice.
1833
        // If we do the sortorder of the table will be broken.
1834
        if (!empty($this->setup)) {
1835
            return;
1836
        }
1837
        parent::setup();
1838
    }
1441 ariadna 1839
 
1840
    /**
1841
     * Returns the html for the paging bar.
1842
     *
1843
     * @return string
1844
     */
1845
    public function get_paging_bar(): string {
1846
        global $OUTPUT;
1847
 
1848
        if ($this->use_pages) {
1849
            $pagingbar = new paging_bar($this->totalrows, $this->currpage, $this->pagesize, $this->baseurl);
1850
            $pagingbar->pagevar = $this->request[TABLE_VAR_PAGE];
1851
            return $OUTPUT->render($pagingbar);
1852
        }
1853
 
1854
        return '';
1855
    }
1856
 
1857
    /**
1858
     * Returns the html for the paging selector.
1859
     *
1860
     * @return string
1861
     */
1862
    public function get_paging_selector(): string {
1863
        global $OUTPUT;
1864
 
1865
        if ($this->use_pages) {
1866
            $pagingoptions = [...$this->pagingoptions, $this->perpage]; // To make sure the actual page size is within the options.
1867
            $pagingoptions = array_unique($pagingoptions);
1868
            sort($pagingoptions);
1869
            $pagingoptions = array_combine($pagingoptions, $pagingoptions);
1870
            $maxperpage = get_config('assign', 'maxperpage');
1871
            if (isset($maxperpage) && $maxperpage != -1) {
1872
                // Remove any options that are greater than the maxperpage.
1873
                $pagingoptions = array_filter($pagingoptions, fn($value) => $value <= $maxperpage);
1874
            } else {
1875
                $pagingoptions[-1] = get_string('all');
1876
            }
1877
 
1878
            $data = [
1879
                'baseurl' => $this->baseurl->out(false),
1880
                'options' => array_map(fn($key, $name): array => [
1881
                    'name' => $name,
1882
                    'value' => $key,
1883
                    'selected' => $key == $this->perpage,
1884
                ], array_keys($pagingoptions), $pagingoptions),
1885
            ];
1886
 
1887
            return $OUTPUT->render_from_template('mod_assign/grading_paging_selector', $data);
1888
        }
1889
 
1890
        return '';
1891
    }
1892
 
1893
    /**
1894
     * Finish the HTML output.
1895
     * This function is essentially a copy of the parent function except the paging bar not being rendered.
1896
     *
1897
     * @return void
1898
     */
1899
    public function finish_html(): void {
1900
        if (!$this->started_output) {
1901
            // No data has been added to the table.
1902
            $this->print_nothing_to_display();
1903
        } else {
1904
            // Print empty rows to fill the table to the current pagesize.
1905
            // This is done so the header aria-controls attributes do not point to
1906
            // non-existent elements.
1907
            $emptyrow = array_fill(0, count($this->columns), '');
1908
            while ($this->currentrow < $this->pagesize) {
1909
                $this->print_row($emptyrow, 'emptyrow');
1910
            }
1911
 
1912
            echo html_writer::end_tag('tbody');
1913
            echo html_writer::end_tag('table');
1914
            if ($this->responsive) {
1915
                echo html_writer::end_tag('div');
1916
            }
1917
            $this->wrap_html_finish();
1918
 
1919
            if (in_array(TABLE_P_BOTTOM, $this->showdownloadbuttonsat)) {
1920
                echo $this->download_buttons();
1921
            }
1922
 
1923
            // Render the dynamic table footer.
1924
            echo $this->get_dynamic_table_html_end();
1925
        }
1926
    }
1927
 
1928
    /**
1929
     * Start the HTML output.
1930
     * This function is essentially a copy of the parent function except the paging bar not being rendered.
1931
     *
1932
     * @return void
1933
     */
1934
    public function start_html(): void {
1935
        // Render the dynamic table header.
1936
        echo $this->get_dynamic_table_html_start();
1937
 
1938
        // Render button to allow user to reset table preferences.
1939
        echo $this->render_reset_button();
1940
 
1941
        // Do we need to print initial bars?
1942
        $this->print_initials_bar();
1943
 
1944
        if (in_array(TABLE_P_TOP, $this->showdownloadbuttonsat)) {
1945
            echo $this->download_buttons();
1946
        }
1947
 
1948
        $this->wrap_html_start();
1949
        // Start of main data table.
1950
 
1951
        if ($this->responsive) {
1952
            echo html_writer::start_tag('div', ['class' => 'table-responsive']);
1953
        }
1954
        echo html_writer::start_tag('table', $this->attributes) . $this->render_caption();
1955
    }
1 efrain 1956
}